diff --git a/.github/workflows/analyzers.yaml b/.github/workflows/analyzers.yaml index 1902284..5438b66 100644 --- a/.github/workflows/analyzers.yaml +++ b/.github/workflows/analyzers.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['7.4', '8.0', '8.1', '8.2'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index 9ec2876..78a43af 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['7.4', '8.0', '8.1', '8.2'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/functional.yaml b/.github/workflows/functional.yaml index 5ae17eb..8180291 100644 --- a/.github/workflows/functional.yaml +++ b/.github/workflows/functional.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest, windows-latest, macos-latest] - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['7.4', '8.0', '8.1', '8.2'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 49f207a..3af4824 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['7.4', '8.0', '8.1', '8.2'] fail-fast: false name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }} steps: diff --git a/.phive/phars.xml b/.phive/phars.xml index cd0e142..696eec2 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/composer.json b/composer.json index 979b9c5..e45acfc 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 ", "composer-plugin-api": "~2.0" }, "require-dev": { diff --git a/psalm.xml b/psalm.xml index e8a8b3a..66c8125 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,7 +1,5 @@ ')) { fwrite( STDERR, sprintf( - 'PHPUnit 9.5.10 by Sebastian Bergmann and contributors.' . PHP_EOL . PHP_EOL . + 'PHPUnit 9.5.26 by Sebastian Bergmann and contributors.' . PHP_EOL . PHP_EOL . 'This version of PHPUnit requires PHP >= 7.3.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, @@ -52,7 +52,7 @@ if (__FILE__ === realpath($_SERVER['SCRIPT_NAME'])) { $execute = false; } -$options = getopt('', array('prepend:', 'manifest')); +$options = getopt('', array('prepend:', 'manifest', 'sbom')); if (isset($options['prepend'])) { require $options['prepend']; @@ -60,14 +60,16 @@ if (isset($options['prepend'])) { if (isset($options['manifest'])) { $printManifest = true; +} elseif (isset($options['sbom'])) { + $printSbom = true; } unset($options); define('__PHPUNIT_PHAR__', str_replace(DIRECTORY_SEPARATOR, '/', __FILE__)); -define('__PHPUNIT_PHAR_ROOT__', 'phar://phpunit-9.5.10.phar'); +define('__PHPUNIT_PHAR_ROOT__', 'phar://phpunit-9.5.26.phar'); -Phar::mapPhar('phpunit-9.5.10.phar'); +Phar::mapPhar('phpunit-9.5.26.phar'); spl_autoload_register( function ($class) { @@ -223,7 +225,6 @@ spl_autoload_register( 'PHPUnit\\Framework\\MockObject\\MockObject' => '/phpunit/Framework/MockObject/MockObject.php', 'PHPUnit\\Framework\\MockObject\\MockTrait' => '/phpunit/Framework/MockObject/MockTrait.php', 'PHPUnit\\Framework\\MockObject\\MockType' => '/phpunit/Framework/MockObject/MockType.php', - 'PHPUnit\\Framework\\MockObject\\MockedCloneMethod' => '/phpunit/Framework/MockObject/Api/MockedCloneMethod.php', 'PHPUnit\\Framework\\MockObject\\OriginalConstructorInvocationRequiredException' => '/phpunit/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php', 'PHPUnit\\Framework\\MockObject\\ReflectionException' => '/phpunit/Framework/MockObject/Exception/ReflectionException.php', 'PHPUnit\\Framework\\MockObject\\ReturnValueNotConfiguredException' => '/phpunit/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php', @@ -254,7 +255,6 @@ spl_autoload_register( 'PHPUnit\\Framework\\MockObject\\UnknownClassException' => '/phpunit/Framework/MockObject/Exception/UnknownClassException.php', 'PHPUnit\\Framework\\MockObject\\UnknownTraitException' => '/phpunit/Framework/MockObject/Exception/UnknownTraitException.php', 'PHPUnit\\Framework\\MockObject\\UnknownTypeException' => '/phpunit/Framework/MockObject/Exception/UnknownTypeException.php', - 'PHPUnit\\Framework\\MockObject\\UnmockedCloneMethod' => '/phpunit/Framework/MockObject/Api/UnmockedCloneMethod.php', 'PHPUnit\\Framework\\MockObject\\Verifiable' => '/phpunit/Framework/MockObject/Verifiable.php', 'PHPUnit\\Framework\\NoChildTestSuiteException' => '/phpunit/Framework/Exception/NoChildTestSuiteException.php', 'PHPUnit\\Framework\\OutputError' => '/phpunit/Framework/Exception/OutputError.php', @@ -333,11 +333,13 @@ spl_autoload_register( 'PHPUnit\\PharIo\\Version\\AbstractVersionConstraint' => '/phar-io-version/constraints/AbstractVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\AndVersionConstraintGroup' => '/phar-io-version/constraints/AndVersionConstraintGroup.php', 'PHPUnit\\PharIo\\Version\\AnyVersionConstraint' => '/phar-io-version/constraints/AnyVersionConstraint.php', + 'PHPUnit\\PharIo\\Version\\BuildMetaData' => '/phar-io-version/BuildMetaData.php', 'PHPUnit\\PharIo\\Version\\ExactVersionConstraint' => '/phar-io-version/constraints/ExactVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\Exception' => '/phar-io-version/exceptions/Exception.php', 'PHPUnit\\PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => '/phar-io-version/constraints/GreaterThanOrEqualToVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\InvalidPreReleaseSuffixException' => '/phar-io-version/exceptions/InvalidPreReleaseSuffixException.php', 'PHPUnit\\PharIo\\Version\\InvalidVersionException' => '/phar-io-version/exceptions/InvalidVersionException.php', + 'PHPUnit\\PharIo\\Version\\NoBuildMetaDataException' => '/phar-io-version/exceptions/NoBuildMetaDataException.php', 'PHPUnit\\PharIo\\Version\\NoPreReleaseSuffixException' => '/phar-io-version/exceptions/NoPreReleaseSuffixException.php', 'PHPUnit\\PharIo\\Version\\OrVersionConstraintGroup' => '/phar-io-version/constraints/OrVersionConstraintGroup.php', 'PHPUnit\\PharIo\\Version\\PreReleaseSuffix' => '/phar-io-version/PreReleaseSuffix.php', @@ -355,6 +357,8 @@ spl_autoload_register( 'PHPUnit\\PhpParser\\Builder\\ClassConst' => '/nikic-php-parser/PhpParser/Builder/ClassConst.php', 'PHPUnit\\PhpParser\\Builder\\Class_' => '/nikic-php-parser/PhpParser/Builder/Class_.php', 'PHPUnit\\PhpParser\\Builder\\Declaration' => '/nikic-php-parser/PhpParser/Builder/Declaration.php', + 'PHPUnit\\PhpParser\\Builder\\EnumCase' => '/nikic-php-parser/PhpParser/Builder/EnumCase.php', + 'PHPUnit\\PhpParser\\Builder\\Enum_' => '/nikic-php-parser/PhpParser/Builder/Enum_.php', 'PHPUnit\\PhpParser\\Builder\\FunctionLike' => '/nikic-php-parser/PhpParser/Builder/FunctionLike.php', 'PHPUnit\\PhpParser\\Builder\\Function_' => '/nikic-php-parser/PhpParser/Builder/Function_.php', 'PHPUnit\\PhpParser\\Builder\\Interface_' => '/nikic-php-parser/PhpParser/Builder/Interface_.php', @@ -636,10 +640,7 @@ spl_autoload_register( 'PHPUnit\\SebastianBergmann\\CliParser\\UnknownOptionException' => '/sebastian-cli-parser/exceptions/UnknownOptionException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\BranchAndPathCoverageNotSupportedException' => '/php-code-coverage/Exception/BranchAndPathCoverageNotSupportedException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\CodeCoverage' => '/php-code-coverage/CodeCoverage.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\CrapIndex' => '/php-code-coverage/CrapIndex.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\DeadCodeDetectionNotSupportedException' => '/php-code-coverage/Exception/DeadCodeDetectionNotSupportedException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Directory' => '/php-code-coverage/Directory.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\DirectoryCouldNotBeCreatedException' => '/php-code-coverage/Exception/DirectoryCouldNotBeCreatedException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\Driver' => '/php-code-coverage/Driver/Driver.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\PathExistsButIsNotDirectoryException' => '/php-code-coverage/Exception/PathExistsButIsNotDirectoryException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\PcovDriver' => '/php-code-coverage/Driver/PcovDriver.php', @@ -661,11 +662,11 @@ spl_autoload_register( 'PHPUnit\\SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverWithPathCoverageSupportAvailableException' => '/php-code-coverage/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => '/php-code-coverage/Node/AbstractNode.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Builder' => '/php-code-coverage/Node/Builder.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\CrapIndex' => '/php-code-coverage/Node/CrapIndex.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Directory' => '/php-code-coverage/Node/Directory.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\File' => '/php-code-coverage/Node/File.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Iterator' => '/php-code-coverage/Node/Iterator.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ParserException' => '/php-code-coverage/Exception/ParserException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Percentage' => '/php-code-coverage/Percentage.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ProcessedCodeCoverageData' => '/php-code-coverage/ProcessedCodeCoverageData.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\RawCodeCoverageData' => '/php-code-coverage/RawCodeCoverageData.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ReflectionException' => '/php-code-coverage/Exception/ReflectionException.php', @@ -694,19 +695,18 @@ spl_autoload_register( 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => '/php-code-coverage/Report/Xml/Totals.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => '/php-code-coverage/Report/Xml/Unit.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysisCacheNotConfiguredException' => '/php-code-coverage/Exception/StaticAnalysisCacheNotConfiguredException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\Cache' => '/php-code-coverage/StaticAnalysis/Cache.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CacheWarmer' => '/php-code-coverage/StaticAnalysis/CacheWarmer.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingCoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingCoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingUncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingUncoveredFileAnalyser.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CodeUnitFindingVisitor' => '/php-code-coverage/StaticAnalysis/CodeUnitFindingVisitor.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CoveredFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ExecutableLinesFindingVisitor' => '/php-code-coverage/StaticAnalysis/ExecutableLinesFindingVisitor.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\FileAnalyser' => '/php-code-coverage/StaticAnalysis/FileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\IgnoredLinesFindingVisitor' => '/php-code-coverage/StaticAnalysis/IgnoredLinesFindingVisitor.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingCoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingCoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingUncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingUncoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\UncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/UncoveredFileAnalyser.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\TestIdMissingException' => '/php-code-coverage/Exception/TestIdMissingException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => '/php-code-coverage/Exception/UnintentionallyCoveredCodeException.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\DirectoryCouldNotBeCreatedException' => '/php-code-coverage/Exception/DirectoryCouldNotBeCreatedException.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\Filesystem' => '/php-code-coverage/Util/Filesystem.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\Percentage' => '/php-code-coverage/Util/Percentage.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Version' => '/php-code-coverage/Version.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\XmlException' => '/php-code-coverage/Exception/XmlException.php', 'PHPUnit\\SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => '/sebastian-code-unit-reverse-lookup/Wizard.php', @@ -810,26 +810,28 @@ spl_autoload_register( 'PHPUnit\\SebastianBergmann\\Timer\\ResourceUsageFormatter' => '/php-timer/ResourceUsageFormatter.php', 'PHPUnit\\SebastianBergmann\\Timer\\TimeSinceStartOfRequestNotAvailableException' => '/php-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.php', 'PHPUnit\\SebastianBergmann\\Timer\\Timer' => '/php-timer/Timer.php', - 'PHPUnit\\SebastianBergmann\\Type\\CallableType' => '/sebastian-type/CallableType.php', + 'PHPUnit\\SebastianBergmann\\Type\\CallableType' => '/sebastian-type/type/CallableType.php', 'PHPUnit\\SebastianBergmann\\Type\\Exception' => '/sebastian-type/exception/Exception.php', - 'PHPUnit\\SebastianBergmann\\Type\\FalseType' => '/sebastian-type/FalseType.php', - 'PHPUnit\\SebastianBergmann\\Type\\GenericObjectType' => '/sebastian-type/GenericObjectType.php', - 'PHPUnit\\SebastianBergmann\\Type\\IterableType' => '/sebastian-type/IterableType.php', - 'PHPUnit\\SebastianBergmann\\Type\\LogicException' => '/sebastian-type/exception/LogicException.php', - 'PHPUnit\\SebastianBergmann\\Type\\MixedType' => '/sebastian-type/MixedType.php', - 'PHPUnit\\SebastianBergmann\\Type\\NullType' => '/sebastian-type/NullType.php', - 'PHPUnit\\SebastianBergmann\\Type\\ObjectType' => '/sebastian-type/ObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\FalseType' => '/sebastian-type/type/FalseType.php', + 'PHPUnit\\SebastianBergmann\\Type\\GenericObjectType' => '/sebastian-type/type/GenericObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\IntersectionType' => '/sebastian-type/type/IntersectionType.php', + 'PHPUnit\\SebastianBergmann\\Type\\IterableType' => '/sebastian-type/type/IterableType.php', + 'PHPUnit\\SebastianBergmann\\Type\\MixedType' => '/sebastian-type/type/MixedType.php', + 'PHPUnit\\SebastianBergmann\\Type\\NeverType' => '/sebastian-type/type/NeverType.php', + 'PHPUnit\\SebastianBergmann\\Type\\NullType' => '/sebastian-type/type/NullType.php', + 'PHPUnit\\SebastianBergmann\\Type\\ObjectType' => '/sebastian-type/type/ObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\Parameter' => '/sebastian-type/Parameter.php', 'PHPUnit\\SebastianBergmann\\Type\\ReflectionMapper' => '/sebastian-type/ReflectionMapper.php', 'PHPUnit\\SebastianBergmann\\Type\\RuntimeException' => '/sebastian-type/exception/RuntimeException.php', - 'PHPUnit\\SebastianBergmann\\Type\\SimpleType' => '/sebastian-type/SimpleType.php', - 'PHPUnit\\SebastianBergmann\\Type\\StaticType' => '/sebastian-type/StaticType.php', - 'PHPUnit\\SebastianBergmann\\Type\\Type' => '/sebastian-type/Type.php', + 'PHPUnit\\SebastianBergmann\\Type\\SimpleType' => '/sebastian-type/type/SimpleType.php', + 'PHPUnit\\SebastianBergmann\\Type\\StaticType' => '/sebastian-type/type/StaticType.php', + 'PHPUnit\\SebastianBergmann\\Type\\TrueType' => '/sebastian-type/type/TrueType.php', + 'PHPUnit\\SebastianBergmann\\Type\\Type' => '/sebastian-type/type/Type.php', 'PHPUnit\\SebastianBergmann\\Type\\TypeName' => '/sebastian-type/TypeName.php', - 'PHPUnit\\SebastianBergmann\\Type\\UnionType' => '/sebastian-type/UnionType.php', - 'PHPUnit\\SebastianBergmann\\Type\\UnknownType' => '/sebastian-type/UnknownType.php', - 'PHPUnit\\SebastianBergmann\\Type\\VoidType' => '/sebastian-type/VoidType.php', + 'PHPUnit\\SebastianBergmann\\Type\\UnionType' => '/sebastian-type/type/UnionType.php', + 'PHPUnit\\SebastianBergmann\\Type\\UnknownType' => '/sebastian-type/type/UnknownType.php', + 'PHPUnit\\SebastianBergmann\\Type\\VoidType' => '/sebastian-type/type/VoidType.php', 'PHPUnit\\SebastianBergmann\\Version' => '/sebastian-version/Version.php', - 'PHPUnit\\Symfony\\Polyfill\\Ctype\\Ctype' => '/symfony-polyfill-ctype/Ctype.php', 'PHPUnit\\TextUI\\CliArguments\\Builder' => '/phpunit/TextUI/CliArguments/Builder.php', 'PHPUnit\\TextUI\\CliArguments\\Configuration' => '/phpunit/TextUI/CliArguments/Configuration.php', 'PHPUnit\\TextUI\\CliArguments\\Exception' => '/phpunit/TextUI/CliArguments/Exception.php', @@ -935,6 +937,7 @@ spl_autoload_register( 'PHPUnit\\Util\\Annotation\\DocBlock' => '/phpunit/Util/Annotation/DocBlock.php', 'PHPUnit\\Util\\Annotation\\Registry' => '/phpunit/Util/Annotation/Registry.php', 'PHPUnit\\Util\\Blacklist' => '/phpunit/Util/Blacklist.php', + 'PHPUnit\\Util\\Cloner' => '/phpunit/Util/Cloner.php', 'PHPUnit\\Util\\Color' => '/phpunit/Util/Color.php', 'PHPUnit\\Util\\ErrorHandler' => '/phpunit/Util/ErrorHandler.php', 'PHPUnit\\Util\\Exception' => '/phpunit/Util/Exception.php', @@ -951,6 +954,7 @@ spl_autoload_register( 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => '/phpunit/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => '/phpunit/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => '/phpunit/Util/Printer.php', + 'PHPUnit\\Util\\Reflection' => '/phpunit/Util/Reflection.php', 'PHPUnit\\Util\\RegularExpression' => '/phpunit/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => '/phpunit/Util/Test.php', 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => '/phpunit/Util/TestDox/CliTestDoxPrinter.php', @@ -1030,10 +1034,15 @@ spl_autoload_register( 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\CallableString' => '/phpdocumentor-type-resolver/PseudoTypes/CallableString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\False_' => '/phpdocumentor-type-resolver/PseudoTypes/False_.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\HtmlEscapedString' => '/phpdocumentor-type-resolver/PseudoTypes/HtmlEscapedString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\IntegerRange' => '/phpdocumentor-type-resolver/PseudoTypes/IntegerRange.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\List_' => '/phpdocumentor-type-resolver/PseudoTypes/List_.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\LiteralString' => '/phpdocumentor-type-resolver/PseudoTypes/LiteralString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\LowercaseString' => '/phpdocumentor-type-resolver/PseudoTypes/LowercaseString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NegativeInteger' => '/phpdocumentor-type-resolver/PseudoTypes/NegativeInteger.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NonEmptyLowercaseString' => '/phpdocumentor-type-resolver/PseudoTypes/NonEmptyLowercaseString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NonEmptyString' => '/phpdocumentor-type-resolver/PseudoTypes/NonEmptyString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NumericString' => '/phpdocumentor-type-resolver/PseudoTypes/NumericString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\Numeric_' => '/phpdocumentor-type-resolver/PseudoTypes/Numeric_.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\PositiveInteger' => '/phpdocumentor-type-resolver/PseudoTypes/PositiveInteger.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\TraitString' => '/phpdocumentor-type-resolver/PseudoTypes/TraitString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\True_' => '/phpdocumentor-type-resolver/PseudoTypes/True_.php', @@ -1167,7 +1176,7 @@ spl_autoload_register( } if (isset($classes[$class])) { - require_once 'phar://phpunit-9.5.10.phar' . $classes[$class]; + require_once 'phar://phpunit-9.5.26.phar' . $classes[$class]; } }, true, @@ -1323,7 +1332,6 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\Framework\\MockObject\\MockObject' => '/phpunit/Framework/MockObject/MockObject.php', 'PHPUnit\\Framework\\MockObject\\MockTrait' => '/phpunit/Framework/MockObject/MockTrait.php', 'PHPUnit\\Framework\\MockObject\\MockType' => '/phpunit/Framework/MockObject/MockType.php', - 'PHPUnit\\Framework\\MockObject\\MockedCloneMethod' => '/phpunit/Framework/MockObject/Api/MockedCloneMethod.php', 'PHPUnit\\Framework\\MockObject\\OriginalConstructorInvocationRequiredException' => '/phpunit/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php', 'PHPUnit\\Framework\\MockObject\\ReflectionException' => '/phpunit/Framework/MockObject/Exception/ReflectionException.php', 'PHPUnit\\Framework\\MockObject\\ReturnValueNotConfiguredException' => '/phpunit/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php', @@ -1354,7 +1362,6 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\Framework\\MockObject\\UnknownClassException' => '/phpunit/Framework/MockObject/Exception/UnknownClassException.php', 'PHPUnit\\Framework\\MockObject\\UnknownTraitException' => '/phpunit/Framework/MockObject/Exception/UnknownTraitException.php', 'PHPUnit\\Framework\\MockObject\\UnknownTypeException' => '/phpunit/Framework/MockObject/Exception/UnknownTypeException.php', - 'PHPUnit\\Framework\\MockObject\\UnmockedCloneMethod' => '/phpunit/Framework/MockObject/Api/UnmockedCloneMethod.php', 'PHPUnit\\Framework\\MockObject\\Verifiable' => '/phpunit/Framework/MockObject/Verifiable.php', 'PHPUnit\\Framework\\NoChildTestSuiteException' => '/phpunit/Framework/Exception/NoChildTestSuiteException.php', 'PHPUnit\\Framework\\OutputError' => '/phpunit/Framework/Exception/OutputError.php', @@ -1433,11 +1440,13 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\PharIo\\Version\\AbstractVersionConstraint' => '/phar-io-version/constraints/AbstractVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\AndVersionConstraintGroup' => '/phar-io-version/constraints/AndVersionConstraintGroup.php', 'PHPUnit\\PharIo\\Version\\AnyVersionConstraint' => '/phar-io-version/constraints/AnyVersionConstraint.php', + 'PHPUnit\\PharIo\\Version\\BuildMetaData' => '/phar-io-version/BuildMetaData.php', 'PHPUnit\\PharIo\\Version\\ExactVersionConstraint' => '/phar-io-version/constraints/ExactVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\Exception' => '/phar-io-version/exceptions/Exception.php', 'PHPUnit\\PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => '/phar-io-version/constraints/GreaterThanOrEqualToVersionConstraint.php', 'PHPUnit\\PharIo\\Version\\InvalidPreReleaseSuffixException' => '/phar-io-version/exceptions/InvalidPreReleaseSuffixException.php', 'PHPUnit\\PharIo\\Version\\InvalidVersionException' => '/phar-io-version/exceptions/InvalidVersionException.php', + 'PHPUnit\\PharIo\\Version\\NoBuildMetaDataException' => '/phar-io-version/exceptions/NoBuildMetaDataException.php', 'PHPUnit\\PharIo\\Version\\NoPreReleaseSuffixException' => '/phar-io-version/exceptions/NoPreReleaseSuffixException.php', 'PHPUnit\\PharIo\\Version\\OrVersionConstraintGroup' => '/phar-io-version/constraints/OrVersionConstraintGroup.php', 'PHPUnit\\PharIo\\Version\\PreReleaseSuffix' => '/phar-io-version/PreReleaseSuffix.php', @@ -1455,6 +1464,8 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\PhpParser\\Builder\\ClassConst' => '/nikic-php-parser/PhpParser/Builder/ClassConst.php', 'PHPUnit\\PhpParser\\Builder\\Class_' => '/nikic-php-parser/PhpParser/Builder/Class_.php', 'PHPUnit\\PhpParser\\Builder\\Declaration' => '/nikic-php-parser/PhpParser/Builder/Declaration.php', + 'PHPUnit\\PhpParser\\Builder\\EnumCase' => '/nikic-php-parser/PhpParser/Builder/EnumCase.php', + 'PHPUnit\\PhpParser\\Builder\\Enum_' => '/nikic-php-parser/PhpParser/Builder/Enum_.php', 'PHPUnit\\PhpParser\\Builder\\FunctionLike' => '/nikic-php-parser/PhpParser/Builder/FunctionLike.php', 'PHPUnit\\PhpParser\\Builder\\Function_' => '/nikic-php-parser/PhpParser/Builder/Function_.php', 'PHPUnit\\PhpParser\\Builder\\Interface_' => '/nikic-php-parser/PhpParser/Builder/Interface_.php', @@ -1736,10 +1747,7 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\SebastianBergmann\\CliParser\\UnknownOptionException' => '/sebastian-cli-parser/exceptions/UnknownOptionException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\BranchAndPathCoverageNotSupportedException' => '/php-code-coverage/Exception/BranchAndPathCoverageNotSupportedException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\CodeCoverage' => '/php-code-coverage/CodeCoverage.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\CrapIndex' => '/php-code-coverage/CrapIndex.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\DeadCodeDetectionNotSupportedException' => '/php-code-coverage/Exception/DeadCodeDetectionNotSupportedException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Directory' => '/php-code-coverage/Directory.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\DirectoryCouldNotBeCreatedException' => '/php-code-coverage/Exception/DirectoryCouldNotBeCreatedException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\Driver' => '/php-code-coverage/Driver/Driver.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\PathExistsButIsNotDirectoryException' => '/php-code-coverage/Exception/PathExistsButIsNotDirectoryException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Driver\\PcovDriver' => '/php-code-coverage/Driver/PcovDriver.php', @@ -1761,11 +1769,11 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverWithPathCoverageSupportAvailableException' => '/php-code-coverage/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => '/php-code-coverage/Node/AbstractNode.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Builder' => '/php-code-coverage/Node/Builder.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\CrapIndex' => '/php-code-coverage/Node/CrapIndex.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Directory' => '/php-code-coverage/Node/Directory.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\File' => '/php-code-coverage/Node/File.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Node\\Iterator' => '/php-code-coverage/Node/Iterator.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ParserException' => '/php-code-coverage/Exception/ParserException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Percentage' => '/php-code-coverage/Percentage.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ProcessedCodeCoverageData' => '/php-code-coverage/ProcessedCodeCoverageData.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\RawCodeCoverageData' => '/php-code-coverage/RawCodeCoverageData.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\ReflectionException' => '/php-code-coverage/Exception/ReflectionException.php', @@ -1794,19 +1802,18 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => '/php-code-coverage/Report/Xml/Totals.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => '/php-code-coverage/Report/Xml/Unit.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysisCacheNotConfiguredException' => '/php-code-coverage/Exception/StaticAnalysisCacheNotConfiguredException.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\Cache' => '/php-code-coverage/StaticAnalysis/Cache.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CacheWarmer' => '/php-code-coverage/StaticAnalysis/CacheWarmer.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingCoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingCoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingUncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingUncoveredFileAnalyser.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingFileAnalyser' => '/php-code-coverage/StaticAnalysis/CachingFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CodeUnitFindingVisitor' => '/php-code-coverage/StaticAnalysis/CodeUnitFindingVisitor.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/CoveredFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ExecutableLinesFindingVisitor' => '/php-code-coverage/StaticAnalysis/ExecutableLinesFindingVisitor.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\FileAnalyser' => '/php-code-coverage/StaticAnalysis/FileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\IgnoredLinesFindingVisitor' => '/php-code-coverage/StaticAnalysis/IgnoredLinesFindingVisitor.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingCoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingCoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingUncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingUncoveredFileAnalyser.php', - 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\UncoveredFileAnalyser' => '/php-code-coverage/StaticAnalysis/UncoveredFileAnalyser.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingFileAnalyser' => '/php-code-coverage/StaticAnalysis/ParsingFileAnalyser.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\TestIdMissingException' => '/php-code-coverage/Exception/TestIdMissingException.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => '/php-code-coverage/Exception/UnintentionallyCoveredCodeException.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\DirectoryCouldNotBeCreatedException' => '/php-code-coverage/Exception/DirectoryCouldNotBeCreatedException.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\Filesystem' => '/php-code-coverage/Util/Filesystem.php', + 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Util\\Percentage' => '/php-code-coverage/Util/Percentage.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\Version' => '/php-code-coverage/Version.php', 'PHPUnit\\SebastianBergmann\\CodeCoverage\\XmlException' => '/php-code-coverage/Exception/XmlException.php', 'PHPUnit\\SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => '/sebastian-code-unit-reverse-lookup/Wizard.php', @@ -1910,26 +1917,28 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\SebastianBergmann\\Timer\\ResourceUsageFormatter' => '/php-timer/ResourceUsageFormatter.php', 'PHPUnit\\SebastianBergmann\\Timer\\TimeSinceStartOfRequestNotAvailableException' => '/php-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.php', 'PHPUnit\\SebastianBergmann\\Timer\\Timer' => '/php-timer/Timer.php', - 'PHPUnit\\SebastianBergmann\\Type\\CallableType' => '/sebastian-type/CallableType.php', + 'PHPUnit\\SebastianBergmann\\Type\\CallableType' => '/sebastian-type/type/CallableType.php', 'PHPUnit\\SebastianBergmann\\Type\\Exception' => '/sebastian-type/exception/Exception.php', - 'PHPUnit\\SebastianBergmann\\Type\\FalseType' => '/sebastian-type/FalseType.php', - 'PHPUnit\\SebastianBergmann\\Type\\GenericObjectType' => '/sebastian-type/GenericObjectType.php', - 'PHPUnit\\SebastianBergmann\\Type\\IterableType' => '/sebastian-type/IterableType.php', - 'PHPUnit\\SebastianBergmann\\Type\\LogicException' => '/sebastian-type/exception/LogicException.php', - 'PHPUnit\\SebastianBergmann\\Type\\MixedType' => '/sebastian-type/MixedType.php', - 'PHPUnit\\SebastianBergmann\\Type\\NullType' => '/sebastian-type/NullType.php', - 'PHPUnit\\SebastianBergmann\\Type\\ObjectType' => '/sebastian-type/ObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\FalseType' => '/sebastian-type/type/FalseType.php', + 'PHPUnit\\SebastianBergmann\\Type\\GenericObjectType' => '/sebastian-type/type/GenericObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\IntersectionType' => '/sebastian-type/type/IntersectionType.php', + 'PHPUnit\\SebastianBergmann\\Type\\IterableType' => '/sebastian-type/type/IterableType.php', + 'PHPUnit\\SebastianBergmann\\Type\\MixedType' => '/sebastian-type/type/MixedType.php', + 'PHPUnit\\SebastianBergmann\\Type\\NeverType' => '/sebastian-type/type/NeverType.php', + 'PHPUnit\\SebastianBergmann\\Type\\NullType' => '/sebastian-type/type/NullType.php', + 'PHPUnit\\SebastianBergmann\\Type\\ObjectType' => '/sebastian-type/type/ObjectType.php', + 'PHPUnit\\SebastianBergmann\\Type\\Parameter' => '/sebastian-type/Parameter.php', 'PHPUnit\\SebastianBergmann\\Type\\ReflectionMapper' => '/sebastian-type/ReflectionMapper.php', 'PHPUnit\\SebastianBergmann\\Type\\RuntimeException' => '/sebastian-type/exception/RuntimeException.php', - 'PHPUnit\\SebastianBergmann\\Type\\SimpleType' => '/sebastian-type/SimpleType.php', - 'PHPUnit\\SebastianBergmann\\Type\\StaticType' => '/sebastian-type/StaticType.php', - 'PHPUnit\\SebastianBergmann\\Type\\Type' => '/sebastian-type/Type.php', + 'PHPUnit\\SebastianBergmann\\Type\\SimpleType' => '/sebastian-type/type/SimpleType.php', + 'PHPUnit\\SebastianBergmann\\Type\\StaticType' => '/sebastian-type/type/StaticType.php', + 'PHPUnit\\SebastianBergmann\\Type\\TrueType' => '/sebastian-type/type/TrueType.php', + 'PHPUnit\\SebastianBergmann\\Type\\Type' => '/sebastian-type/type/Type.php', 'PHPUnit\\SebastianBergmann\\Type\\TypeName' => '/sebastian-type/TypeName.php', - 'PHPUnit\\SebastianBergmann\\Type\\UnionType' => '/sebastian-type/UnionType.php', - 'PHPUnit\\SebastianBergmann\\Type\\UnknownType' => '/sebastian-type/UnknownType.php', - 'PHPUnit\\SebastianBergmann\\Type\\VoidType' => '/sebastian-type/VoidType.php', + 'PHPUnit\\SebastianBergmann\\Type\\UnionType' => '/sebastian-type/type/UnionType.php', + 'PHPUnit\\SebastianBergmann\\Type\\UnknownType' => '/sebastian-type/type/UnknownType.php', + 'PHPUnit\\SebastianBergmann\\Type\\VoidType' => '/sebastian-type/type/VoidType.php', 'PHPUnit\\SebastianBergmann\\Version' => '/sebastian-version/Version.php', - 'PHPUnit\\Symfony\\Polyfill\\Ctype\\Ctype' => '/symfony-polyfill-ctype/Ctype.php', 'PHPUnit\\TextUI\\CliArguments\\Builder' => '/phpunit/TextUI/CliArguments/Builder.php', 'PHPUnit\\TextUI\\CliArguments\\Configuration' => '/phpunit/TextUI/CliArguments/Configuration.php', 'PHPUnit\\TextUI\\CliArguments\\Exception' => '/phpunit/TextUI/CliArguments/Exception.php', @@ -2035,6 +2044,7 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\Util\\Annotation\\DocBlock' => '/phpunit/Util/Annotation/DocBlock.php', 'PHPUnit\\Util\\Annotation\\Registry' => '/phpunit/Util/Annotation/Registry.php', 'PHPUnit\\Util\\Blacklist' => '/phpunit/Util/Blacklist.php', + 'PHPUnit\\Util\\Cloner' => '/phpunit/Util/Cloner.php', 'PHPUnit\\Util\\Color' => '/phpunit/Util/Color.php', 'PHPUnit\\Util\\ErrorHandler' => '/phpunit/Util/ErrorHandler.php', 'PHPUnit\\Util\\Exception' => '/phpunit/Util/Exception.php', @@ -2051,6 +2061,7 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => '/phpunit/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => '/phpunit/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => '/phpunit/Util/Printer.php', + 'PHPUnit\\Util\\Reflection' => '/phpunit/Util/Reflection.php', 'PHPUnit\\Util\\RegularExpression' => '/phpunit/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => '/phpunit/Util/Test.php', 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => '/phpunit/Util/TestDox/CliTestDoxPrinter.php', @@ -2130,10 +2141,15 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\CallableString' => '/phpdocumentor-type-resolver/PseudoTypes/CallableString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\False_' => '/phpdocumentor-type-resolver/PseudoTypes/False_.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\HtmlEscapedString' => '/phpdocumentor-type-resolver/PseudoTypes/HtmlEscapedString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\IntegerRange' => '/phpdocumentor-type-resolver/PseudoTypes/IntegerRange.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\List_' => '/phpdocumentor-type-resolver/PseudoTypes/List_.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\LiteralString' => '/phpdocumentor-type-resolver/PseudoTypes/LiteralString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\LowercaseString' => '/phpdocumentor-type-resolver/PseudoTypes/LowercaseString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NegativeInteger' => '/phpdocumentor-type-resolver/PseudoTypes/NegativeInteger.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NonEmptyLowercaseString' => '/phpdocumentor-type-resolver/PseudoTypes/NonEmptyLowercaseString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NonEmptyString' => '/phpdocumentor-type-resolver/PseudoTypes/NonEmptyString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\NumericString' => '/phpdocumentor-type-resolver/PseudoTypes/NumericString.php', + 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\Numeric_' => '/phpdocumentor-type-resolver/PseudoTypes/Numeric_.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\PositiveInteger' => '/phpdocumentor-type-resolver/PseudoTypes/PositiveInteger.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\TraitString' => '/phpdocumentor-type-resolver/PseudoTypes/TraitString.php', 'PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\True_' => '/phpdocumentor-type-resolver/PseudoTypes/True_.php', @@ -2264,7 +2280,7 @@ foreach (['PHPUnit\\DeepCopy\\DeepCopy' => '/myclabs-deep-copy/DeepCopy/DeepCopy 'Prophecy\\Prophet' => '/phpspec-prophecy/Prophecy/Prophet.php', 'Prophecy\\Util\\ExportUtil' => '/phpspec-prophecy/Prophecy/Util/ExportUtil.php', 'Prophecy\\Util\\StringUtil' => '/phpspec-prophecy/Prophecy/Util/StringUtil.php'] as $file) { - require_once 'phar://phpunit-9.5.10.phar' . $file; + require_once 'phar://phpunit-9.5.26.phar' . $file; } require __PHPUNIT_PHAR_ROOT__ . '/phpunit/Framework/Assert/Functions.php'; @@ -2276,48287 +2292,50049 @@ if ($execute) { exit; } + if (isset($printSbom)) { + print file_get_contents(__PHPUNIT_PHAR_ROOT__ . '/sbom.xml'); + + exit; + } + unset($execute); PHPUnit\TextUI\Command::main(); } __HALT_COMPILER(); ?> -ghphpunit-9.5.10.pharwebmozart-assert/Assert.php NavA8Xwebmozart-assert/Mixin.php( Na(G Zlwebmozart-assert/LICENSE< Na<t}-webmozart-assert/InvalidArgumentException.phpb NabA)phpdocumentor-reflection-common/Fqsen.php Na?2phpdocumentor-reflection-common/ProjectFactory.php_ Na_j\"(phpdocumentor-reflection-common/File.php NaI),phpdocumentor-reflection-common/Location.php Na=(+phpdocumentor-reflection-common/Project.php NaJ+phpdocumentor-reflection-common/Element.php  Na %'phpdocumentor-reflection-common/LICENSE9 Na9*2Ȑphp-text-template/Template.php( Na( 1php-text-template/exceptions/RuntimeException.php NaYm'*php-text-template/exceptions/Exception.phpy Nayn9php-text-template/exceptions/InvalidArgumentException.php NaaMphp-text-template/LICENSE Nau.sebastian-object-reflector/ObjectReflector.php NaƤ(sebastian-object-reflector/Exception.php NaЬۤ7sebastian-object-reflector/InvalidArgumentException.php Na -Mphp-invoker/Invoker.php  Na ODphp-invoker/exceptions/ProcessControlExtensionNotLoadedException.php Na +php-invoker/exceptions/TimeoutException.php Na.$php-invoker/exceptions/Exception.phpr Narvvdu8phar-io-version/constraints/OrVersionConstraintGroup.php NaM%1phar-io-version/constraints/VersionConstraint.php NaeDq6phar-io-version/constraints/ExactVersionConstraint.php> Na>tΤ4phar-io-version/constraints/AnyVersionConstraint.phpR NaR #>phar-io-version/constraints/SpecificMajorVersionConstraint.php Na`9q:Fphar-io-version/constraints/SpecificMajorAndMinorVersionConstraint.php NaɍEphar-io-version/constraints/GreaterThanOrEqualToVersionConstraint.php NaVU9phar-io-version/constraints/AbstractVersionConstraint.php NaxB9phar-io-version/constraints/AndVersionConstraintGroup.php NaY!phar-io-version/VersionNumber.php NaO1+phar-io-version/VersionConstraintParser.php< Na< ͮ*Ҥphar-io-version/Version.php Na<&?phar-io-version/exceptions/InvalidPreReleaseSuffixException.php NaҵDphar-io-version/exceptions/UnsupportedVersionConstraintException.php Na9(phar-io-version/exceptions/Exception.php Na$eb:phar-io-version/exceptions/NoPreReleaseSuffixException.php NaT46phar-io-version/exceptions/InvalidVersionException.php Na4/S$phar-io-version/PreReleaseSuffix.phpf Naf#Ѥphar-io-version/LICENSE1 Na1>:*phar-io-version/VersionConstraintValue.phpH - NaH -F{~4*sebastian-object-enumerator/Enumerator.php Nax})sebastian-object-enumerator/Exception.php Na}Ȥ8sebastian-object-enumerator/InvalidArgumentException.php Naâ phpunit.xsd F Na Fuynikic-php-parser/LICENSE Na*)nikic-php-parser/PhpParser/NodeDumper.phpd NadY l3nikic-php-parser/PhpParser/Internal/TokenStream.php%# Na%# D0nikic-php-parser/PhpParser/Internal/DiffElem.php7 Na7$.nikic-php-parser/PhpParser/Internal/Differ.php- Na-^Anikic-php-parser/PhpParser/Internal/PrintableNewAnonClassNode.php$ Na$'c4nikic-php-parser/PhpParser/PrettyPrinterAbstract.php Naޤ*nikic-php-parser/PhpParser/Comment/Doc.phpx Naxp0nikic-php-parser/PhpParser/Builder/Function_.phpF NaFux1nikic-php-parser/PhpParser/Builder/Interface_.php Na -nikic-php-parser/PhpParser/Builder/Method.php Na}-nikic-php-parser/PhpParser/Builder/Class_.php Na -nikic-php-parser/PhpParser/Builder/Trait_.php Nakj3nikic-php-parser/PhpParser/Builder/FunctionLike.php NaZqe/nikic-php-parser/PhpParser/Builder/TraitUse.phpW NaWL@2nikic-php-parser/PhpParser/Builder/Declaration.php NaE7,nikic-php-parser/PhpParser/Builder/Param.php Na ֤+nikic-php-parser/PhpParser/Builder/Use_.php Nas1nikic-php-parser/PhpParser/Builder/Namespace_.php: Na:ˆp1nikic-php-parser/PhpParser/Builder/ClassConst.phpm Nam z/nikic-php-parser/PhpParser/Builder/Property.php| Na|O 9nikic-php-parser/PhpParser/Builder/TraitUseAdaptation.php NaUVx$nikic-php-parser/PhpParser/Error.php NaQZ7nikic-php-parser/PhpParser/NodeVisitor/NameResolver.phpm& Nam&f[&Bnikic-php-parser/PhpParser/NodeVisitor/ParentConnectingVisitor.phpu NauME9nikic-php-parser/PhpParser/NodeVisitor/CloningVisitor.php Na"WJ9nikic-php-parser/PhpParser/NodeVisitor/FindingVisitor.php NaB>nikic-php-parser/PhpParser/NodeVisitor/FirstFindingVisitor.php Nam4Ť@nikic-php-parser/PhpParser/NodeVisitor/NodeConnectingVisitor.php Nau -ä,nikic-php-parser/PhpParser/ParserFactory.php Na -~&1nikic-php-parser/PhpParser/ConstExprEvaluator.php$ Na$.( Hnikic-php-parser/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php Na*#Dnikic-php-parser/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php NaLFLnikic-php-parser/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php  Na *§o@nikic-php-parser/PhpParser/Lexer/TokenEmulator/TokenEmulator.phpu NauD4hDnikic-php-parser/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php NarBnikic-php-parser/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php NaI}Bnikic-php-parser/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php NajHnikic-php-parser/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php Na0kEnikic-php-parser/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php Nac/Lnikic-php-parser/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.phpn Nan 1Rnikic-php-parser/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.phpV NaVBnikic-php-parser/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php Na Hnikic-php-parser/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php Na:&E.nikic-php-parser/PhpParser/Lexer/Emulative.php" Na"A*nikic-php-parser/PhpParser/NameContext.php% Na%G-&nikic-php-parser/PhpParser/Comment.php NaA-nikic-php-parser/PhpParser/ParserAbstract.php Na;nikic-php-parser/PhpParser/ConstExprEvaluationException.php_ Na_I 2nikic-php-parser/PhpParser/NodeVisitorAbstract.php Na-nikic-php-parser/PhpParser/BuilderFactory.php( Na(5nikic-php-parser/PhpParser/NodeTraverserInterface.php| Na|Ś &nikic-php-parser/PhpParser/Builder.php Na64nikic-php-parser/PhpParser/ErrorHandler/Throwing.php NaS}<6nikic-php-parser/PhpParser/ErrorHandler/Collecting.php Na&Ȥ)nikic-php-parser/PhpParser/NodeFinder.php Na #nikic-php-parser/PhpParser/Node.php Nayݗ-nikic-php-parser/PhpParser/BuilderHelpers.php_# Na_#,15nikic-php-parser/PhpParser/PrettyPrinter/Standard.phpx NaxLu+nikic-php-parser/PhpParser/ErrorHandler.php/ Na/#\-nikic-php-parser/PhpParser/Node/Attribute.phpH NaHhqK5nikic-php-parser/PhpParser/Node/VarLikeIdentifier.php Na&'nikic-php-parser/PhpParser/Node/Arg.php0 Na0q H-nikic-php-parser/PhpParser/Node/UnionType.php NajViΤ/nikic-php-parser/PhpParser/Node/ComplexType.phpS NaS(0nikic-php-parser/PhpParser/Node/FunctionLike.php Na4ͤ5nikic-php-parser/PhpParser/Node/Stmt/HaltCompiler.php Na];/nikic-php-parser/PhpParser/Node/Stmt/Break_.php Na֤,nikic-php-parser/PhpParser/Node/Stmt/If_.php: Na:u٤/nikic-php-parser/PhpParser/Node/Stmt/While_.phpE NaEա2nikic-php-parser/PhpParser/Node/Stmt/Function_.php - Na -̤0nikic-php-parser/PhpParser/Node/Stmt/ElseIf_.phpI NaIEä3nikic-php-parser/PhpParser/Node/Stmt/Interface_.php NaL/Ǥ/nikic-php-parser/PhpParser/Node/Stmt/Class_.phpA NaAO1nikic-php-parser/PhpParser/Node/Stmt/GroupUse.php - Na -ߎ0|/nikic-php-parser/PhpParser/Node/Stmt/Throw_.php Na2nikic-php-parser/PhpParser/Node/Stmt/Continue_.php Na.nikic-php-parser/PhpParser/Node/Stmt/Else_.php Na|ä/nikic-php-parser/PhpParser/Node/Stmt/Trait_.php Na$v/nikic-php-parser/PhpParser/Node/Stmt/Unset_.php Na=oB.nikic-php-parser/PhpParser/Node/Stmt/Echo_.php Na͘Ƥ.nikic-php-parser/PhpParser/Node/Stmt/Label.php NaӤ,nikic-php-parser/PhpParser/Node/Stmt/Do_.phpB NaB -@7nikic-php-parser/PhpParser/Node/Stmt/DeclareDeclare.php Naƀ1nikic-php-parser/PhpParser/Node/Stmt/Finally_.php Na1AAnikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.phpA NaAdFnikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.phpZ NaZP֤0nikic-php-parser/PhpParser/Node/Stmt/Switch_.php5 Na5FFY1nikic-php-parser/PhpParser/Node/Stmt/TraitUse.php Nag,3nikic-php-parser/PhpParser/Node/Stmt/InlineHTML.php Na]2nikic-php-parser/PhpParser/Node/Stmt/ClassLike.php Na .nikic-php-parser/PhpParser/Node/Stmt/Goto_.php NaVyPn0nikic-php-parser/PhpParser/Node/Stmt/Return_.php NaͿ)e1nikic-php-parser/PhpParser/Node/Stmt/Declare_.php Na.. -2nikic-php-parser/PhpParser/Node/Stmt/StaticVar.php Na.nikic-php-parser/PhpParser/Node/Stmt/Case_.phpl Nalu/nikic-php-parser/PhpParser/Node/Stmt/Const_.php Na,nikic-php-parser/PhpParser/Node/Stmt/Nop.php@ Na@G0nikic-php-parser/PhpParser/Node/Stmt/Static_.php Na1nikic-php-parser/PhpParser/Node/Stmt/TryCatch.php$ Na$W1nikic-php-parser/PhpParser/Node/Stmt/EnumCase.php NajD1nikic-php-parser/PhpParser/Node/Stmt/Foreach_.phpo Nao9/nikic-php-parser/PhpParser/Node/Stmt/UseUse.phpd Nadb.nikic-php-parser/PhpParser/Node/Stmt/Enum_.php= Na=dA3nikic-php-parser/PhpParser/Node/Stmt/Expression.php NaRK4nikic-php-parser/PhpParser/Node/Stmt/ClassMethod.php Na(( -/nikic-php-parser/PhpParser/Node/Stmt/Catch_.php| Na|*V>0nikic-php-parser/PhpParser/Node/Stmt/Global_.php Na9nikic-php-parser/PhpParser/Node/Stmt/PropertyProperty.php Na҉-nikic-php-parser/PhpParser/Node/Stmt/For_.php> Na>NQ-nikic-php-parser/PhpParser/Node/Stmt/Use_.phpl Nal9=|3nikic-php-parser/PhpParser/Node/Stmt/Namespace_.php Na㹀3nikic-php-parser/PhpParser/Node/Stmt/ClassConst.php NaeX?ͤ1nikic-php-parser/PhpParser/Node/Stmt/Property.phpO - NaO -=;nikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation.php Naa8>nikic-php-parser/PhpParser/Node/Expr/NullsafePropertyFetch.php Na /N2nikic-php-parser/PhpParser/Node/Expr/ArrayItem.phpx Nax| 2/nikic-php-parser/PhpParser/Node/Expr/PreDec.php Natg-nikic-php-parser/PhpParser/Node/Expr/Cast.phpA NaA:Vs/nikic-php-parser/PhpParser/Node/Expr/Clone_.php NaW2nikic-php-parser/PhpParser/Node/Expr/UnaryPlus.php Nae̤1nikic-php-parser/PhpParser/Node/Expr/AssignOp.php Na,1nikic-php-parser/PhpParser/Node/Expr/CallLike.php& Na&KS0/nikic-php-parser/PhpParser/Node/Expr/Throw_.php Na ?/nikic-php-parser/PhpParser/Node/Expr/Yield_.php\ Na\ /nikic-php-parser/PhpParser/Node/Expr/Match_.php NaW /nikic-php-parser/PhpParser/Node/Expr/Print_.php NanX1nikic-php-parser/PhpParser/Node/Expr/Variable.php NamJr.nikic-php-parser/PhpParser/Node/Expr/Error.php Naa\0nikic-php-parser/PhpParser/Node/Expr/PostDec.php Naw:.nikic-php-parser/PhpParser/Node/Expr/Eval_.php Na356-nikic-php-parser/PhpParser/Node/Expr/New_.php NaiĤ6nikic-php-parser/PhpParser/Node/Expr/ArrowFunction.php Na K5nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Mul.phpB NaB|;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/BooleanOr.phpO NaOeӸ9nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Smaller.phpJ NaJf<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/LogicalXor.phpR NaR4e5nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Pow.phpC NaC;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Identical.phpP NaP";nikic-php-parser/PhpParser/Node/Expr/BinaryOp/BitwiseOr.phpN NaN_|7nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Equal.phpG NaGݙʤ;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Spaceship.phpP NaPHƉ.6nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Plus.phpD NaD' ,@nikic-php-parser/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.phpY NaY⍤<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.phpP NaP6L6<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/BitwiseXor.phpP NaP~Ƥ;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/LogicalOr.phpO NaO@<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/BooleanAnd.phpQ NaQ5v<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/LogicalAnd.phpR NaRi9nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Greater.phpJ NaJ4ͤ5nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Div.phpB NaBi<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/ShiftRight.phpQ NaQǤ:nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Coalesce.phpM NaMY ;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/ShiftLeft.phpO NaOQ#5nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Mod.phpB NaBʤ:nikic-php-parser/PhpParser/Node/Expr/BinaryOp/NotEqual.phpM NaM7nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Minus.phpF NaF$Lˤ>nikic-php-parser/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpV NaVh< -@nikic-php-parser/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.phpY NaY^ز8nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Concat.phpH NaH @q0nikic-php-parser/PhpParser/Node/Expr/PostInc.php Naᦦ!0nikic-php-parser/PhpParser/Node/Expr/Ternary.php NaQͤ;nikic-php-parser/PhpParser/Node/Expr/NullsafeMethodCall.php NaY,l6nikic-php-parser/PhpParser/Node/Expr/PropertyFetch.php Naɾ4nikic-php-parser/PhpParser/Node/Expr/Instanceof_.phpa Naa< /nikic-php-parser/PhpParser/Node/Expr/Empty_.php Na'6nikic-php-parser/PhpParser/Node/Expr/ArrayDimFetch.phpM NaMIY8nikic-php-parser/PhpParser/Node/Expr/ClassConstFetch.php Na3nikic-php-parser/PhpParser/Node/Expr/ConstFetch.php Na޶%3nikic-php-parser/PhpParser/Node/Expr/StaticCall.phpe Nae1nikic-php-parser/PhpParser/Node/Expr/FuncCall.php3 Na3%A.nikic-php-parser/PhpParser/Node/Expr/List_.php Na/nikic-php-parser/PhpParser/Node/Expr/Isset_.php NaI5nikic-php-parser/PhpParser/Node/Expr/AssignOp/Mul.php Naπ/5nikic-php-parser/PhpParser/Node/Expr/AssignOp/Pow.php NayV;nikic-php-parser/PhpParser/Node/Expr/AssignOp/BitwiseOr.php Na;6nikic-php-parser/PhpParser/Node/Expr/AssignOp/Plus.php Na&|<nikic-php-parser/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php Nau<nikic-php-parser/PhpParser/Node/Expr/AssignOp/BitwiseXor.php NalϚ5nikic-php-parser/PhpParser/Node/Expr/AssignOp/Div.php NaYP -<nikic-php-parser/PhpParser/Node/Expr/AssignOp/ShiftRight.php Nas*:nikic-php-parser/PhpParser/Node/Expr/AssignOp/Coalesce.php Naq,;nikic-php-parser/PhpParser/Node/Expr/AssignOp/ShiftLeft.php Na5nikic-php-parser/PhpParser/Node/Expr/AssignOp/Mod.php Na]10Y7nikic-php-parser/PhpParser/Node/Expr/AssignOp/Minus.php Na隤8nikic-php-parser/PhpParser/Node/Expr/AssignOp/Concat.php Na6nikic-php-parser/PhpParser/Node/Expr/ErrorSuppress.php Nag3nikic-php-parser/PhpParser/Node/Expr/BooleanNot.php NaDC/nikic-php-parser/PhpParser/Node/Expr/Array_.php8 Na8;p3nikic-php-parser/PhpParser/Node/Expr/UnaryMinus.php NalA0nikic-php-parser/PhpParser/Node/Expr/Closure.php - Na -U;4nikic-php-parser/PhpParser/Node/Expr/Cast/Double.php Na>,4nikic-php-parser/PhpParser/Node/Expr/Cast/Unset_.php Na1Ӥ2nikic-php-parser/PhpParser/Node/Expr/Cast/Int_.php Nac5nikic-php-parser/PhpParser/Node/Expr/Cast/String_.php Na3nikic-php-parser/PhpParser/Node/Expr/Cast/Bool_.php Na V]S4nikic-php-parser/PhpParser/Node/Expr/Cast/Array_.php NaI|5nikic-php-parser/PhpParser/Node/Expr/Cast/Object_.php Na/nikic-php-parser/PhpParser/Node/Expr/PreInc.php NaYä/nikic-php-parser/PhpParser/Node/Expr/Assign.php Na1nikic-php-parser/PhpParser/Node/Expr/BinaryOp.phpo Nao Ѥ2nikic-php-parser/PhpParser/Node/Expr/YieldFrom.php Naw8<nikic-php-parser/PhpParser/Node/Expr/StaticPropertyFetch.php& Na&ܐ2nikic-php-parser/PhpParser/Node/Expr/AssignRef.phpH NaHE`ob3nikic-php-parser/PhpParser/Node/Expr/MethodCall.phpO NaODWX3nikic-php-parser/PhpParser/Node/Expr/BitwiseNot.php Na~'3nikic-php-parser/PhpParser/Node/Expr/ClosureUse.php Nah2nikic-php-parser/PhpParser/Node/Expr/ShellExec.php Nahy.nikic-php-parser/PhpParser/Node/Expr/Exit_.php Na1nikic-php-parser/PhpParser/Node/Expr/Include_.php Nai0nikic-php-parser/PhpParser/Node/NullableType.php Na6C5nikic-php-parser/PhpParser/Node/Scalar/MagicConst.phpc Nac,xG=nikic-php-parser/PhpParser/Node/Scalar/EncapsedStringPart.php Na%2nikic-php-parser/PhpParser/Node/Scalar/LNumber.php Na v6훤2nikic-php-parser/PhpParser/Node/Scalar/String_.phpR NaRؽ?nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Function_.php] Na]HnY<nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Method.phpV NaVΤ<nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Class_.phpT NaT㨘X9nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Dir.phpM NaMal<nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Trait_.phpT NaTd:nikic-php-parser/PhpParser/Node/Scalar/MagicConst/File.phpP NaP#:nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Line.phpP NaPM4@nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Namespace_.php` Na`>3nikic-php-parser/PhpParser/Node/Scalar/Encapsed.php NaRU2nikic-php-parser/PhpParser/Node/Scalar/DNumber.phpQ NaQ_ 7nikic-php-parser/PhpParser/Node/VariadicPlaceholder.php NaP(nikic-php-parser/PhpParser/Node/Name.php Na-g2nikic-php-parser/PhpParser/Node/AttributeGroup.php NaB9*nikic-php-parser/PhpParser/Node/Const_.php Nayh1nikic-php-parser/PhpParser/Node/Name/Relative.php NaǛEf7nikic-php-parser/PhpParser/Node/Name/FullyQualified.php Na .nikic-php-parser/PhpParser/Node/Identifier.php NaJa*nikic-php-parser/PhpParser/Node/Scalar.phpk Nak,ߤ(nikic-php-parser/PhpParser/Node/Expr.php Nah傤(nikic-php-parser/PhpParser/Node/Stmt.php Nav2/)nikic-php-parser/PhpParser/Node/Param.phpb NabMߤ,nikic-php-parser/PhpParser/Node/MatchArm.php Na+m64nikic-php-parser/PhpParser/Node/IntersectionType.php Nao,nikic-php-parser/PhpParser/NodeTraverser.php]' Na]'TG:Ƥ%nikic-php-parser/PhpParser/Parser.php} Na}{*nikic-php-parser/PhpParser/NodeVisitor.php Na3*nikic-php-parser/PhpParser/JsonDecoder.php Na xg+nikic-php-parser/PhpParser/NodeAbstract.phpZ NaZ׻@$nikic-php-parser/PhpParser/Lexer.phpuZ NauZ~g*nikic-php-parser/PhpParser/Parser/Php7.phpA NaA,nikic-php-parser/PhpParser/Parser/Tokens.php& Na&<.nikic-php-parser/PhpParser/Parser/Multiple.php NasF)7*nikic-php-parser/PhpParser/Parser/Php5.php* Na* [Qphp-timer/Timer.php NacAɤ$php-timer/ResourceUsageFormatter.php NaPھphp-timer/Duration.php - Na -tXy/php-timer/exceptions/NoActiveTimerException.php Nal٤Ephp-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.php Na$b"php-timer/exceptions/Exception.phpn Naniuۤphp-timer/LICENSE Nax#sebastian-global-state/Snapshot.php) Na)'sebastian-global-state/CodeExporter.php Na j!դ#sebastian-global-state/Restorer.php NaGJ 6sebastian-global-state/exceptions/RuntimeException.php Na;/sebastian-global-state/exceptions/Exception.phpy NayJsebastian-global-state/LICENSE Na&sebastian-global-state/ExcludeList.php - Na -R{:myclabs-deep-copy/DeepCopy/Matcher/PropertyNameMatcher.php NaR6myclabs-deep-copy/DeepCopy/Matcher/PropertyMatcher.php Na=Bv.myclabs-deep-copy/DeepCopy/Matcher/Matcher.php NaDmyclabs-deep-copy/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php NaP:myclabs-deep-copy/DeepCopy/Matcher/PropertyTypeMatcher.php2 Na2ZQͤ:myclabs-deep-copy/DeepCopy/Exception/PropertyException.php Na3Gz7myclabs-deep-copy/DeepCopy/Exception/CloneException.php Na {ˤ(myclabs-deep-copy/DeepCopy/deep_copy.php Narx3myclabs-deep-copy/DeepCopy/Filter/ReplaceFilter.php Na3myclabs-deep-copy/DeepCopy/Filter/SetNullFilter.php Na䊉0myclabs-deep-copy/DeepCopy/Filter/KeepFilter.php NaYn,myclabs-deep-copy/DeepCopy/Filter/Filter.phpd NadMGmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php - Na -DgLmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php Na)$Bmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php Na)6myclabs-deep-copy/DeepCopy/TypeMatcher/TypeMatcher.php NaQBŤ7myclabs-deep-copy/DeepCopy/TypeFilter/ReplaceFilter.php Naz4myclabs-deep-copy/DeepCopy/TypeFilter/TypeFilter.php NaVD?myclabs-deep-copy/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php Na^Amyclabs-deep-copy/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php Nav|Gmyclabs-deep-copy/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php NaT+;myclabs-deep-copy/DeepCopy/TypeFilter/ShallowCopyFilter.php NaؤAmyclabs-deep-copy/DeepCopy/TypeFilter/Date/DateIntervalFilter.php NaƤ:myclabs-deep-copy/DeepCopy/Reflection/ReflectionHelper.php5 Na5ى'myclabs-deep-copy/DeepCopy/DeepCopy.php Nawv˾myclabs-deep-copy/LICENSE5 Na5ʭ˄sebastian-type/UnknownType.php- Na-}Y$sebastian-type/GenericObjectType.php Na+Msebastian-type/UnionType.phpO NaO Esebastian-type/MixedType.php Na -HKsebastian-type/CallableType.php4 Na4/psebastian-type/VoidType.phpQ NaQsebastian-type/NullType.php< Na<4sebastian-type/ObjectType.php Nałsebastian-type/SimpleType.php  Na Ťsebastian-type/TypeName.php~ Na~ h=xsebastian-type/StaticType.php@ Na@`sebastian-type/FalseType.php NamZ#sebastian-type/ReflectionMapper.php Na )R sebastian-type/IterableType.php Na+sebastian-type/Type.php Na [sebastian-type/LICENSE  Na  +Τ-sebastian-type/exception/RuntimeException.php Na%+sebastian-type/exception/LogicException.php} Na}/[H&sebastian-type/exception/Exception.phpj Najbᮧsebastian-version/Version.php Na ƪsebastian-version/LICENSE NaZschema/8.5.xsdB NaB贅schema/9.2.xsdB NaB|lAphpdocumentor-reflection-docblock/DocBlock/StandardTagFactory.php0 Na0<9phpdocumentor-reflection-docblock/DocBlock/Serializer.php NaiB9phpdocumentor-reflection-docblock/DocBlock/TagFactory.php NaJMx7phpdocumentor-reflection-docblock/DocBlock/Tags/See.php Na  -;phpdocumentor-reflection-docblock/DocBlock/Tags/Generic.phpx Nax Bn>phpdocumentor-reflection-docblock/DocBlock/Tags/InvalidTag.php& Na&,l:phpdocumentor-reflection-docblock/DocBlock/Tags/Source.phpH NaH Q%΋:phpdocumentor-reflection-docblock/DocBlock/Tags/Method.php NaBD7=phpdocumentor-reflection-docblock/DocBlock/Tags/Formatter.php Na}BܤAphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Url.php Nac[]Cphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Fqsen.php, Na,%8Gphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Reference.php Na ;phpdocumentor-reflection-docblock/DocBlock/Tags/BaseTag.php NaZr:phpdocumentor-reflection-docblock/DocBlock/Tags/Author.php Na Lt9phpdocumentor-reflection-docblock/DocBlock/Tags/Since.phpW - NaW -1>Rphpdocumentor-reflection-docblock/DocBlock/Tags/Formatter/PassthroughFormatter.php NaP~Lphpdocumentor-reflection-docblock/DocBlock/Tags/Formatter/AlignFormatter.phpq Naq@phpdocumentor-reflection-docblock/DocBlock/Tags/PropertyRead.php Na Q'H?phpdocumentor-reflection-docblock/DocBlock/Tags/TagWithType.php Na;uHphpdocumentor-reflection-docblock/DocBlock/Tags/Factory/StaticMethod.php Na.ͤAphpdocumentor-reflection-docblock/DocBlock/Tags/PropertyWrite.php Na };phpdocumentor-reflection-docblock/DocBlock/Tags/Version.php - Na -@S>phpdocumentor-reflection-docblock/DocBlock/Tags/Deprecated.php - Na -}CO8phpdocumentor-reflection-docblock/DocBlock/Tags/Link.php Na8phpdocumentor-reflection-docblock/DocBlock/Tags/Var_.php Na oԤ;phpdocumentor-reflection-docblock/DocBlock/Tags/Return_.php% Na%5ä:phpdocumentor-reflection-docblock/DocBlock/Tags/Covers.phpg - Nag -w8;phpdocumentor-reflection-docblock/DocBlock/Tags/Example.php NaUVb9phpdocumentor-reflection-docblock/DocBlock/Tags/Param.php NaW8phpdocumentor-reflection-docblock/DocBlock/Tags/Uses.php> - Na> +jphpunit-9.5.26.pharLdoctrine-instantiator/Doctrine/Instantiator/Exception/ExceptionInterface.phpp[cbRdoctrine-instantiator/Doctrine/Instantiator/Exception/InvalidArgumentException.phpp[cRdoctrine-instantiator/Doctrine/Instantiator/Exception/UnexpectedValueException.php:p[c:_Y%[<doctrine-instantiator/Doctrine/Instantiator/Instantiator.phpp[c5Edoctrine-instantiator/Doctrine/Instantiator/InstantiatorInterface.php p[c LȤdoctrine-instantiator/LICENSE$p[c$ +͂ manifest.txtp[c¤'myclabs-deep-copy/DeepCopy/DeepCopy.php>p[c>ʼY7myclabs-deep-copy/DeepCopy/Exception/CloneException.phpp[c {ˤ:myclabs-deep-copy/DeepCopy/Exception/PropertyException.phpp[c3GzGmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php +p[c +DgLmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.phpp[c)$Bmyclabs-deep-copy/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.phpp[c),myclabs-deep-copy/DeepCopy/Filter/Filter.phpdp[cdM0myclabs-deep-copy/DeepCopy/Filter/KeepFilter.phpp[cYn3myclabs-deep-copy/DeepCopy/Filter/ReplaceFilter.phpp[c3myclabs-deep-copy/DeepCopy/Filter/SetNullFilter.phpp[c䊉Dmyclabs-deep-copy/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.phpp[cpr.myclabs-deep-copy/DeepCopy/Matcher/Matcher.phpp[c6myclabs-deep-copy/DeepCopy/Matcher/PropertyMatcher.phpp[c=Bv:myclabs-deep-copy/DeepCopy/Matcher/PropertyNameMatcher.phpp[cR:myclabs-deep-copy/DeepCopy/Matcher/PropertyTypeMatcher.php2p[c2ZQͤ:myclabs-deep-copy/DeepCopy/Reflection/ReflectionHelper.php5p[c5ىAmyclabs-deep-copy/DeepCopy/TypeFilter/Date/DateIntervalFilter.phpp[cƤ7myclabs-deep-copy/DeepCopy/TypeFilter/ReplaceFilter.phpp[cz;myclabs-deep-copy/DeepCopy/TypeFilter/ShallowCopyFilter.phpp[cؤ?myclabs-deep-copy/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.phpp[c^Amyclabs-deep-copy/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.phpp[cv|Gmyclabs-deep-copy/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.phpp[cT+4myclabs-deep-copy/DeepCopy/TypeFilter/TypeFilter.phpp[cVD6myclabs-deep-copy/DeepCopy/TypeMatcher/TypeMatcher.phpp[cQBŤ(myclabs-deep-copy/DeepCopy/deep_copy.phpp[crxmyclabs-deep-copy/LICENSE5p[c5ʭ˄nikic-php-parser/LICENSEp[c*&nikic-php-parser/PhpParser/Builder.phpp[c61nikic-php-parser/PhpParser/Builder/ClassConst.phpm p[cm z-nikic-php-parser/PhpParser/Builder/Class_.phpp[cc32nikic-php-parser/PhpParser/Builder/Declaration.phpp[cE7/nikic-php-parser/PhpParser/Builder/EnumCase.php^p[c^ɤ,nikic-php-parser/PhpParser/Builder/Enum_.php p[c #3nikic-php-parser/PhpParser/Builder/FunctionLike.phpp[cZqe0nikic-php-parser/PhpParser/Builder/Function_.phpFp[cFux1nikic-php-parser/PhpParser/Builder/Interface_.php p[c -nikic-php-parser/PhpParser/Builder/Method.phpp[c}1nikic-php-parser/PhpParser/Builder/Namespace_.php:p[c:ˆp,nikic-php-parser/PhpParser/Builder/Param.php p[c ֤/nikic-php-parser/PhpParser/Builder/Property.php|p[c|O /nikic-php-parser/PhpParser/Builder/TraitUse.phpWp[cWL@9nikic-php-parser/PhpParser/Builder/TraitUseAdaptation.phpp[cUVx-nikic-php-parser/PhpParser/Builder/Trait_.phpp[ckj+nikic-php-parser/PhpParser/Builder/Use_.phpp[cs-nikic-php-parser/PhpParser/BuilderFactory.php+p[c+޶-nikic-php-parser/PhpParser/BuilderHelpers.php$p[c$6N&nikic-php-parser/PhpParser/Comment.phpp[cA*nikic-php-parser/PhpParser/Comment/Doc.phpxp[cxp;nikic-php-parser/PhpParser/ConstExprEvaluationException.php_p[c_I 1nikic-php-parser/PhpParser/ConstExprEvaluator.phpl%p[cl%evQ$nikic-php-parser/PhpParser/Error.phpp[cQZ+nikic-php-parser/PhpParser/ErrorHandler.php/p[c/#\6nikic-php-parser/PhpParser/ErrorHandler/Collecting.phpp[c&Ȥ4nikic-php-parser/PhpParser/ErrorHandler/Throwing.phpp[cS}<0nikic-php-parser/PhpParser/Internal/DiffElem.php7p[c7$.nikic-php-parser/PhpParser/Internal/Differ.php-p[c-^Anikic-php-parser/PhpParser/Internal/PrintableNewAnonClassNode.php$p[c$'c3nikic-php-parser/PhpParser/Internal/TokenStream.php%#p[c%# D*nikic-php-parser/PhpParser/JsonDecoder.php p[c xg$nikic-php-parser/PhpParser/Lexer.phpZp[cZSnikic-php-parser/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpVp[cVh< +6nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Plus.phpDp[cD' ,5nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Pow.phpCp[cC;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/ShiftLeft.phpOp[cOQ#<nikic-php-parser/PhpParser/Node/Expr/BinaryOp/ShiftRight.phpQp[cQǤ9nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Smaller.phpJp[cJf@nikic-php-parser/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.phpYp[cY⍤;nikic-php-parser/PhpParser/Node/Expr/BinaryOp/Spaceship.phpPp[cPHƉ.3nikic-php-parser/PhpParser/Node/Expr/BitwiseNot.phpp[c~'3nikic-php-parser/PhpParser/Node/Expr/BooleanNot.phpp[cDC1nikic-php-parser/PhpParser/Node/Expr/CallLike.php&p[c&KS0-nikic-php-parser/PhpParser/Node/Expr/Cast.phpAp[cA:Vs4nikic-php-parser/PhpParser/Node/Expr/Cast/Array_.phpp[cI|3nikic-php-parser/PhpParser/Node/Expr/Cast/Bool_.phpp[c V]S4nikic-php-parser/PhpParser/Node/Expr/Cast/Double.phpp[c>,2nikic-php-parser/PhpParser/Node/Expr/Cast/Int_.phpp[cc5nikic-php-parser/PhpParser/Node/Expr/Cast/Object_.phpp[c5nikic-php-parser/PhpParser/Node/Expr/Cast/String_.phpp[c4nikic-php-parser/PhpParser/Node/Expr/Cast/Unset_.phpp[c1Ӥ8nikic-php-parser/PhpParser/Node/Expr/ClassConstFetch.phpp[c/nikic-php-parser/PhpParser/Node/Expr/Clone_.phpp[cW0nikic-php-parser/PhpParser/Node/Expr/Closure.php +p[c +U;3nikic-php-parser/PhpParser/Node/Expr/ClosureUse.phpp[ch3nikic-php-parser/PhpParser/Node/Expr/ConstFetch.phpp[c޶%/nikic-php-parser/PhpParser/Node/Expr/Empty_.phpp[c'.nikic-php-parser/PhpParser/Node/Expr/Error.phpp[ca\6nikic-php-parser/PhpParser/Node/Expr/ErrorSuppress.phpp[cg.nikic-php-parser/PhpParser/Node/Expr/Eval_.phpp[c356.nikic-php-parser/PhpParser/Node/Expr/Exit_.phpp[c1nikic-php-parser/PhpParser/Node/Expr/FuncCall.php3p[c3%A1nikic-php-parser/PhpParser/Node/Expr/Include_.phpp[ci4nikic-php-parser/PhpParser/Node/Expr/Instanceof_.phpap[ca< /nikic-php-parser/PhpParser/Node/Expr/Isset_.phpp[cI.nikic-php-parser/PhpParser/Node/Expr/List_.phpp[c/nikic-php-parser/PhpParser/Node/Expr/Match_.phpp[cW 3nikic-php-parser/PhpParser/Node/Expr/MethodCall.phpOp[cODWX-nikic-php-parser/PhpParser/Node/Expr/New_.phpp[ciĤ;nikic-php-parser/PhpParser/Node/Expr/NullsafeMethodCall.phpfp[cfɤ>nikic-php-parser/PhpParser/Node/Expr/NullsafePropertyFetch.phpp[c /N0nikic-php-parser/PhpParser/Node/Expr/PostDec.phpp[cw:0nikic-php-parser/PhpParser/Node/Expr/PostInc.phpp[cᦦ!/nikic-php-parser/PhpParser/Node/Expr/PreDec.phpp[ctg/nikic-php-parser/PhpParser/Node/Expr/PreInc.phpp[cYä/nikic-php-parser/PhpParser/Node/Expr/Print_.phpp[cnX6nikic-php-parser/PhpParser/Node/Expr/PropertyFetch.phpp[cɾ2nikic-php-parser/PhpParser/Node/Expr/ShellExec.phpp[chy3nikic-php-parser/PhpParser/Node/Expr/StaticCall.phpep[ce<nikic-php-parser/PhpParser/Node/Expr/StaticPropertyFetch.php&p[c&ܐ0nikic-php-parser/PhpParser/Node/Expr/Ternary.phpp[cQͤ/nikic-php-parser/PhpParser/Node/Expr/Throw_.phpp[c ?3nikic-php-parser/PhpParser/Node/Expr/UnaryMinus.phpp[clA2nikic-php-parser/PhpParser/Node/Expr/UnaryPlus.phpp[ce̤1nikic-php-parser/PhpParser/Node/Expr/Variable.phpp[cmJr2nikic-php-parser/PhpParser/Node/Expr/YieldFrom.phpp[cw8/nikic-php-parser/PhpParser/Node/Expr/Yield_.php\p[c\ 0nikic-php-parser/PhpParser/Node/FunctionLike.phpp[c4ͤ.nikic-php-parser/PhpParser/Node/Identifier.phpp[cJa4nikic-php-parser/PhpParser/Node/IntersectionType.phpp[co,nikic-php-parser/PhpParser/Node/MatchArm.phpp[c+m6(nikic-php-parser/PhpParser/Node/Name.php p[c Q酯7nikic-php-parser/PhpParser/Node/Name/FullyQualified.phpp[c 1nikic-php-parser/PhpParser/Node/Name/Relative.phpp[cǛEf0nikic-php-parser/PhpParser/Node/NullableType.phpp[c6C)nikic-php-parser/PhpParser/Node/Param.phpbp[cbMߤ*nikic-php-parser/PhpParser/Node/Scalar.phpkp[ck,ߤ2nikic-php-parser/PhpParser/Node/Scalar/DNumber.phpap[cak3nikic-php-parser/PhpParser/Node/Scalar/Encapsed.phpp[cRU=nikic-php-parser/PhpParser/Node/Scalar/EncapsedStringPart.phpp[c%2nikic-php-parser/PhpParser/Node/Scalar/LNumber.php p[c z5nikic-php-parser/PhpParser/Node/Scalar/MagicConst.phpcp[cc,xG<nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Class_.phpTp[cT㨘X9nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Dir.phpMp[cMal:nikic-php-parser/PhpParser/Node/Scalar/MagicConst/File.phpPp[cP#?nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Function_.php]p[c]HnY:nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Line.phpPp[cPM4<nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Method.phpVp[cVΤ@nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Namespace_.php`p[c`><nikic-php-parser/PhpParser/Node/Scalar/MagicConst/Trait_.phpTp[cTd2nikic-php-parser/PhpParser/Node/Scalar/String_.phpqp[cqT$Q(nikic-php-parser/PhpParser/Node/Stmt.phpp[cv2//nikic-php-parser/PhpParser/Node/Stmt/Break_.phpp[c֤.nikic-php-parser/PhpParser/Node/Stmt/Case_.phplp[clu/nikic-php-parser/PhpParser/Node/Stmt/Catch_.php|p[c|*V>3nikic-php-parser/PhpParser/Node/Stmt/ClassConst.phpp[ceX?ͤ2nikic-php-parser/PhpParser/Node/Stmt/ClassLike.php p[c 04nikic-php-parser/PhpParser/Node/Stmt/ClassMethod.phpp[cX/nikic-php-parser/PhpParser/Node/Stmt/Class_.phpup[cu_ļ/nikic-php-parser/PhpParser/Node/Stmt/Const_.phpp[c2nikic-php-parser/PhpParser/Node/Stmt/Continue_.phpp[c7nikic-php-parser/PhpParser/Node/Stmt/DeclareDeclare.phpp[cƀ1nikic-php-parser/PhpParser/Node/Stmt/Declare_.phpp[c.. +,nikic-php-parser/PhpParser/Node/Stmt/Do_.phpBp[cB +@.nikic-php-parser/PhpParser/Node/Stmt/Echo_.phpp[c͘Ƥ0nikic-php-parser/PhpParser/Node/Stmt/ElseIf_.phpIp[cIEä.nikic-php-parser/PhpParser/Node/Stmt/Else_.phpp[c|ä1nikic-php-parser/PhpParser/Node/Stmt/EnumCase.phpp[cjD.nikic-php-parser/PhpParser/Node/Stmt/Enum_.php=p[c=dA3nikic-php-parser/PhpParser/Node/Stmt/Expression.phpp[cRK1nikic-php-parser/PhpParser/Node/Stmt/Finally_.phpp[c1A-nikic-php-parser/PhpParser/Node/Stmt/For_.php>p[c>NQ1nikic-php-parser/PhpParser/Node/Stmt/Foreach_.phpop[co92nikic-php-parser/PhpParser/Node/Stmt/Function_.php, +p[c, +nL0nikic-php-parser/PhpParser/Node/Stmt/Global_.phpp[c.nikic-php-parser/PhpParser/Node/Stmt/Goto_.phpp[cVyPn1nikic-php-parser/PhpParser/Node/Stmt/GroupUse.php +p[c +ߎ0|5nikic-php-parser/PhpParser/Node/Stmt/HaltCompiler.phpp[c];,nikic-php-parser/PhpParser/Node/Stmt/If_.php:p[c:u٤3nikic-php-parser/PhpParser/Node/Stmt/InlineHTML.phpp[c]3nikic-php-parser/PhpParser/Node/Stmt/Interface_.phpp[cL/Ǥ.nikic-php-parser/PhpParser/Node/Stmt/Label.phpp[cӤ3nikic-php-parser/PhpParser/Node/Stmt/Namespace_.phpp[c㹀,nikic-php-parser/PhpParser/Node/Stmt/Nop.php@p[c@G1nikic-php-parser/PhpParser/Node/Stmt/Property.phpO +p[cO +=9nikic-php-parser/PhpParser/Node/Stmt/PropertyProperty.phpp[c҉0nikic-php-parser/PhpParser/Node/Stmt/Return_.phpp[cͿ)e2nikic-php-parser/PhpParser/Node/Stmt/StaticVar.phpp[c0nikic-php-parser/PhpParser/Node/Stmt/Static_.phpp[c0nikic-php-parser/PhpParser/Node/Stmt/Switch_.php5p[c5FFY/nikic-php-parser/PhpParser/Node/Stmt/Throw_.phpp[c1nikic-php-parser/PhpParser/Node/Stmt/TraitUse.phpp[cg,;nikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation.phpp[ca8Anikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.phpAp[cAdFnikic-php-parser/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.phpZp[cZP֤/nikic-php-parser/PhpParser/Node/Stmt/Trait_.phpp[c$v1nikic-php-parser/PhpParser/Node/Stmt/TryCatch.php$p[c$W/nikic-php-parser/PhpParser/Node/Stmt/Unset_.phpp[c=oB/nikic-php-parser/PhpParser/Node/Stmt/UseUse.phpdp[cdb-nikic-php-parser/PhpParser/Node/Stmt/Use_.phplp[cl9=|/nikic-php-parser/PhpParser/Node/Stmt/While_.phpEp[cEա-nikic-php-parser/PhpParser/Node/UnionType.phpp[c^m5nikic-php-parser/PhpParser/Node/VarLikeIdentifier.phpp[c&7nikic-php-parser/PhpParser/Node/VariadicPlaceholder.phpp[cP+nikic-php-parser/PhpParser/NodeAbstract.phpZp[cZ׻@)nikic-php-parser/PhpParser/NodeDumper.phpdp[cdY l)nikic-php-parser/PhpParser/NodeFinder.php p[c ,nikic-php-parser/PhpParser/NodeTraverser.php]'p[c]'TG:Ƥ5nikic-php-parser/PhpParser/NodeTraverserInterface.php|p[c|Ś *nikic-php-parser/PhpParser/NodeVisitor.phpp[c39nikic-php-parser/PhpParser/NodeVisitor/CloningVisitor.phpp[c"WJ9nikic-php-parser/PhpParser/NodeVisitor/FindingVisitor.phpp[cB>nikic-php-parser/PhpParser/NodeVisitor/FirstFindingVisitor.phpp[cm4Ť7nikic-php-parser/PhpParser/NodeVisitor/NameResolver.phpm&p[cm&f[&@nikic-php-parser/PhpParser/NodeVisitor/NodeConnectingVisitor.phpp[cu +äBnikic-php-parser/PhpParser/NodeVisitor/ParentConnectingVisitor.phpup[cuME2nikic-php-parser/PhpParser/NodeVisitorAbstract.phpp[c%nikic-php-parser/PhpParser/Parser.php}p[c}{.nikic-php-parser/PhpParser/Parser/Multiple.phpp[csF)7*nikic-php-parser/PhpParser/Parser/Php5.php*(p[c*(2l=*nikic-php-parser/PhpParser/Parser/Php7.phpSHp[cSHt55,nikic-php-parser/PhpParser/Parser/Tokens.php&p[c&<-nikic-php-parser/PhpParser/ParserAbstract.phpp[c6(,nikic-php-parser/PhpParser/ParserFactory.phpp[c +~&5nikic-php-parser/PhpParser/PrettyPrinter/Standard.phpp[c'4nikic-php-parser/PhpParser/PrettyPrinterAbstract.phpzp[cz>(object-enumerator/LICENSEp[cy{object-reflector/LICENSEp[c9vphar-io-manifest/LICENSE`p[c`p+phar-io-manifest/ManifestDocumentMapper.phpp[c:#phar-io-manifest/ManifestLoader.phpp[c.-a'phar-io-manifest/ManifestSerializer.phpp[crp:phar-io-manifest/exceptions/ElementCollectionException.phpp[c I)phar-io-manifest/exceptions/Exception.phpp[c?phar-io-manifest/exceptions/InvalidApplicationNameException.phpp[c:@>5phar-io-manifest/exceptions/InvalidEmailException.phpp[c<3phar-io-manifest/exceptions/InvalidUrlException.phpp[c 9phar-io-manifest/exceptions/ManifestDocumentException.phpp[c!P4@phar-io-manifest/exceptions/ManifestDocumentLoadingException.phpHp[cHǃ?phar-io-manifest/exceptions/ManifestDocumentMapperException.phpp[c:9z8phar-io-manifest/exceptions/ManifestElementException.phpp[cA47phar-io-manifest/exceptions/ManifestLoaderException.phpp[cD>'phar-io-manifest/values/Application.phpp[cI$ۤ+phar-io-manifest/values/ApplicationName.php;p[c;D"phar-io-manifest/values/Author.phpp[cF,phar-io-manifest/values/AuthorCollection.phpp[co4phar-io-manifest/values/AuthorCollectionIterator.php3p[c3џ,phar-io-manifest/values/BundledComponent.php@p[c@DP`6phar-io-manifest/values/BundledComponentCollection.php p[c ¾W6>phar-io-manifest/values/BundledComponentCollectionIterator.phpp[cVh0phar-io-manifest/values/CopyrightInformation.phpPp[cP ai!phar-io-manifest/values/Email.phpNp[cNZ&%phar-io-manifest/values/Extension.phpp[cq}#phar-io-manifest/values/Library.phpp[cO#phar-io-manifest/values/License.phpp[c&!o$phar-io-manifest/values/Manifest.php +p[c +=La3phar-io-manifest/values/PhpExtensionRequirement.phpp[c11phar-io-manifest/values/PhpVersionRequirement.phpp[cm?'phar-io-manifest/values/Requirement.phpp[cd1phar-io-manifest/values/RequirementCollection.phpp[cP9phar-io-manifest/values/RequirementCollectionIterator.phpjp[cjܭ: phar-io-manifest/values/Type.phpp[c=%phar-io-manifest/values/Url.phpp[c͚&phar-io-manifest/xml/AuthorElement.phprp[cr<0phar-io-manifest/xml/AuthorElementCollection.php,p[c,-'phar-io-manifest/xml/BundlesElement.phpSp[cSWN>)phar-io-manifest/xml/ComponentElement.phpyp[cyݤ3phar-io-manifest/xml/ComponentElementCollection.php5p[c5(\(phar-io-manifest/xml/ContainsElement.phpnp[cnf)phar-io-manifest/xml/CopyrightElement.phpp[c7*phar-io-manifest/xml/ElementCollection.phpp[c@ #phar-io-manifest/xml/ExtElement.php p[c y>-phar-io-manifest/xml/ExtElementCollection.php#p[c#E)phar-io-manifest/xml/ExtensionElement.php}p[c}0'phar-io-manifest/xml/LicenseElement.phpop[co%:')phar-io-manifest/xml/ManifestDocument.php + p[c + 4(phar-io-manifest/xml/ManifestElement.php4p[c4#phar-io-manifest/xml/PhpElement.phpp[cB:5(phar-io-manifest/xml/RequiresElement.php$p[c$>!phar-io-version/BuildMetaData.phpp[cphar-io-version/LICENSE&p[c&Ҫ $phar-io-version/PreReleaseSuffix.phpp[c:phar-io-version/Version.phpp[cu#+phar-io-version/VersionConstraintParser.phpT p[cT Ф*phar-io-version/VersionConstraintValue.phpH +p[cH +F{~4!phar-io-version/VersionNumber.phpp[cO19phar-io-version/constraints/AbstractVersionConstraint.phpp[cxB9phar-io-version/constraints/AndVersionConstraintGroup.phpp[cY4phar-io-version/constraints/AnyVersionConstraint.phpRp[cR #6phar-io-version/constraints/ExactVersionConstraint.phpp[c!Ephar-io-version/constraints/GreaterThanOrEqualToVersionConstraint.phpp[cVU8phar-io-version/constraints/OrVersionConstraintGroup.phpp[cM%Fphar-io-version/constraints/SpecificMajorAndMinorVersionConstraint.phpp[cɍ>phar-io-version/constraints/SpecificMajorVersionConstraint.phpp[c`9q:1phar-io-version/constraints/VersionConstraint.phpp[ceDq(phar-io-version/exceptions/Exception.phpp[c$eb?phar-io-version/exceptions/InvalidPreReleaseSuffixException.phpp[cҵ6phar-io-version/exceptions/InvalidVersionException.phpp[c4/S7phar-io-version/exceptions/NoBuildMetaDataException.phpp[c]:phar-io-version/exceptions/NoPreReleaseSuffixException.phpp[cT4Dphar-io-version/exceptions/UnsupportedVersionConstraintException.phpp[c9"php-code-coverage/CodeCoverage.phpBp[cBw#php-code-coverage/Driver/Driver.phpp[c3A'php-code-coverage/Driver/PcovDriver.phpJp[cJ)php-code-coverage/Driver/PhpdbgDriver.php^ +p[c^ +_2G%php-code-coverage/Driver/Selector.php p[c 6]*php-code-coverage/Driver/Xdebug2Driver.phpA p[cA *php-code-coverage/Driver/Xdebug3Driver.php p[c h*Jphp-code-coverage/Exception/BranchAndPathCoverageNotSupportedException.phpp[c77Fphp-code-coverage/Exception/DeadCodeDetectionNotSupportedException.phpp[cCphp-code-coverage/Exception/DirectoryCouldNotBeCreatedException.phpp[c)php-code-coverage/Exception/Exception.php}p[c}z8php-code-coverage/Exception/InvalidArgumentException.phpp[cK.nFphp-code-coverage/Exception/NoCodeCoverageDriverAvailableException.php/p[c/6R]php-code-coverage/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.phpap[ca"A/php-code-coverage/Exception/ParserException.phpp[c,/Dphp-code-coverage/Exception/PathExistsButIsNotDirectoryException.phpp[c.29php-code-coverage/Exception/PcovNotAvailableException.phpap[caj;php-code-coverage/Exception/PhpdbgNotAvailableException.php`p[c`3php-code-coverage/Exception/ReflectionException.phpp[ck)?php-code-coverage/Exception/ReportAlreadyFinalizedException.php:p[c:d%6Iphp-code-coverage/Exception/StaticAnalysisCacheNotConfiguredException.phpp[c}6php-code-coverage/Exception/TestIdMissingException.phpp[c +Cphp-code-coverage/Exception/UnintentionallyCoveredCodeException.php+p[c+Q_ª=php-code-coverage/Exception/WriteOperationFailedException.phpp[c(e;php-code-coverage/Exception/WrongXdebugVersionException.phpp[c Ȥ:php-code-coverage/Exception/Xdebug2NotEnabledException.phpfp[cf,':php-code-coverage/Exception/Xdebug3NotEnabledException.phpyp[cy<>;php-code-coverage/Exception/XdebugNotAvailableException.phpep[ceNG,php-code-coverage/Exception/XmlException.phpp[cWܤphp-code-coverage/Filter.php p[c 4php-code-coverage/LICENSEp[c?i'php-code-coverage/Node/AbstractNode.php:p[c:%^"php-code-coverage/Node/Builder.php p[c 2N$php-code-coverage/Node/CrapIndex.phpp[c# $php-code-coverage/Node/Directory.php +&p[c +&}php-code-coverage/Node/File.phpKp[cK{#php-code-coverage/Node/Iterator.phpp[cHk/php-code-coverage/ProcessedCodeCoverageData.php$p[c$')php-code-coverage/RawCodeCoverageData.phpp[c>}#php-code-coverage/Report/Clover.php'p[c'l4&php-code-coverage/Report/Cobertura.php0p[c0Ȇ̤#php-code-coverage/Report/Crap4j.phpp[cJ#D(php-code-coverage/Report/Html/Facade.php"p[c";ڤ*php-code-coverage/Report/Html/Renderer.phpU!p[cU!}4php-code-coverage/Report/Html/Renderer/Dashboard.phpC p[cC L+4php-code-coverage/Report/Html/Renderer/Directory.php p[c (/php-code-coverage/Report/Html/Renderer/File.phpՌp[cՌ=EBphp-code-coverage/Report/Html/Renderer/Template/branches.html.distp[ch2+Fphp-code-coverage/Report/Html/Renderer/Template/coverage_bar.html.dist'p[c'O}Mphp-code-coverage/Report/Html/Renderer/Template/coverage_bar_branch.html.dist'p[c'O}Ephp-code-coverage/Report/Html/Renderer/Template/css/bootstrap.min.cssxp[cxvX>php-code-coverage/Report/Html/Renderer/Template/css/custom.cssp[cAphp-code-coverage/Report/Html/Renderer/Template/css/nv.d3.min.cssX%p[cX%0,@php-code-coverage/Report/Html/Renderer/Template/css/octicons.cssXp[cX'#=php-code-coverage/Report/Html/Renderer/Template/css/style.cssp[cY Cphp-code-coverage/Report/Html/Renderer/Template/dashboard.html.distGp[cGlJphp-code-coverage/Report/Html/Renderer/Template/dashboard_branch.html.distGp[cGlCphp-code-coverage/Report/Html/Renderer/Template/directory.html.distp[cGMJphp-code-coverage/Report/Html/Renderer/Template/directory_branch.html.distjp[cjHHphp-code-coverage/Report/Html/Renderer/Template/directory_item.html.distAp[cAdsOphp-code-coverage/Report/Html/Renderer/Template/directory_item_branch.html.dist;p[c;mۤ>php-code-coverage/Report/Html/Renderer/Template/file.html.distp[cGd=rEphp-code-coverage/Report/Html/Renderer/Template/file_branch.html.dist p[c gCphp-code-coverage/Report/Html/Renderer/Template/file_item.html.distrp[cr/yJphp-code-coverage/Report/Html/Renderer/Template/file_item_branch.html.distlp[cl-Cphp-code-coverage/Report/Html/Renderer/Template/icons/file-code.svg0p[c0QUUHphp-code-coverage/Report/Html/Renderer/Template/icons/file-directory.svgp[cZCphp-code-coverage/Report/Html/Renderer/Template/js/bootstrap.min.jsp[cQU<<php-code-coverage/Report/Html/Renderer/Template/js/d3.min.jsPp[cPhb:php-code-coverage/Report/Html/Renderer/Template/js/file.jsp[cb䆤@php-code-coverage/Report/Html/Renderer/Template/js/jquery.min.js]p[c]2]?php-code-coverage/Report/Html/Renderer/Template/js/nv.d3.min.jsRp[cRphp-code-coverage/Report/Html/Renderer/Template/line.html.distp[c{?php-code-coverage/Report/Html/Renderer/Template/lines.html.distep[cedf Ephp-code-coverage/Report/Html/Renderer/Template/method_item.html.distp[cjפLphp-code-coverage/Report/Html/Renderer/Template/method_item_branch.html.distp[cyĎk?php-code-coverage/Report/Html/Renderer/Template/paths.html.distp[c*'ݤ php-code-coverage/Report/PHP.phpp[c$&a!php-code-coverage/Report/Text.php'p[c' 6H1php-code-coverage/Report/Xml/BuildInformation.php p[c T3e)php-code-coverage/Report/Xml/Coverage.php+p[c+9E*php-code-coverage/Report/Xml/Directory.phpp[cAf'php-code-coverage/Report/Xml/Facade.php"p[c"O}%php-code-coverage/Report/Xml/File.php+p[c+g׃'php-code-coverage/Report/Xml/Method.phpWp[cW ʤ%php-code-coverage/Report/Xml/Node.php3p[c3(php-code-coverage/Report/Xml/Project.phpfp[cfPe'php-code-coverage/Report/Xml/Report.php p[c HC'php-code-coverage/Report/Xml/Source.phpzp[cz'1&php-code-coverage/Report/Xml/Tests.phpp[c'php-code-coverage/Report/Xml/Totals.phpp[c:6%php-code-coverage/Report/Xml/Unit.phpp[cY0php-code-coverage/StaticAnalysis/CacheWarmer.php)p[c) ۤ8php-code-coverage/StaticAnalysis/CachingFileAnalyser.phpp[cZ*;php-code-coverage/StaticAnalysis/CodeUnitFindingVisitor.php_&p[c_&mqiBphp-code-coverage/StaticAnalysis/ExecutableLinesFindingVisitor.php$p[c$39Ӑ1php-code-coverage/StaticAnalysis/FileAnalyser.phpp[cJ?php-code-coverage/StaticAnalysis/IgnoredLinesFindingVisitor.php p[c 8php-code-coverage/StaticAnalysis/ParsingFileAnalyser.phpp[c}%php-code-coverage/Util/Filesystem.phpp[c%php-code-coverage/Util/Percentage.phpp[cphp-code-coverage/Version.phpp[c y[֤php-file-iterator/Facade.php% +p[c% +Τphp-file-iterator/Factory.phpp[cg ,php-file-iterator/Iterator.phpZ p[cZ C܎php-file-iterator/LICENSEp[co:php-invoker/Invoker.phpp[c+L$php-invoker/exceptions/Exception.phprp[crvvduDphp-invoker/exceptions/ProcessControlExtensionNotLoadedException.phpp[c +php-invoker/exceptions/TimeoutException.phpp[c.php-text-template/LICENSEp[cuphp-text-template/Template.php( p[c( *php-text-template/exceptions/Exception.phpyp[cyn9php-text-template/exceptions/InvalidArgumentException.phpp[caM1php-text-template/exceptions/RuntimeException.phpp[cYm'php-timer/Duration.php +p[c +tXyphp-timer/LICENSEp[cx$php-timer/ResourceUsageFormatter.phpp[cPھphp-timer/Timer.phpp[ccAɤ"php-timer/exceptions/Exception.phpnp[cniuۤ/php-timer/exceptions/NoActiveTimerException.phpp[cl٤Ephp-timer/exceptions/TimeSinceStartOfRequestNotAvailableException.phpp[c$b+phpdocumentor-reflection-common/Element.php p[c %(phpdocumentor-reflection-common/File.phpp[cI))phpdocumentor-reflection-common/Fqsen.phpp[c?'phpdocumentor-reflection-common/LICENSE9p[c9*2Ȑ,phpdocumentor-reflection-common/Location.phpp[c=(+phpdocumentor-reflection-common/Project.phpp[cJ2phpdocumentor-reflection-common/ProjectFactory.php_p[c_j\".phpdocumentor-reflection-docblock/DocBlock.phpp[cHx>$:phpdocumentor-reflection-docblock/DocBlock/Description.php p[c 54Aphpdocumentor-reflection-docblock/DocBlock/DescriptionFactory.phpp[cd=<phpdocumentor-reflection-docblock/DocBlock/ExampleFinder.php,p[c,ׯf9phpdocumentor-reflection-docblock/DocBlock/Serializer.php p[c ]Aphpdocumentor-reflection-docblock/DocBlock/StandardTagFactory.php0p[c0<2phpdocumentor-reflection-docblock/DocBlock/Tag.phpp[c9phpdocumentor-reflection-docblock/DocBlock/TagFactory.phpp[cJMx:phpdocumentor-reflection-docblock/DocBlock/Tags/Author.php p[c ;phpdocumentor-reflection-docblock/DocBlock/Tags/BaseTag.phpp[cZr:phpdocumentor-reflection-docblock/DocBlock/Tags/Covers.phpg +p[cg +w8>phpdocumentor-reflection-docblock/DocBlock/Tags/Deprecated.php +p[c +}CO;phpdocumentor-reflection-docblock/DocBlock/Tags/Example.phpp[calN@Hphpdocumentor-reflection-docblock/DocBlock/Tags/Factory/StaticMethod.phpp[c.ͤ=phpdocumentor-reflection-docblock/DocBlock/Tags/Formatter.phpp[c}BܤLphpdocumentor-reflection-docblock/DocBlock/Tags/Formatter/AlignFormatter.phpqp[cqRphpdocumentor-reflection-docblock/DocBlock/Tags/Formatter/PassthroughFormatter.phpp[cP~;phpdocumentor-reflection-docblock/DocBlock/Tags/Generic.phpx p[cx Bn>phpdocumentor-reflection-docblock/DocBlock/Tags/InvalidTag.php,p[c,Md88phpdocumentor-reflection-docblock/DocBlock/Tags/Link.phpp[cG:phpdocumentor-reflection-docblock/DocBlock/Tags/Method.phpp[cYKc9phpdocumentor-reflection-docblock/DocBlock/Tags/Param.phpp[cB<phpdocumentor-reflection-docblock/DocBlock/Tags/Property.php p[c |yCϤ@phpdocumentor-reflection-docblock/DocBlock/Tags/PropertyRead.php p[c #k:Aphpdocumentor-reflection-docblock/DocBlock/Tags/PropertyWrite.php p[c v Cphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Fqsen.php,p[c,%8Gphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Reference.phpp[c Aphpdocumentor-reflection-docblock/DocBlock/Tags/Reference/Url.phpp[cc[];phpdocumentor-reflection-docblock/DocBlock/Tags/Return_.phpp[c N7phpdocumentor-reflection-docblock/DocBlock/Tags/See.php p[c :e9phpdocumentor-reflection-docblock/DocBlock/Tags/Since.phpW +p[cW +1>:phpdocumentor-reflection-docblock/DocBlock/Tags/Source.php p[c [K?phpdocumentor-reflection-docblock/DocBlock/Tags/TagWithType.phpp[c;u:phpdocumentor-reflection-docblock/DocBlock/Tags/Throws.phpp[c"G8phpdocumentor-reflection-docblock/DocBlock/Tags/Uses.php> +p[c>  -:phpdocumentor-reflection-docblock/DocBlock/Tags/Throws.php Na"G<phpdocumentor-reflection-docblock/DocBlock/Tags/Property.php Na e_2phpdocumentor-reflection-docblock/DocBlock/Tag.php Na78^<phpdocumentor-reflection-docblock/DocBlock/ExampleFinder.php0 Na0ێAphpdocumentor-reflection-docblock/DocBlock/DescriptionFactory.php Na"L:phpdocumentor-reflection-docblock/DocBlock/Description.php Na 545phpdocumentor-reflection-docblock/DocBlockFactory.php$ Na$br+phpdocumentor-reflection-docblock/Utils.php@ Na@ $昤=phpdocumentor-reflection-docblock/Exception/PcreException.php NaϊWƤ.phpdocumentor-reflection-docblock/DocBlock.php; Na;%p)phpdocumentor-reflection-docblock/LICENSE8 Na8ʤ>phpdocumentor-reflection-docblock/DocBlockFactoryInterface.php Na)%ߤ/phpdocumentor-type-resolver/Types/Resource_.php NaŞX.phpdocumentor-type-resolver/Types/Compound.php Na>7-phpdocumentor-type-resolver/Types/Integer.phpj Najv,phpdocumentor-type-resolver/Types/Never_.php Naj+phpdocumentor-type-resolver/Types/Null_.phpx Naxs-phpdocumentor-type-resolver/Types/Parent_.php NaO!.+phpdocumentor-type-resolver/Types/Void_.php Nak-phpdocumentor-type-resolver/Types/Boolean.phpn NanrĤ.phpdocumentor-type-resolver/Types/ArrayKey.php Naʫ 4phpdocumentor-type-resolver/Types/ContextFactory.php7 Na7q⬤2phpdocumentor-type-resolver/Types/AbstractList.phpt Natt-phpdocumentor-type-resolver/Types/String_.phps NasH1phpdocumentor-type-resolver/Types/ClassString.php Na=t+phpdocumentor-type-resolver/Types/Self_.php NaoȤ,phpdocumentor-type-resolver/Types/Float_.phpm Nam)J-phpdocumentor-type-resolver/Types/Static_.php Na8-phpdocumentor-type-resolver/Types/Context.php Na ]Z,phpdocumentor-type-resolver/Types/Array_.php Na섍D-phpdocumentor-type-resolver/Types/Object_.php NawEhN/phpdocumentor-type-resolver/Types/Callable_.php{ Na{E0phpdocumentor-type-resolver/Types/Collection.php Na?,phpdocumentor-type-resolver/Types/Scalar.php Na0phpdocumentor-type-resolver/Types/Expression.php8 Na8g,phpdocumentor-type-resolver/Types/Mixed_.php Na3i4phpdocumentor-type-resolver/Types/AggregatedType.php - Na -Hɵ/phpdocumentor-type-resolver/Types/Iterable_.php? Na?Q82phpdocumentor-type-resolver/Types/Intersection.php NaUz$*phpdocumentor-type-resolver/Types/This.phpY NaY^?ֈ.phpdocumentor-type-resolver/Types/Nullable.phpR NaRCp\5phpdocumentor-type-resolver/Types/InterfaceString.php Na-phpdocumentor-type-resolver/FqsenResolver.php Naj^=phpdocumentor-type-resolver/PseudoTypes/HtmlEscapedString.phpg Nagwe2phpdocumentor-type-resolver/PseudoTypes/False_.php Nao䈤:phpdocumentor-type-resolver/PseudoTypes/NonEmptyString.phpa Naa²,7phpdocumentor-type-resolver/PseudoTypes/TraitString.phpZ NaZgC1phpdocumentor-type-resolver/PseudoTypes/True_.php Nal:phpdocumentor-type-resolver/PseudoTypes/CallableString.php` Na`ZCphpdocumentor-type-resolver/PseudoTypes/NonEmptyLowercaseString.phpt Nat)9phpdocumentor-type-resolver/PseudoTypes/NumericString.php^ Na^8M;phpdocumentor-type-resolver/PseudoTypes/LowercaseString.phpb Nab7 ;phpdocumentor-type-resolver/PseudoTypes/PositiveInteger.php^ Na^$phpdocumentor-type-resolver/Type.php Nab&*phpdocumentor-type-resolver/PseudoType.phpu Nau]\#phpdocumentor-type-resolver/LICENSE8 Na8ʤ,phpdocumentor-type-resolver/TypeResolver.phpJ NaJ!sebastian-environment/Console.php NauҤ)sebastian-environment/OperatingSystem.php Na^!sebastian-environment/Runtime.php Nar*usebastian-environment/LICENSE Nalm|*sebastian-comparator/ComparisonFailure.php Na %+sebastian-comparator/ResourceComparator.php NaJ)sebastian-comparator/DoubleComparator.php Naa*sebastian-comparator/NumericComparator.php Na I sebastian-comparator/Factory.php Na$/*sebastian-comparator/DOMNodeComparator.php Na 1i)sebastian-comparator/ObjectComparator.phpX NaX ׌)sebastian-comparator/ScalarComparator.php Na d+sebastian-comparator/DateTimeComparator.php Na KQ'sebastian-comparator/TypeComparator.php NacX\,sebastian-comparator/ExceptionComparator.php Na14sebastian-comparator/exceptions/RuntimeException.php NaV'-sebastian-comparator/exceptions/Exception.phpv NavEᵤ3sebastian-comparator/SplObjectStorageComparator.php Na?/#sebastian-comparator/Comparator.php Nat(sebastian-comparator/ArrayComparator.phpu NauEmhfsebastian-comparator/LICENSE  Na =(-sebastian-comparator/MockObjectComparator.php NaI+phar-io-manifest/ManifestDocumentMapper.php Na:?phar-io-manifest/exceptions/ManifestDocumentMapperException.php Na:9z3phar-io-manifest/exceptions/InvalidUrlException.php Na ?phar-io-manifest/exceptions/InvalidApplicationNameException.php Na:@>8phar-io-manifest/exceptions/ManifestElementException.php NaA47phar-io-manifest/exceptions/ManifestLoaderException.php NaD>9phar-io-manifest/exceptions/ManifestDocumentException.php Na!P45phar-io-manifest/exceptions/InvalidEmailException.php Na<)phar-io-manifest/exceptions/Exception.php Na@phar-io-manifest/exceptions/ManifestDocumentLoadingException.phpH NaHǃ:phar-io-manifest/exceptions/ElementCollectionException.php Na I'phar-io-manifest/ManifestSerializer.php Narp#phar-io-manifest/xml/ExtElement.php  Na y>)phar-io-manifest/xml/CopyrightElement.php Na7#phar-io-manifest/xml/PhpElement.php NaB:50phar-io-manifest/xml/AuthorElementCollection.php, Na,-*phar-io-manifest/xml/ElementCollection.php Na@ )phar-io-manifest/xml/ExtensionElement.php} Na}0'phar-io-manifest/xml/LicenseElement.phpo Nao%:'-phar-io-manifest/xml/ExtElementCollection.php# Na#E(phar-io-manifest/xml/RequiresElement.php$ Na$>'phar-io-manifest/xml/BundlesElement.phpS NaSWN>(phar-io-manifest/xml/ContainsElement.phpn Nanf)phar-io-manifest/xml/ManifestDocument.php - Na - 4)phar-io-manifest/xml/ComponentElement.phpy Nayݤ(phar-io-manifest/xml/ManifestElement.php4 Na4&phar-io-manifest/xml/AuthorElement.phpr Nar<3phar-io-manifest/xml/ComponentElementCollection.php5 Na5(\phar-io-manifest/LICENSE` Na`p$phar-io-manifest/values/Manifest.php - Na -=Laphar-io-manifest/values/Url.php Na͚4phar-io-manifest/values/AuthorCollectionIterator.php3 Na3џ"phar-io-manifest/values/Author.php NaF+phar-io-manifest/values/ApplicationName.php; Na;D'phar-io-manifest/values/Application.php NaI$ۤ1phar-io-manifest/values/PhpVersionRequirement.php Nam?#phar-io-manifest/values/License.php Na&!o#phar-io-manifest/values/Library.php NaO,phar-io-manifest/values/AuthorCollection.php Nao'phar-io-manifest/values/Requirement.php Nad%phar-io-manifest/values/Extension.php Naq}>phar-io-manifest/values/BundledComponentCollectionIterator.php NaVh1phar-io-manifest/values/RequirementCollection.php NaP9phar-io-manifest/values/RequirementCollectionIterator.phpj Najܭ:!phar-io-manifest/values/Email.phpN NaNZ& phar-io-manifest/values/Type.php Na=%6phar-io-manifest/values/BundledComponentCollection.php  Na ¾W63phar-io-manifest/values/PhpExtensionRequirement.php Na1,phar-io-manifest/values/BundledComponent.php@ Na@DP`0phar-io-manifest/values/CopyrightInformation.phpP NaP ai#phar-io-manifest/ManifestLoader.php Na.-a<sebastian-cli-parser/exceptions/AmbiguousOptionException.phpF NaFm\:sebastian-cli-parser/exceptions/UnknownOptionException.php? Na?vDGsebastian-cli-parser/exceptions/OptionDoesNotAllowArgumentException.php_ Na_|13-sebastian-cli-parser/exceptions/Exception.phpu NauӫJsebastian-cli-parser/exceptions/RequiredOptionArgumentMissingException.phph NahCsebastian-cli-parser/Parser.php NakMsebastian-cli-parser/LICENSE Nau$symfony-polyfill-ctype/bootstrap.php Na(S&symfony-polyfill-ctype/bootstrap80.phpR NaRL - symfony-polyfill-ctype/Ctype.php~ Na~JҤsymfony-polyfill-ctype/LICENSE) Na)`e03sebastian-complexity/Exception/RuntimeException.php NaCdW,sebastian-complexity/Exception/Exception.phpv Nav7#sebastian-complexity/Calculator.phpe Nae (68sebastian-complexity/Complexity/ComplexityCollection.php Nail.sebastian-complexity/Complexity/Complexity.phpQ NaQl@sebastian-complexity/Complexity/ComplexityCollectionIterator.php, Na,esebastian-complexity/LICENSE Na=ݤ=sebastian-complexity/Visitor/ComplexityCalculatingVisitor.php Na OGsebastian-complexity/Visitor/CyclomaticComplexityCalculatingVisitor.php  Na 7Yobject-reflector/LICENSE Na9v<sebastian-lines-of-code/Exception/NegativeValueException.php Na -ڤ>sebastian-lines-of-code/Exception/IllogicalValuesException.php NaG6sebastian-lines-of-code/Exception/RuntimeException.php NaK/sebastian-lines-of-code/Exception/Exception.phpz Naz aV#sebastian-lines-of-code/Counter.php NaH5'sebastian-lines-of-code/LinesOfCode.php Na fӤ/sebastian-lines-of-code/LineCountingVisitor.php Na~Asebastian-lines-of-code/LICENSE NabS~ manifest.txt Na.Rdoctrine-instantiator/Doctrine/Instantiator/Exception/UnexpectedValueException.php, Na,ALdoctrine-instantiator/Doctrine/Instantiator/Exception/ExceptionInterface.php NabRdoctrine-instantiator/Doctrine/Instantiator/Exception/InvalidArgumentException.php Na+<doctrine-instantiator/Doctrine/Instantiator/Instantiator.php Na=Edoctrine-instantiator/Doctrine/Instantiator/InstantiatorInterface.php Navhdoctrine-instantiator/LICENSE$ Na$ -͂4sebastian-resource-operations/ResourceOperations.php߲ Na߲%sebastian-resource-operations/LICENSE Na]<$sebastian-code-unit/FunctionUnit.php Na`2sebastian-code-unit/CodeUnitCollectionIterator.php; Na;Lʤsebastian-code-unit/Mapper.php- Na-#%sebastian-code-unit/InterfaceUnit.php Nac sebastian-code-unit/CodeUnit.php~% Na~%D){!sebastian-code-unit/TraitUnit.php NaXA+sebastian-code-unit/InterfaceMethodUnit.php NaǦ'sebastian-code-unit/ClassMethodUnit.php Na@[3sebastian-code-unit/exceptions/NoTraitException.php NaQ36sebastian-code-unit/exceptions/ReflectionException.php Na$,sebastian-code-unit/exceptions/Exception.phps Nastg;sebastian-code-unit/exceptions/InvalidCodeUnitException.php Na6-!sebastian-code-unit/ClassUnit.php NaF'sebastian-code-unit/TraitMethodUnit.php Naqzsebastian-code-unit/LICENSE  Na p*sebastian-code-unit/CodeUnitCollection.php NaJ.phpstorm.meta.php NaO;php-code-coverage/Exception/WrongXdebugVersionException.php Na Ȥ;php-code-coverage/Exception/PhpdbgNotAvailableException.php` Na`Fphp-code-coverage/Exception/NoCodeCoverageDriverAvailableException.php/ Na/6R,php-code-coverage/Exception/XmlException.php NaWܤ?php-code-coverage/Exception/ReportAlreadyFinalizedException.php: Na:d%6;php-code-coverage/Exception/XdebugNotAvailableException.phpe NaeNG=php-code-coverage/Exception/WriteOperationFailedException.php Na(eCphp-code-coverage/Exception/DirectoryCouldNotBeCreatedException.php NaC*eFphp-code-coverage/Exception/DeadCodeDetectionNotSupportedException.php Na:php-code-coverage/Exception/Xdebug2NotEnabledException.phpf Naf,'6php-code-coverage/Exception/TestIdMissingException.php Na -3php-code-coverage/Exception/ReflectionException.php Nak):php-code-coverage/Exception/Xdebug3NotEnabledException.phpy Nay<>]php-code-coverage/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.phpa Naa"A)php-code-coverage/Exception/Exception.php} Na}z9php-code-coverage/Exception/PcovNotAvailableException.phpa NaajDphp-code-coverage/Exception/PathExistsButIsNotDirectoryException.php Na.2Iphp-code-coverage/Exception/StaticAnalysisCacheNotConfiguredException.php Na}Cphp-code-coverage/Exception/UnintentionallyCoveredCodeException.php+ Na+Q_ªJphp-code-coverage/Exception/BranchAndPathCoverageNotSupportedException.php Na778php-code-coverage/Exception/InvalidArgumentException.php NaK.n/php-code-coverage/Exception/ParserException.php Na,/ php-code-coverage/Percentage.php Naݤphp-code-coverage/Filter.php Na }'php-code-coverage/Driver/PcovDriver.php Na#php-code-coverage/Driver/Driver.php Na3A*php-code-coverage/Driver/Xdebug3Driver.php Na TJ*php-code-coverage/Driver/Xdebug2Driver.phpO NaO =󛫤)php-code-coverage/Driver/PhpdbgDriver.php` - Na` -WH%php-code-coverage/Driver/Selector.php Na 6]?php-code-coverage/StaticAnalysis/CachingCoveredFileAnalyser.php - Na -Ȥ:php-code-coverage/StaticAnalysis/UncoveredFileAnalyser.php+ Na+B=?php-code-coverage/StaticAnalysis/ParsingCoveredFileAnalyser.phpF NaFiaBphp-code-coverage/StaticAnalysis/ExecutableLinesFindingVisitor.php - Na -zp{*php-code-coverage/StaticAnalysis/Cache.php6 Na6M`;php-code-coverage/StaticAnalysis/CodeUnitFindingVisitor.php Na8Aphp-code-coverage/StaticAnalysis/ParsingUncoveredFileAnalyser.phpo Nao 㭤Aphp-code-coverage/StaticAnalysis/CachingUncoveredFileAnalyser.php Nahf8php-code-coverage/StaticAnalysis/CoveredFileAnalyser.phpN NaN|֤?php-code-coverage/StaticAnalysis/IgnoredLinesFindingVisitor.php Na v٤0php-code-coverage/StaticAnalysis/CacheWarmer.phpB NaBJphp-code-coverage/Version.php NaCK(*"php-code-coverage/CodeCoverage.phpD NaDPL/php-code-coverage/ProcessedCodeCoverageData.php$ Na$'#php-code-coverage/Report/Crap4j.php Na+!php-code-coverage/Report/Text.php' Na' (php-code-coverage/Report/Html/Facade.php4 Na4k*php-code-coverage/Report/Html/Renderer.phpU! NaU!}>php-code-coverage/Report/Html/Renderer/Template/file.html.dist NaFMphp-code-coverage/Report/Html/Renderer/Template/coverage_bar_branch.html.dist' Na'O}Cphp-code-coverage/Report/Html/Renderer/Template/dashboard.html.distG NaGl?php-code-coverage/Report/Html/Renderer/Template/paths.html.dist Na*'ݤCphp-code-coverage/Report/Html/Renderer/Template/directory.html.dist NaGM?php-code-coverage/Report/Html/Renderer/Template/js/nv.d3.min.jsR NaRphp-code-coverage/Report/Html/Renderer/Template/line.html.dist Na{Hphp-code-coverage/Report/Html/Renderer/Template/directory_item.html.distA NaAdsFphp-code-coverage/Report/Html/Renderer/Template/coverage_bar.html.dist' Na'O}Ephp-code-coverage/Report/Html/Renderer/Template/method_item.html.dist Nas:>php-code-coverage/Report/Html/Renderer/Template/css/custom.css NaEphp-code-coverage/Report/Html/Renderer/Template/css/bootstrap.min.cssr Nar?l=php-code-coverage/Report/Html/Renderer/Template/css/style.css Na:@php-code-coverage/Report/Html/Renderer/Template/css/octicons.cssX NaX'#Aphp-code-coverage/Report/Html/Renderer/Template/css/nv.d3.min.cssX% NaX%0,Jphp-code-coverage/Report/Html/Renderer/Template/directory_branch.html.distj NajHBphp-code-coverage/Report/Html/Renderer/Template/branches.html.dist Nah2+Cphp-code-coverage/Report/Html/Renderer/Template/file_item.html.distt NatOphp-code-coverage/Report/Html/Renderer/Template/directory_item_branch.html.dist; Na;mۤJphp-code-coverage/Report/Html/Renderer/Template/dashboard_branch.html.distG NaGlCphp-code-coverage/Report/Html/Renderer/Template/icons/file-code.svg0 Na0QUUHphp-code-coverage/Report/Html/Renderer/Template/icons/file-directory.svg NaZ/php-code-coverage/Report/Html/Renderer/File.phpS NaSY4php-code-coverage/Report/Html/Renderer/Dashboard.phpC NaC L+4php-code-coverage/Report/Html/Renderer/Directory.php  Na ( php-code-coverage/Report/PHP.php Na>*\1php-code-coverage/Report/Xml/BuildInformation.php Na T3e'php-code-coverage/Report/Xml/Source.phpz Naz'1'php-code-coverage/Report/Xml/Facade.php! Na! P'php-code-coverage/Report/Xml/Method.phpW NaW ʤ'php-code-coverage/Report/Xml/Report.php Na HC%php-code-coverage/Report/Xml/File.php+ Na+g׃'php-code-coverage/Report/Xml/Totals.php Nak &php-code-coverage/Report/Xml/Tests.php Na%php-code-coverage/Report/Xml/Node.php3 Na3(php-code-coverage/Report/Xml/Project.phpf NafPe)php-code-coverage/Report/Xml/Coverage.php+ Na+9E*php-code-coverage/Report/Xml/Directory.php NaAf%php-code-coverage/Report/Xml/Unit.php NaY#php-code-coverage/Report/Clover.php' Na'-&php-code-coverage/Report/Cobertura.php0 Na0q)php-code-coverage/RawCodeCoverageData.php Na<php-code-coverage/CrapIndex.php NakS;php-code-coverage/Node/File.phpJ NaJK"php-code-coverage/Node/Builder.php Naq#php-code-coverage/Node/Iterator.php Na&$php-code-coverage/Node/Directory.phpK$ NaK$m''php-code-coverage/Node/AbstractNode.php Na5sphp-code-coverage/LICENSE Na\Tphp-code-coverage/Directory.php Na Hphpunit/Util/Xml.php Nax@phpunit/Util/GlobalState.php Na=Ut(phpunit/Util/InvalidDataSetException.php Na1 phpunit/Util/Log/JUnit.phpn* Nan*)LBphpunit/Util/Log/TeamCity.php& Na&cphpunit/Util/Blacklist.php Nasphpunit/Util/Filter.php Na l* phpunit/Util/Test.php] Na]phpunit/Util/FileLoader.php Na Mꖤ$phpunit/Util/XmlTestListRenderer.php Na Z,phpunit/Util/XdebugFilterScriptGenerator.phpx Nax=phpunit/Util/Filesystem.php Nafޤ*phpunit/Util/VersionComparisonOperator.php Na`,phpunit/Util/Color.php Naפ%phpunit/Util/TextTestListRenderer.php8 Na8>6"phpunit/Util/RegularExpression.php Na0uR)phpunit/Util/ErrorHandler.php Na`uΤphpunit/Util/Exception.php Na다,phpunit/Util/PHP/Template/TestCaseMethod.tpl Na mD+phpunit/Util/PHP/Template/TestCaseClass.tplp Nap 3 H݀*phpunit/Util/PHP/Template/PhptTestCase.tpl Na1&phpunit/Util/PHP/WindowsPhpProcess.php Na$&phpunit/Util/PHP/DefaultPhpProcess.phpz NazCp'phpunit/Util/PHP/AbstractPhpProcess.php& Na&{99phpunit/Util/Printer.php Na aVphpunit/Util/Type.php Na܌*phpunit/Util/Json.phpH NaH A$$phpunit/Util/Annotation/DocBlock.phpA NaA.$phpunit/Util/Annotation/Registry.phpL - NaL -I phpunit/Util/ExcludeList.php= Na=(1m!phpunit/Util/Xml/SchemaFinder.php NaSphpunit/Util/Xml/Validator.php NaVphpunit/Util/Xml/Loader.php Na ,?%phpunit/Util/Xml/ValidationResult.php Naxv:phpunit/Util/Xml/Exception.php NaӤ0phpunit/Util/Xml/FailedSchemaDetectionResult.php Na#S*phpunit/Util/Xml/SchemaDetectionResult.php Na4χz#phpunit/Util/Xml/SchemaDetector.php- Na-4phpunit/Util/Xml/SuccessfulSchemaDetectionResult.php' Na'g%phpunit/Util/Xml/SnapshotNodeList.php Na`6*phpunit/Util/TestDox/TextResultPrinter.php Naȹ!.'phpunit/Util/TestDox/NamePrettifier.php# Na#[֤*phpunit/Util/TestDox/CliTestDoxPrinter.php1* Na1*Dl&phpunit/Util/TestDox/ResultPrinter.php" Na"1q$)phpunit/Util/TestDox/XmlResultPrinter.php Nay'phpunit/Util/TestDox/TestDoxPrinter.php) Na)ڤ*phpunit/Util/TestDox/HtmlResultPrinter.php - Na -t&*phpunit/Runner/StandardTestSuiteLoader.php> Na> "u"phpunit/Runner/TestSuiteSorter.php, Na,kڤ"phpunit/Runner/TestSuiteLoader.php Naޤ"phpunit/Runner/TestResultCache.php NaK'phpunit/Runner/Extension/PharLoader.php Na R -phpunit/Runner/Extension/ExtensionHandler.phpz Naz 3Τ4phpunit/Runner/Filter/ExcludeGroupFilterIterator.phps Nas} -Z4phpunit/Runner/Filter/IncludeGroupFilterIterator.phpr NarP;AD!phpunit/Runner/Filter/Factory.php NadcΤ-phpunit/Runner/Filter/GroupFilterIterator.php Na=;,phpunit/Runner/Filter/NameFilterIterator.phpv Nav Zphpunit/Runner/Version.php Nakr3)phpunit/Runner/DefaultTestResultCache.php NaWphpunit/Runner/Exception.php NazZ*phpunit/Runner/Hook/AfterTestErrorHook.php+ Na+ fz,phpunit/Runner/Hook/AfterTestWarningHook.php/ Na/)pԤ,phpunit/Runner/Hook/AfterTestFailureHook.php/ Na/u./phpunit/Runner/Hook/AfterIncompleteTestHook.php5 Na5 ɤ,phpunit/Runner/Hook/AfterSkippedTestHook.php/ Na/7Ԥ+phpunit/Runner/Hook/TestListenerAdapter.php Na\6E*phpunit/Runner/Hook/AfterRiskyTestHook.php+ Na+5ÿ+phpunit/Runner/Hook/BeforeFirstTestHook.php Na?]{դ/phpunit/Runner/Hook/AfterSuccessfulTestHook.php$ Na$]O phpunit/Runner/Hook/TestHook.php NaaB&phpunit/Runner/Hook/BeforeTestHook.php NaR -%phpunit/Runner/Hook/AfterTestHook.php Na3uݤphpunit/Runner/Hook/Hook.php Nab)phpunit/Runner/Hook/AfterLastTestHook.php Na -"ܤ&phpunit/Runner/NullTestResultCache.php NaW<phpunit/Runner/PhptTestCase.phpWV NaWVT_y!phpunit/Runner/BaseTestRunner.php Na C +'phpunit/Runner/ResultCacheExtension.php< Na<6 _Aphpunit/TextUI/XmlConfiguration/Group/GroupCollectionIterator.phpA NaA8%9phpunit/TextUI/XmlConfiguration/Group/GroupCollection.php Nak0phpunit/TextUI/XmlConfiguration/Group/Groups.php Na/phpunit/TextUI/XmlConfiguration/Group/Group.php NaEL-phpunit/TextUI/XmlConfiguration/Generator.php NaF 0phpunit/TextUI/XmlConfiguration/Logging/Text.php Na-301phpunit/TextUI/XmlConfiguration/Logging/Junit.php Naz珥3phpunit/TextUI/XmlConfiguration/Logging/Logging.php Na d44phpunit/TextUI/XmlConfiguration/Logging/TeamCity.php Nam7phpunit/TextUI/XmlConfiguration/Logging/TestDox/Xml.php Nao8phpunit/TextUI/XmlConfiguration/Logging/TestDox/Html.php Na}78phpunit/TextUI/XmlConfiguration/Logging/TestDox/Text.php Na*JjJphpunit/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.phpo Nao\VBphpunit/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php NaV93phpunit/TextUI/XmlConfiguration/Filesystem/File.php NaEphpunit/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php7 Na7te7=phpunit/TextUI/XmlConfiguration/Filesystem/FileCollection.phpC NaC8phpunit/TextUI/XmlConfiguration/Filesystem/Directory.php NasO*phpunit/TextUI/XmlConfiguration/Loader.php̗ Na̗m_|`1phpunit/TextUI/XmlConfiguration/Configuration.php2 Na2N'Ƥ3phpunit/TextUI/XmlConfiguration/PHPUnit/PHPUnit.phpiC NaiC 65phpunit/TextUI/XmlConfiguration/PHPUnit/Extension.php Na -J.?phpunit/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php NaܔGphpunit/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.phpi Naio(7phpunit/TextUI/XmlConfiguration/TestSuite/TestSuite.php Na;phpunit/TextUI/XmlConfiguration/TestSuite/TestDirectory.php@ Na@)eIphpunit/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.phpi Nai #6phpunit/TextUI/XmlConfiguration/TestSuite/TestFile.php Na Ephpunit/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php Na4XAphpunit/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php NaCSHphpunit/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.phpG NaGwǤ@phpunit/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php_ Na_.+ޤMphpunit/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php NaypSphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php NajeKphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php NaQ4XAphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php Na ϕ=phpunit/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php Nac%=phpunit/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php Na}ݚ;phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php Na8;phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php NaN_A<phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php NaڴQ>phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php NacaѤ<phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php Na>phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php Na: Aphpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php Na"e-phpunit/TextUI/XmlConfiguration/Exception.php NaN5++phpunit/TextUI/XmlConfiguration/PHP/Php.php Na:phpunit/TextUI/XmlConfiguration/PHP/VariableCollection.php- Na-c0phpunit/TextUI/XmlConfiguration/PHP/Variable.php Na\M,Bphpunit/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php_ Na_4VDphpunit/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.phps NasiBt <phpunit/TextUI/XmlConfiguration/PHP/IniSettingCollection.phpM NaMB12phpunit/TextUI/XmlConfiguration/PHP/PhpHandler.phpy Nay]b0phpunit/TextUI/XmlConfiguration/PHP/Constant.php4 Na4,-qBphpunit/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php_ Na_K:phpunit/TextUI/XmlConfiguration/PHP/ConstantCollection.php- Na-U%2phpunit/TextUI/XmlConfiguration/PHP/IniSetting.phpG NaGT+6phpunit/TextUI/XmlConfiguration/Migration/Migrator.php Nao$VGphpunit/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php NaUWĝdphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php NaU%5Yphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.phpC NaCcFSphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php NawHphpunit/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php NahoeOphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.phpX NaXijLphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.phpF NaF^ӤBphpunit/TextUI/XmlConfiguration/Migration/Migrations/Migration.php Na'Qphpunit/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php NabJOphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php Na$i'Qphpunit/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php NaUJphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php{ Na{K[phpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.php Na†踤Gphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php& Na&q3{Mphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php NaՄjMphpunit/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php NaUMphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php NaV_Xphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php} Na} -Lphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.phpK NaK_ >phpunit/TextUI/XmlConfiguration/Migration/MigrationBuilder.php# Na# g@phpunit/TextUI/XmlConfiguration/Migration/MigrationException.php Na\Zphpunit/TextUI/Help.php. Na.%phpunit/TextUI/TestRunner.php Na6phpunit/TextUI/Exception/TestFileNotFoundException.php NapC0phpunit/TextUI/Exception/ReflectionException.php Na Y-phpunit/TextUI/Exception/RuntimeException.php NaF;phpunit/TextUI/Exception/TestDirectoryNotFoundException.php Na&phpunit/TextUI/Exception/Exception.php NaD{i phpunit/TextUI/ResultPrinter.phpp Napܤ'phpunit/TextUI/DefaultResultPrinter.php67 Na67l&phpunit/TextUI/CliArguments/Mapper.php+, Na+,'a-phpunit/TextUI/CliArguments/Configuration.php Na0'phpunit/TextUI/CliArguments/Builder.phpT NaT66)phpunit/TextUI/CliArguments/Exception.php Na%zEphpunit/TextUI/Command.php&f Na&f"phpunit/TextUI/TestSuiteMapper.php Na A+8#phpunit/Framework/ErrorTestCase.php Na̤!phpunit/Framework/SkippedTest.php NaS.phpunit/Framework/TestSuite.phpb Nab!S+phpunit/Framework/DataProviderTestSuite.php Na\8$phpunit/Framework/IncompleteTest.php Na,+!phpunit/Framework/TestFailure.php Na'q%phpunit/Framework/SkippedTestCase.php Nal]8phpunit/Framework/Exception/PHPTAssertionFailedError.php4 Na4#MSphpunit/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.phpl Nal.~25phpunit/Framework/Exception/SyntheticSkippedError.php NaԗEphpunit/Framework/Exception/ComparisonMethodDoesNotExistException.php0 Na0}c+phpunit/Framework/Exception/OutputError.php Na3phpunit/Framework/Exception/IncompleteTestError.php Naםܤ5phpunit/Framework/Exception/CodeCoverageException.php Na[.phpunit/Framework/Exception/RiskyTestError.php Na*y%phpunit/Framework/Exception/Error.php Naj10phpunit/Framework/Exception/SkippedTestError.php Na O~?phpunit/Framework/Exception/CoveredCodeNotExecutedException.php Na8YФ9phpunit/Framework/Exception/NoChildTestSuiteException.php NaP$'phpunit/Framework/Exception/Warning.php Na8;Zphpunit/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php] Na]Ns~:phpunit/Framework/Exception/ExpectationFailedException.php Nag٤5phpunit/Framework/Exception/SkippedTestSuiteError.php Nax@phpunit/Framework/Exception/MissingCoversAnnotationException.php Na|Aphpunit/Framework/Exception/ActualValueIsNotAnObjectException.php NaiOp.phpunit/Framework/Exception/SyntheticError.php5 Na5KY?phpunit/Framework/Exception/UnintentionallyCoveredCodeError.php Na<phpunit/Framework/Exception/InvalidDataProviderException.php Na.ڜɤ)phpunit/Framework/Exception/Exception.php Na w4phpunit/Framework/Exception/AssertionFailedError.php NaUphpunit/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.phpS NaSzGt}<phpunit/Framework/Exception/InvalidCoversTargetException.php Nao苤Tphpunit/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php[ Na[]8A8phpunit/Framework/Exception/InvalidArgumentException.php  Na &phpunit/Framework/Assert/Functions.phpa Naaphpunit/Framework/Test.php~ Na~wtphpunit/Framework/Assert.phppR NapR4b'phpunit/Framework/TestSuiteIterator.php Na򨔬%phpunit/Framework/MockObject/Stub.php NaŎ*phpunit/Framework/MockObject/MockClass.php Na'C)phpunit/Framework/MockObject/MockType.php NaFFt8phpunit/Framework/MockObject/Builder/MethodNameMatch.phpw NawTy-phpunit/Framework/MockObject/Builder/Stub.php Na(:phpunit/Framework/MockObject/Builder/InvocationStubber.php Na1phpunit/Framework/MockObject/Builder/Identity.php Na8phpunit/Framework/MockObject/Builder/ParametersMatch.php Naڃ9phpunit/Framework/MockObject/Builder/InvocationMocker.php Na bKLphpunit/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php Naz'Yphpunit/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php  Na ɅWFphpunit/Framework/MockObject/Exception/ClassAlreadyExistsException.php NaLphpunit/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php! Na!$ہKphpunit/Framework/MockObject/Exception/IncompatibleReturnValueException.php Na8=Mphpunit/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php NazGphpunit/Framework/MockObject/Exception/CannotUseAddMethodsException.php5 Na5{Uphpunit/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php Na rHphpunit/Framework/MockObject/Exception/MatchBuilderNotFoundException.php NaYphpunit/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php Naک?phpunit/Framework/MockObject/Exception/UnknownTypeException.php Na~Hphpunit/Framework/MockObject/Exception/CannotUseOnlyMethodsException.phpE NaE>phpunit/Framework/MockObject/Exception/ReflectionException.php Na.ؔ@phpunit/Framework/MockObject/Exception/UnknownClassException.php Na5uW;phpunit/Framework/MockObject/Exception/RuntimeException.php Na_|Aphpunit/Framework/MockObject/Exception/BadMethodCallException.php NaΫXOphpunit/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php NaӁƤ4phpunit/Framework/MockObject/Exception/Exception.php NaB'@phpunit/Framework/MockObject/Exception/UnknownTraitException.php Naq¥Lphpunit/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php Na}QKphpunit/Framework/MockObject/Exception/MethodNameNotConfiguredException.php~ Na~x1)Cphpunit/Framework/MockObject/Exception/DuplicateMethodException.php Na_Ephpunit/Framework/MockObject/Exception/InvalidMethodNameException.php Na ܤ@phpunit/Framework/MockObject/Exception/ClassIsFinalException.php Na()5phpunit/Framework/MockObject/Stub/ReturnReference.php  Na f*phpunit/Framework/MockObject/Stub/Stub.php3 Na3>+4phpunit/Framework/MockObject/Stub/ReturnValueMap.php Naۤ4phpunit/Framework/MockObject/Stub/ReturnCallback.php NaD0Ӥ0phpunit/Framework/MockObject/Stub/ReturnSelf.php4 Na4DD/phpunit/Framework/MockObject/Stub/Exception.php( Na(J4phpunit/Framework/MockObject/Stub/ReturnArgument.php Na?}60phpunit/Framework/MockObject/Stub/ReturnStub.php Na6phpunit/Framework/MockObject/Stub/ConsecutiveCalls.php  Na .*phpunit/Framework/MockObject/Generator.phpw Naw4Iɇ=phpunit/Framework/MockObject/Generator/mocked_method_void.tpl Nap7phpunit/Framework/MockObject/Generator/mocked_class.tpl NawZ8phpunit/Framework/MockObject/Generator/mocked_method.tplF NaFK6phpunit/Framework/MockObject/Generator/wsdl_method.tpl< Na<i5phpunit/Framework/MockObject/Generator/wsdl_class.tpl Na9phpunit/Framework/MockObject/Generator/proxied_method.tpl} Na}@ė>phpunit/Framework/MockObject/Generator/proxied_method_void.tplv NavT?phpunit/Framework/MockObject/Generator/mocked_static_method.tpl Na 4R6phpunit/Framework/MockObject/Generator/trait_class.tplQ NaQ<Ȥ6phpunit/Framework/MockObject/Generator/deprecation.tpl; Na;O5s,phpunit/Framework/MockObject/MockBuilder.php=+ Na=+B5.phpunit/Framework/MockObject/MockMethodSet.php8 Na8G\+phpunit/Framework/MockObject/Invocation.php Na +phpunit/Framework/MockObject/MockMethod.php) Na)ޙ5phpunit/Framework/MockObject/MethodNameConstraint.php - Na -A1|;phpunit/Framework/MockObject/Rule/ConsecutiveParameters.phpl Nal z'%0phpunit/Framework/MockObject/Rule/MethodName.php Na -WG4phpunit/Framework/MockObject/Rule/InvokedAtIndex.php) Na)A3phpunit/Framework/MockObject/Rule/AnyParameters.php Na~'5phpunit/Framework/MockObject/Rule/InvocationOrder.php NaLDӤ8phpunit/Framework/MockObject/Rule/InvokedAtLeastOnce.php- Na- (8phpunit/Framework/MockObject/Rule/InvokedAtMostCount.php NagY9phpunit/Framework/MockObject/Rule/InvokedAtLeastCount.php NaB5phpunit/Framework/MockObject/Rule/AnyInvokedCount.phpj Naj`Ť4phpunit/Framework/MockObject/Rule/ParametersRule.phpc Nac?(2phpunit/Framework/MockObject/Rule/InvokedCount.php Na ^ 0phpunit/Framework/MockObject/Rule/Parameters.phpQ NaQ`g|*phpunit/Framework/MockObject/MockTrait.php Na&nä3phpunit/Framework/MockObject/ConfigurableMethod.php Na+phpunit/Framework/MockObject/Verifiable.php Na̐ s+phpunit/Framework/MockObject/Api/Method.php Na(phpunit/Framework/MockObject/Api/Api.php Na s6phpunit/Framework/MockObject/Api/MockedCloneMethod.php* Na*?z8phpunit/Framework/MockObject/Api/UnmockedCloneMethod.phpG NaGө(phpunit/Framework/MockObject/Matcher.phpB NaB*2phpunit/Framework/MockObject/InvocationHandler.php: Na:ˤ+phpunit/Framework/MockObject/MockObject.php Nabt$phpunit/Framework/SelfDescribing.php - Na -sphpunit/Framework/TestCase.php" Na"`q%phpunit/Framework/WarningTestCase.php$ Na$Hޤ4phpunit/Framework/InvalidParameterGroupException.php Na;phpunit/Framework/Constraint/Exception/ExceptionMessage.php Naw;8phpunit/Framework/Constraint/Exception/ExceptionCode.php NaiأLphpunit/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php NaLj[i4phpunit/Framework/Constraint/Exception/Exception.php NaRu{8phpunit/Framework/Constraint/Operator/BinaryOperator.php) Na)Ow4phpunit/Framework/Constraint/Operator/LogicalXor.php$ Na$O4phpunit/Framework/Constraint/Operator/LogicalNot.php Na 7phpunit/Framework/Constraint/Operator/UnaryOperator.php - Na - a2phpunit/Framework/Constraint/Operator/Operator.php& Na& Dܤ3phpunit/Framework/Constraint/Operator/LogicalOr.php NaZ4phpunit/Framework/Constraint/Operator/LogicalAnd.php NabJ6phpunit/Framework/Constraint/Filesystem/FileExists.phpe NaeK6phpunit/Framework/Constraint/Filesystem/IsWritable.phpe Nae;phpunit/Framework/Constraint/Filesystem/DirectoryExists.phpj Naji+6phpunit/Framework/Constraint/Filesystem/IsReadable.phpe Nae1/phpunit/Framework/Constraint/Boolean/IsTrue.php Na}0phpunit/Framework/Constraint/Boolean/IsFalse.php Na?phpunit/Framework/Constraint/Equality/IsEqualCanonicalizing.php - Na +8phpdocumentor-reflection-docblock/DocBlock/Tags/Var_.php p[c u:ڤ;phpdocumentor-reflection-docblock/DocBlock/Tags/Version.php +p[c +@S5phpdocumentor-reflection-docblock/DocBlockFactory.php$p[c$br>phpdocumentor-reflection-docblock/DocBlockFactoryInterface.phpp[c)%ߤ=phpdocumentor-reflection-docblock/Exception/PcreException.phpp[c V)phpdocumentor-reflection-docblock/LICENSE8p[c8ʤ+phpdocumentor-reflection-docblock/Utils.php p[c -phpdocumentor-type-resolver/FqsenResolver.phpp[cj^#phpdocumentor-type-resolver/LICENSE8p[c8ʤ*phpdocumentor-type-resolver/PseudoType.phpup[cu]\:phpdocumentor-type-resolver/PseudoTypes/CallableString.php`p[c`Z2phpdocumentor-type-resolver/PseudoTypes/False_.phpp[co䈤=phpdocumentor-type-resolver/PseudoTypes/HtmlEscapedString.phpgp[cgwe8phpdocumentor-type-resolver/PseudoTypes/IntegerRange.php%p[c%R1phpdocumentor-type-resolver/PseudoTypes/List_.phpp[cwu9phpdocumentor-type-resolver/PseudoTypes/LiteralString.php^p[c^=oNW;phpdocumentor-type-resolver/PseudoTypes/LowercaseString.phpbp[cb7 ;phpdocumentor-type-resolver/PseudoTypes/NegativeInteger.php[p[c[DEۤCphpdocumentor-type-resolver/PseudoTypes/NonEmptyLowercaseString.phptp[ct):phpdocumentor-type-resolver/PseudoTypes/NonEmptyString.phpap[ca²,9phpdocumentor-type-resolver/PseudoTypes/NumericString.php^p[c^8M4phpdocumentor-type-resolver/PseudoTypes/Numeric_.phpp[c=k;phpdocumentor-type-resolver/PseudoTypes/PositiveInteger.php[p[c[H7phpdocumentor-type-resolver/PseudoTypes/TraitString.phpZp[cZgC1phpdocumentor-type-resolver/PseudoTypes/True_.phpp[cl$phpdocumentor-type-resolver/Type.phpp[cb&,phpdocumentor-type-resolver/TypeResolver.php%Up[c%UU2phpdocumentor-type-resolver/Types/AbstractList.phptp[ctt4phpdocumentor-type-resolver/Types/AggregatedType.php +p[c +Hɵ.phpdocumentor-type-resolver/Types/ArrayKey.phpp[cPĤ,phpdocumentor-type-resolver/Types/Array_.phpp[c4-phpdocumentor-type-resolver/Types/Boolean.phpnp[cnrĤ/phpdocumentor-type-resolver/Types/Callable_.php{p[c{E1phpdocumentor-type-resolver/Types/ClassString.phpCp[cCrvy0phpdocumentor-type-resolver/Types/Collection.phpp[c?.phpdocumentor-type-resolver/Types/Compound.phpp[c>7-phpdocumentor-type-resolver/Types/Context.php p[c ]Z4phpdocumentor-type-resolver/Types/ContextFactory.php6p[c6\0phpdocumentor-type-resolver/Types/Expression.php8p[c8g,phpdocumentor-type-resolver/Types/Float_.phpmp[cm)J-phpdocumentor-type-resolver/Types/Integer.phpjp[cjv5phpdocumentor-type-resolver/Types/InterfaceString.phpp[c2phpdocumentor-type-resolver/Types/Intersection.phpp[cUz$/phpdocumentor-type-resolver/Types/Iterable_.php?p[c?Q8,phpdocumentor-type-resolver/Types/Mixed_.phpp[c3i,phpdocumentor-type-resolver/Types/Never_.phpp[cj+phpdocumentor-type-resolver/Types/Null_.phpxp[cxs.phpdocumentor-type-resolver/Types/Nullable.phpRp[cRCp\-phpdocumentor-type-resolver/Types/Object_.phpp[cwEhN-phpdocumentor-type-resolver/Types/Parent_.phpp[cO!./phpdocumentor-type-resolver/Types/Resource_.phpp[cŞX,phpdocumentor-type-resolver/Types/Scalar.phpp[c+phpdocumentor-type-resolver/Types/Self_.phpp[coȤ-phpdocumentor-type-resolver/Types/Static_.phpp[c8-phpdocumentor-type-resolver/Types/String_.phpsp[csH*phpdocumentor-type-resolver/Types/This.phpYp[cY^?ֈ+phpdocumentor-type-resolver/Types/Void_.phpp[ckphpspec-prophecy/LICENSE}p[c} ߦ&phpspec-prophecy/Prophecy/Argument.phpp[cn8phpspec-prophecy/Prophecy/Argument/ArgumentsWildcard.phpY p[cY 0?:phpspec-prophecy/Prophecy/Argument/Token/AnyValueToken.phpp[c{ܤ;phpspec-prophecy/Prophecy/Argument/Token/AnyValuesToken.phpp[c'`Bphpspec-prophecy/Prophecy/Argument/Token/ApproximateValueToken.phpp[c <phpspec-prophecy/Prophecy/Argument/Token/ArrayCountToken.phpp[c/*2<phpspec-prophecy/Prophecy/Argument/Token/ArrayEntryToken.phpp[cAphpspec-prophecy/Prophecy/Argument/Token/ArrayEveryEntryToken.phpp[c#:phpspec-prophecy/Prophecy/Argument/Token/CallbackToken.phpp[cv<phpspec-prophecy/Prophecy/Argument/Token/ExactValueToken.php p[c j\@phpspec-prophecy/Prophecy/Argument/Token/IdenticalValueToken.phpp[cu`S9phpspec-prophecy/Prophecy/Argument/Token/InArrayToken.phpp[c?xn<phpspec-prophecy/Prophecy/Argument/Token/LogicalAndToken.phpDp[cD(bL<phpspec-prophecy/Prophecy/Argument/Token/LogicalNotToken.phpXp[cX5)<phpspec-prophecy/Prophecy/Argument/Token/NotInArrayToken.phpp[c;=phpspec-prophecy/Prophecy/Argument/Token/ObjectStateToken.php p[c T@phpspec-prophecy/Prophecy/Argument/Token/StringContainsToken.php-p[c-3xD;phpspec-prophecy/Prophecy/Argument/Token/TokenInterface.phpp[c(nGw6phpspec-prophecy/Prophecy/Argument/Token/TypeToken.phpp[c$'phpspec-prophecy/Prophecy/Call/Call.phpc p[cc ڟJ-phpspec-prophecy/Prophecy/Call/CallCenter.phpp[cɝ.:phpspec-prophecy/Prophecy/Comparator/ClosureComparator.phpp[c40phpspec-prophecy/Prophecy/Comparator/Factory.phpp[c8! +Ԥ;phpspec-prophecy/Prophecy/Comparator/ProphecyComparator.phpp[c^^3phpspec-prophecy/Prophecy/Doubler/CachedDoubler.phpp[cOd\Dphpspec-prophecy/Prophecy/Doubler/ClassPatch/ClassPatchInterface.phphp[chq!ʤHphpspec-prophecy/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.phpp[cCphpspec-prophecy/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.phpp[c9ڤ=phpspec-prophecy/Prophecy/Doubler/ClassPatch/KeywordPatch.php p[c ?phpspec-prophecy/Prophecy/Doubler/ClassPatch/MagicCallPatch.php p[c Q)7Ephpspec-prophecy/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php p[c /Pphpspec-prophecy/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.phpp[cۤAphpspec-prophecy/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.phpi p[ci [ꢤ?phpspec-prophecy/Prophecy/Doubler/ClassPatch/ThrowablePatch.php p[c 83Aphpspec-prophecy/Prophecy/Doubler/ClassPatch/TraversablePatch.php p[c wp5phpspec-prophecy/Prophecy/Doubler/DoubleInterface.phpp[cBۤ-phpspec-prophecy/Prophecy/Doubler/Doubler.php5p[c55Bphpspec-prophecy/Prophecy/Doubler/Generator/ClassCodeGenerator.php p[c phpspec-prophecy/Prophecy/Doubler/Generator/Node/ClassNode.phpp[c"B(?phpspec-prophecy/Prophecy/Doubler/Generator/Node/MethodNode.phpop[co}-Cphpspec-prophecy/Prophecy/Doubler/Generator/Node/ReturnTypeNode.phpp[c,X;Ephpspec-prophecy/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php p[c Cphpspec-prophecy/Prophecy/Doubler/Generator/ReflectionInterface.phpp[c YAphpspec-prophecy/Prophecy/Doubler/Generator/TypeHintReference.phpp[ci0phpspec-prophecy/Prophecy/Doubler/LazyDouble.php p[c 3phpspec-prophecy/Prophecy/Doubler/NameGenerator.phpp[cdDphpspec-prophecy/Prophecy/Exception/Call/UnexpectedCallException.phpp[c}Ephpspec-prophecy/Prophecy/Exception/Doubler/ClassCreatorException.phpp[crDphpspec-prophecy/Prophecy/Exception/Doubler/ClassMirrorException.phpp[cbFphpspec-prophecy/Prophecy/Exception/Doubler/ClassNotFoundException.phpp[c>?phpspec-prophecy/Prophecy/Exception/Doubler/DoubleException.phpp[cV"^@phpspec-prophecy/Prophecy/Exception/Doubler/DoublerException.phpp[chJphpspec-prophecy/Prophecy/Exception/Doubler/InterfaceNotFoundException.phpp[c&qLphpspec-prophecy/Prophecy/Exception/Doubler/MethodNotExtendableException.phpp[c[Gphpspec-prophecy/Prophecy/Exception/Doubler/MethodNotFoundException.phpp[ce:Jphpspec-prophecy/Prophecy/Exception/Doubler/ReturnByReferenceException.phpp[c0+5,1phpspec-prophecy/Prophecy/Exception/Exception.phpp[cx@phpspec-prophecy/Prophecy/Exception/InvalidArgumentException.phpp[c󱙤Ephpspec-prophecy/Prophecy/Exception/Prediction/AggregateException.php8p[c8 .ڤLphpspec-prophecy/Prophecy/Exception/Prediction/FailedPredictionException.phpgp[cg3'}}Cphpspec-prophecy/Prophecy/Exception/Prediction/NoCallsException.phpp[cZFphpspec-prophecy/Prophecy/Exception/Prediction/PredictionException.phpp[cR2ͤPphpspec-prophecy/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php#p[c#ߤKphpspec-prophecy/Prophecy/Exception/Prediction/UnexpectedCallsException.phpFp[cF|bHphpspec-prophecy/Prophecy/Exception/Prophecy/MethodProphecyException.phpAp[cAcHphpspec-prophecy/Prophecy/Exception/Prophecy/ObjectProphecyException.php2p[c2eBphpspec-prophecy/Prophecy/Exception/Prophecy/ProphecyException.phpp[cD7jIphpspec-prophecy/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.phpp[cƤ=phpspec-prophecy/Prophecy/PhpDocumentor/ClassTagRetriever.phpp[c@%Cphpspec-prophecy/Prophecy/PhpDocumentor/LegacyClassTagRetriever.phpp[c|6Gphpspec-prophecy/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.phpp[c17phpspec-prophecy/Prophecy/Prediction/CallPrediction.phpZp[cZ%U<phpspec-prophecy/Prophecy/Prediction/CallTimesPrediction.php +p[c +#c;phpspec-prophecy/Prophecy/Prediction/CallbackPrediction.phpp[c~*:phpspec-prophecy/Prophecy/Prediction/NoCallsPrediction.phpp[cܼ<phpspec-prophecy/Prophecy/Prediction/PredictionInterface.phpp[cv5phpspec-prophecy/Prophecy/Promise/CallbackPromise.phpp[cԌӤ6phpspec-prophecy/Prophecy/Promise/PromiseInterface.phpIp[cIyv;phpspec-prophecy/Prophecy/Promise/ReturnArgumentPromise.php +p[c +,s3phpspec-prophecy/Prophecy/Promise/ReturnPromise.php%p[c%&2phpspec-prophecy/Prophecy/Promise/ThrowPromise.php% p[c% Q35phpspec-prophecy/Prophecy/Prophecy/MethodProphecy.php29p[c29S5phpspec-prophecy/Prophecy/Prophecy/ObjectProphecy.phpp[c#=8phpspec-prophecy/Prophecy/Prophecy/ProphecyInterface.php+p[c+X?phpspec-prophecy/Prophecy/Prophecy/ProphecySubjectInterface.phpp[c</phpspec-prophecy/Prophecy/Prophecy/Revealer.phpp[c m8phpspec-prophecy/Prophecy/Prophecy/RevealerInterface.phpGp[cGWnZ%phpspec-prophecy/Prophecy/Prophet.phpEp[cE:.b-phpspec-prophecy/Prophecy/Util/ExportUtil.phpdp[cd/,-phpspec-prophecy/Prophecy/Util/StringUtil.php +p[c +S phpunit.xsdDFp[cDFs|phpunit/Exception.phpp[ca#phpunit/Framework/Assert.phpRp[cR6뒤&phpunit/Framework/Assert/Functions.phpp[c lO0phpunit/Framework/Constraint/Boolean/IsFalse.phpp[c/phpunit/Framework/Constraint/Boolean/IsTrue.phpp[c})phpunit/Framework/Constraint/Callback.php?p[c? +b2phpunit/Framework/Constraint/Cardinality/Count.phpj p[cj xR@ؤ8phpunit/Framework/Constraint/Cardinality/GreaterThan.phpp[ch,d}4phpunit/Framework/Constraint/Cardinality/IsEmpty.phpp[chf5phpunit/Framework/Constraint/Cardinality/LessThan.phpp[ca T5phpunit/Framework/Constraint/Cardinality/SameSize.php_p[c_uŤ+phpunit/Framework/Constraint/Constraint.phpk"p[ck"@ƍ1phpunit/Framework/Constraint/Equality/IsEqual.php p[c Ӥ?phpunit/Framework/Constraint/Equality/IsEqualCanonicalizing.php +p[c ~=phpunit/Framework/Constraint/Equality/IsEqualIgnoringCase.php - Na +p[c C\:phpunit/Framework/Constraint/Equality/IsEqualWithDelta.php - Na -61phpunit/Framework/Constraint/Equality/IsEqual.php Na Ӥ)phpunit/Framework/Constraint/Callback.php? Na? -b+phpunit/Framework/Constraint/IsAnything.php NaE+phpunit/Framework/Constraint/Math/IsNan.php Na4g0.phpunit/Framework/Constraint/Math/IsFinite.php NaZҗ0phpunit/Framework/Constraint/Math/IsInfinite.php Na'*~2phpunit/Framework/Constraint/Cardinality/Count.phpj Naj xR@ؤ8phpunit/Framework/Constraint/Cardinality/GreaterThan.php Nah,d}4phpunit/Framework/Constraint/Cardinality/IsEmpty.php Nahf5phpunit/Framework/Constraint/Cardinality/LessThan.php Naa T5phpunit/Framework/Constraint/Cardinality/SameSize.php_ Na_uŤ,phpunit/Framework/Constraint/IsIdentical.php NaO4phpunit/Framework/Constraint/Object/ObjectEquals.php - Na -0W:phpunit/Framework/Constraint/Object/ObjectHasAttribute.php[ Na[Fm9phpunit/Framework/Constraint/Object/ClassHasAttribute.phpn Nan9<?phpunit/Framework/Constraint/Object/ClassHasStaticAttribute.php NadR8phpunit/Framework/Constraint/Traversable/ArrayHasKey.php Na6@!Iphpunit/Framework/Constraint/Traversable/TraversableContainsIdentical.php' Na'sӤ@phpunit/Framework/Constraint/Traversable/TraversableContains.php NaEphpunit/Framework/Constraint/Traversable/TraversableContainsEqual.phpa NaawADphpunit/Framework/Constraint/Traversable/TraversableContainsOnly.php Na RuФ+phpunit/Framework/Constraint/Constraint.phpc" Nac"e@@phpunit/Framework/Constraint/JsonMatchesErrorMessageProvider.php; Na;त,phpunit/Framework/Constraint/JsonMatches.phpz Naz 'RFphpunit/Framework/Constraint/String/StringMatchesFormatDescription.php - Na -1.phpunit/Framework/Constraint/String/IsJson.php Na\@8phpunit/Framework/Constraint/String/StringStartsWith.phpB NaBߤ6phpunit/Framework/Constraint/String/StringContains.php Naij"9phpunit/Framework/Constraint/String/RegularExpression.php Na+J6phpunit/Framework/Constraint/String/StringEndsWith.php Na{,phpunit/Framework/Constraint/Type/IsNull.php Na?),phpunit/Framework/Constraint/Type/IsType.php NaGȤ2phpunit/Framework/Constraint/Type/IsInstanceOf.php: Na:@.phpunit/Framework/ExecutionOrderDependency.php NaR- "phpunit/Framework/TestListener.php Na b!phpunit/Framework/TestBuilder.php" Na"14j phpunit/Framework/TestResult.phpC| NaC|d$!phpunit/Framework/Reorderable.php Naz07phpunit/Framework/TestListenerDefaultImplementation.php$ Na$C!phpunit/Framework/Error/Error.phpl Nal]#phpunit/Framework/Error/Warning.phpw NawG&phpunit/Framework/Error/Deprecated.phpz NazV"phpunit/Framework/Error/Notice.phpv Navˤ&phpunit/Framework/ExceptionWrapper.php Na m(phpunit/Framework/IncompleteTestCase.php NaI phpunit/Exception.php Naa#sebastian-exporter/Exporter.phpy" Nay"Osebastian-exporter/LICENSE Na螷ͤphp-file-iterator/Facade.php* - Na* - Kphp-file-iterator/Factory.php Na{0Ԥphp-file-iterator/Iterator.phpZ NaZ C܎php-file-iterator/LICENSE Na./phpspec-prophecy/Prophecy/Prophecy/Revealer.php Na m5phpspec-prophecy/Prophecy/Prophecy/MethodProphecy.php29 Na29S8phpspec-prophecy/Prophecy/Prophecy/RevealerInterface.phpG NaGWnZ?phpspec-prophecy/Prophecy/Prophecy/ProphecySubjectInterface.php Na<8phpspec-prophecy/Prophecy/Prophecy/ProphecyInterface.php+ Na+X5phpspec-prophecy/Prophecy/Prophecy/ObjectProphecy.php Na#=&phpspec-prophecy/Prophecy/Argument.php Nan-phpspec-prophecy/Prophecy/Util/StringUtil.php - Na -S-phpspec-prophecy/Prophecy/Util/ExportUtil.phpb NabBphpspec-prophecy/Prophecy/Exception/Prophecy/ProphecyException.php NaD7jHphpspec-prophecy/Prophecy/Exception/Prophecy/MethodProphecyException.phpA NaAcHphpspec-prophecy/Prophecy/Exception/Prophecy/ObjectProphecyException.php2 Na2ePphpspec-prophecy/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php# Na#ߤKphpspec-prophecy/Prophecy/Exception/Prediction/UnexpectedCallsException.phpF NaF|bLphpspec-prophecy/Prophecy/Exception/Prediction/FailedPredictionException.phpg Nag3'}}Ephpspec-prophecy/Prophecy/Exception/Prediction/AggregateException.php8 Na8 .ڤCphpspec-prophecy/Prophecy/Exception/Prediction/NoCallsException.php NaZFphpspec-prophecy/Prophecy/Exception/Prediction/PredictionException.php NaR2ͤDphpspec-prophecy/Prophecy/Exception/Call/UnexpectedCallException.php Na}1phpspec-prophecy/Prophecy/Exception/Exception.php NaxGphpspec-prophecy/Prophecy/Exception/Doubler/MethodNotFoundException.php Nae:Fphpspec-prophecy/Prophecy/Exception/Doubler/ClassNotFoundException.php Na>Dphpspec-prophecy/Prophecy/Exception/Doubler/ClassMirrorException.php Nab@phpspec-prophecy/Prophecy/Exception/Doubler/DoublerException.php NahJphpspec-prophecy/Prophecy/Exception/Doubler/InterfaceNotFoundException.php Na&qJphpspec-prophecy/Prophecy/Exception/Doubler/ReturnByReferenceException.php Na0+5,Lphpspec-prophecy/Prophecy/Exception/Doubler/MethodNotExtendableException.php Na[?phpspec-prophecy/Prophecy/Exception/Doubler/DoubleException.php NaV"^Ephpspec-prophecy/Prophecy/Exception/Doubler/ClassCreatorException.php Nar@phpspec-prophecy/Prophecy/Exception/InvalidArgumentException.php Na󱙤6phpspec-prophecy/Prophecy/Promise/PromiseInterface.phpI NaIyv2phpspec-prophecy/Prophecy/Promise/ThrowPromise.php% Na% Q33phpspec-prophecy/Prophecy/Promise/ReturnPromise.php% Na%&;phpspec-prophecy/Prophecy/Promise/ReturnArgumentPromise.php - Na -,s5phpspec-prophecy/Prophecy/Promise/CallbackPromise.php NaԌӤ;phpspec-prophecy/Prophecy/Prediction/CallbackPrediction.php Na~*7phpspec-prophecy/Prophecy/Prediction/CallPrediction.phpZ NaZ%U<phpspec-prophecy/Prophecy/Prediction/CallTimesPrediction.php - Na -#c<phpspec-prophecy/Prophecy/Prediction/PredictionInterface.php Nav:phpspec-prophecy/Prophecy/Prediction/NoCallsPrediction.php NaܼCphpspec-prophecy/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php Na|6Gphpspec-prophecy/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php Na1=phpspec-prophecy/Prophecy/PhpDocumentor/ClassTagRetriever.php Na@%Iphpspec-prophecy/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php NaƤ-phpspec-prophecy/Prophecy/Call/CallCenter.php Naɝ.'phpspec-prophecy/Prophecy/Call/Call.phpc Nac ڟJ;phpspec-prophecy/Prophecy/Comparator/ProphecyComparator.php| Na|b0phpspec-prophecy/Prophecy/Comparator/Factory.php Na8! -Ԥ:phpspec-prophecy/Prophecy/Comparator/ClosureComparator.php NaWp%phpspec-prophecy/Prophecy/Prophet.phpE NaE:.b3phpspec-prophecy/Prophecy/Doubler/NameGenerator.php Nad0phpspec-prophecy/Prophecy/Doubler/LazyDouble.php Na 3phpspec-prophecy/Prophecy/Doubler/CachedDoubler.php NaOd\5phpspec-prophecy/Prophecy/Doubler/DoubleInterface.php NaBۤAphpspec-prophecy/Prophecy/Doubler/Generator/TypeHintReference.php NaiBphpspec-prophecy/Prophecy/Doubler/Generator/ClassCodeGenerator.php Na phpspec-prophecy/Prophecy/Doubler/Generator/Node/ClassNode.php Na"B(?phpspec-prophecy/Prophecy/Doubler/Generator/Node/MethodNode.phpo Nao}-Aphpspec-prophecy/Prophecy/Doubler/Generator/Node/ArgumentNode.phpz Naz Ci;phpspec-prophecy/Prophecy/Doubler/Generator/ClassMirror.php NatCphpspec-prophecy/Prophecy/Doubler/Generator/ReflectionInterface.php Na Y-phpspec-prophecy/Prophecy/Doubler/Doubler.php5 Na55?phpspec-prophecy/Prophecy/Doubler/ClassPatch/ThrowablePatch.php Na 83Hphpspec-prophecy/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php Na?phpspec-prophecy/Prophecy/Doubler/ClassPatch/MagicCallPatch.php Na Q)7Dphpspec-prophecy/Prophecy/Doubler/ClassPatch/ClassPatchInterface.phph Nahq!ʤEphpspec-prophecy/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php Na /Aphpspec-prophecy/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.phpi Nai [ꢤ=phpspec-prophecy/Prophecy/Doubler/ClassPatch/KeywordPatch.php  Na Cphpspec-prophecy/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php Na雷Aphpspec-prophecy/Prophecy/Doubler/ClassPatch/TraversablePatch.php Na wpPphpspec-prophecy/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php Naۤ<phpspec-prophecy/Prophecy/Argument/Token/LogicalNotToken.phpX NaX5)Aphpspec-prophecy/Prophecy/Argument/Token/ArrayEveryEntryToken.php Na#<phpspec-prophecy/Prophecy/Argument/Token/LogicalAndToken.phpD NaD(bL<phpspec-prophecy/Prophecy/Argument/Token/ArrayEntryToken.php Na<phpspec-prophecy/Prophecy/Argument/Token/NotInArrayToken.php Na;@phpspec-prophecy/Prophecy/Argument/Token/IdenticalValueToken.php Nau`S6phpspec-prophecy/Prophecy/Argument/Token/TypeToken.php Na$=phpspec-prophecy/Prophecy/Argument/Token/ObjectStateToken.php Na T9phpspec-prophecy/Prophecy/Argument/Token/InArrayToken.php Na?xn:phpspec-prophecy/Prophecy/Argument/Token/AnyValueToken.php Na{ܤBphpspec-prophecy/Prophecy/Argument/Token/ApproximateValueToken.php Na ;phpspec-prophecy/Prophecy/Argument/Token/AnyValuesToken.php Na'`<phpspec-prophecy/Prophecy/Argument/Token/ExactValueToken.php Na j\<phpspec-prophecy/Prophecy/Argument/Token/ArrayCountToken.php Na/*2@phpspec-prophecy/Prophecy/Argument/Token/StringContainsToken.php- Na-3xD;phpspec-prophecy/Prophecy/Argument/Token/TokenInterface.php Na(nGw:phpspec-prophecy/Prophecy/Argument/Token/CallbackToken.php Nav8phpspec-prophecy/Prophecy/Argument/ArgumentsWildcard.phpY NaY 0?phpspec-prophecy/LICENSE} Na} ߦ/sebastian-diff/Output/DiffOnlyOutputBuilder.phpz Nazc4sebastian-diff/Output/DiffOutputBuilderInterface.php NaV2sebastian-diff/Output/UnifiedDiffOutputBuilder.php> Na>'q)8sebastian-diff/Output/StrictUnifiedDiffOutputBuilder.php( Na(kv4sebastian-diff/Output/AbstractChunkOutputBuilder.php Na\t3sebastian-diff/Exception/ConfigurationException.php= Na=1/Ff&sebastian-diff/Exception/Exception.phpj Naj05sebastian-diff/Exception/InvalidArgumentException.php NaqBsebastian-diff/TimeEfficientLongestCommonSubsequenceCalculator.php Nat٤Dsebastian-diff/MemoryEfficientLongestCommonSubsequenceCalculator.php Na9 sebastian-diff/Chunk.php_ Na_vsebastian-diff/Diff.phpj NajbXA5sebastian-diff/LongestCommonSubsequenceCalculator.php Na}e7zsebastian-diff/Differ.php$ Na$]Esebastian-diff/Parser.php Na X{sebastian-diff/Line.phpL NaL -qsebastian-diff/LICENSE  Na a1theseer-tokenizer/Token.php Na4theseer-tokenizer/Tokenizer.php - Na -zl+theseer-tokenizer/NamespaceUriException.phpy Nay'He.theseer-tokenizer/TokenCollectionException.php| Na|`g-theseer-tokenizer/Exception.phpn Nan'Ǥ#theseer-tokenizer/XMLSerializer.php Nag; "theseer-tokenizer/NamespaceUri.phpH NaH=Ctheseer-tokenizer/LICENSE NaR (%theseer-tokenizer/TokenCollection.php - Na -aobject-enumerator/LICENSE Nay{-sebastian-code-unit-reverse-lookup/Wizard.php Na }Z[*sebastian-code-unit-reverse-lookup/LICENSE Na3G ('sebastian-recursion-context/Context.php Na`Mm)sebastian-recursion-context/Exception.php NaPFA#sebastian-recursion-context/LICENSE Na`8sebastian-recursion-context/InvalidArgumentException.php Nab21phpunit/Framework/MockObject/Exception/ReflectionException.phpp[c.ؔLphpunit/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php6p[c6?먙;phpunit/Framework/MockObject/Exception/RuntimeException.phpp[c_|Mphpunit/Framework/MockObject/Exception/SoapExtensionNotAvailableException.phpp[cz@phpunit/Framework/MockObject/Exception/UnknownClassException.phpp[c5uW@phpunit/Framework/MockObject/Exception/UnknownTraitException.phpp[cq¥?phpunit/Framework/MockObject/Exception/UnknownTypeException.phpp[c~*phpunit/Framework/MockObject/Generator.phpp[ciĆ6phpunit/Framework/MockObject/Generator/deprecation.tpl;p[c;O5s7phpunit/Framework/MockObject/Generator/intersection.tplLp[cL-X7phpunit/Framework/MockObject/Generator/mocked_class.tplp[cwZ8phpunit/Framework/MockObject/Generator/mocked_method.tplFp[cFKFphpunit/Framework/MockObject/Generator/mocked_method_never_or_void.tplp[cp?phpunit/Framework/MockObject/Generator/mocked_static_method.tplp[c 4R9phpunit/Framework/MockObject/Generator/proxied_method.tpl}p[c}@ėGphpunit/Framework/MockObject/Generator/proxied_method_never_or_void.tplvp[cvT6phpunit/Framework/MockObject/Generator/trait_class.tplQp[cQ<Ȥ5phpunit/Framework/MockObject/Generator/wsdl_class.tplp[c6phpunit/Framework/MockObject/Generator/wsdl_method.tpl<p[c<i+phpunit/Framework/MockObject/Invocation.phpp[cid2phpunit/Framework/MockObject/InvocationHandler.php:p[c:ˤ(phpunit/Framework/MockObject/Matcher.phpp[cD-5phpunit/Framework/MockObject/MethodNameConstraint.php +p[c +A1|,phpunit/Framework/MockObject/MockBuilder.php=+p[c=+B5*phpunit/Framework/MockObject/MockClass.phpp[c'C+phpunit/Framework/MockObject/MockMethod.php&p[c&[(.phpunit/Framework/MockObject/MockMethodSet.php8p[c8G\+phpunit/Framework/MockObject/MockObject.phpp[cbt*phpunit/Framework/MockObject/MockTrait.phpp[c&nä)phpunit/Framework/MockObject/MockType.phpp[cFFt5phpunit/Framework/MockObject/Rule/AnyInvokedCount.phpjp[cj`Ť3phpunit/Framework/MockObject/Rule/AnyParameters.phpp[c~';phpunit/Framework/MockObject/Rule/ConsecutiveParameters.phpl p[cl z'%5phpunit/Framework/MockObject/Rule/InvocationOrder.phpp[cLDӤ4phpunit/Framework/MockObject/Rule/InvokedAtIndex.php,p[c,kK9phpunit/Framework/MockObject/Rule/InvokedAtLeastCount.phpp[cB8phpunit/Framework/MockObject/Rule/InvokedAtLeastOnce.php-p[c- (8phpunit/Framework/MockObject/Rule/InvokedAtMostCount.phpp[cgY2phpunit/Framework/MockObject/Rule/InvokedCount.php p[c ^ 0phpunit/Framework/MockObject/Rule/MethodName.phpp[c +WG0phpunit/Framework/MockObject/Rule/Parameters.phpQp[cQ`g|4phpunit/Framework/MockObject/Rule/ParametersRule.phpcp[cc?(%phpunit/Framework/MockObject/Stub.phpp[cŎ6phpunit/Framework/MockObject/Stub/ConsecutiveCalls.php p[c ./phpunit/Framework/MockObject/Stub/Exception.php(p[c(J4phpunit/Framework/MockObject/Stub/ReturnArgument.phpp[c?}64phpunit/Framework/MockObject/Stub/ReturnCallback.phpp[cD0Ӥ5phpunit/Framework/MockObject/Stub/ReturnReference.php p[c f0phpunit/Framework/MockObject/Stub/ReturnSelf.php4p[c4DD0phpunit/Framework/MockObject/Stub/ReturnStub.phpp[c4phpunit/Framework/MockObject/Stub/ReturnValueMap.phpp[cۤ*phpunit/Framework/MockObject/Stub/Stub.php3p[c3>++phpunit/Framework/MockObject/Verifiable.phpp[c̐ s!phpunit/Framework/Reorderable.phpp[cz0$phpunit/Framework/SelfDescribing.php +p[c +s!phpunit/Framework/SkippedTest.phpp[cS.%phpunit/Framework/SkippedTestCase.phpp[cl]phpunit/Framework/Test.php~p[c~wt!phpunit/Framework/TestBuilder.php"p[c"14jphpunit/Framework/TestCase.php $p[c $ƌh!phpunit/Framework/TestFailure.phpp[c'q"phpunit/Framework/TestListener.phprp[crӪc^7phpunit/Framework/TestListenerDefaultImplementation.php'p[c'! phpunit/Framework/TestResult.php~p[c~ւtphpunit/Framework/TestSuite.php)cp[c)cwe:ä'phpunit/Framework/TestSuiteIterator.phpp[c򨔬%phpunit/Framework/WarningTestCase.php$p[c$Hޤ!phpunit/Runner/BaseTestRunner.php p[c C +)phpunit/Runner/DefaultTestResultCache.php!p[c!/i^phpunit/Runner/Exception.phpp[czZ-phpunit/Runner/Extension/ExtensionHandler.php p[c +1 'phpunit/Runner/Extension/PharLoader.php p[c c4phpunit/Runner/Filter/ExcludeGroupFilterIterator.phpsp[cs} +Z!phpunit/Runner/Filter/Factory.phpp[cdcΤ-phpunit/Runner/Filter/GroupFilterIterator.phpp[c=;4phpunit/Runner/Filter/IncludeGroupFilterIterator.phprp[crP;AD,phpunit/Runner/Filter/NameFilterIterator.phpv p[cv Z/phpunit/Runner/Hook/AfterIncompleteTestHook.php-p[c-zԤ)phpunit/Runner/Hook/AfterLastTestHook.phpp[c0B֤*phpunit/Runner/Hook/AfterRiskyTestHook.php#p[c#dm,phpunit/Runner/Hook/AfterSkippedTestHook.php'p[c':/phpunit/Runner/Hook/AfterSuccessfulTestHook.phpp[c5w*phpunit/Runner/Hook/AfterTestErrorHook.php#p[c#ݮ,phpunit/Runner/Hook/AfterTestFailureHook.php'p[c'2F%phpunit/Runner/Hook/AfterTestHook.phpp[c;gA,phpunit/Runner/Hook/AfterTestWarningHook.php'p[c'':+phpunit/Runner/Hook/BeforeFirstTestHook.phpp[chWt&phpunit/Runner/Hook/BeforeTestHook.phpp[c"bphpunit/Runner/Hook/Hook.phpp[c. phpunit/Runner/Hook/TestHook.phpp[cZ_ ++phpunit/Runner/Hook/TestListenerAdapter.phpp[c\6E&phpunit/Runner/NullTestResultCache.phpp[cW<phpunit/Runner/PhptTestCase.php\Vp[c\VǙ'phpunit/Runner/ResultCacheExtension.php<p[c<6 _*phpunit/Runner/StandardTestSuiteLoader.php p[c Hm"phpunit/Runner/TestResultCache.phpp[cK"phpunit/Runner/TestSuiteLoader.phpp[cޤ"phpunit/Runner/TestSuiteSorter.php,p[c,kڤphpunit/Runner/Version.phpp[cȄQ'phpunit/TextUI/CliArguments/Builder.phpTp[cT66-phpunit/TextUI/CliArguments/Configuration.phpp[cX)phpunit/TextUI/CliArguments/Exception.phpp[c%zE&phpunit/TextUI/CliArguments/Mapper.php+,p[c+,'aphpunit/TextUI/Command.phpnp[cnhN'phpunit/TextUI/DefaultResultPrinter.phpY7p[cY7}G(J&phpunit/TextUI/Exception/Exception.phpp[cD{i0phpunit/TextUI/Exception/ReflectionException.phpp[c Y-phpunit/TextUI/Exception/RuntimeException.phpp[cF;phpunit/TextUI/Exception/TestDirectoryNotFoundException.phpp[c6phpunit/TextUI/Exception/TestFileNotFoundException.phpp[cpCphpunit/TextUI/Help.php.p[c.  phpunit/TextUI/ResultPrinter.phppp[cpܤphpunit/TextUI/TestRunner.phpp[c&"phpunit/TextUI/TestSuiteMapper.php p[c Ȥ=phpunit/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.phpp[crAphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.phpp[cc{Kphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.phpp[c͋Sphpunit/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.phpp[cje=phpunit/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.phpp[c}ݚ>phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.phpp[c=CAphpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.phpp[ci>phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.phpp[cG<phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Html.phpp[cE6;phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Php.phpp[cpS<phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Text.phpp[cKkw;phpunit/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.phpp[c?u1phpunit/TextUI/XmlConfiguration/Configuration.php5p[c5˞-phpunit/TextUI/XmlConfiguration/Exception.phpp[cN5+8phpunit/TextUI/XmlConfiguration/Filesystem/Directory.phpp[c@Bphpunit/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.phpp[cylr:Jphpunit/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.phpop[co\V3phpunit/TextUI/XmlConfiguration/Filesystem/File.phpp[c.P =phpunit/TextUI/XmlConfiguration/Filesystem/FileCollection.phpFp[cFU5bEphpunit/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php7p[c7te7-phpunit/TextUI/XmlConfiguration/Generator.phpp[cF /phpunit/TextUI/XmlConfiguration/Group/Group.phpp[c9phpunit/TextUI/XmlConfiguration/Group/GroupCollection.phpp[c Aphpunit/TextUI/XmlConfiguration/Group/GroupCollectionIterator.phpAp[cA8%0phpunit/TextUI/XmlConfiguration/Group/Groups.phpp[c@I*phpunit/TextUI/XmlConfiguration/Loader.phpɗp[cɗf1phpunit/TextUI/XmlConfiguration/Logging/Junit.phpp[cciG3phpunit/TextUI/XmlConfiguration/Logging/Logging.php p[c ]٤4phpunit/TextUI/XmlConfiguration/Logging/TeamCity.phpp[c7Z鵤8phpunit/TextUI/XmlConfiguration/Logging/TestDox/Html.phpp[cV2ܤ8phpunit/TextUI/XmlConfiguration/Logging/TestDox/Text.phpp[cώ7phpunit/TextUI/XmlConfiguration/Logging/TestDox/Xml.phpp[ct0phpunit/TextUI/XmlConfiguration/Logging/Text.phpp[cCn>phpunit/TextUI/XmlConfiguration/Migration/MigrationBuilder.php# p[c# gGphpunit/TextUI/XmlConfiguration/Migration/MigrationBuilderException.phpp[cUWĝ@phpunit/TextUI/XmlConfiguration/Migration/MigrationException.phpp[c\ZHphpunit/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.phpp[choeOphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.phpXp[cXijOphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.phpp[c$i'Mphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.phpp[cՄjLphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.phpFp[cF^ӤMphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.phpp[cV_Lphpunit/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.phpKp[cK_ Qphpunit/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.phpp[cUMphpunit/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.phpp[cUBphpunit/TextUI/XmlConfiguration/Migration/Migrations/Migration.phpp[c'dphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.phpp[cU%5Yphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.phpCp[cCcF[phpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistDirectoriesToCoverage.phpp[c†踤Xphpunit/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.phpp[cSphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.phpp[cwJphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php{p[c{KGphpunit/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php&p[c&q3{Qphpunit/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.phpp[cbJ6phpunit/TextUI/XmlConfiguration/Migration/Migrator.phpp[co$V0phpunit/TextUI/XmlConfiguration/PHP/Constant.php7p[c7$Ҥ:phpunit/TextUI/XmlConfiguration/PHP/ConstantCollection.php0p[c0:Bphpunit/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php_p[c_4V2phpunit/TextUI/XmlConfiguration/PHP/IniSetting.phpJp[cJOt<phpunit/TextUI/XmlConfiguration/PHP/IniSettingCollection.phpPp[cPnDphpunit/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.phpsp[csiBt +phpunit/TextUI/XmlConfiguration/PHP/Php.phpp[c6僤2phpunit/TextUI/XmlConfiguration/PHP/PhpHandler.phpwp[cw` +0phpunit/TextUI/XmlConfiguration/PHP/Variable.phpp[cN:phpunit/TextUI/XmlConfiguration/PHP/VariableCollection.php0p[c0׈TBphpunit/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php_p[c_K5phpunit/TextUI/XmlConfiguration/PHPUnit/Extension.phpp[c}Q?phpunit/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.phpp[c1WGphpunit/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.phpip[cio(3phpunit/TextUI/XmlConfiguration/PHPUnit/PHPUnit.phplCp[clCv;phpunit/TextUI/XmlConfiguration/TestSuite/TestDirectory.phpCp[cC0Ephpunit/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.phpp[cȤMphpunit/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.phpp[cyp6phpunit/TextUI/XmlConfiguration/TestSuite/TestFile.phpp[c?y@phpunit/TextUI/XmlConfiguration/TestSuite/TestFileCollection.phpbp[cbͤHphpunit/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.phpGp[cGwǤ7phpunit/TextUI/XmlConfiguration/TestSuite/TestSuite.phpp[c8wAphpunit/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.phpp[c)2Iphpunit/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.phpip[ci #$phpunit/Util/Annotation/DocBlock.phpAp[cA:$phpunit/Util/Annotation/Registry.phpZ +p[cZ +D]Xphpunit/Util/Blacklist.phpp[csphpunit/Util/Cloner.phpp[c"Ɩܤphpunit/Util/Color.phpp[cj?phpunit/Util/ErrorHandler.phpp[c=phpunit/Util/Exception.phpp[c다phpunit/Util/ExcludeList.phpp[cphpunit/Util/FileLoader.php p[c 'phpunit/Util/Filesystem.phpp[cܐphpunit/Util/Filter.php p[c ćphpunit/Util/GlobalState.php>p[c>phǤ(phpunit/Util/InvalidDataSetException.phpp[c1 phpunit/Util/Json.phpE p[cE !phpunit/Util/Log/JUnit.phpn*p[cn*)LBphpunit/Util/Log/TeamCity.php&p[c&c'phpunit/Util/PHP/AbstractPhpProcess.php&p[c&%m&phpunit/Util/PHP/DefaultPhpProcess.phpzp[czCp*phpunit/Util/PHP/Template/PhptTestCase.tplp[c+phpunit/Util/PHP/Template/TestCaseClass.tplp p[cp 3 H݀,phpunit/Util/PHP/Template/TestCaseMethod.tpl p[c mD&phpunit/Util/PHP/WindowsPhpProcess.phpp[c)aBphpunit/Util/Printer.php p[c shphpunit/Util/Reflection.phpp[cW챤"phpunit/Util/RegularExpression.phpp[c0uR)phpunit/Util/Test.php]p[c]qo$*phpunit/Util/TestDox/CliTestDoxPrinter.php(*p[c(*@f*phpunit/Util/TestDox/HtmlResultPrinter.php +p[c +t&'phpunit/Util/TestDox/NamePrettifier.php;"p[c;"45hդ&phpunit/Util/TestDox/ResultPrinter.php"p[c"1q$'phpunit/Util/TestDox/TestDoxPrinter.php)p[c)K̤*phpunit/Util/TestDox/TextResultPrinter.phpp[cȹ!.)phpunit/Util/TestDox/XmlResultPrinter.phpp[cy%phpunit/Util/TextTestListRenderer.php6p[c6.phpunit/Util/Type.phpp[c|ä*phpunit/Util/VersionComparisonOperator.phpp[cb,phpunit/Util/XdebugFilterScriptGenerator.phpwp[cwتphpunit/Util/Xml.phpp[c[phpunit/Util/Xml/Exception.phpp[cӤ0phpunit/Util/Xml/FailedSchemaDetectionResult.phpp[c#Sphpunit/Util/Xml/Loader.php p[c ,?*phpunit/Util/Xml/SchemaDetectionResult.phpp[c4χz#phpunit/Util/Xml/SchemaDetector.php-p[c-!phpunit/Util/Xml/SchemaFinder.phpp[c9:8%phpunit/Util/Xml/SnapshotNodeList.php p[c Bɢ4phpunit/Util/Xml/SuccessfulSchemaDetectionResult.php'p[c'g%phpunit/Util/Xml/ValidationResult.phpp[cxv:phpunit/Util/Xml/Validator.phpp[cV$phpunit/Util/XmlTestListRenderer.php +p[c +8sbom.xml /p[c /Bschema/8.5.xsdBp[cB贅schema/9.2.xsdBp[cB|lsebastian-cli-parser/LICENSEp[cusebastian-cli-parser/Parser.phpp[ckM<sebastian-cli-parser/exceptions/AmbiguousOptionException.phpFp[cFm\-sebastian-cli-parser/exceptions/Exception.phpup[cuӫGsebastian-cli-parser/exceptions/OptionDoesNotAllowArgumentException.php_p[c_|13Jsebastian-cli-parser/exceptions/RequiredOptionArgumentMissingException.phphp[chC:sebastian-cli-parser/exceptions/UnknownOptionException.php?p[c?vD*sebastian-code-unit-reverse-lookup/LICENSEp[c3G (-sebastian-code-unit-reverse-lookup/Wizard.php p[c }Z['sebastian-code-unit/ClassMethodUnit.phpp[c@[!sebastian-code-unit/ClassUnit.phpp[cF sebastian-code-unit/CodeUnit.php~%p[c~%D){*sebastian-code-unit/CodeUnitCollection.phpp[cJ2sebastian-code-unit/CodeUnitCollectionIterator.php;p[c;Lʤ$sebastian-code-unit/FunctionUnit.phpp[c`+sebastian-code-unit/InterfaceMethodUnit.phpp[cǦ%sebastian-code-unit/InterfaceUnit.phpp[ccsebastian-code-unit/LICENSE p[c psebastian-code-unit/Mapper.php-p[c-#'sebastian-code-unit/TraitMethodUnit.phpp[cqz!sebastian-code-unit/TraitUnit.phpp[cXA,sebastian-code-unit/exceptions/Exception.phpsp[cstg;sebastian-code-unit/exceptions/InvalidCodeUnitException.phpp[c6-3sebastian-code-unit/exceptions/NoTraitException.phpp[cQ36sebastian-code-unit/exceptions/ReflectionException.phpp[c$(sebastian-comparator/ArrayComparator.phpup[cuEmhf#sebastian-comparator/Comparator.phpp[ct*sebastian-comparator/ComparisonFailure.php p[c %*sebastian-comparator/DOMNodeComparator.php p[c 1i+sebastian-comparator/DateTimeComparator.php p[c KQ)sebastian-comparator/DoubleComparator.phpp[c:n,sebastian-comparator/ExceptionComparator.phpp[c1 sebastian-comparator/Factory.phpp[c?Nsebastian-comparator/LICENSE p[c =(-sebastian-comparator/MockObjectComparator.phpp[cI*sebastian-comparator/NumericComparator.php3 p[c3 i{l)sebastian-comparator/ObjectComparator.phpX p[cX ׌+sebastian-comparator/ResourceComparator.phpp[cJ)sebastian-comparator/ScalarComparator.php/ p[c/ dF3sebastian-comparator/SplObjectStorageComparator.phpp[c?/'sebastian-comparator/TypeComparator.phpp[ccX\-sebastian-comparator/exceptions/Exception.phpvp[cvEᵤ4sebastian-comparator/exceptions/RuntimeException.phpp[cV'#sebastian-complexity/Calculator.phpe p[ce (6.sebastian-complexity/Complexity/Complexity.phpQp[cQl8sebastian-complexity/Complexity/ComplexityCollection.phpp[cil@sebastian-complexity/Complexity/ComplexityCollectionIterator.php,p[c,e,sebastian-complexity/Exception/Exception.phpvp[cv73sebastian-complexity/Exception/RuntimeException.phpp[cCdWsebastian-complexity/LICENSEp[c=ݤ=sebastian-complexity/Visitor/ComplexityCalculatingVisitor.php p[c OGsebastian-complexity/Visitor/CyclomaticComplexityCalculatingVisitor.php p[c 7Ysebastian-diff/Chunk.php_p[c_vsebastian-diff/Diff.phpjp[cjbXAsebastian-diff/Differ.php $p[c $wkz3sebastian-diff/Exception/ConfigurationException.php=p[c=1/Ff&sebastian-diff/Exception/Exception.phpjp[cj05sebastian-diff/Exception/InvalidArgumentException.phpp[cqsebastian-diff/LICENSE p[c a1sebastian-diff/Line.phpLp[cL +q5sebastian-diff/LongestCommonSubsequenceCalculator.phpp[c}e7zDsebastian-diff/MemoryEfficientLongestCommonSubsequenceCalculator.phpp[c9 4sebastian-diff/Output/AbstractChunkOutputBuilder.phpp[c\t/sebastian-diff/Output/DiffOnlyOutputBuilder.phpzp[czc4sebastian-diff/Output/DiffOutputBuilderInterface.phpp[cV8sebastian-diff/Output/StrictUnifiedDiffOutputBuilder.php(p[c(kv2sebastian-diff/Output/UnifiedDiffOutputBuilder.php>p[c>'q)sebastian-diff/Parser.php p[c X{Bsebastian-diff/TimeEfficientLongestCommonSubsequenceCalculator.phpp[ct٤!sebastian-environment/Console.phpp[c72e4sebastian-environment/LICENSEp[cFy٤)sebastian-environment/OperatingSystem.phpp[c̄!sebastian-environment/Runtime.phpp[cY/sebastian-exporter/Exporter.phpx$p[cx$sebastian-exporter/LICENSEp[c 5٤'sebastian-global-state/CodeExporter.php p[c &sebastian-global-state/ExcludeList.php +p[c +R{sebastian-global-state/LICENSEp[cJ#sebastian-global-state/Restorer.phpp[cGJ #sebastian-global-state/Snapshot.php*p[c*X%/sebastian-global-state/exceptions/Exception.phpyp[cyJ6sebastian-global-state/exceptions/RuntimeException.phpp[c;#sebastian-lines-of-code/Counter.phpp[cH5/sebastian-lines-of-code/Exception/Exception.phpzp[cz aV>sebastian-lines-of-code/Exception/IllogicalValuesException.phpp[cG<sebastian-lines-of-code/Exception/NegativeValueException.phpp[c +ڤ6sebastian-lines-of-code/Exception/RuntimeException.phpp[cKsebastian-lines-of-code/LICENSEp[cbS~/sebastian-lines-of-code/LineCountingVisitor.phpp[c~A'sebastian-lines-of-code/LinesOfCode.php p[c fӤ*sebastian-object-enumerator/Enumerator.phpp[cx})sebastian-object-enumerator/Exception.phpp[c}Ȥ8sebastian-object-enumerator/InvalidArgumentException.phpp[câ(sebastian-object-reflector/Exception.phpp[cЬۤ7sebastian-object-reflector/InvalidArgumentException.phpp[c +M.sebastian-object-reflector/ObjectReflector.phpp[c_'sebastian-recursion-context/Context.phpp[caDy)sebastian-recursion-context/Exception.phpp[cPFA8sebastian-recursion-context/InvalidArgumentException.phpp[cb21#sebastian-recursion-context/LICENSEp[c`%sebastian-resource-operations/LICENSEp[c]<4sebastian-resource-operations/ResourceOperations.php߲p[c߲sebastian-type/LICENSE p[c &.sebastian-type/Parameter.phpp[c,#sebastian-type/ReflectionMapper.phppp[cp&ޤsebastian-type/TypeName.php:p[c:n +&sebastian-type/exception/Exception.phpjp[cjbᮧ-sebastian-type/exception/RuntimeException.phpp[c%$sebastian-type/type/CallableType.phpp[cŵ`!sebastian-type/type/FalseType.phpbp[cb_&)sebastian-type/type/GenericObjectType.php<p[c<Ch(sebastian-type/type/IntersectionType.phpd +p[cd +nc$sebastian-type/type/IterableType.phpp[cf!sebastian-type/type/MixedType.php'p[c'o!sebastian-type/type/NeverType.phpp[cFҹ sebastian-type/type/NullType.php"p[c"9$F"sebastian-type/type/ObjectType.php]p[c]L&"sebastian-type/type/SimpleType.phpp[c]"sebastian-type/type/StaticType.phpp[cj~ sebastian-type/type/TrueType.php]p[c]<iפsebastian-type/type/Type.phpp[cDje!sebastian-type/type/UnionType.php$ p[c$ )#sebastian-type/type/UnknownType.phpp[cǤ sebastian-type/type/VoidType.phpp[csebastian-version/LICENSEp[cZsebastian-version/Version.phpp[c ƪtheseer-tokenizer/Exception.phpnp[cn'Ǥtheseer-tokenizer/LICENSEp[cR ("theseer-tokenizer/NamespaceUri.phpHp[cH=C+theseer-tokenizer/NamespaceUriException.phpyp[cy'Hetheseer-tokenizer/Token.phpp[c4%theseer-tokenizer/TokenCollection.php +p[c +a.theseer-tokenizer/TokenCollectionException.php|p[c|`g-theseer-tokenizer/Tokenizer.php +p[c +zl#theseer-tokenizer/XMLSerializer.phpp[cg; webmozart-assert/Assert.phpp[cYT-webmozart-assert/InvalidArgumentException.phpbp[cbAwebmozart-assert/LICENSE<p[c<t}webmozart-assert/Mixin.php.p[c. a.phpstorm.meta.phpp[cO - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Webmozart\Assert; +namespace PHPUnit\Doctrine\Instantiator\Exception; -use ArrayAccess; -use BadMethodCallException; -use Closure; -use Countable; -use DateTime; -use DateTimeImmutable; -use Exception; -use ResourceBundle; -use SimpleXMLElement; use Throwable; -use Traversable; /** - * Efficient assertions to validate the input/output of your methods. - * - * @since 1.0 - * - * @author Bernhard Schussek + * Base exception marker interface for the instantiator component */ -class Assert +interface ExceptionInterface extends Throwable { - use Mixin; - /** - * @psalm-pure - * @psalm-assert string $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function string($value, $message = '') +} + $reflectionClass * - * @throws InvalidArgumentException + * @template T of object */ - public static function stringNotEmpty($value, $message = '') + public static function fromAbstractClass(ReflectionClass $reflectionClass) : self { - static::string($value, $message); - static::notEq($value, '', $message); + return new self(sprintf('The provided class "%s" is abstract, and cannot be instantiated', $reflectionClass->getName())); + } + public static function fromEnum(string $className) : self + { + return new self(sprintf('The provided class "%s" is an enum, and cannot be instantiated', $className)); } +} + $reflectionClass * - * @throws InvalidArgumentException + * @template T of object */ - public static function integer($value, $message = '') + public static function fromSerializationTriggeredException(ReflectionClass $reflectionClass, Exception $exception) : self { - if (!\is_int($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an integer. Got: %s', static::typeToString($value))); - } + return new self(sprintf('An exception was raised while trying to instantiate an instance of "%s" via un-serialization', $reflectionClass->getName()), 0, $exception); } /** - * @psalm-pure - * @psalm-assert numeric $value - * - * @param mixed $value - * @param string $message + * @phpstan-param ReflectionClass $reflectionClass * - * @throws InvalidArgumentException + * @template T of object */ - public static function integerish($value, $message = '') + public static function fromUncleanUnSerialization(ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine) : self { - if (!\is_numeric($value) || $value != (int) $value) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an integerish value. Got: %s', static::typeToString($value))); - } + return new self(sprintf('Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine), 0, new Exception($errorString, $errorCode)); } +} + 0)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a positive integer. Got: %s', static::valueToString($value))); - } - } + private static $cachedCloneables = []; /** - * @psalm-pure - * @psalm-assert float $value + * @param string $className + * @phpstan-param class-string $className * - * @param mixed $value - * @param string $message + * @return object + * @phpstan-return T * - * @throws InvalidArgumentException + * @throws ExceptionInterface + * + * @template T of object */ - public static function float($value, $message = '') + public function instantiate($className) { - if (!\is_float($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a float. Got: %s', static::typeToString($value))); + if (isset(self::$cachedCloneables[$className])) { + /** + * @phpstan-var T + */ + $cachedCloneable = self::$cachedCloneables[$className]; + return clone $cachedCloneable; + } + if (isset(self::$cachedInstantiators[$className])) { + $factory = self::$cachedInstantiators[$className]; + return $factory(); } + return $this->buildAndCacheFromFactory($className); } /** - * @psalm-pure - * @psalm-assert numeric $value + * Builds the requested object and caches it in static properties for performance * - * @param mixed $value - * @param string $message + * @phpstan-param class-string $className * - * @throws InvalidArgumentException + * @return object + * @phpstan-return T + * + * @template T of object */ - public static function numeric($value, $message = '') + private function buildAndCacheFromFactory(string $className) { - if (!\is_numeric($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a numeric. Got: %s', static::typeToString($value))); + $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); + $instance = $factory(); + if ($this->isSafeToClone(new ReflectionClass($instance))) { + self::$cachedCloneables[$className] = clone $instance; } + return $instance; } /** - * @psalm-pure - * @psalm-assert positive-int|0 $value + * Builds a callable capable of instantiating the given $className without + * invoking its constructor. * - * @param mixed $value - * @param string $message + * @phpstan-param class-string $className + * + * @phpstan-return callable(): T * * @throws InvalidArgumentException + * @throws UnexpectedValueException + * @throws ReflectionException + * + * @template T of object */ - public static function natural($value, $message = '') + private function buildFactory(string $className) : callable { - if (!\is_int($value) || $value < 0) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a non-negative integer. Got: %s', static::valueToString($value))); + $reflectionClass = $this->getReflectionClass($className); + if ($this->isInstantiableViaReflection($reflectionClass)) { + return [$reflectionClass, 'newInstanceWithoutConstructor']; } + $serializedString = sprintf('%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className); + $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); + return static function () use($serializedString) { + return unserialize($serializedString); + }; } /** - * @psalm-pure - * @psalm-assert bool $value + * @phpstan-param class-string $className * - * @param mixed $value - * @param string $message + * @phpstan-return ReflectionClass * * @throws InvalidArgumentException + * @throws ReflectionException + * + * @template T of object */ - public static function boolean($value, $message = '') + private function getReflectionClass(string $className) : ReflectionClass { - if (!\is_bool($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a boolean. Got: %s', static::typeToString($value))); + if (!class_exists($className)) { + throw InvalidArgumentException::fromNonExistingClass($className); + } + if (PHP_VERSION_ID >= 80100 && enum_exists($className, \false)) { + throw InvalidArgumentException::fromEnum($className); + } + $reflection = new ReflectionClass($className); + if ($reflection->isAbstract()) { + throw InvalidArgumentException::fromAbstractClass($reflection); } + return $reflection; } /** - * @psalm-pure - * @psalm-assert scalar $value + * @phpstan-param ReflectionClass $reflectionClass * - * @param mixed $value - * @param string $message + * @throws UnexpectedValueException * - * @throws InvalidArgumentException + * @template T of object */ - public static function scalar($value, $message = '') + private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString) : void { - if (!\is_scalar($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a scalar. Got: %s', static::typeToString($value))); + set_error_handler(static function (int $code, string $message, string $file, int $line) use($reflectionClass, &$error) : bool { + $error = UnexpectedValueException::fromUncleanUnSerialization($reflectionClass, $message, $code, $file, $line); + return \true; + }); + try { + $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); + } finally { + restore_error_handler(); + } + if ($error) { + throw $error; } } /** - * @psalm-pure - * @psalm-assert object $value + * @phpstan-param ReflectionClass $reflectionClass * - * @param mixed $value - * @param string $message + * @throws UnexpectedValueException * - * @throws InvalidArgumentException + * @template T of object */ - public static function object($value, $message = '') + private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString) : void { - if (!\is_object($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an object. Got: %s', static::typeToString($value))); + try { + unserialize($serializedString); + } catch (Exception $exception) { + throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } /** - * @psalm-pure - * @psalm-assert resource $value - * - * @param mixed $value - * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php - * @param string $message + * @phpstan-param ReflectionClass $reflectionClass * - * @throws InvalidArgumentException + * @template T of object */ - public static function resource($value, $type = null, $message = '') + private function isInstantiableViaReflection(ReflectionClass $reflectionClass) : bool { - if (!\is_resource($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a resource. Got: %s', static::typeToString($value))); - } - if ($type && $type !== \get_resource_type($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a resource of type %2$s. Got: %s', static::typeToString($value), $type)); - } + return !($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } /** - * @psalm-pure - * @psalm-assert callable $value + * Verifies whether the given class is to be considered internal * - * @param mixed $value - * @param string $message + * @phpstan-param ReflectionClass $reflectionClass * - * @throws InvalidArgumentException + * @template T of object */ - public static function isCallable($value, $message = '') + private function hasInternalAncestors(ReflectionClass $reflectionClass) : bool { - if (!\is_callable($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a callable. Got: %s', static::typeToString($value))); - } + do { + if ($reflectionClass->isInternal()) { + return \true; + } + $reflectionClass = $reflectionClass->getParentClass(); + } while ($reflectionClass); + return \false; } /** - * @psalm-pure - * @psalm-assert array $value + * Checks if a class is cloneable * - * @param mixed $value - * @param string $message + * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. * - * @throws InvalidArgumentException + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object */ - public static function isArray($value, $message = '') + private function isSafeToClone(ReflectionClass $reflectionClass) : bool { - if (!\is_array($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array. Got: %s', static::typeToString($value))); - } + return $reflectionClass->isCloneable() && !$reflectionClass->hasMethod('__clone') && !$reflectionClass->isSubclassOf(ArrayIterator::class); } +} + $className * - * @deprecated use "isIterable" or "isInstanceOf" instead + * @return object + * @phpstan-return T * - * @param mixed $value - * @param string $message + * @throws ExceptionInterface * - * @throws InvalidArgumentException + * @template T of object */ - public static function isTraversable($value, $message = '') - { - @\trigger_error(\sprintf('The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', __METHOD__), \E_USER_DEPRECATED); - if (!\is_array($value) && !$value instanceof Traversable) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a traversable. Got: %s', static::typeToString($value))); - } - } + public function instantiate($className); +} +Copyright (c) 2014 Doctrine Project + +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. +phpunit/phpunit: 9.5.26 +doctrine/instantiator: 1.4.1 +myclabs/deep-copy: 1.11.0 +nikic/php-parser: v4.15.1 +phar-io/manifest: 2.0.3 +phar-io/version: 3.2.1 +phpdocumentor/reflection-common: 2.2.0 +phpdocumentor/reflection-docblock: 5.3.0 +phpdocumentor/type-resolver: 1.6.1 +phpspec/prophecy: v1.15.0 +phpunit/php-code-coverage: 9.2.18 +phpunit/php-file-iterator: 3.0.6 +phpunit/php-invoker: 3.1.1 +phpunit/php-text-template: 2.0.4 +phpunit/php-timer: 5.0.3 +sebastian/cli-parser: 1.0.1 +sebastian/code-unit: 1.0.8 +sebastian/code-unit-reverse-lookup: 2.0.3 +sebastian/comparator: 4.0.8 +sebastian/complexity: 2.0.2 +sebastian/diff: 4.0.4 +sebastian/environment: 5.1.4 +sebastian/exporter: 4.0.5 +sebastian/global-state: 5.0.5 +sebastian/lines-of-code: 1.0.3 +sebastian/object-enumerator: 4.0.4 +sebastian/object-reflector: 2.0.4 +sebastian/recursion-context: 4.0.4 +sebastian/resource-operations: 3.0.3 +sebastian/type: 3.2.0 +sebastian/version: 3.0.2 +theseer/tokenizer: 1.2.1 +webmozart/assert: 1.11.0 + Filter, 'matcher' => Matcher] pairs. + */ + private $filters = []; + /** + * Type Filters to apply. * - * @throws InvalidArgumentException + * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs. */ - public static function isArrayAccessible($value, $message = '') + private $typeFilters = []; + /** + * @var bool + */ + private $skipUncloneable = \false; + /** + * @var bool + */ + private $useCloneMethod; + /** + * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used + * instead of the regular deep cloning. + */ + public function __construct($useCloneMethod = \false) { - if (!\is_array($value) && !$value instanceof ArrayAccess) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array accessible. Got: %s', static::typeToString($value))); - } + $this->useCloneMethod = $useCloneMethod; + $this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class)); + $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); + $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); } /** - * @psalm-pure - * @psalm-assert countable $value + * If enabled, will not throw an exception when coming across an uncloneable property. * - * @param mixed $value - * @param string $message + * @param $skipUncloneable * - * @throws InvalidArgumentException + * @return $this */ - public static function isCountable($value, $message = '') + public function skipUncloneable($skipUncloneable = \true) { - if (!\is_array($value) && !$value instanceof Countable && !$value instanceof ResourceBundle && !$value instanceof SimpleXMLElement) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a countable. Got: %s', static::typeToString($value))); - } + $this->skipUncloneable = $skipUncloneable; + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value + * Deep copies the given object. * - * @param mixed $value - * @param string $message + * @param mixed $object * - * @throws InvalidArgumentException + * @return mixed */ - public static function isIterable($value, $message = '') + public function copy($object) { - if (!\is_array($value) && !$value instanceof Traversable) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an iterable. Got: %s', static::typeToString($value))); - } - } - /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert ExpectedType $value - * - * @param mixed $value - * @param string|object $class - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function isInstanceOf($value, $class, $message = '') + $this->hashMap = []; + return $this->recursiveCopy($object); + } + public function addFilter(Filter $filter, Matcher $matcher) { - if (!$value instanceof $class) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of %2$s. Got: %s', static::typeToString($value), $class)); + $this->filters[] = ['matcher' => $matcher, 'filter' => $filter]; + } + public function prependFilter(Filter $filter, Matcher $matcher) + { + \array_unshift($this->filters, ['matcher' => $matcher, 'filter' => $filter]); + } + public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) + { + $this->typeFilters[] = ['matcher' => $matcher, 'filter' => $filter]; + } + private function recursiveCopy($var) + { + // Matches Type Filter + if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { + return $filter->apply($var); + } + // Resource + if (\is_resource($var)) { + return $var; + } + // Array + if (\is_array($var)) { + return $this->copyArray($var); + } + // Scalar + if (!\is_object($var)) { + return $var; + } + // Enum + if (\PHP_VERSION_ID >= 80100 && \enum_exists(\get_class($var))) { + return $var; } + // Object + return $this->copyObject($var); } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert !ExpectedType $value - * - * @param mixed $value - * @param string|object $class - * @param string $message - * - * @throws InvalidArgumentException + * Copy an array + * @param array $array + * @return array */ - public static function notInstanceOf($value, $class, $message = '') + private function copyArray(array $array) { - if ($value instanceof $class) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance other than %2$s. Got: %s', static::typeToString($value), $class)); + foreach ($array as $key => $value) { + $array[$key] = $this->recursiveCopy($value); } + return $array; } /** - * @psalm-pure - * @psalm-param array $classes + * Copies an object. * - * @param mixed $value - * @param array $classes - * @param string $message + * @param object $object * - * @throws InvalidArgumentException + * @throws CloneException + * + * @return object */ - public static function isInstanceOfAny($value, array $classes, $message = '') + private function copyObject($object) { - foreach ($classes as $class) { - if ($value instanceof $class) { - return; + $objectHash = \spl_object_hash($object); + if (isset($this->hashMap[$objectHash])) { + return $this->hashMap[$objectHash]; + } + $reflectedObject = new ReflectionObject($object); + $isCloneable = $reflectedObject->isCloneable(); + if (\false === $isCloneable) { + if ($this->skipUncloneable) { + $this->hashMap[$objectHash] = $object; + return $object; } + throw new CloneException(\sprintf('The class "%s" is not cloneable.', $reflectedObject->getName())); + } + $newObject = clone $object; + $this->hashMap[$objectHash] = $newObject; + if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { + return $newObject; + } + if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { + return $newObject; + } + foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { + $this->copyObjectProperty($newObject, $property); } - static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of any of %2$s. Got: %s', static::typeToString($value), \implode(', ', \array_map(array('static', 'valueToString'), $classes)))); + return $newObject; } - /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert ExpectedType|class-string $value - * - * @param object|string $value - * @param string $class - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function isAOf($value, $class, $message = '') + private function copyObjectProperty($object, ReflectionProperty $property) { - static::string($class, 'Expected class as a string. Got: %s'); - if (!\is_a($value, $class, \is_string($value))) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of this class or to this class among his parents %2$s. Got: %s', static::typeToString($value), $class)); + // Ignore static properties + if ($property->isStatic()) { + return; } + // Apply the filters + foreach ($this->filters as $item) { + /** @var Matcher $matcher */ + $matcher = $item['matcher']; + /** @var Filter $filter */ + $filter = $item['filter']; + if ($matcher->matches($object, $property->getName())) { + $filter->apply($object, $property->getName(), function ($object) { + return $this->recursiveCopy($object); + }); + // If a filter matches, we stop processing this property + return; + } + } + $property->setAccessible(\true); + // Ignore uninitialized properties (for PHP >7.4) + if (\method_exists($property, 'isInitialized') && !$property->isInitialized($object)) { + return; + } + $propertyValue = $property->getValue($object); + // Copy the property + $property->setValue($object, $this->recursiveCopy($propertyValue)); } /** - * @psalm-pure - * @psalm-template UnexpectedType of object - * @psalm-param class-string $class - * @psalm-assert !UnexpectedType $value - * @psalm-assert !class-string $value + * Returns first filter that matches variable, `null` if no such filter found. * - * @param object|string $value - * @param string $class - * @param string $message + * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and + * 'matcher' with value of type {@see TypeMatcher} + * @param mixed $var * - * @throws InvalidArgumentException + * @return TypeFilter|null */ - public static function isNotA($value, $class, $message = '') + private function getFirstMatchedTypeFilter(array $filterRecords, $var) { - static::string($class, 'Expected class as a string. Got: %s'); - if (\is_a($value, $class, \is_string($value))) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of this class or to this class among his parents other than %2$s. Got: %s', static::typeToString($value), $class)); - } + $matched = $this->first($filterRecords, function (array $record) use($var) { + /* @var TypeMatcher $matcher */ + $matcher = $record['matcher']; + return $matcher->matches($var); + }); + return isset($matched) ? $matched['filter'] : null; } /** - * @psalm-pure - * @psalm-param array $classes + * Returns first element that matches predicate, `null` if no such element found. * - * @param object|string $value - * @param string[] $classes - * @param string $message + * @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + * @param callable $predicate Predicate arguments are: element. * - * @throws InvalidArgumentException + * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher' + * with value of type {@see TypeMatcher} or `null`. */ - public static function isAnyOf($value, array $classes, $message = '') + private function first(array $elements, callable $predicate) { - foreach ($classes as $class) { - static::string($class, 'Expected class as a string. Got: %s'); - if (\is_a($value, $class, \is_string($value))) { - return; + foreach ($elements as $element) { + if (\call_user_func($predicate, $element)) { + return $element; } } - static::reportInvalidArgument(\sprintf($message ?: 'Expected an any of instance of this class or to this class among his parents other than %2$s. Got: %s', static::typeToString($value), \implode(', ', \array_map(array('static', 'valueToString'), $classes)))); + return null; } +} +setAccessible(\true); + $oldCollection = $reflectionProperty->getValue($object); + $newCollection = $oldCollection->map(function ($item) use($objectCopier) { + return $objectCopier($item); + }); + $reflectionProperty->setValue($object, $newCollection); } +} +setAccessible(\true); + $reflectionProperty->setValue($object, new ArrayCollection()); } +} +__load(); } +} +callback = $callable; } /** - * @psalm-pure - * @psalm-assert false $value - * - * @param mixed $value - * @param string $message + * Replaces the object property by the result of the callback called with the object property. * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function false($value, $message = '') + public function apply($object, $property, $objectCopier) { - if (\false !== $value) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be false. Got: %s', static::valueToString($value))); - } + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + $reflectionProperty->setAccessible(\true); + $value = \call_user_func($this->callback, $reflectionProperty->getValue($object)); + $reflectionProperty->setValue($object, $value); } +} +setAccessible(\true); + $reflectionProperty->setValue($object, null); } +} +class = $class; + $this->property = $property; } /** - * @param mixed $value - * @param string $message + * Matches a specific property of a specific class. * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function ipv6($value, $message = '') + public function matches($object, $property) { - if (\false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be an IPv6. Got: %s', static::valueToString($value))); - } + return $object instanceof $this->class && $property == $this->property; } +} +property = $property; } /** - * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. - * - * @param array $values - * @param string $message + * Matches a property by its name. * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function uniqueValues(array $values, $message = '') + public function matches($object, $property) { - $allValues = \count($values); - $uniqueValues = \count(\array_unique($values)); - if ($allValues !== $uniqueValues) { - $difference = $allValues - $uniqueValues; - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array of unique values, but %s of them %s duplicated', $difference, 1 === $difference ? 'is' : 'are')); - } + return $property == $this->property; } +} +propertyType = $propertyType; } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $expect - * @param string $message - * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function same($value, $expect, $message = '') + public function matches($object, $property) { - if ($expect !== $value) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value identical to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect))); + try { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + } catch (ReflectionException $exception) { + return \false; } - } - /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $expect - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function notSame($value, $expect, $message = '') - { - if ($expect === $value) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value not identical to %s.', static::valueToString($expect))); + $reflectionProperty->setAccessible(\true); + // Uninitialized properties (for PHP >7.4) + if (\method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) { + // null instanceof $this->propertyType + return \false; } + return $reflectionProperty->getValue($object) instanceof $this->propertyType; } +} +getProperties() does not return private properties from ancestor classes. * - * @throws InvalidArgumentException - */ - public static function greaterThan($value, $limit, $message = '') - { - if ($value <= $limit) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value greater than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); - } - } - /** - * @psalm-pure + * @author muratyaman@gmail.com + * @see http://php.net/manual/en/reflectionclass.getproperties.php * - * @param mixed $value - * @param mixed $limit - * @param string $message + * @param ReflectionClass $ref * - * @throws InvalidArgumentException + * @return ReflectionProperty[] */ - public static function greaterThanEq($value, $limit, $message = '') + public static function getProperties(ReflectionClass $ref) { - if ($value < $limit) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value greater than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); + $props = $ref->getProperties(); + $propsArr = array(); + foreach ($props as $prop) { + $propertyName = $prop->getName(); + $propsArr[$propertyName] = $prop; + } + if ($parentClass = $ref->getParentClass()) { + $parentPropsArr = self::getProperties($parentClass); + foreach ($propsArr as $key => $property) { + $parentPropsArr[$key] = $property; + } + return $parentPropsArr; } + return $propsArr; } /** - * @psalm-pure + * Retrieves property by name from object and all its ancestors. * - * @param mixed $value - * @param mixed $limit - * @param string $message + * @param object|string $object + * @param string $name * - * @throws InvalidArgumentException + * @throws PropertyException + * @throws ReflectionException + * + * @return ReflectionProperty */ - public static function lessThan($value, $limit, $message = '') + public static function getProperty($object, $name) { - if ($value >= $limit) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value less than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); + $reflection = \is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); + if ($reflection->hasProperty($name)) { + return $reflection->getProperty($name); + } + if ($parentClass = $reflection->getParentClass()) { + return self::getProperty($parentClass->getName(), $name); } + throw new PropertyException(\sprintf('The class "%s" doesn\'t have a property with the given name: "%s".', \is_object($object) ? \get_class($object) : $object, $name)); } +} + $limit) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value less than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); + $copy = new DateInterval('P0D'); + foreach ($element as $propertyName => $propertyValue) { + $copy->{$propertyName} = $propertyValue; } + return $copy; } +} + $max) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value between %2$s and %3$s. Got: %s', static::valueToString($value), static::valueToString($min), static::valueToString($max))); - } - } + protected $callback; /** - * A more human-readable alias of Assert::inArray(). - * - * @psalm-pure - * - * @param mixed $value - * @param array $values - * @param string $message - * - * @throws InvalidArgumentException + * @param callable $callable Will be called to get the new value for each element to replace */ - public static function oneOf($value, array $values, $message = '') + public function __construct(callable $callable) { - static::inArray($value, $values, $message); + $this->callback = $callable; } /** - * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. - * - * @psalm-pure - * - * @param mixed $value - * @param array $values - * @param string $message - * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function inArray($value, array $values, $message = '') + public function apply($element) { - if (!\in_array($value, $values, \true)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected one of: %2$s. Got: %s', static::valueToString($value), \implode(', ', \array_map(array('static', 'valueToString'), $values)))); - } + return \call_user_func($this->callback, $element); } +} +copier = $copier; } /** - * @psalm-pure - * - * @param string $value - * @param string $message - * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function notWhitespaceOnly($value, $message = '') + public function apply($arrayObject) { - if (\preg_match('/^\\s*$/', $value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a non-whitespace string. Got: %s', static::valueToString($value))); + $clone = clone $arrayObject; + foreach ($arrayObject->getArrayCopy() as $k => $v) { + $clone->offsetSet($k, $this->copier->copy($v)); } + return $clone; } - /** - * @psalm-pure - * - * @param string $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function startsWith($value, $prefix, $message = '') +} +copier = $copier; } /** - * @psalm-pure - * - * @param string $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException + * {@inheritdoc} */ - public static function notStartsWith($value, $prefix, $message = '') + public function apply($element) { - if (0 === \strpos($value, $prefix)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value not to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix))); - } + $newElement = clone $element; + $copy = $this->createCopyClosure(); + return $copy($newElement); } - /** - * @psalm-pure - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException - */ - public static function startsWithLetter($value, $message = '') + private function createCopyClosure() { - static::string($value); - $valid = isset($value[0]); - if ($valid) { - $locale = \setlocale(\LC_CTYPE, 0); - \setlocale(\LC_CTYPE, 'C'); - $valid = \ctype_alpha($value[0]); - \setlocale(\LC_CTYPE, $locale); - } - if (!$valid) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to start with a letter. Got: %s', static::valueToString($value))); - } + $copier = $this->copier; + $copy = function (SplDoublyLinkedList $list) use($copier) { + // Replace each element in the list with a deep copy of itself + for ($i = 1; $i <= $list->count(); $i++) { + $copy = $copier->recursiveCopy($list->shift()); + $list->push($copy); + } + return $list; + }; + return Closure::bind($copy, null, DeepCopy::class); } +} +type = $type; } /** - * @psalm-pure - * - * @param string $value - * @param string $pattern - * @param string $message + * @param mixed $element * - * @throws InvalidArgumentException + * @return boolean */ - public static function notRegex($value, $pattern, $message = '') + public function matches($element) { - if (\preg_match($pattern, $value, $matches, \PREG_OFFSET_CAPTURE)) { - static::reportInvalidArgument(\sprintf($message ?: 'The value %s matches the pattern %s (at offset %d).', static::valueToString($value), static::valueToString($pattern), $matches[0][1])); - } + return \is_object($element) ? \is_a($element, $this->type) : \gettype($element) === $this->type; } +} +copy($value); } +} +The MIT License (MIT) + +Copyright (c) 2013 My C-Sense + +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. +BSD 3-Clause License + +Copyright (c) 2011, Nikita Popov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; } /** - * @psalm-pure + * Add another constant to const group * - * @param string $value - * @param string $message + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function alnum($value, $message = '') + public function addConst($name, $value) { - $locale = \setlocale(\LC_CTYPE, 0); - \setlocale(\LC_CTYPE, 'C'); - $valid = !\ctype_alnum($value); - \setlocale(\LC_CTYPE, $locale); - if ($valid) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain letters and digits only. Got: %s', static::valueToString($value))); - } + $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); + return $this; } /** - * @psalm-pure - * @psalm-assert lowercase-string $value - * - * @param string $value - * @param string $message + * Makes the constant public. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function lower($value, $message = '') + public function makePublic() { - $locale = \setlocale(\LC_CTYPE, 0); - \setlocale(\LC_CTYPE, 'C'); - $valid = !\ctype_lower($value); - \setlocale(\LC_CTYPE, $locale); - if ($valid) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain lowercase characters only. Got: %s', static::valueToString($value))); - } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + return $this; } /** - * @psalm-pure - * @psalm-assert !lowercase-string $value - * - * @param string $value - * @param string $message + * Makes the constant protected. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function upper($value, $message = '') + public function makeProtected() { - $locale = \setlocale(\LC_CTYPE, 0); - \setlocale(\LC_CTYPE, 'C'); - $valid = !\ctype_upper($value); - \setlocale(\LC_CTYPE, $locale); - if ($valid) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain uppercase characters only. Got: %s', static::valueToString($value))); - } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + return $this; } /** - * @psalm-pure - * - * @param string $value - * @param int $length - * @param string $message + * Makes the constant private. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function length($value, $length, $message = '') + public function makePrivate() { - if ($length !== static::strlen($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain %2$s characters. Got: %s', static::valueToString($value), $length)); - } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + return $this; } /** - * Inclusive min. - * - * @psalm-pure - * - * @param string $value - * @param int|float $min - * @param string $message + * Makes the constant final. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function minLength($value, $min, $message = '') + public function makeFinal() { - if (static::strlen($value) < $min) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain at least %2$s characters. Got: %s', static::valueToString($value), $min)); - } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + return $this; } /** - * Inclusive max. - * - * @psalm-pure + * Sets doc comment for the constant. * - * @param string $value - * @param int|float $max - * @param string $message + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function maxLength($value, $max, $message = '') + public function setDocComment($docComment) { - if (static::strlen($value) > $max) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain at most %2$s characters. Got: %s', static::valueToString($value), $max)); - } + $this->attributes = ['comments' => [BuilderHelpers::normalizeDocComment($docComment)]]; + return $this; } /** - * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. - * - * @psalm-pure + * Adds an attribute group. * - * @param string $value - * @param int|float $min - * @param int|float $max - * @param string $message + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function lengthBetween($value, $min, $max, $message = '') + public function addAttribute($attribute) { - $length = static::strlen($value); - if ($length < $min || $length > $max) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', static::valueToString($value), $min, $max)); - } + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. - * - * @param mixed $value - * @param string $message + * Returns the built class node. * - * @throws InvalidArgumentException + * @return Stmt\ClassConst The built constant node */ - public static function fileExists($value, $message = '') + public function getNode() : PhpParser\Node { - static::string($value); - if (!\file_exists($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'The file %s does not exist.', static::valueToString($value))); - } + return new Stmt\ClassConst($this->constants, $this->flags, $this->attributes, $this->attributeGroups); } +} +name = $name; } /** - * @param mixed $value - * @param string $message + * Extends a class. * - * @throws InvalidArgumentException + * @param Name|string $class Name of class to extend + * + * @return $this The builder instance (for fluid interface) */ - public static function directory($value, $message = '') + public function extend($class) { - static::fileExists($value, $message); - if (!\is_dir($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'The path %s is no directory.', static::valueToString($value))); - } + $this->extends = BuilderHelpers::normalizeName($class); + return $this; } /** - * @param string $value - * @param string $message + * Implements one or more interfaces. * - * @throws InvalidArgumentException + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) */ - public static function readable($value, $message = '') + public function implement(...$interfaces) { - if (!\is_readable($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'The path %s is not readable.', static::valueToString($value))); + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); } + return $this; } /** - * @param string $value - * @param string $message + * Makes the class abstract. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function writable($value, $message = '') + public function makeAbstract() { - if (!\is_writable($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'The path %s is not writable.', static::valueToString($value))); - } + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + return $this; } /** - * @psalm-assert class-string $value - * - * @param mixed $value - * @param string $message + * Makes the class final. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function classExists($value, $message = '') + public function makeFinal() { - if (!\class_exists($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an existing class name. Got: %s', static::valueToString($value))); - } + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + return $this; + } + public function makeReadonly() + { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); + return $this; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert class-string|ExpectedType $value + * Adds a statement. * - * @param mixed $value - * @param string|object $class - * @param string $message + * @param Stmt|PhpParser\Builder $stmt The statement to add * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function subclassOf($value, $class, $message = '') + public function addStmt($stmt) { - if (!\is_subclass_of($value, $class)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected a sub-class of %2$s. Got: %s', static::valueToString($value), static::valueToString($class))); + $stmt = BuilderHelpers::normalizeNode($stmt); + $targets = [Stmt\TraitUse::class => &$this->uses, Stmt\ClassConst::class => &$this->constants, Stmt\Property::class => &$this->properties, Stmt\ClassMethod::class => &$this->methods]; + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); } + $targets[$class][] = $stmt; + return $this; } /** - * @psalm-assert class-string $value + * Adds an attribute group. * - * @param mixed $value - * @param string $message + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function interfaceExists($value, $message = '') + public function addAttribute($attribute) { - if (!\interface_exists($value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an existing interface name. got %s', static::valueToString($value))); - } + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $interface - * @psalm-assert class-string $value - * - * @param mixed $value - * @param mixed $interface - * @param string $message + * Returns the built class node. * - * @throws InvalidArgumentException + * @return Stmt\Class_ The built class node */ - public static function implementsInterface($value, $interface, $message = '') + public function getNode() : PhpParser\Node { - if (!\in_array($interface, \class_implements($value))) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an implementation of %2$s. Got: %s', static::valueToString($value), static::valueToString($interface))); - } + return new Stmt\Class_($this->name, ['flags' => $this->flags, 'extends' => $this->extends, 'implements' => $this->implements, 'stmts' => \array_merge($this->uses, $this->constants, $this->properties, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); } +} +addStmt($stmt); } + return $this; } /** - * @psalm-pure - * @psalm-param class-string|object $classOrObject + * Sets doc comment for the declaration. * - * @param string|object $classOrObject - * @param mixed $property - * @param string $message + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function propertyNotExists($classOrObject, $property, $message = '') + public function setDocComment($docComment) { - if (\property_exists($classOrObject, $property)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected the property %s to not exist.', static::valueToString($property))); - } + $this->attributes['comments'] = [BuilderHelpers::normalizeDocComment($docComment)]; + return $this; } +} +name = $name; } /** - * @psalm-pure - * @psalm-param class-string|object $classOrObject + * Sets the value. * - * @param string|object $classOrObject - * @param mixed $method - * @param string $message + * @param Node\Expr|string|int $value * - * @throws InvalidArgumentException + * @return $this */ - public static function methodNotExists($classOrObject, $method, $message = '') + public function setValue($value) { - if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected the method %s to not exist.', static::valueToString($method))); - } + $this->value = BuilderHelpers::normalizeValue($value); + return $this; } /** - * @psalm-pure + * Sets doc comment for the constant. * - * @param array $array - * @param string|int $key - * @param string $message + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function keyExists($array, $key, $message = '') + public function setDocComment($docComment) { - if (!(isset($array[$key]) || \array_key_exists($key, $array))) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected the key %s to exist.', static::valueToString($key))); - } + $this->attributes = ['comments' => [BuilderHelpers::normalizeDocComment($docComment)]]; + return $this; } /** - * @psalm-pure + * Adds an attribute group. * - * @param array $array - * @param string|int $key - * @param string $message + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function keyNotExists($array, $key, $message = '') + public function addAttribute($attribute) { - if (isset($array[$key]) || \array_key_exists($key, $array)) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected the key %s to not exist.', static::valueToString($key))); - } + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * Checks if a value is a valid array key (int or string). + * Returns the built enum case node. * - * @psalm-pure - * @psalm-assert array-key $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * @return Stmt\EnumCase The built constant node */ - public static function validArrayKey($value, $message = '') + public function getNode() : PhpParser\Node { - if (!(\is_int($value) || \is_string($value))) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected string or integer. Got: %s', static::typeToString($value))); - } + return new Stmt\EnumCase($this->name, $this->value, $this->attributes, $this->attributeGroups); } +} +name = $name; } /** - * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * Sets the scalar type. * - * @param Countable|array $array - * @param int|float $min - * @param string $message + * @param string|Identifier $type * - * @throws InvalidArgumentException + * @return $this */ - public static function minCount($array, $min, $message = '') + public function setScalarType($scalarType) { - if (\count($array) < $min) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain at least %2$d elements. Got: %d', \count($array), $min)); - } + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + return $this; } /** - * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * Implements one or more interfaces. * - * @param Countable|array $array - * @param int|float $max - * @param string $message + * @param Name|string ...$interfaces Names of interfaces to implement * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function maxCount($array, $max, $message = '') + public function implement(...$interfaces) { - if (\count($array) > $max) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain at most %2$d elements. Got: %d', \count($array), $max)); + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); } + return $this; } /** - * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * Adds a statement. * - * @param Countable|array $array - * @param int|float $min - * @param int|float $max - * @param string $message + * @param Stmt|PhpParser\Builder $stmt The statement to add * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function countBetween($array, $min, $max, $message = '') + public function addStmt($stmt) { - $count = \count($array); - if ($count < $min || $count > $max) { - static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', $count, $min, $max)); + $stmt = BuilderHelpers::normalizeNode($stmt); + $targets = [Stmt\TraitUse::class => &$this->uses, Stmt\EnumCase::class => &$this->enumCases, Stmt\ClassConst::class => &$this->constants, Stmt\ClassMethod::class => &$this->methods]; + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); } + $targets[$class][] = $stmt; + return $this; } /** - * @psalm-pure - * @psalm-assert list $array + * Adds an attribute group. * - * @param mixed $array - * @param string $message + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function isList($array, $message = '') + public function addAttribute($attribute) { - if (!\is_array($array) || $array !== \array_values($array)) { - static::reportInvalidArgument($message ?: 'Expected list - non-associative array.'); - } + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-assert non-empty-list $array - * - * @param mixed $array - * @param string $message + * Returns the built class node. * - * @throws InvalidArgumentException + * @return Stmt\Enum_ The built enum node */ - public static function isNonEmptyList($array, $message = '') + public function getNode() : PhpParser\Node { - static::isList($array, $message); - static::notEmpty($array, $message); + return new Stmt\Enum_($this->name, ['scalarType' => $this->scalarType, 'implements' => $this->implements, 'stmts' => \array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); } +} + $array - * @psalm-assert array $array - * - * @param mixed $array - * @param string $message + * Make the function return by reference. * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function isMap($array, $message = '') + public function makeReturnByRef() { - if (!\is_array($array) || \array_keys($array) !== \array_filter(\array_keys($array), '\\is_string')) { - static::reportInvalidArgument($message ?: 'Expected map - associative array with string keys.'); - } + $this->returnByRef = \true; + return $this; } /** - * @psalm-pure - * @psalm-template T - * @psalm-param mixed|array $array - * @psalm-assert array $array - * @psalm-assert !empty $array + * Adds a parameter. * - * @param mixed $array - * @param string $message + * @param Node\Param|Param $param The parameter to add * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function isNonEmptyMap($array, $message = '') + public function addParam($param) { - static::isMap($array, $message); - static::notEmpty($array, $message); + $param = BuilderHelpers::normalizeNode($param); + if (!$param instanceof Node\Param) { + throw new \LogicException(\sprintf('Expected parameter node, got "%s"', $param->getType())); + } + $this->params[] = $param; + return $this; } /** - * @psalm-pure + * Adds multiple parameters. * - * @param string $value - * @param string $message + * @param array $params The parameters to add * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function uuid($value, $message = '') + public function addParams(array $params) { - $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); - // The nil UUID is special form of UUID that is specified to have all - // 128 bits set to zero. - if ('00000000-0000-0000-0000-000000000000' === $value) { - return; - } - if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { - static::reportInvalidArgument(\sprintf($message ?: 'Value %s is not a valid UUID.', static::valueToString($value))); + foreach ($params as $param) { + $this->addParam($param); } + return $this; } /** - * @psalm-param class-string $class + * Sets the return type for PHP 7. * - * @param Closure $expression - * @param string $class - * @param string $message + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) */ - public static function throws(Closure $expression, $class = 'Exception', $message = '') + public function setReturnType($type) { - static::string($class); - $actual = 'none'; - try { - $expression(); - } catch (Exception $e) { - $actual = \get_class($e); - if ($e instanceof $class) { - return; - } - } catch (Throwable $e) { - $actual = \get_class($e); - if ($e instanceof $class) { - return; - } - } - static::reportInvalidArgument($message ?: \sprintf('Expected to throw "%s", got "%s"', $class, $actual)); + $this->returnType = BuilderHelpers::normalizeType($type); + return $this; } +} +name = $name; } /** - * @param mixed $value + * Adds a statement. * - * @return string + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) */ - protected static function valueToString($value) + public function addStmt($stmt) { - if (null === $value) { - return 'null'; - } - if (\true === $value) { - return 'true'; - } - if (\false === $value) { - return 'false'; - } - if (\is_array($value)) { - return 'array'; - } - if (\is_object($value)) { - if (\method_exists($value, '__toString')) { - return \get_class($value) . ': ' . self::valueToString($value->__toString()); - } - if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { - return \get_class($value) . ': ' . self::valueToString($value->format('c')); - } - return \get_class($value); - } - if (\is_resource($value)) { - return 'resource'; - } - if (\is_string($value)) { - return '"' . $value . '"'; - } - return (string) $value; + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + return $this; } /** - * @param mixed $value + * Adds an attribute group. * - * @return string + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) */ - protected static function typeToString($value) - { - return \is_object($value) ? \get_class($value) : \gettype($value); - } - protected static function strlen($value) + public function addAttribute($attribute) { - if (!\function_exists('mb_detect_encoding')) { - return \strlen($value); - } - if (\false === ($encoding = \mb_detect_encoding($value))) { - return \strlen($value); - } - return \mb_strlen($value, $encoding); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built function node. * - * @psalm-pure this method is not supposed to perform side-effects + * @return Stmt\Function_ The built function node */ - protected static function reportInvalidArgument($message) - { - throw new InvalidArgumentException($message); - } - private function __construct() + public function getNode() : Node { + return new Stmt\Function_($this->name, ['byRef' => $this->returnByRef, 'params' => $this->params, 'returnType' => $this->returnType, 'stmts' => $this->stmts, 'attrGroups' => $this->attributeGroups], $this->attributes); } } name = $name; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Extends one or more interfaces. * - * @throws InvalidArgumentException + * @param Name|string ...$interfaces Names of interfaces to extend * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allString($value, $message = '') + public function extend(...$interfaces) { - static::__callStatic('allString', array($value, $message)); + foreach ($interfaces as $interface) { + $this->extends[] = BuilderHelpers::normalizeName($interface); + } + return $this; } /** - * @psalm-pure - * @psalm-assert non-empty-string|null $value - * - * @param mixed $value - * @param string $message + * Adds a statement. * - * @throws InvalidArgumentException + * @param Stmt|PhpParser\Builder $stmt The statement to add * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrStringNotEmpty($value, $message = '') + public function addStmt($stmt) { - static::__callStatic('nullOrStringNotEmpty', array($value, $message)); + $stmt = BuilderHelpers::normalizeNode($stmt); + if ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + // we erase all statements in the body of an interface method + $stmt->stmts = null; + $this->methods[] = $stmt; + } else { + throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Adds an attribute group. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allStringNotEmpty($value, $message = '') + public function addAttribute($attribute) { - static::__callStatic('allStringNotEmpty', array($value, $message)); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-assert int|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built interface node. * - * @return void + * @return Stmt\Interface_ The built interface node */ - public static function nullOrInteger($value, $message = '') + public function getNode() : PhpParser\Node { - static::__callStatic('nullOrInteger', array($value, $message)); + return new Stmt\Interface_($this->name, ['extends' => $this->extends, 'stmts' => \array_merge($this->constants, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); } +} + $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Creates a method builder. * - * @return void + * @param string $name Name of the method */ - public static function allInteger($value, $message = '') + public function __construct(string $name) { - static::__callStatic('allInteger', array($value, $message)); + $this->name = $name; } /** - * @psalm-pure - * @psalm-assert numeric|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method public. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIntegerish($value, $message = '') + public function makePublic() { - static::__callStatic('nullOrIntegerish', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method protected. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIntegerish($value, $message = '') + public function makeProtected() { - static::__callStatic('allIntegerish', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + return $this; } /** - * @psalm-pure - * @psalm-assert positive-int|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method private. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrPositiveInteger($value, $message = '') + public function makePrivate() { - static::__callStatic('nullOrPositiveInteger', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method static. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allPositiveInteger($value, $message = '') + public function makeStatic() { - static::__callStatic('allPositiveInteger', array($value, $message)); - } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); + return $this; + } /** - * @psalm-pure - * @psalm-assert float|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method abstract. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrFloat($value, $message = '') + public function makeAbstract() { - static::__callStatic('nullOrFloat', array($value, $message)); + if (!empty($this->stmts)) { + throw new \LogicException('Cannot make method with statements abstract'); + } + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + $this->stmts = null; + // abstract methods don't have statements + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the method final. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allFloat($value, $message = '') + public function makeFinal() { - static::__callStatic('allFloat', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + return $this; } /** - * @psalm-pure - * @psalm-assert numeric|null $value - * - * @param mixed $value - * @param string $message + * Adds a statement. * - * @throws InvalidArgumentException + * @param Node|PhpParser\Builder $stmt The statement to add * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrNumeric($value, $message = '') + public function addStmt($stmt) { - static::__callStatic('nullOrNumeric', array($value, $message)); + if (null === $this->stmts) { + throw new \LogicException('Cannot add statements to an abstract method'); + } + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Adds an attribute group. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allNumeric($value, $message = '') + public function addAttribute($attribute) { - static::__callStatic('allNumeric', array($value, $message)); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-assert positive-int|0|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built method node. * - * @return void + * @return Stmt\ClassMethod The built method node */ - public static function nullOrNatural($value, $message = '') + public function getNode() : Node { - static::__callStatic('nullOrNatural', array($value, $message)); + return new Stmt\ClassMethod($this->name, ['flags' => $this->flags, 'byRef' => $this->returnByRef, 'params' => $this->params, 'returnType' => $this->returnType, 'stmts' => $this->stmts, 'attrGroups' => $this->attributeGroups], $this->attributes); } +} + $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Creates a namespace builder. * - * @return void + * @param Node\Name|string|null $name Name of the namespace */ - public static function allNatural($value, $message = '') + public function __construct($name) { - static::__callStatic('allNatural', array($value, $message)); + $this->name = null !== $name ? BuilderHelpers::normalizeName($name) : null; } /** - * @psalm-pure - * @psalm-assert bool|null $value - * - * @param mixed $value - * @param string $message + * Adds a statement. * - * @throws InvalidArgumentException + * @param Node|PhpParser\Builder $stmt The statement to add * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrBoolean($value, $message = '') + public function addStmt($stmt) { - static::__callStatic('nullOrBoolean', array($value, $message)); + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built node. * - * @return void + * @return Stmt\Namespace_ The built node */ - public static function allBoolean($value, $message = '') + public function getNode() : Node { - static::__callStatic('allBoolean', array($value, $message)); + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); } +} +name = $name; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Sets default value for the parameter. * - * @throws InvalidArgumentException + * @param mixed $value Default value to use * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allScalar($value, $message = '') + public function setDefault($value) { - static::__callStatic('allScalar', array($value, $message)); + $this->default = BuilderHelpers::normalizeValue($value); + return $this; } /** - * @psalm-pure - * @psalm-assert object|null $value - * - * @param mixed $value - * @param string $message + * Sets type for the parameter. * - * @throws InvalidArgumentException + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrObject($value, $message = '') + public function setType($type) { - static::__callStatic('nullOrObject', array($value, $message)); + $this->type = BuilderHelpers::normalizeType($type); + if ($this->type == 'void') { + throw new \LogicException('Parameter type cannot be void'); + } + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value + * Sets type for the parameter. * - * @param mixed $value - * @param string $message + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type * - * @throws InvalidArgumentException + * @return $this The builder instance (for fluid interface) * - * @return void + * @deprecated Use setType() instead */ - public static function allObject($value, $message = '') + public function setTypeHint($type) { - static::__callStatic('allObject', array($value, $message)); + return $this->setType($type); } /** - * @psalm-pure - * @psalm-assert resource|null $value - * - * @param mixed $value - * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php - * @param string $message - * - * @throws InvalidArgumentException + * Make the parameter accept the value by reference. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrResource($value, $type = null, $message = '') + public function makeByRef() { - static::__callStatic('nullOrResource', array($value, $type, $message)); + $this->byRef = \true; + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php - * @param string $message - * - * @throws InvalidArgumentException + * Make the parameter variadic * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allResource($value, $type = null, $message = '') + public function makeVariadic() { - static::__callStatic('allResource', array($value, $type, $message)); + $this->variadic = \true; + return $this; } /** - * @psalm-pure - * @psalm-assert callable|null $value - * - * @param mixed $value - * @param string $message + * Adds an attribute group. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsCallable($value, $message = '') + public function addAttribute($attribute) { - static::__callStatic('nullOrIsCallable', array($value, $message)); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built parameter node. * - * @return void + * @return Node\Param The built parameter node */ - public static function allIsCallable($value, $message = '') + public function getNode() : Node { - static::__callStatic('allIsCallable', array($value, $message)); + return new Node\Param(new Node\Expr\Variable($this->name), $this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups); } +} +name = $name; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the property public. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsArray($value, $message = '') + public function makePublic() { - static::__callStatic('allIsArray', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable|null $value - * - * @deprecated use "isIterable" or "isInstanceOf" instead - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the property protected. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsTraversable($value, $message = '') + public function makeProtected() { - static::__callStatic('nullOrIsTraversable', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @deprecated use "isIterable" or "isInstanceOf" instead - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the property private. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsTraversable($value, $message = '') + public function makePrivate() { - static::__callStatic('allIsTraversable', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + return $this; } /** - * @psalm-pure - * @psalm-assert array|ArrayAccess|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the property static. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsArrayAccessible($value, $message = '') + public function makeStatic() { - static::__callStatic('nullOrIsArrayAccessible', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Makes the property readonly. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsArrayAccessible($value, $message = '') + public function makeReadonly() { - static::__callStatic('allIsArrayAccessible', array($value, $message)); + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); + return $this; } /** - * @psalm-pure - * @psalm-assert countable|null $value - * - * @param mixed $value - * @param string $message + * Sets default value for the property. * - * @throws InvalidArgumentException + * @param mixed $value Default value to use * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsCountable($value, $message = '') + public function setDefault($value) { - static::__callStatic('nullOrIsCountable', array($value, $message)); + $this->default = BuilderHelpers::normalizeValue($value); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Sets doc comment for the property. * - * @throws InvalidArgumentException + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsCountable($value, $message = '') + public function setDocComment($docComment) { - static::__callStatic('allIsCountable', array($value, $message)); + $this->attributes = ['comments' => [BuilderHelpers::normalizeDocComment($docComment)]]; + return $this; } /** - * @psalm-pure - * @psalm-assert iterable|null $value - * - * @param mixed $value - * @param string $message + * Sets the property type for PHP 7.4+. * - * @throws InvalidArgumentException + * @param string|Name|Identifier|ComplexType $type * - * @return void + * @return $this */ - public static function nullOrIsIterable($value, $message = '') + public function setType($type) { - static::__callStatic('nullOrIsIterable', array($value, $message)); + $this->type = BuilderHelpers::normalizeType($type); + return $this; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Adds an attribute group. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsIterable($value, $message = '') + public function addAttribute($attribute) { - static::__callStatic('allIsIterable', array($value, $message)); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert ExpectedType|null $value - * - * @param mixed $value - * @param string|object $class - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built class node. * - * @return void + * @return Stmt\Property The built property node */ - public static function nullOrIsInstanceOf($value, $class, $message = '') + public function getNode() : PhpParser\Node { - static::__callStatic('nullOrIsInstanceOf', array($value, $class, $message)); + return new Stmt\Property($this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC, [new Stmt\PropertyProperty($this->name, $this->default)], $this->attributes, $this->type, $this->attributeGroups); } +} + $class - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string|object $class - * @param string $message - * - * @throws InvalidArgumentException + * Creates a trait use builder. * - * @return void + * @param Node\Name|string ...$traits Names of used traits */ - public static function allIsInstanceOf($value, $class, $message = '') + public function __construct(...$traits) { - static::__callStatic('allIsInstanceOf', array($value, $class, $message)); + foreach ($traits as $trait) { + $this->and($trait); + } } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * - * @param mixed $value - * @param string|object $class - * @param string $message + * Adds used trait. * - * @throws InvalidArgumentException + * @param Node\Name|string $trait Trait name * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrNotInstanceOf($value, $class, $message = '') + public function and($trait) { - static::__callStatic('nullOrNotInstanceOf', array($value, $class, $message)); + $this->traits[] = BuilderHelpers::normalizeName($trait); + return $this; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * - * @param mixed $value - * @param string|object $class - * @param string $message + * Adds trait adaptation. * - * @throws InvalidArgumentException + * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allNotInstanceOf($value, $class, $message = '') + public function with($adaptation) { - static::__callStatic('allNotInstanceOf', array($value, $class, $message)); - } + $adaptation = BuilderHelpers::normalizeNode($adaptation); + if (!$adaptation instanceof Stmt\TraitUseAdaptation) { + throw new \LogicException('Adaptation must have type TraitUseAdaptation'); + } + $this->adaptations[] = $adaptation; + return $this; + } /** - * @psalm-pure - * @psalm-param array $classes - * - * @param mixed $value - * @param array $classes - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built node. * - * @return void + * @return Node The built node */ - public static function nullOrIsInstanceOfAny($value, $classes, $message = '') + public function getNode() : Node { - static::__callStatic('nullOrIsInstanceOfAny', array($value, $classes, $message)); + return new Stmt\TraitUse($this->traits, $this->adaptations); } +} + $classes - * - * @param mixed $value - * @param array $classes - * @param string $message - * - * @throws InvalidArgumentException + * Creates a trait use adaptation builder. * - * @return void + * @param Node\Name|string|null $trait Name of adaptated trait + * @param Node\Identifier|string $method Name of adaptated method */ - public static function allIsInstanceOfAny($value, $classes, $message = '') + public function __construct($trait, $method) { - static::__callStatic('allIsInstanceOfAny', array($value, $classes, $message)); + $this->type = self::TYPE_UNDEFINED; + $this->trait = \is_null($trait) ? null : BuilderHelpers::normalizeName($trait); + $this->method = BuilderHelpers::normalizeIdentifier($method); } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert ExpectedType|class-string|null $value - * - * @param object|string|null $value - * @param string $class - * @param string $message + * Sets alias of method. * - * @throws InvalidArgumentException + * @param Node\Identifier|string $alias Alias for adaptated method * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsAOf($value, $class, $message = '') + public function as($alias) { - static::__callStatic('nullOrIsAOf', array($value, $class, $message)); + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set alias for not alias adaptation buider'); + } + $this->alias = $alias; + return $this; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert iterable> $value - * - * @param iterable $value - * @param string $class - * @param string $message - * - * @throws InvalidArgumentException + * Sets adaptated method public. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsAOf($value, $class, $message = '') + public function makePublic() { - static::__callStatic('allIsAOf', array($value, $class, $message)); + $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC); + return $this; } /** - * @psalm-pure - * @psalm-template UnexpectedType of object - * @psalm-param class-string $class - * - * @param object|string|null $value - * @param string $class - * @param string $message - * - * @throws InvalidArgumentException + * Sets adaptated method protected. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsNotA($value, $class, $message = '') + public function makeProtected() { - static::__callStatic('nullOrIsNotA', array($value, $class, $message)); + $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED); + return $this; } /** - * @psalm-pure - * @psalm-template UnexpectedType of object - * @psalm-param class-string $class - * - * @param iterable $value - * @param string $class - * @param string $message - * - * @throws InvalidArgumentException + * Sets adaptated method private. * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsNotA($value, $class, $message = '') + public function makePrivate() { - static::__callStatic('allIsNotA', array($value, $class, $message)); + $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE); + return $this; } /** - * @psalm-pure - * @psalm-param array $classes - * - * @param object|string|null $value - * @param string[] $classes - * @param string $message + * Adds overwritten traits. * - * @throws InvalidArgumentException + * @param Node\Name|string ...$traits Traits for overwrite * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrIsAnyOf($value, $classes, $message = '') + public function insteadof(...$traits) + { + if ($this->type === self::TYPE_UNDEFINED) { + if (\is_null($this->trait)) { + throw new \LogicException('Precedence adaptation must have trait'); + } + $this->type = self::TYPE_PRECEDENCE; + } + if ($this->type !== self::TYPE_PRECEDENCE) { + throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); + } + foreach ($traits as $trait) { + $this->insteadof[] = BuilderHelpers::normalizeName($trait); + } + return $this; + } + protected function setModifier(int $modifier) { - static::__callStatic('nullOrIsAnyOf', array($value, $classes, $message)); + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); + } + if (\is_null($this->modifier)) { + $this->modifier = $modifier; + } else { + throw new \LogicException('Multiple access type modifiers are not allowed'); + } } /** - * @psalm-pure - * @psalm-param array $classes - * - * @param iterable $value - * @param string[] $classes - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built node. * - * @return void + * @return Node The built node */ - public static function allIsAnyOf($value, $classes, $message = '') + public function getNode() : Node { - static::__callStatic('allIsAnyOf', array($value, $classes, $message)); + switch ($this->type) { + case self::TYPE_ALIAS: + return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); + case self::TYPE_PRECEDENCE: + return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); + default: + throw new \LogicException('Type of adaptation is not defined'); + } } +} +name = $name; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Adds a statement. * - * @throws InvalidArgumentException + * @param Stmt|PhpParser\Builder $stmt The statement to add * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allIsEmpty($value, $message = '') + public function addStmt($stmt) { - static::__callStatic('allIsEmpty', array($value, $message)); + $stmt = BuilderHelpers::normalizeNode($stmt); + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } else { + throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + return $this; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Adds an attribute group. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function nullOrNotEmpty($value, $message = '') + public function addAttribute($attribute) { - static::__callStatic('nullOrNotEmpty', array($value, $message)); + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + return $this; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built trait node. * - * @return void + * @return Stmt\Trait_ The built interface node */ - public static function allNotEmpty($value, $message = '') + public function getNode() : PhpParser\Node { - static::__callStatic('allNotEmpty', array($value, $message)); + return new Stmt\Trait_($this->name, ['stmts' => \array_merge($this->uses, $this->properties, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); } +} + $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Creates a name use (alias) builder. * - * @return void + * @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias + * @param int $type One of the Stmt\Use_::TYPE_* constants */ - public static function allNull($value, $message = '') + public function __construct($name, int $type) { - static::__callStatic('allNull', array($value, $message)); + $this->name = BuilderHelpers::normalizeName($name); + $this->type = $type; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Sets alias for used name. * - * @throws InvalidArgumentException + * @param string $alias Alias to use (last component of full name by default) * - * @return void + * @return $this The builder instance (for fluid interface) */ - public static function allNotNull($value, $message = '') + public function as(string $alias) { - static::__callStatic('allNotNull', array($value, $message)); + $this->alias = $alias; + return $this; } /** - * @psalm-pure - * @psalm-assert true|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Returns the built node. * - * @return void + * @return Stmt\Use_ The built node */ - public static function nullOrTrue($value, $message = '') + public function getNode() : Node { - static::__callStatic('nullOrTrue', array($value, $message)); + return new Stmt\Use_([new Stmt\UseUse($this->name, $this->alias)], $this->type); } +} + $value - * - * @param mixed $value - * @param string $message + * Creates an attribute node. * - * @throws InvalidArgumentException + * @param string|Name $name Name of the attribute + * @param array $args Attribute named arguments * - * @return void + * @return Node\Attribute */ - public static function allTrue($value, $message = '') + public function attribute($name, array $args = []) : Node\Attribute { - static::__callStatic('allTrue', array($value, $message)); + return new Node\Attribute(BuilderHelpers::normalizeName($name), $this->args($args)); } /** - * @psalm-pure - * @psalm-assert false|null $value - * - * @param mixed $value - * @param string $message + * Creates a namespace builder. * - * @throws InvalidArgumentException + * @param null|string|Node\Name $name Name of the namespace * - * @return void + * @return Builder\Namespace_ The created namespace builder */ - public static function nullOrFalse($value, $message = '') + public function namespace($name) : Builder\Namespace_ { - static::__callStatic('nullOrFalse', array($value, $message)); + return new Builder\Namespace_($name); } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message + * Creates a class builder. * - * @throws InvalidArgumentException + * @param string $name Name of the class * - * @return void + * @return Builder\Class_ The created class builder */ - public static function allFalse($value, $message = '') + public function class(string $name) : Builder\Class_ { - static::__callStatic('allFalse', array($value, $message)); + return new Builder\Class_($name); } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Creates an interface builder. * - * @throws InvalidArgumentException + * @param string $name Name of the interface * - * @return void + * @return Builder\Interface_ The created interface builder */ - public static function nullOrNotFalse($value, $message = '') + public function interface(string $name) : Builder\Interface_ { - static::__callStatic('nullOrNotFalse', array($value, $message)); + return new Builder\Interface_($name); } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Creates a trait builder. * - * @throws InvalidArgumentException + * @param string $name Name of the trait * - * @return void + * @return Builder\Trait_ The created trait builder */ - public static function allNotFalse($value, $message = '') + public function trait(string $name) : Builder\Trait_ { - static::__callStatic('allNotFalse', array($value, $message)); + return new Builder\Trait_($name); } /** - * @param mixed $value - * @param string $message + * Creates an enum builder. * - * @throws InvalidArgumentException + * @param string $name Name of the enum * - * @return void + * @return Builder\Enum_ The created enum builder */ - public static function nullOrIp($value, $message = '') + public function enum(string $name) : Builder\Enum_ { - static::__callStatic('nullOrIp', array($value, $message)); + return new Builder\Enum_($name); } /** - * @param mixed $value - * @param string $message + * Creates a trait use builder. * - * @throws InvalidArgumentException + * @param Node\Name|string ...$traits Trait names * - * @return void + * @return Builder\TraitUse The create trait use builder */ - public static function allIp($value, $message = '') + public function useTrait(...$traits) : Builder\TraitUse { - static::__callStatic('allIp', array($value, $message)); + return new Builder\TraitUse(...$traits); } /** - * @param mixed $value - * @param string $message + * Creates a trait use adaptation builder. * - * @throws InvalidArgumentException + * @param Node\Name|string|null $trait Trait name + * @param Node\Identifier|string $method Method name * - * @return void + * @return Builder\TraitUseAdaptation The create trait use adaptation builder */ - public static function nullOrIpv4($value, $message = '') + public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation { - static::__callStatic('nullOrIpv4', array($value, $message)); + if ($method === null) { + $method = $trait; + $trait = null; + } + return new Builder\TraitUseAdaptation($trait, $method); } /** - * @param mixed $value - * @param string $message + * Creates a method builder. * - * @throws InvalidArgumentException + * @param string $name Name of the method * - * @return void + * @return Builder\Method The created method builder */ - public static function allIpv4($value, $message = '') + public function method(string $name) : Builder\Method { - static::__callStatic('allIpv4', array($value, $message)); + return new Builder\Method($name); } /** - * @param mixed $value - * @param string $message + * Creates a parameter builder. * - * @throws InvalidArgumentException + * @param string $name Name of the parameter * - * @return void + * @return Builder\Param The created parameter builder */ - public static function nullOrIpv6($value, $message = '') + public function param(string $name) : Builder\Param { - static::__callStatic('nullOrIpv6', array($value, $message)); + return new Builder\Param($name); } /** - * @param mixed $value - * @param string $message + * Creates a property builder. * - * @throws InvalidArgumentException + * @param string $name Name of the property * - * @return void + * @return Builder\Property The created property builder */ - public static function allIpv6($value, $message = '') + public function property(string $name) : Builder\Property { - static::__callStatic('allIpv6', array($value, $message)); + return new Builder\Property($name); } /** - * @param mixed $value - * @param string $message + * Creates a function builder. * - * @throws InvalidArgumentException + * @param string $name Name of the function * - * @return void + * @return Builder\Function_ The created function builder */ - public static function nullOrEmail($value, $message = '') + public function function(string $name) : Builder\Function_ { - static::__callStatic('nullOrEmail', array($value, $message)); + return new Builder\Function_($name); } /** - * @param mixed $value - * @param string $message + * Creates a namespace/class use builder. * - * @throws InvalidArgumentException + * @param Node\Name|string $name Name of the entity (namespace or class) to alias * - * @return void + * @return Builder\Use_ The created use builder */ - public static function allEmail($value, $message = '') + public function use($name) : Builder\Use_ { - static::__callStatic('allEmail', array($value, $message)); + return new Builder\Use_($name, Use_::TYPE_NORMAL); } /** - * @param array|null $values - * @param string $message + * Creates a function use builder. * - * @throws InvalidArgumentException + * @param Node\Name|string $name Name of the function to alias * - * @return void + * @return Builder\Use_ The created use function builder */ - public static function nullOrUniqueValues($values, $message = '') + public function useFunction($name) : Builder\Use_ { - static::__callStatic('nullOrUniqueValues', array($values, $message)); + return new Builder\Use_($name, Use_::TYPE_FUNCTION); } /** - * @param iterable $values - * @param string $message + * Creates a constant use builder. * - * @throws InvalidArgumentException + * @param Node\Name|string $name Name of the const to alias * - * @return void + * @return Builder\Use_ The created use const builder */ - public static function allUniqueValues($values, $message = '') + public function useConst($name) : Builder\Use_ { - static::__callStatic('allUniqueValues', array($values, $message)); + return new Builder\Use_($name, Use_::TYPE_CONSTANT); } /** - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates a class constant builder. * - * @throws InvalidArgumentException + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value * - * @return void + * @return Builder\ClassConst The created use const builder */ - public static function nullOrEq($value, $expect, $message = '') + public function classConst($name, $value) : Builder\ClassConst { - static::__callStatic('nullOrEq', array($value, $expect, $message)); + return new Builder\ClassConst($name, $value); } /** - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates an enum case builder. * - * @throws InvalidArgumentException + * @param string|Identifier $name Name * - * @return void + * @return Builder\EnumCase The created use const builder */ - public static function allEq($value, $expect, $message = '') + public function enumCase($name) : Builder\EnumCase { - static::__callStatic('allEq', array($value, $expect, $message)); + return new Builder\EnumCase($name); } /** - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates node a for a literal value. * - * @throws InvalidArgumentException + * @param Expr|bool|null|int|float|string|array $value $value * - * @return void + * @return Expr */ - public static function nullOrNotEq($value, $expect, $message = '') + public function val($value) : Expr { - static::__callStatic('nullOrNotEq', array($value, $expect, $message)); + return BuilderHelpers::normalizeValue($value); } /** - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates variable node. * - * @throws InvalidArgumentException + * @param string|Expr $name Name * - * @return void + * @return Expr\Variable */ - public static function allNotEq($value, $expect, $message = '') + public function var($name) : Expr\Variable { - static::__callStatic('allNotEq', array($value, $expect, $message)); + if (!\is_string($name) && !$name instanceof Expr) { + throw new \LogicException('Variable name must be string or Expr'); + } + return new Expr\Variable($name); } /** - * @psalm-pure + * Normalizes an argument list. * - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates Arg nodes for all arguments and converts literal values to expressions. * - * @throws InvalidArgumentException + * @param array $args List of arguments to normalize * - * @return void + * @return Arg[] */ - public static function nullOrSame($value, $expect, $message = '') + public function args(array $args) : array { - static::__callStatic('nullOrSame', array($value, $expect, $message)); + $normalizedArgs = []; + foreach ($args as $key => $arg) { + if (!$arg instanceof Arg) { + $arg = new Arg(BuilderHelpers::normalizeValue($arg)); + } + if (\is_string($key)) { + $arg->name = BuilderHelpers::normalizeIdentifier($key); + } + $normalizedArgs[] = $arg; + } + return $normalizedArgs; } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates a function call node. * - * @throws InvalidArgumentException + * @param string|Name|Expr $name Function name + * @param array $args Function arguments * - * @return void + * @return Expr\FuncCall */ - public static function allSame($value, $expect, $message = '') + public function funcCall($name, array $args = []) : Expr\FuncCall { - static::__callStatic('allSame', array($value, $expect, $message)); + return new Expr\FuncCall(BuilderHelpers::normalizeNameOrExpr($name), $this->args($args)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates a method call node. * - * @throws InvalidArgumentException + * @param Expr $var Variable the method is called on + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments * - * @return void + * @return Expr\MethodCall */ - public static function nullOrNotSame($value, $expect, $message = '') + public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall { - static::__callStatic('nullOrNotSame', array($value, $expect, $message)); + return new Expr\MethodCall($var, BuilderHelpers::normalizeIdentifierOrExpr($name), $this->args($args)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $expect - * @param string $message + * Creates a static method call node. * - * @throws InvalidArgumentException + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments * - * @return void + * @return Expr\StaticCall */ - public static function allNotSame($value, $expect, $message = '') + public function staticCall($class, $name, array $args = []) : Expr\StaticCall { - static::__callStatic('allNotSame', array($value, $expect, $message)); + return new Expr\StaticCall(BuilderHelpers::normalizeNameOrExpr($class), BuilderHelpers::normalizeIdentifierOrExpr($name), $this->args($args)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Creates an object creation node. * - * @throws InvalidArgumentException + * @param string|Name|Expr $class Class name + * @param array $args Constructor arguments * - * @return void + * @return Expr\New_ */ - public static function nullOrGreaterThan($value, $limit, $message = '') + public function new($class, array $args = []) : Expr\New_ { - static::__callStatic('nullOrGreaterThan', array($value, $limit, $message)); + return new Expr\New_(BuilderHelpers::normalizeNameOrExpr($class), $this->args($args)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Creates a constant fetch node. * - * @throws InvalidArgumentException + * @param string|Name $name Constant name * - * @return void + * @return Expr\ConstFetch */ - public static function allGreaterThan($value, $limit, $message = '') + public function constFetch($name) : Expr\ConstFetch { - static::__callStatic('allGreaterThan', array($value, $limit, $message)); + return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Creates a property fetch node. * - * @throws InvalidArgumentException + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name * - * @return void + * @return Expr\PropertyFetch */ - public static function nullOrGreaterThanEq($value, $limit, $message = '') + public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch { - static::__callStatic('nullOrGreaterThanEq', array($value, $limit, $message)); + return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Creates a class constant fetch node. * - * @throws InvalidArgumentException + * @param string|Name|Expr $class Class name + * @param string|Identifier $name Constant name * - * @return void + * @return Expr\ClassConstFetch */ - public static function allGreaterThanEq($value, $limit, $message = '') + public function classConstFetch($class, $name) : Expr\ClassConstFetch { - static::__callStatic('allGreaterThanEq', array($value, $limit, $message)); + return new Expr\ClassConstFetch(BuilderHelpers::normalizeNameOrExpr($class), BuilderHelpers::normalizeIdentifier($name)); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Creates nested Concat nodes from a list of expressions. * - * @throws InvalidArgumentException + * @param Expr|string ...$exprs Expressions or literal strings * - * @return void + * @return Concat */ - public static function nullOrLessThan($value, $limit, $message = '') + public function concat(...$exprs) : Concat { - static::__callStatic('nullOrLessThan', array($value, $limit, $message)); + $numExprs = \count($exprs); + if ($numExprs < 2) { + throw new \LogicException('Expected at least two expressions'); + } + $lastConcat = $this->normalizeStringExpr($exprs[0]); + for ($i = 1; $i < $numExprs; $i++) { + $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); + } + return $lastConcat; } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $limit - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * @param string|Expr $expr + * @return Expr */ - public static function allLessThan($value, $limit, $message = '') + private function normalizeStringExpr($expr) : Expr { - static::__callStatic('allLessThan', array($value, $limit, $message)); + if ($expr instanceof Expr) { + return $expr; + } + if (\is_string($expr)) { + return new String_($expr); + } + throw new \LogicException('Expected string or Expr'); } +} +getNode(); + } + if ($node instanceof Node) { + return $node; + } + throw new \LogicException('Expected node or builder object'); } /** - * @psalm-pure + * Normalizes a node to a statement. * - * @param mixed $value - * @param mixed $limit - * @param string $message + * Expressions are wrapped in a Stmt\Expression node. * - * @throws InvalidArgumentException + * @param Node|Builder $node The node to normalize * - * @return void + * @return Stmt The normalized statement node */ - public static function allLessThanEq($value, $limit, $message = '') + public static function normalizeStmt($node) : Stmt { - static::__callStatic('allLessThanEq', array($value, $limit, $message)); + $node = self::normalizeNode($node); + if ($node instanceof Stmt) { + return $node; + } + if ($node instanceof Expr) { + return new Stmt\Expression($node); + } + throw new \LogicException('Expected statement or expression node'); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $min - * @param mixed $max - * @param string $message + * Normalizes strings to Identifier. * - * @throws InvalidArgumentException + * @param string|Identifier $name The identifier to normalize * - * @return void + * @return Identifier The normalized identifier */ - public static function nullOrRange($value, $min, $max, $message = '') + public static function normalizeIdentifier($name) : Identifier { - static::__callStatic('nullOrRange', array($value, $min, $max, $message)); + if ($name instanceof Identifier) { + return $name; + } + if (\is_string($name)) { + return new Identifier($name); + } + throw new \LogicException('PHPUnit\\Expected string or instance of Node\\Identifier'); } /** - * @psalm-pure - * - * @param mixed $value - * @param mixed $min - * @param mixed $max - * @param string $message + * Normalizes strings to Identifier, also allowing expressions. * - * @throws InvalidArgumentException + * @param string|Identifier|Expr $name The identifier to normalize * - * @return void + * @return Identifier|Expr The normalized identifier or expression */ - public static function allRange($value, $min, $max, $message = '') + public static function normalizeIdentifierOrExpr($name) { - static::__callStatic('allRange', array($value, $min, $max, $message)); + if ($name instanceof Identifier || $name instanceof Expr) { + return $name; + } + if (\is_string($name)) { + return new Identifier($name); + } + throw new \LogicException('PHPUnit\\Expected string or instance of Node\\Identifier or Node\\Expr'); } /** - * @psalm-pure - * - * @param mixed $value - * @param array $values - * @param string $message + * Normalizes a name: Converts string names to Name nodes. * - * @throws InvalidArgumentException + * @param Name|string $name The name to normalize * - * @return void + * @return Name The normalized name */ - public static function nullOrOneOf($value, $values, $message = '') + public static function normalizeName($name) : Name { - static::__callStatic('nullOrOneOf', array($value, $values, $message)); + if ($name instanceof Name) { + return $name; + } + if (\is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + if ($name[0] === '\\') { + return new Name\FullyQualified(\substr($name, 1)); + } + if (0 === \strpos($name, 'namespace\\')) { + return new Name\Relative(\substr($name, \strlen('namespace\\'))); + } + return new Name($name); + } + throw new \LogicException('PHPUnit\\Name must be a string or an instance of Node\\Name'); } /** - * @psalm-pure - * - * @param mixed $value - * @param array $values - * @param string $message + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. * - * @throws InvalidArgumentException + * @param Expr|Name|string $name The name to normalize * - * @return void + * @return Name|Expr The normalized name or expression */ - public static function allOneOf($value, $values, $message = '') + public static function normalizeNameOrExpr($name) { - static::__callStatic('allOneOf', array($value, $values, $message)); + if ($name instanceof Expr) { + return $name; + } + if (!\is_string($name) && !$name instanceof Name) { + throw new \LogicException('PHPUnit\\Name must be a string or an instance of Node\\Name or Node\\Expr'); + } + return self::normalizeName($name); } /** - * @psalm-pure + * Normalizes a type: Converts plain-text type names into proper AST representation. * - * @param mixed $value - * @param array $values - * @param string $message + * In particular, builtin types become Identifiers, custom types become Names and nullables + * are wrapped in NullableType nodes. * - * @throws InvalidArgumentException + * @param string|Name|Identifier|ComplexType $type The type to normalize * - * @return void + * @return Name|Identifier|ComplexType The normalized type */ - public static function nullOrInArray($value, $values, $message = '') + public static function normalizeType($type) { - static::__callStatic('nullOrInArray', array($value, $values, $message)); + if (!\is_string($type)) { + if (!$type instanceof Name && !$type instanceof Identifier && !$type instanceof ComplexType) { + throw new \LogicException('Type must be a string, or an instance of Name, Identifier or ComplexType'); + } + return $type; + } + $nullable = \false; + if (\strlen($type) > 0 && $type[0] === '?') { + $nullable = \true; + $type = \substr($type, 1); + } + $builtinTypes = ['array', 'callable', 'bool', 'int', 'float', 'string', 'iterable', 'void', 'object', 'null', 'false', 'mixed', 'never', 'true']; + $lowerType = \strtolower($type); + if (\in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + $notNullableTypes = ['void', 'mixed', 'never']; + if ($nullable && \in_array((string) $type, $notNullableTypes)) { + throw new \LogicException(\sprintf('%s type cannot be nullable', $type)); + } + return $nullable ? new NullableType($type) : $type; } /** - * @psalm-pure - * - * @param mixed $value - * @param array $values - * @param string $message + * Normalizes a value: Converts nulls, booleans, integers, + * floats, strings and arrays into their respective nodes * - * @throws InvalidArgumentException + * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize * - * @return void + * @return Expr The normalized value */ - public static function allInArray($value, $values, $message = '') + public static function normalizeValue($value) : Expr { - static::__callStatic('allInArray', array($value, $values, $message)); + if ($value instanceof Node\Expr) { + return $value; + } + if (\is_null($value)) { + return new Expr\ConstFetch(new Name('null')); + } + if (\is_bool($value)) { + return new Expr\ConstFetch(new Name($value ? 'true' : 'false')); + } + if (\is_int($value)) { + return new Scalar\LNumber($value); + } + if (\is_float($value)) { + return new Scalar\DNumber($value); + } + if (\is_string($value)) { + return new Scalar\String_($value); + } + if (\is_array($value)) { + $items = []; + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Expr\ArrayItem(self::normalizeValue($itemValue)); + } else { + $lastKey = null; + $items[] = new Expr\ArrayItem(self::normalizeValue($itemValue), self::normalizeValue($itemKey)); + } + } + return new Expr\Array_($items); + } + throw new \LogicException('Invalid value'); } /** - * @psalm-pure - * - * @param string|null $value - * @param string $subString - * @param string $message + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. * - * @throws InvalidArgumentException + * @param Comment\Doc|string $docComment The doc comment to normalize * - * @return void + * @return Comment\Doc The normalized doc comment */ - public static function nullOrContains($value, $subString, $message = '') + public static function normalizeDocComment($docComment) : Comment\Doc { - static::__callStatic('nullOrContains', array($value, $subString, $message)); + if ($docComment instanceof Comment\Doc) { + return $docComment; + } + if (\is_string($docComment)) { + return new Comment\Doc($docComment); + } + throw new \LogicException('PHPUnit\\Doc comment must be a string or an instance of PhpParser\\Comment\\Doc'); } /** - * @psalm-pure - * - * @param iterable $value - * @param string $subString - * @param string $message + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. * - * @throws InvalidArgumentException + * @param Node\Attribute|Node\AttributeGroup $attribute * - * @return void + * @return Node\AttributeGroup The Attribute Group */ - public static function allContains($value, $subString, $message = '') + public static function normalizeAttribute($attribute) : Node\AttributeGroup { - static::__callStatic('allContains', array($value, $subString, $message)); + if ($attribute instanceof Node\AttributeGroup) { + return $attribute; + } + if (!$attribute instanceof Node\Attribute) { + throw new \LogicException('PHPUnit\\Attribute must be an instance of PhpParser\\Node\\Attribute or PhpParser\\Node\\AttributeGroup'); + } + return new Node\AttributeGroup([$attribute]); } /** - * @psalm-pure - * - * @param string|null $value - * @param string $subString - * @param string $message + * Adds a modifier and returns new modifier bitmask. * - * @throws InvalidArgumentException + * @param int $modifiers Existing modifiers + * @param int $modifier Modifier to set * - * @return void + * @return int New modifiers */ - public static function nullOrNotContains($value, $subString, $message = '') + public static function addModifier(int $modifiers, int $modifier) : int { - static::__callStatic('nullOrNotContains', array($value, $subString, $message)); + Stmt\Class_::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $subString - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers */ - public static function allNotContains($value, $subString, $message = '') + public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int { - static::__callStatic('allNotContains', array($value, $subString, $message)); + Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; } +} + $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * @param string $text Comment text (including comment delimiters like /*) + * @param int $startLine Line number the comment started on + * @param int $startFilePos File offset the comment started on + * @param int $startTokenPos Token offset the comment started on */ - public static function allNotWhitespaceOnly($value, $message = '') + public function __construct(string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1, int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1) { - static::__callStatic('allNotWhitespaceOnly', array($value, $message)); + $this->text = $text; + $this->startLine = $startLine; + $this->startFilePos = $startFilePos; + $this->startTokenPos = $startTokenPos; + $this->endLine = $endLine; + $this->endFilePos = $endFilePos; + $this->endTokenPos = $endTokenPos; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException + * Gets the comment text. * - * @return void + * @return string The comment text (including comment delimiters like /*) */ - public static function nullOrStartsWith($value, $prefix, $message = '') + public function getText() : string { - static::__callStatic('nullOrStartsWith', array($value, $prefix, $message)); + return $this->text; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException + * Gets the line number the comment started on. * - * @return void + * @return int Line number (or -1 if not available) */ - public static function allStartsWith($value, $prefix, $message = '') + public function getStartLine() : int { - static::__callStatic('allStartsWith', array($value, $prefix, $message)); + return $this->startLine; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException + * Gets the file offset the comment started on. * - * @return void + * @return int File offset (or -1 if not available) */ - public static function nullOrNotStartsWith($value, $prefix, $message = '') + public function getStartFilePos() : int { - static::__callStatic('nullOrNotStartsWith', array($value, $prefix, $message)); + return $this->startFilePos; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $prefix - * @param string $message - * - * @throws InvalidArgumentException + * Gets the token offset the comment started on. * - * @return void + * @return int Token offset (or -1 if not available) */ - public static function allNotStartsWith($value, $prefix, $message = '') + public function getStartTokenPos() : int { - static::__callStatic('allNotStartsWith', array($value, $prefix, $message)); + return $this->startTokenPos; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Gets the line number the comment ends on. * - * @return void + * @return int Line number (or -1 if not available) */ - public static function nullOrStartsWithLetter($value, $message = '') + public function getEndLine() : int { - static::__callStatic('nullOrStartsWithLetter', array($value, $message)); + return $this->endLine; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Gets the file offset the comment ends on. * - * @return void + * @return int File offset (or -1 if not available) */ - public static function allStartsWithLetter($value, $message = '') + public function getEndFilePos() : int { - static::__callStatic('allStartsWithLetter', array($value, $message)); + return $this->endFilePos; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $suffix - * @param string $message - * - * @throws InvalidArgumentException + * Gets the token offset the comment ends on. * - * @return void + * @return int Token offset (or -1 if not available) */ - public static function nullOrEndsWith($value, $suffix, $message = '') + public function getEndTokenPos() : int { - static::__callStatic('nullOrEndsWith', array($value, $suffix, $message)); + return $this->endTokenPos; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $suffix - * @param string $message + * Gets the line number the comment started on. * - * @throws InvalidArgumentException + * @deprecated Use getStartLine() instead * - * @return void + * @return int Line number */ - public static function allEndsWith($value, $suffix, $message = '') + public function getLine() : int { - static::__callStatic('allEndsWith', array($value, $suffix, $message)); + return $this->startLine; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $suffix - * @param string $message + * Gets the file offset the comment started on. * - * @throws InvalidArgumentException + * @deprecated Use getStartFilePos() instead * - * @return void + * @return int File offset */ - public static function nullOrNotEndsWith($value, $suffix, $message = '') + public function getFilePos() : int { - static::__callStatic('nullOrNotEndsWith', array($value, $suffix, $message)); + return $this->startFilePos; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $suffix - * @param string $message + * Gets the token offset the comment started on. * - * @throws InvalidArgumentException + * @deprecated Use getStartTokenPos() instead * - * @return void + * @return int Token offset */ - public static function allNotEndsWith($value, $suffix, $message = '') + public function getTokenPos() : int { - static::__callStatic('allNotEndsWith', array($value, $suffix, $message)); + return $this->startTokenPos; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $pattern - * @param string $message - * - * @throws InvalidArgumentException + * Gets the comment text. * - * @return void + * @return string The comment text (including comment delimiters like /*) */ - public static function nullOrRegex($value, $pattern, $message = '') + public function __toString() : string { - static::__callStatic('nullOrRegex', array($value, $pattern, $message)); + return $this->text; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $pattern - * @param string $message + * Gets the reformatted comment text. * - * @throws InvalidArgumentException + * "Reformatted" here means that we try to clean up the whitespace at the + * starts of the lines. This is necessary because we receive the comments + * without trailing whitespace on the first line, but with trailing whitespace + * on all subsequent lines. * - * @return void + * @return mixed|string */ - public static function allRegex($value, $pattern, $message = '') + public function getReformattedText() { - static::__callStatic('allRegex', array($value, $pattern, $message)); + $text = \trim($this->text); + $newlinePos = \strpos($text, "\n"); + if (\false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } elseif (\preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\\R\\s+\\*.*)+$)', $text)) { + // Multi line comment of the type + // + // /* + // * Some text. + // * Some more text. + // */ + // + // is handled by replacing the whitespace sequences before the * by a single space + return \preg_replace('(^\\s+\\*)m', ' *', $this->text); + } elseif (\preg_match('(^/\\*\\*?\\s*[\\r\\n])', $text) && \preg_match('(\\n(\\s*)\\*/$)', $text, $matches)) { + // Multi line comment of the type + // + // /* + // Some text. + // Some more text. + // */ + // + // is handled by removing the whitespace sequence on the line before the closing + // */ on all lines. So if the last line is " */", then " " is removed at the + // start of all lines. + return \preg_replace('(^' . \preg_quote($matches[1]) . ')m', '', $text); + } elseif (\preg_match('(^/\\*\\*?\\s*(?!\\s))', $text, $matches)) { + // Multi line comment of the type + // + // /* Some text. + // Some more text. + // Indented text. + // Even more text. */ + // + // is handled by removing the difference between the shortest whitespace prefix on all + // lines and the length of the "/* " opening sequence. + $prefixLen = $this->getShortestWhitespacePrefixLen(\substr($text, $newlinePos + 1)); + $removeLen = $prefixLen - \strlen($matches[0]); + return \preg_replace('(^\\s{' . $removeLen . '})m', '', $text); + } + // No idea how to format this comment, so simply return as is + return $text; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $pattern - * @param string $message + * Get length of shortest whitespace prefix (at the start of a line). * - * @throws InvalidArgumentException + * If there is a line with no prefix whitespace, 0 is a valid return value. * - * @return void + * @param string $str String to check + * @return int Length in characters. Tabs count as single characters. */ - public static function nullOrNotRegex($value, $pattern, $message = '') + private function getShortestWhitespacePrefixLen(string $str) : int { - static::__callStatic('nullOrNotRegex', array($value, $pattern, $message)); + $lines = \explode("\n", $str); + $shortestPrefixLen = \INF; + foreach ($lines as $line) { + \preg_match('(^\\s*)', $line, $matches); + $prefixLen = \strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; } /** - * @psalm-pure - * - * @param iterable $value - * @param string $pattern - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * @return array + * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} */ - public static function allNotRegex($value, $pattern, $message = '') + public function jsonSerialize() : array { - static::__callStatic('allNotRegex', array($value, $pattern, $message)); + // Technically not a node, but we make it look like one anyway + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; + return [ + 'nodeType' => $type, + 'text' => $this->text, + // TODO: Rename these to include "start". + 'line' => $this->startLine, + 'filePos' => $this->startFilePos, + 'tokenPos' => $this->startTokenPos, + 'endLine' => $this->endLine, + 'endFilePos' => $this->endFilePos, + 'endTokenPos' => $this->endTokenPos, + ]; } +} +fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) { + throw new ConstExprEvaluationException("Expression of type {$expr->getType()} cannot be evaluated"); + }; } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Silently evaluates a constant expression into a PHP value. * - * @throws InvalidArgumentException + * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. + * The original source of the exception is available through getPrevious(). * - * @return void - */ - public static function allUnicodeLetters($value, $message = '') - { - static::__callStatic('allUnicodeLetters', array($value, $message)); - } - /** - * @psalm-pure + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. * - * @param mixed $value - * @param string $message + * See class doc comment for caveats and limitations. * - * @throws InvalidArgumentException + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation * - * @return void + * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred */ - public static function nullOrAlpha($value, $message = '') + public function evaluateSilently(Expr $expr) { - static::__callStatic('nullOrAlpha', array($value, $message)); + \set_error_handler(function ($num, $str, $file, $line) { + throw new \ErrorException($str, 0, $num, $file, $line); + }); + try { + return $this->evaluate($expr); + } catch (\Throwable $e) { + if (!$e instanceof ConstExprEvaluationException) { + $e = new ConstExprEvaluationException("An error occurred during constant expression evaluation", 0, $e); + } + throw $e; + } finally { + \restore_error_handler(); + } } /** - * @psalm-pure - * - * @param mixed $value - * @param string $message + * Directly evaluates a constant expression into a PHP value. * - * @throws InvalidArgumentException + * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these + * into a ConstExprEvaluationException. * - * @return void - */ - public static function allAlpha($value, $message = '') - { - static::__callStatic('allAlpha', array($value, $message)); - } - /** - * @psalm-pure + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. * - * @param string|null $value - * @param string $message + * See class doc comment for caveats and limitations. * - * @throws InvalidArgumentException + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation * - * @return void + * @throws ConstExprEvaluationException if the expression cannot be evaluated */ - public static function nullOrDigits($value, $message = '') + public function evaluateDirectly(Expr $expr) { - static::__callStatic('nullOrDigits', array($value, $message)); + return $this->evaluate($expr); } - /** - * @psalm-pure - * - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allDigits($value, $message = '') + private function evaluate(Expr $expr) { - static::__callStatic('allDigits', array($value, $message)); + if ($expr instanceof Scalar\LNumber || $expr instanceof Scalar\DNumber || $expr instanceof Scalar\String_) { + return $expr->value; + } + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + return ($this->fallbackEvaluator)($expr); } - /** - * @psalm-pure - * - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrAlnum($value, $message = '') + private function evaluateArray(Expr\Array_ $expr) { - static::__callStatic('nullOrAlnum', array($value, $message)); + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; } - /** - * @psalm-pure - * - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allAlnum($value, $message = '') + private function evaluateTernary(Expr\Ternary $expr) { - static::__callStatic('allAlnum', array($value, $message)); + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + return $this->evaluate($expr->cond) ? $this->evaluate($expr->if) : $this->evaluate($expr->else); } - /** - * @psalm-pure - * @psalm-assert lowercase-string|null $value - * - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrLower($value, $message = '') + private function evaluateBinaryOp(Expr\BinaryOp $expr) { - static::__callStatic('nullOrLower', array($value, $message)); - } - /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allLower($value, $message = '') - { - static::__callStatic('allLower', array($value, $message)); + if ($expr instanceof Expr\BinaryOp\Coalesce && $expr->left instanceof Expr\ArrayDimFetch) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] ?? $this->evaluate($expr->right); + } + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': + return $this->evaluate($l) & $this->evaluate($r); + case '|': + return $this->evaluate($l) | $this->evaluate($r); + case '^': + return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': + return $this->evaluate($l) && $this->evaluate($r); + case '||': + return $this->evaluate($l) || $this->evaluate($r); + case '??': + return $this->evaluate($l) ?? $this->evaluate($r); + case '.': + return $this->evaluate($l) . $this->evaluate($r); + case '/': + return $this->evaluate($l) / $this->evaluate($r); + case '==': + return $this->evaluate($l) == $this->evaluate($r); + case '>': + return $this->evaluate($l) > $this->evaluate($r); + case '>=': + return $this->evaluate($l) >= $this->evaluate($r); + case '===': + return $this->evaluate($l) === $this->evaluate($r); + case 'and': + return $this->evaluate($l) and $this->evaluate($r); + case 'or': + return $this->evaluate($l) or $this->evaluate($r); + case 'xor': + return $this->evaluate($l) xor $this->evaluate($r); + case '-': + return $this->evaluate($l) - $this->evaluate($r); + case '%': + return $this->evaluate($l) % $this->evaluate($r); + case '*': + return $this->evaluate($l) * $this->evaluate($r); + case '!=': + return $this->evaluate($l) != $this->evaluate($r); + case '!==': + return $this->evaluate($l) !== $this->evaluate($r); + case '+': + return $this->evaluate($l) + $this->evaluate($r); + case '**': + return $this->evaluate($l) ** $this->evaluate($r); + case '<<': + return $this->evaluate($l) << $this->evaluate($r); + case '>>': + return $this->evaluate($l) >> $this->evaluate($r); + case '<': + return $this->evaluate($l) < $this->evaluate($r); + case '<=': + return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': + return $this->evaluate($l) <=> $this->evaluate($r); + } + throw new \Exception('Should not happen'); } - /** - * @psalm-pure - * - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrUpper($value, $message = '') + private function evaluateConstFetch(Expr\ConstFetch $expr) { - static::__callStatic('nullOrUpper', array($value, $message)); + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': + return null; + case 'false': + return \false; + case 'true': + return \true; + } + return ($this->fallbackEvaluator)($expr); } +} + $value - * @param string $message - * - * @throws InvalidArgumentException + * Creates an Exception signifying a parse error. * - * @return void + * @param string $message Error message + * @param array|int $attributes Attributes of node/token where error occurred + * (or start line of error -- deprecated) */ - public static function allUpper($value, $message = '') + public function __construct(string $message, $attributes = []) { - static::__callStatic('allUpper', array($value, $message)); + $this->rawMessage = $message; + if (\is_array($attributes)) { + $this->attributes = $attributes; + } else { + $this->attributes = ['startLine' => $attributes]; + } + $this->updateMessage(); } /** - * @psalm-pure - * - * @param string|null $value - * @param int $length - * @param string $message - * - * @throws InvalidArgumentException + * Gets the error message * - * @return void + * @return string Error message */ - public static function nullOrLength($value, $length, $message = '') + public function getRawMessage() : string { - static::__callStatic('nullOrLength', array($value, $length, $message)); + return $this->rawMessage; } /** - * @psalm-pure - * - * @param iterable $value - * @param int $length - * @param string $message - * - * @throws InvalidArgumentException + * Gets the line the error starts in. * - * @return void + * @return int Error start line */ - public static function allLength($value, $length, $message = '') + public function getStartLine() : int { - static::__callStatic('allLength', array($value, $length, $message)); + return $this->attributes['startLine'] ?? -1; } /** - * @psalm-pure - * - * @param string|null $value - * @param int|float $min - * @param string $message - * - * @throws InvalidArgumentException + * Gets the line the error ends in. * - * @return void + * @return int Error end line */ - public static function nullOrMinLength($value, $min, $message = '') + public function getEndLine() : int { - static::__callStatic('nullOrMinLength', array($value, $min, $message)); + return $this->attributes['endLine'] ?? -1; } /** - * @psalm-pure - * - * @param iterable $value - * @param int|float $min - * @param string $message - * - * @throws InvalidArgumentException + * Gets the attributes of the node/token the error occurred at. * - * @return void + * @return array */ - public static function allMinLength($value, $min, $message = '') + public function getAttributes() : array { - static::__callStatic('allMinLength', array($value, $min, $message)); + return $this->attributes; } /** - * @psalm-pure - * - * @param string|null $value - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException + * Sets the attributes of the node/token the error occurred at. * - * @return void + * @param array $attributes */ - public static function nullOrMaxLength($value, $max, $message = '') + public function setAttributes(array $attributes) { - static::__callStatic('nullOrMaxLength', array($value, $max, $message)); + $this->attributes = $attributes; + $this->updateMessage(); } /** - * @psalm-pure - * - * @param iterable $value - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException + * Sets the line of the PHP file the error occurred in. * - * @return void + * @param string $message Error message */ - public static function allMaxLength($value, $max, $message = '') + public function setRawMessage(string $message) { - static::__callStatic('allMaxLength', array($value, $max, $message)); + $this->rawMessage = $message; + $this->updateMessage(); } /** - * @psalm-pure - * - * @param string|null $value - * @param int|float $min - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException + * Sets the line the error starts in. * - * @return void + * @param int $line Error start line */ - public static function nullOrLengthBetween($value, $min, $max, $message = '') + public function setStartLine(int $line) { - static::__callStatic('nullOrLengthBetween', array($value, $min, $max, $message)); + $this->attributes['startLine'] = $line; + $this->updateMessage(); } /** - * @psalm-pure - * - * @param iterable $value - * @param int|float $min - * @param int|float $max - * @param string $message + * Returns whether the error has start and end column information. * - * @throws InvalidArgumentException + * For column information enable the startFilePos and endFilePos in the lexer options. * - * @return void + * @return bool */ - public static function allLengthBetween($value, $min, $max, $message = '') + public function hasColumnInfo() : bool { - static::__callStatic('allLengthBetween', array($value, $min, $max, $message)); + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); } /** - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Gets the start column (1-based) into the line where the error started. * - * @return void + * @param string $code Source code of the file + * @return int */ - public static function nullOrFileExists($value, $message = '') + public function getStartColumn(string $code) : int { - static::__callStatic('nullOrFileExists', array($value, $message)); + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + return $this->toColumn($code, $this->attributes['startFilePos']); } /** - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Gets the end column (1-based) into the line where the error ended. * - * @return void + * @param string $code Source code of the file + * @return int */ - public static function allFileExists($value, $message = '') + public function getEndColumn(string $code) : int { - static::__callStatic('allFileExists', array($value, $message)); + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + return $this->toColumn($code, $this->attributes['endFilePos']); } /** - * @param mixed $value - * @param string $message + * Formats message including line and column information. * - * @throws InvalidArgumentException + * @param string $code Source code associated with the error, for calculation of the columns * - * @return void + * @return string Formatted message */ - public static function nullOrFile($value, $message = '') + public function getMessageWithColumnInfo(string $code) : string { - static::__callStatic('nullOrFile', array($value, $message)); + return \sprintf('%s from %d:%d to %d:%d', $this->getRawMessage(), $this->getStartLine(), $this->getStartColumn($code), $this->getEndLine(), $this->getEndColumn($code)); } /** - * @param mixed $value - * @param string $message + * Converts a file offset into a column. * - * @throws InvalidArgumentException + * @param string $code Source code that $pos indexes into + * @param int $pos 0-based position in $code * - * @return void + * @return int 1-based column (relative to start of line) */ - public static function allFile($value, $message = '') + private function toColumn(string $code, int $pos) : int { - static::__callStatic('allFile', array($value, $message)); + if ($pos > \strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + $lineStartPos = \strrpos($code, "\n", $pos - \strlen($code)); + if (\false === $lineStartPos) { + $lineStartPos = -1; + } + return $pos - $lineStartPos; } /** - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * Updates the exception message after a change to rawMessage or rawLine. */ - public static function nullOrDirectory($value, $message = '') + protected function updateMessage() { - static::__callStatic('nullOrDirectory', array($value, $message)); + $this->message = $this->rawMessage; + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } } +} +errors[] = $error; } /** - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException + * Get collected errors. * - * @return void + * @return Error[] */ - public static function nullOrReadable($value, $message = '') + public function getErrors() : array { - static::__callStatic('nullOrReadable', array($value, $message)); + return $this->errors; } /** - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException + * Check whether there are any errors. * - * @return void + * @return bool */ - public static function allReadable($value, $message = '') + public function hasErrors() : bool { - static::__callStatic('allReadable', array($value, $message)); + return !empty($this->errors); } /** - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void + * Reset/clear collected errors. */ - public static function nullOrWritable($value, $message = '') + public function clearErrors() { - static::__callStatic('nullOrWritable', array($value, $message)); + $this->errors = []; } - /** - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allWritable($value, $message = '') +} +type = $type; + $this->old = $old; + $this->new = $new; } +} + $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Create differ over the given equality relation. * - * @return void + * @param callable $isEqual Equality relation with signature function($a, $b) : bool */ - public static function allClassExists($value, $message = '') + public function __construct(callable $isEqual) { - static::__callStatic('allClassExists', array($value, $message)); + $this->isEqual = $isEqual; } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert class-string|ExpectedType|null $value - * - * @param mixed $value - * @param string|object $class - * @param string $message + * Calculate diff (edit script) from $old to $new. * - * @throws InvalidArgumentException + * @param array $old Original array + * @param array $new New array * - * @return void + * @return DiffElem[] Diff (edit script) */ - public static function nullOrSubclassOf($value, $class, $message = '') + public function diff(array $old, array $new) { - static::__callStatic('nullOrSubclassOf', array($value, $class, $message)); + list($trace, $x, $y) = $this->calculateTrace($old, $new); + return $this->extractDiff($trace, $x, $y, $old, $new); } /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $class - * @psalm-assert iterable|ExpectedType> $value + * Calculate diff, including "replace" operations. * - * @param mixed $value - * @param string|object $class - * @param string $message + * If a sequence of remove operations is followed by the same number of add operations, these + * will be coalesced into replace operations. * - * @throws InvalidArgumentException + * @param array $old Original array + * @param array $new New array * - * @return void + * @return DiffElem[] Diff (edit script), including replace operations */ - public static function allSubclassOf($value, $class, $message = '') + public function diffWithReplacements(array $old, array $new) { - static::__callStatic('allSubclassOf', array($value, $class, $message)); + return $this->coalesceReplacements($this->diff($old, $new)); } - /** - * @psalm-assert class-string|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrInterfaceExists($value, $message = '') + private function calculateTrace(array $a, array $b) { - static::__callStatic('nullOrInterfaceExists', array($value, $message)); - } - /** - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allInterfaceExists($value, $message = '') - { - static::__callStatic('allInterfaceExists', array($value, $message)); - } - /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $interface - * @psalm-assert class-string|null $value - * - * @param mixed $value - * @param mixed $interface - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrImplementsInterface($value, $interface, $message = '') - { - static::__callStatic('nullOrImplementsInterface', array($value, $interface, $message)); + $n = \count($a); + $m = \count($b); + $max = $n + $m; + $v = [1 => 0]; + $trace = []; + for ($d = 0; $d <= $max; $d++) { + $trace[] = $v; + for ($k = -$d; $k <= $d; $k += 2) { + if ($k === -$d || $k !== $d && $v[$k - 1] < $v[$k + 1]) { + $x = $v[$k + 1]; + } else { + $x = $v[$k - 1] + 1; + } + $y = $x - $k; + while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) { + $x++; + $y++; + } + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); } - /** - * @psalm-pure - * @psalm-template ExpectedType of object - * @psalm-param class-string $interface - * @psalm-assert iterable> $value - * - * @param mixed $value - * @param mixed $interface - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allImplementsInterface($value, $interface, $message = '') + private function extractDiff(array $trace, int $x, int $y, array $a, array $b) { - static::__callStatic('allImplementsInterface', array($value, $interface, $message)); + $result = []; + for ($d = \count($trace) - 1; $d >= 0; $d--) { + $v = $trace[$d]; + $k = $x - $y; + if ($k === -$d || $k !== $d && $v[$k - 1] < $v[$k + 1]) { + $prevK = $k + 1; + } else { + $prevK = $k - 1; + } + $prevX = $v[$prevK]; + $prevY = $prevX - $prevK; + while ($x > $prevX && $y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x - 1], $b[$y - 1]); + $x--; + $y--; + } + if ($d === 0) { + break; + } + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x - 1], null); + $x--; + } + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y - 1]); + $y--; + } + } + return \array_reverse($result); } /** - * @psalm-pure - * @psalm-param class-string|object|null $classOrObject - * - * @param string|object|null $classOrObject - * @param mixed $property - * @param string $message - * - * @throws InvalidArgumentException + * Coalesce equal-length sequences of remove+add into a replace operation. * - * @return void + * @param DiffElem[] $diff + * @return DiffElem[] */ - public static function nullOrPropertyExists($classOrObject, $property, $message = '') + private function coalesceReplacements(array $diff) { - static::__callStatic('nullOrPropertyExists', array($classOrObject, $property, $message)); + $newDiff = []; + $c = \count($diff); + for ($i = 0; $i < $c; $i++) { + $diffType = $diff[$i]->type; + if ($diffType !== DiffElem::TYPE_REMOVE) { + $newDiff[] = $diff[$i]; + continue; + } + $j = $i; + while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { + $j++; + } + $k = $j; + while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { + $k++; + } + if ($j - $i === $k - $j) { + $len = $j - $i; + for ($n = 0; $n < $len; $n++) { + $newDiff[] = new DiffElem(DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new); + } + } else { + for (; $i < $k; $i++) { + $newDiff[] = $diff[$i]; + } + } + $i = $k - 1; + } + return $newDiff; } - /** - * @psalm-pure - * @psalm-param iterable $classOrObject - * - * @param iterable $classOrObject - * @param mixed $property - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allPropertyExists($classOrObject, $property, $message = '') +} +attrGroups = $attrGroups; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; } - /** - * @psalm-pure - * @psalm-param class-string|object|null $classOrObject - * - * @param string|object|null $classOrObject - * @param mixed $property - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrPropertyNotExists($classOrObject, $property, $message = '') + public static function fromNewNode(Expr\New_ $newNode) { - static::__callStatic('nullOrPropertyNotExists', array($classOrObject, $property, $message)); + $class = $newNode->class; + \assert($class instanceof Node\Stmt\Class_); + // We don't assert that $class->name is null here, to allow consumers to assign unique names + // to anonymous classes for their own purposes. We simplify ignore the name here. + return new self($class->attrGroups, $newNode->args, $class->extends, $class->implements, $class->stmts, $newNode->getAttributes()); } - /** - * @psalm-pure - * @psalm-param iterable $classOrObject - * - * @param iterable $classOrObject - * @param mixed $property - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allPropertyNotExists($classOrObject, $property, $message = '') + public function getType() : string { - static::__callStatic('allPropertyNotExists', array($classOrObject, $property, $message)); + return 'Expr_PrintableNewAnonClass'; } - /** - * @psalm-pure - * @psalm-param class-string|object|null $classOrObject - * - * @param string|object|null $classOrObject - * @param mixed $method - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrMethodExists($classOrObject, $method, $message = '') + public function getSubNodeNames() : array { - static::__callStatic('nullOrMethodExists', array($classOrObject, $method, $message)); + return ['attrGroups', 'args', 'extends', 'implements', 'stmts']; } +} + $classOrObject - * - * @param iterable $classOrObject - * @param mixed $method - * @param string $message - * - * @throws InvalidArgumentException + * Create token stream instance. * - * @return void + * @param array $tokens Tokens in token_get_all() format */ - public static function allMethodExists($classOrObject, $method, $message = '') + public function __construct(array $tokens) { - static::__callStatic('allMethodExists', array($classOrObject, $method, $message)); + $this->tokens = $tokens; + $this->indentMap = $this->calcIndentMap(); } /** - * @psalm-pure - * @psalm-param class-string|object|null $classOrObject - * - * @param string|object|null $classOrObject - * @param mixed $method - * @param string $message + * Whether the given position is immediately surrounded by parenthesis. * - * @throws InvalidArgumentException + * @param int $startPos Start position + * @param int $endPos End position * - * @return void + * @return bool */ - public static function nullOrMethodNotExists($classOrObject, $method, $message = '') + public function haveParens(int $startPos, int $endPos) : bool { - static::__callStatic('nullOrMethodNotExists', array($classOrObject, $method, $message)); + return $this->haveTokenImmediatelyBefore($startPos, '(') && $this->haveTokenImmediatelyAfter($endPos, ')'); } /** - * @psalm-pure - * @psalm-param iterable $classOrObject - * - * @param iterable $classOrObject - * @param mixed $method - * @param string $message + * Whether the given position is immediately surrounded by braces. * - * @throws InvalidArgumentException + * @param int $startPos Start position + * @param int $endPos End position * - * @return void + * @return bool */ - public static function allMethodNotExists($classOrObject, $method, $message = '') + public function haveBraces(int $startPos, int $endPos) : bool { - static::__callStatic('allMethodNotExists', array($classOrObject, $method, $message)); + return ($this->haveTokenImmediatelyBefore($startPos, '{') || $this->haveTokenImmediatelyBefore($startPos, \T_CURLY_OPEN)) && $this->haveTokenImmediatelyAfter($endPos, '}'); } /** - * @psalm-pure + * Check whether the position is directly preceded by a certain token type. * - * @param array|null $array - * @param string|int $key - * @param string $message + * During this check whitespace and comments are skipped. * - * @throws InvalidArgumentException + * @param int $pos Position before which the token should occur + * @param int|string $expectedTokenType Token to check for * - * @return void + * @return bool Whether the expected token was found */ - public static function nullOrKeyExists($array, $key, $message = '') + public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool { - static::__callStatic('nullOrKeyExists', array($array, $key, $message)); + $tokens = $this->tokens; + $pos--; + for (; $pos >= 0; $pos--) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return \true; + } + if ($tokenType !== \T_WHITESPACE && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + break; + } + } + return \false; } /** - * @psalm-pure + * Check whether the position is directly followed by a certain token type. * - * @param iterable $array - * @param string|int $key - * @param string $message + * During this check whitespace and comments are skipped. * - * @throws InvalidArgumentException + * @param int $pos Position after which the token should occur + * @param int|string $expectedTokenType Token to check for * - * @return void + * @return bool Whether the expected token was found */ - public static function allKeyExists($array, $key, $message = '') + public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool { - static::__callStatic('allKeyExists', array($array, $key, $message)); + $tokens = $this->tokens; + $pos++; + for (; $pos < \count($tokens); $pos++) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return \true; + } + if ($tokenType !== \T_WHITESPACE && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + break; + } + } + return \false; } - /** - * @psalm-pure - * - * @param array|null $array - * @param string|int $key - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrKeyNotExists($array, $key, $message = '') + public function skipLeft(int $pos, $skipTokenType) { - static::__callStatic('nullOrKeyNotExists', array($array, $key, $message)); + $tokens = $this->tokens; + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + if ($tokens[$pos][0] !== $skipTokenType) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + return $this->skipLeftWhitespace($pos); } - /** - * @psalm-pure - * - * @param iterable $array - * @param string|int $key - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allKeyNotExists($array, $key, $message = '') + public function skipRight(int $pos, $skipTokenType) { - static::__callStatic('allKeyNotExists', array($array, $key, $message)); + $tokens = $this->tokens; + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + if ($tokens[$pos][0] !== $skipTokenType) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos++; + return $this->skipRightWhitespace($pos); } /** - * @psalm-pure - * @psalm-assert array-key|null $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Return first non-whitespace token position smaller or equal to passed position. * - * @return void + * @param int $pos Token position + * @return int Non-whitespace token position */ - public static function nullOrValidArrayKey($value, $message = '') + public function skipLeftWhitespace(int $pos) { - static::__callStatic('nullOrValidArrayKey', array($value, $message)); + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + break; + } + } + return $pos; } /** - * @psalm-pure - * @psalm-assert iterable $value - * - * @param mixed $value - * @param string $message - * - * @throws InvalidArgumentException + * Return first non-whitespace position greater or equal to passed position. * - * @return void + * @param int $pos Token position + * @return int Non-whitespace token position */ - public static function allValidArrayKey($value, $message = '') + public function skipRightWhitespace(int $pos) { - static::__callStatic('allValidArrayKey', array($value, $message)); + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + break; + } + } + return $pos; } - /** - * @param Countable|array|null $array - * @param int $number - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrCount($array, $number, $message = '') + public function findRight(int $pos, $findTokenType) { - static::__callStatic('nullOrCount', array($array, $number, $message)); + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type === $findTokenType) { + return $pos; + } + } + return -1; } /** - * @param iterable $array - * @param int $number - * @param string $message - * - * @throws InvalidArgumentException + * Whether the given position range contains a certain token type. * - * @return void + * @param int $startPos Starting position (inclusive) + * @param int $endPos Ending position (exclusive) + * @param int|string $tokenType Token type to look for + * @return bool Whether the token occurs in the given range */ - public static function allCount($array, $number, $message = '') + public function haveTokenInRange(int $startPos, int $endPos, $tokenType) { - static::__callStatic('allCount', array($array, $number, $message)); + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos][0] === $tokenType) { + return \true; + } + } + return \false; } - /** - * @param Countable|array|null $array - * @param int|float $min - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrMinCount($array, $min, $message = '') + public function haveBracesInRange(int $startPos, int $endPos) { - static::__callStatic('nullOrMinCount', array($array, $min, $message)); + return $this->haveTokenInRange($startPos, $endPos, '{') || $this->haveTokenInRange($startPos, $endPos, \T_CURLY_OPEN) || $this->haveTokenInRange($startPos, $endPos, '}'); } /** - * @param iterable $array - * @param int|float $min - * @param string $message + * Get indentation before token position. * - * @throws InvalidArgumentException + * @param int $pos Token position * - * @return void + * @return int Indentation depth (in spaces) */ - public static function allMinCount($array, $min, $message = '') + public function getIndentationBefore(int $pos) : int { - static::__callStatic('allMinCount', array($array, $min, $message)); + return $this->indentMap[$pos]; } /** - * @param Countable|array|null $array - * @param int|float $max - * @param string $message + * Get the code corresponding to a token offset range, optionally adjusted for indentation. * - * @throws InvalidArgumentException + * @param int $from Token start position (inclusive) + * @param int $to Token end position (exclusive) + * @param int $indent By how much the code should be indented (can be negative as well) * - * @return void + * @return string Code corresponding to token range, adjusted for indentation */ - public static function nullOrMaxCount($array, $max, $message = '') + public function getTokenCode(int $from, int $to, int $indent) : string { - static::__callStatic('nullOrMaxCount', array($array, $max, $message)); + $tokens = $this->tokens; + $result = ''; + for ($pos = $from; $pos < $to; $pos++) { + $token = $tokens[$pos]; + if (\is_array($token)) { + $type = $token[0]; + $content = $token[1]; + if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $content; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= \str_replace("\n" . \str_repeat(" ", -$indent), "\n", $content); + } elseif ($indent > 0) { + $result .= \str_replace("\n", "\n" . \str_repeat(" ", $indent), $content); + } else { + $result .= $content; + } + } + } else { + $result .= $token; + } + } + return $result; } /** - * @param iterable $array - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException + * Precalculate the indentation at every token position. * - * @return void + * @return int[] Token position to indentation map */ - public static function allMaxCount($array, $max, $message = '') + private function calcIndentMap() { - static::__callStatic('allMaxCount', array($array, $max, $message)); + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $token) { + $indentMap[] = $indent; + if ($token[0] === \T_WHITESPACE) { + $content = $token[1]; + $newlinePos = \strrpos($content, "\n"); + if (\false !== $newlinePos) { + $indent = \strlen($content) - $newlinePos - 1; + } + } + } + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + return $indentMap; } - /** - * @param Countable|array|null $array - * @param int|float $min - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrCountBetween($array, $min, $max, $message = '') +} +decodeRecursive($value); } - /** - * @param iterable $array - * @param int|float $min - * @param int|float $max - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allCountBetween($array, $min, $max, $message = '') + private function decodeRecursive($value) { - static::__callStatic('allCountBetween', array($array, $min, $max, $message)); + if (\is_array($value)) { + if (isset($value['nodeType'])) { + if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { + return $this->decodeComment($value); + } + return $this->decodeNode($value); + } + return $this->decodeArray($value); + } + return $value; } - /** - * @psalm-pure - * @psalm-assert list|null $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrIsList($array, $message = '') + private function decodeArray(array $array) : array { - static::__callStatic('nullOrIsList', array($array, $message)); + $decodedArray = []; + foreach ($array as $key => $value) { + $decodedArray[$key] = $this->decodeRecursive($value); + } + return $decodedArray; } - /** - * @psalm-pure - * @psalm-assert iterable $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allIsList($array, $message = '') + private function decodeNode(array $value) : Node { - static::__callStatic('allIsList', array($array, $message)); + $nodeType = $value['nodeType']; + if (!\is_string($nodeType)) { + throw new \RuntimeException('Node type must be a string'); + } + $reflectionClass = $this->reflectionClassFromNodeType($nodeType); + /** @var Node $node */ + $node = $reflectionClass->newInstanceWithoutConstructor(); + if (isset($value['attributes'])) { + if (!\is_array($value['attributes'])) { + throw new \RuntimeException('Attributes must be an array'); + } + $node->setAttributes($this->decodeArray($value['attributes'])); + } + foreach ($value as $name => $subNode) { + if ($name === 'nodeType' || $name === 'attributes') { + continue; + } + $node->{$name} = $this->decodeRecursive($subNode); + } + return $node; } - /** - * @psalm-pure - * @psalm-assert non-empty-list|null $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrIsNonEmptyList($array, $message = '') + private function decodeComment(array $value) : Comment { - static::__callStatic('nullOrIsNonEmptyList', array($array, $message)); + $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; + if (!isset($value['text'])) { + throw new \RuntimeException('Comment must have text'); + } + return new $className($value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1); } - /** - * @psalm-pure - * @psalm-assert iterable $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allIsNonEmptyList($array, $message = '') + private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass { - static::__callStatic('allIsNonEmptyList', array($array, $message)); + if (!isset($this->reflectionClassCache[$nodeType])) { + $className = $this->classNameFromNodeType($nodeType); + $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); + } + return $this->reflectionClassCache[$nodeType]; } - /** - * @psalm-pure - * @psalm-template T - * @psalm-param mixed|array|null $array - * @psalm-assert array|null $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function nullOrIsMap($array, $message = '') + private function classNameFromNodeType(string $nodeType) : string { - static::__callStatic('nullOrIsMap', array($array, $message)); + $className = 'PhpParser\\Node\\' . \strtr($nodeType, '_', '\\'); + if (\class_exists($className)) { + return $className; + } + $className .= '_'; + if (\class_exists($className)) { + return $className; + } + throw new \RuntimeException("Unknown node type \"{$nodeType}\""); } +} +> $array - * @psalm-assert iterable> $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException + * Creates a Lexer. * - * @return void + * @param array $options Options array. Currently only the 'usedAttributes' option is supported, + * which is an array of attributes to add to the AST nodes. Possible + * attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos', + * 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the + * first three. For more info see getNextToken() docs. */ - public static function allIsMap($array, $message = '') + public function __construct(array $options = []) { - static::__callStatic('allIsMap', array($array, $message)); + // Create Map from internal tokens to PhpParser tokens. + $this->defineCompatibilityTokens(); + $this->tokenMap = $this->createTokenMap(); + $this->identifierTokens = $this->createIdentifierTokenMap(); + // map of tokens to drop while lexing (the map is only used for isset lookup, + // that's why the value is simply set to 1; the value is never actually used.) + $this->dropTokens = \array_fill_keys([\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1); + $defaultAttributes = ['comments', 'startLine', 'endLine']; + $usedAttributes = \array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, \true); + // Create individual boolean properties to make these checks faster. + $this->attributeStartLineUsed = isset($usedAttributes['startLine']); + $this->attributeEndLineUsed = isset($usedAttributes['endLine']); + $this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']); + $this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']); + $this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']); + $this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']); + $this->attributeCommentsUsed = isset($usedAttributes['comments']); } /** - * @psalm-pure - * @psalm-template T - * @psalm-param mixed|array|null $array - * - * @param mixed $array - * @param string $message + * Initializes the lexer for lexing the provided source code. * - * @throws InvalidArgumentException + * This function does not throw if lexing errors occur. Instead, errors may be retrieved using + * the getErrors() method. * - * @return void + * @param string $code The source code to lex + * @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to + * ErrorHandler\Throwing */ - public static function nullOrIsNonEmptyMap($array, $message = '') + public function startLexing(string $code, ErrorHandler $errorHandler = null) { - static::__callStatic('nullOrIsNonEmptyMap', array($array, $message)); + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing(); + } + $this->code = $code; + // keep the code around for __halt_compiler() handling + $this->pos = -1; + $this->line = 1; + $this->filePos = 0; + // If inline HTML occurs without preceding code, treat it as if it had a leading newline. + // This ensures proper composability, because having a newline is the "safe" assumption. + $this->prevCloseTagHasNewline = \true; + $scream = \ini_set('xdebug.scream', '0'); + $this->tokens = @\token_get_all($code); + $this->postprocessTokens($errorHandler); + if (\false !== $scream) { + \ini_set('xdebug.scream', $scream); + } } - /** - * @psalm-pure - * @psalm-template T - * @psalm-param iterable> $array - * - * @param mixed $array - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allIsNonEmptyMap($array, $message = '') + private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) { - static::__callStatic('allIsNonEmptyMap', array($array, $message)); + $tokens = []; + for ($i = $start; $i < $end; $i++) { + $chr = $this->code[$i]; + if ($chr === "\x00") { + // PHP cuts error message after null byte, so need special case + $errorMsg = 'Unexpected null byte'; + } else { + $errorMsg = \sprintf('Unexpected character "%s" (ASCII %d)', $chr, \ord($chr)); + } + $tokens[] = [\T_BAD_CHARACTER, $chr, $line]; + $errorHandler->handleError(new Error($errorMsg, ['startLine' => $line, 'endLine' => $line, 'startFilePos' => $i, 'endFilePos' => $i])); + } + return $tokens; } /** - * @psalm-pure - * - * @param string|null $value - * @param string $message - * - * @throws InvalidArgumentException + * Check whether comment token is unterminated. * - * @return void + * @return bool */ - public static function nullOrUuid($value, $message = '') + private function isUnterminatedComment($token) : bool { - static::__callStatic('nullOrUuid', array($value, $message)); + return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT) && \substr($token[1], 0, 2) === '/*' && \substr($token[1], -2) !== '*/'; } - /** - * @psalm-pure - * - * @param iterable $value - * @param string $message - * - * @throws InvalidArgumentException - * - * @return void - */ - public static function allUuid($value, $message = '') + protected function postprocessTokens(ErrorHandler $errorHandler) { - static::__callStatic('allUuid', array($value, $message)); + // PHP's error handling for token_get_all() is rather bad, so if we want detailed + // error information we need to compute it ourselves. Invalid character errors are + // detected by finding "gaps" in the token array. Unterminated comments are detected + // by checking if a trailing comment has a "*/" at the end. + // + // Additionally, we perform a number of canonicalizations here: + // * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore. + // * Use PHP 8.0 T_NAME_* tokens. + // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and + // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. + $filePos = 0; + $line = 1; + $numTokens = \count($this->tokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $this->tokens[$i]; + // Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token. + // In this case we only need to emit an error. + if ($token[0] === \T_BAD_CHARACTER) { + $this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler); + } + if ($token[0] === \T_COMMENT && \substr($token[1], 0, 2) !== '/*' && \preg_match('/(\\r\\n|\\n|\\r)$/D', $token[1], $matches)) { + $trailingNewline = $matches[0]; + $token[1] = \substr($token[1], 0, -\strlen($trailingNewline)); + $this->tokens[$i] = $token; + if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1]; + $this->tokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + \array_splice($this->tokens, $i + 1, 0, [[\T_WHITESPACE, $trailingNewline, $line]]); + $numTokens++; + } + } + // Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING + // into a single token. + if (\is_array($token) && ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) { + $lastWasSeparator = $token[0] === \T_NS_SEPARATOR; + $text = $token[1]; + for ($j = $i + 1; isset($this->tokens[$j]); $j++) { + if ($lastWasSeparator) { + if (!isset($this->identifierTokens[$this->tokens[$j][0]])) { + break; + } + $lastWasSeparator = \false; + } else { + if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = \true; + } + $text .= $this->tokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $text = \substr($text, 0, -1); + } + if ($j > $i + 1) { + if ($token[0] === \T_NS_SEPARATOR) { + $type = \T_NAME_FULLY_QUALIFIED; + } else { + if ($token[0] === \T_NAMESPACE) { + $type = \T_NAME_RELATIVE; + } else { + $type = \T_NAME_QUALIFIED; + } + } + $token = [$type, $text, $line]; + \array_splice($this->tokens, $i, $j - $i, [$token]); + $numTokens -= $j - $i - 1; + } + } + if ($token === '&') { + $next = $i + 1; + while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) { + $next++; + } + $followedByVarOrVarArg = isset($this->tokens[$next]) && ($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS); + $this->tokens[$i] = $token = [$followedByVarOrVarArg ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&', $line]; + } + $tokenValue = \is_string($token) ? $token : $token[1]; + $tokenLen = \strlen($tokenValue); + if (\substr($this->code, $filePos, $tokenLen) !== $tokenValue) { + // Something is missing, must be an invalid character + $nextFilePos = \strpos($this->code, $tokenValue, $filePos); + $badCharTokens = $this->handleInvalidCharacterRange($filePos, $nextFilePos, $line, $errorHandler); + $filePos = (int) $nextFilePos; + \array_splice($this->tokens, $i, 0, $badCharTokens); + $numTokens += \count($badCharTokens); + $i += \count($badCharTokens); + } + $filePos += $tokenLen; + $line += \substr_count($tokenValue, "\n"); + } + if ($filePos !== \strlen($this->code)) { + if (\substr($this->code, $filePos, 2) === '/*') { + // Unlike PHP, HHVM will drop unterminated comments entirely + $comment = \substr($this->code, $filePos); + $errorHandler->handleError(new Error('Unterminated comment', ['startLine' => $line, 'endLine' => $line + \substr_count($comment, "\n"), 'startFilePos' => $filePos, 'endFilePos' => $filePos + \strlen($comment)])); + // Emulate the PHP behavior + $isDocComment = isset($comment[3]) && $comment[3] === '*'; + $this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line]; + } else { + // Invalid characters at the end of the input + $badCharTokens = $this->handleInvalidCharacterRange($filePos, \strlen($this->code), $line, $errorHandler); + $this->tokens = \array_merge($this->tokens, $badCharTokens); + } + return; + } + if (\count($this->tokens) > 0) { + // Check for unterminated comment + $lastToken = $this->tokens[\count($this->tokens) - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', ['startLine' => $line - \substr_count($lastToken[1], "\n"), 'endLine' => $line, 'startFilePos' => $filePos - \strlen($lastToken[1]), 'endFilePos' => $filePos])); + } + } } /** - * @psalm-param class-string $class - * - * @param Closure|null $expression - * @param string $class - * @param string $message - * - * @throws InvalidArgumentException + * Fetches the next token. * - * @return void - */ - public static function nullOrThrows($expression, $class = 'Exception', $message = '') - { - static::__callStatic('nullOrThrows', array($expression, $class, $message)); - } - /** - * @psalm-param class-string $class + * The available attributes are determined by the 'usedAttributes' option, which can + * be specified in the constructor. The following attributes are supported: * - * @param iterable $expression - * @param string $class - * @param string $message + * * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances, + * representing all comments that occurred between the previous + * non-discarded token and the current one. + * * 'startLine' => Line in which the node starts. + * * 'endLine' => Line in which the node ends. + * * 'startTokenPos' => Offset into the token array of the first token in the node. + * * 'endTokenPos' => Offset into the token array of the last token in the node. + * * 'startFilePos' => Offset into the code string of the first character that is part of the node. + * * 'endFilePos' => Offset into the code string of the last character that is part of the node. * - * @throws InvalidArgumentException + * @param mixed $value Variable to store token content in + * @param mixed $startAttributes Variable to store start attributes in + * @param mixed $endAttributes Variable to store end attributes in * - * @return void + * @return int Token id */ - public static function allThrows($expression, $class = 'Exception', $message = '') + public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int { - static::__callStatic('allThrows', array($expression, $class, $message)); - } -} -The MIT License (MIT) - -Copyright (c) 2014 Bernhard Schussek - -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. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Webmozart\Assert; - -class InvalidArgumentException extends \InvalidArgumentException -{ -} -tokens[++$this->pos])) { + $token = $this->tokens[$this->pos]; + } else { + // EOF token with ID 0 + $token = "\x00"; + } + if ($this->attributeStartLineUsed) { + $startAttributes['startLine'] = $this->line; + } + if ($this->attributeStartTokenPosUsed) { + $startAttributes['startTokenPos'] = $this->pos; + } + if ($this->attributeStartFilePosUsed) { + $startAttributes['startFilePos'] = $this->filePos; + } + if (\is_string($token)) { + $value = $token; + if (isset($token[1])) { + // bug in token_get_all + $this->filePos += 2; + $id = \ord('"'); + } else { + $this->filePos += 1; + $id = \ord($token); + } + } elseif (!isset($this->dropTokens[$token[0]])) { + $value = $token[1]; + $id = $this->tokenMap[$token[0]]; + if (\T_CLOSE_TAG === $token[0]) { + $this->prevCloseTagHasNewline = \false !== \strpos($token[1], "\n") || \false !== \strpos($token[1], "\r"); + } elseif (\T_INLINE_HTML === $token[0]) { + $startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline; + } + $this->line += \substr_count($value, "\n"); + $this->filePos += \strlen($value); + } else { + $origLine = $this->line; + $origFilePos = $this->filePos; + $this->line += \substr_count($token[1], "\n"); + $this->filePos += \strlen($token[1]); + if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) { + if ($this->attributeCommentsUsed) { + $comment = \T_DOC_COMMENT === $token[0] ? new Comment\Doc($token[1], $origLine, $origFilePos, $this->pos, $this->line, $this->filePos - 1, $this->pos) : new Comment($token[1], $origLine, $origFilePos, $this->pos, $this->line, $this->filePos - 1, $this->pos); + $startAttributes['comments'][] = $comment; + } + } + continue; + } + if ($this->attributeEndLineUsed) { + $endAttributes['endLine'] = $this->line; + } + if ($this->attributeEndTokenPosUsed) { + $endAttributes['endTokenPos'] = $this->pos; + } + if ($this->attributeEndFilePosUsed) { + $endAttributes['endFilePos'] = $this->filePos - 1; + } + return $id; + } + throw new \RuntimeException('Reached end of lexer loop'); + } /** - * Initializes the object. + * Returns the token array for current code. * - * @throws InvalidArgumentException when $fqsen is not matching the format. + * The token array is in the same format as provided by the + * token_get_all() function and does not discard tokens (i.e. + * whitespace and comments are included). The token position + * attributes are against this token array. + * + * @return array Array of tokens in token_get_all() format */ - public function __construct(string $fqsen) + public function getTokens() : array { - $matches = []; - $result = preg_match( - //phpcs:ignore Generic.Files.LineLength.TooLong - '/^\\\\([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff\\\\]*)?(?:[:]{2}\\$?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*))?(?:\\(\\))?$/', - $fqsen, - $matches - ); - if ($result === 0) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid Fqsen.', $fqsen)); - } - $this->fqsen = $fqsen; - if (isset($matches[2])) { - $this->name = $matches[2]; - } else { - $matches = explode('\\', $fqsen); - $name = end($matches); - assert(is_string($name)); - $this->name = trim($name, '()'); - } + return $this->tokens; } /** - * converts this class to string. + * Handles __halt_compiler() by returning the text after it. + * + * @return string Remaining text */ - public function __toString() : string + public function handleHaltCompiler() : string { - return $this->fqsen; + // text after T_HALT_COMPILER, still including (); + $textAfter = \substr($this->code, $this->filePos); + // ensure that it is followed by (); + // this simplifies the situation, by not allowing any comments + // in between of the tokens. + if (!\preg_match('~^\\s*\\(\\s*\\)\\s*(?:;|\\?>\\r?\\n?)~', $textAfter, $matches)) { + throw new Error('__HALT_COMPILER must be followed by "();"'); + } + // prevent the lexer from returning any further tokens + $this->pos = \count($this->tokens); + // return with (); removed + return \substr($textAfter, \strlen($matches[0])); } - /** - * Returns the name of the element without path. - */ - public function getName() : string + private function defineCompatibilityTokens() { - return $this->name; + static $compatTokensDefined = \false; + if ($compatTokensDefined) { + return; + } + $compatTokens = [ + // PHP 7.4 + 'T_BAD_CHARACTER', + 'T_FN', + 'T_COALESCE_EQUAL', + // PHP 8.0 + 'T_NAME_QUALIFIED', + 'T_NAME_FULLY_QUALIFIED', + 'T_NAME_RELATIVE', + 'T_MATCH', + 'T_NULLSAFE_OBJECT_OPERATOR', + 'T_ATTRIBUTE', + // PHP 8.1 + 'T_ENUM', + 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', + 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', + 'T_READONLY', + ]; + // PHP-Parser might be used together with another library that also emulates some or all + // of these tokens. Perform a sanity-check that all already defined tokens have been + // assigned a unique ID. + $usedTokenIds = []; + foreach ($compatTokens as $token) { + if (\defined($token)) { + $tokenId = \constant($token); + $clashingToken = $usedTokenIds[$tokenId] ?? null; + if ($clashingToken !== null) { + throw new \Error(\sprintf('Token %s has same ID as token %s, ' . 'you may be using a library with broken token emulation', $token, $clashingToken)); + } + $usedTokenIds[$tokenId] = $token; + } + } + // Now define any tokens that have not yet been emulated. Try to assign IDs from -1 + // downwards, but skip any IDs that may already be in use. + $newTokenId = -1; + foreach ($compatTokens as $token) { + if (!\defined($token)) { + while (isset($usedTokenIds[$newTokenId])) { + $newTokenId--; + } + \define($token, $newTokenId); + $newTokenId--; + } + } + $compatTokensDefined = \true; } -} -lineNumber = $lineNumber; - $this->columnNumber = $columnNumber; + $this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1; + unset($options['phpVersion']); + parent::__construct($options); + $emulators = [new FlexibleDocStringEmulator(), new FnTokenEmulator(), new MatchTokenEmulator(), new CoaleseEqualTokenEmulator(), new NumericLiteralSeparatorEmulator(), new NullsafeTokenEmulator(), new AttributeEmulator(), new EnumTokenEmulator(), new ReadonlyTokenEmulator(), new ExplicitOctalEmulator()]; + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } else { + if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } } - /** - * Returns the line number that is covered by this location. - */ - public function getLineNumber() : int + public function startLexing(string $code, ErrorHandler $errorHandler = null) { - return $this->lineNumber; + $emulators = \array_filter($this->emulators, function ($emulator) use($code) { + return $emulator->isEmulationNeeded($code); + }); + if (empty($emulators)) { + // Nothing to emulate, yay + parent::startLexing($code, $errorHandler); + return; + } + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + $collector = new ErrorHandler\Collecting(); + parent::startLexing($code, $collector); + $this->sortPatches(); + $this->fixupTokens(); + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + foreach ($emulators as $emulator) { + $this->tokens = $emulator->emulate($code, $this->tokens); + } } - /** - * Returns the column number (character position on a line) for this location object. - */ - public function getColumnNumber() : int + private function isForwardEmulationNeeded(string $emulatorPhpVersion) : bool { - return $this->columnNumber; + return \version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') && \version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>='); } -} -=') && \version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<'); + } + private function sortPatches() + { + // Patches may be contributed by different emulators. + // Make sure they are sorted by increasing patch position. + \usort($this->patches, function ($p1, $p2) { + return $p1[0] <=> $p2[0]; + }); + } + private function fixupTokens() + { + if (\count($this->patches) === 0) { + return; + } + // Load first patch + $patchIdx = 0; + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + // We use a manual loop over the tokens, because we modify the array on the fly + $pos = 0; + for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { + $token = $this->tokens[$i]; + if (\is_string($token)) { + if ($patchPos === $pos) { + // Only support replacement for string tokens. + \assert($patchType === 'replace'); + $this->tokens[$i] = $patchText; + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + $pos += \strlen($token); + continue; + } + $len = \strlen($token[1]); + $posDelta = 0; + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + \array_splice($this->tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $this->tokens[$i][1] = \substr_replace($token[1], '', $patchPos - $pos + $posDelta, $patchTextLen); + $posDelta -= $patchTextLen; + } + } elseif ($patchType === 'add') { + // Insert into the token string + $this->tokens[$i][1] = \substr_replace($token[1], $patchText, $patchPos - $pos + $posDelta, 0); + $posDelta += $patchTextLen; + } else { + if ($patchType === 'replace') { + // Replace inside the token string + $this->tokens[$i][1] = \substr_replace($token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen); + } else { + \assert(\false); + } + } + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + // Multiple patches may apply to the same token. Reload the current one to check + // If the new patch applies + $token = $this->tokens[$i]; + } + $pos += $len; + } + // A patch did not apply + \assert(\false); + } + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors) + { + foreach ($errors as $error) { + $attrs = $error->getAttributes(); + $posDelta = 0; + $lineDelta = 0; + foreach ($this->patches as $patch) { + list($patchPos, $patchType, $patchText) = $patch; + if ($patchPos >= $attrs['startFilePos']) { + // No longer relevant + break; + } + if ($patchType === 'add') { + $posDelta += \strlen($patchText); + $lineDelta += \substr_count($patchText, "\n"); + } else { + if ($patchType === 'remove') { + $posDelta -= \strlen($patchText); + $lineDelta -= \substr_count($patchText, "\n"); + } + } + } + $attrs['startFilePos'] += $posDelta; + $attrs['endFilePos'] += $posDelta; + $attrs['startLine'] += $lineDelta; + $attrs['endLine'] += $lineDelta; + $error->setAttributes($attrs); + } + } +} - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Template; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -use function array_merge; -use function file_exists; -use function file_get_contents; -use function file_put_contents; -use function sprintf; -use function str_replace; -final class Template +use PHPUnit\PhpParser\Lexer\Emulative; +final class AttributeEmulator extends TokenEmulator { - /** - * @var string - */ - private $template = ''; - /** - * @var string - */ - private $openDelimiter; - /** - * @var string - */ - private $closeDelimiter; - /** - * @var array - */ - private $values = []; - /** - * @throws InvalidArgumentException - */ - public function __construct(string $file = '', string $openDelimiter = '{', string $closeDelimiter = '}') + public function getPhpVersion() : string { - $this->setFile($file); - $this->openDelimiter = $openDelimiter; - $this->closeDelimiter = $closeDelimiter; + return Emulative::PHP_8_0; } - /** - * @throws InvalidArgumentException - */ - public function setFile(string $file) : void + public function isEmulationNeeded(string $code) : bool { - $distFile = $file . '.dist'; - if (file_exists($file)) { - $this->template = file_get_contents($file); - } elseif (file_exists($distFile)) { - $this->template = file_get_contents($distFile); - } else { - throw new InvalidArgumentException(sprintf('Failed to load template "%s"', $file)); - } + return \strpos($code, '#[') !== \false; } - public function setVar(array $values, bool $merge = \true) : void + public function emulate(string $code, array $tokens) : array { - if (!$merge || empty($this->values)) { - $this->values = $values; - } else { - $this->values = array_merge($this->values, $values); + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way. + $line = 1; + for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { + if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') { + \array_splice($tokens, $i, 2, [[\T_ATTRIBUTE, '#[', $line]]); + $c--; + continue; + } + if (\is_array($tokens[$i])) { + $line += \substr_count($tokens[$i][1], "\n"); + } } + return $tokens; } - public function render() : string + public function reverseEmulate(string $code, array $tokens) : array { - $keys = []; - foreach ($this->values as $key => $value) { - $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; - } - return str_replace($keys, $this->values, $this->template); + // TODO + return $tokens; } - /** - * @codeCoverageIgnore - */ - public function renderTo(string $target) : void + public function preprocessCode(string $code, array &$patches) : string { - if (!file_put_contents($target, $this->render())) { - throw new RuntimeException(sprintf('Writing rendered result to "%s" failed', $target)); + $pos = 0; + while (\false !== ($pos = \strpos($code, '#[', $pos))) { + // Replace #[ with %[ + $code[$pos] = '%'; + $patches[] = [$pos, 'replace', '#']; + $pos += 2; } + return $code; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Template; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -use InvalidArgumentException; -final class RuntimeException extends InvalidArgumentException implements Exception +use PHPUnit\PhpParser\Lexer\Emulative; +final class CoaleseEqualTokenEmulator extends TokenEmulator { + public function getPhpVersion() : string + { + return Emulative::PHP_7_4; + } + public function isEmulationNeeded(string $code) : bool + { + return \strpos($code, '??=') !== \false; + } + public function emulate(string $code, array $tokens) : array + { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + $line = 1; + for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { + if (isset($tokens[$i + 1])) { + if ($tokens[$i][0] === \T_COALESCE && $tokens[$i + 1] === '=') { + \array_splice($tokens, $i, 2, [[\T_COALESCE_EQUAL, '??=', $line]]); + $c--; + continue; + } + } + if (\is_array($tokens[$i])) { + $line += \substr_count($tokens[$i][1], "\n"); + } + } + return $tokens; + } + public function reverseEmulate(string $code, array $tokens) : array + { + // ??= was not valid code previously, don't bother. + return $tokens; + } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Template; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -use Throwable; -interface Exception extends Throwable +use PHPUnit\PhpParser\Lexer\Emulative; +final class EnumTokenEmulator extends KeywordEmulator { + public function getPhpVersion() : string + { + return Emulative::PHP_8_1; + } + public function getKeywordString() : string + { + return 'enum'; + } + public function getKeywordToken() : int + { + return \T_ENUM; + } + protected function isKeywordContext(array $tokens, int $pos) : bool + { + return parent::isKeywordContext($tokens, $pos) && isset($tokens[$pos + 2]) && $tokens[$pos + 1][0] === \T_WHITESPACE && $tokens[$pos + 2][0] === \T_STRING; + } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Template; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -final class InvalidArgumentException extends \InvalidArgumentException implements Exception +use PHPUnit\PhpParser\Lexer\Emulative; +class ExplicitOctalEmulator extends TokenEmulator { + public function getPhpVersion() : string + { + return Emulative::PHP_8_1; + } + public function isEmulationNeeded(string $code) : bool + { + return \strpos($code, '0o') !== \false || \strpos($code, '0O') !== \false; + } + public function emulate(string $code, array $tokens) : array + { + for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { + if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING && \preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])) { + $tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]); + \array_splice($tokens, $i, 2, [[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]]]); + $c--; + } + } + return $tokens; + } + private function resolveIntegerOrFloatToken(string $str) : int + { + $str = \substr($str, 1); + $str = \str_replace('_', '', $str); + $num = \octdec($str); + return \is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + public function reverseEmulate(string $code, array $tokens) : array + { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } } -phpunit/php-text-template - -Copyright (c) 2009-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectReflector; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -use function count; -use function explode; -use function get_class; -use function is_object; -class ObjectReflector +use PHPUnit\PhpParser\Lexer\Emulative; +final class FlexibleDocStringEmulator extends TokenEmulator { - /** - * @param object $object - * - * @throws InvalidArgumentException - */ - public function getAttributes($object) : array + const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX' +/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n +(?:.*\r?\n)*? +(?\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?(?:;?[\r\n])?)/x +REGEX; + public function getPhpVersion() : string { - if (!is_object($object)) { - throw new InvalidArgumentException(); + return Emulative::PHP_7_3; + } + public function isEmulationNeeded(string $code) : bool + { + return \strpos($code, '<<<') !== \false; + } + public function emulate(string $code, array $tokens) : array + { + // Handled by preprocessing + fixup. + return $tokens; + } + public function reverseEmulate(string $code, array $tokens) : array + { + // Not supported. + return $tokens; + } + public function preprocessCode(string $code, array &$patches) : string + { + if (!\preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE)) { + // No heredoc/nowdoc found + return $code; } - $attributes = []; - $className = get_class($object); - foreach ((array) $object as $name => $value) { - $name = explode("\0", (string) $name); - if (count($name) === 1) { - $name = $name[0]; - } else { - if ($name[1] !== $className) { - $name = $name[1] . '::' . $name[2]; - } else { - $name = $name[2]; - } + // Keep track of how much we need to adjust string offsets due to the modifications we + // already made + $posDelta = 0; + foreach ($matches as $match) { + $indentation = $match['indentation'][0]; + $indentationStart = $match['indentation'][1]; + $separator = $match['separator'][0]; + $separatorStart = $match['separator'][1]; + if ($indentation === '' && $separator !== '') { + // Ordinary heredoc/nowdoc + continue; + } + if ($indentation !== '') { + // Remove indentation + $indentationLen = \strlen($indentation); + $code = \substr_replace($code, '', $indentationStart + $posDelta, $indentationLen); + $patches[] = [$indentationStart + $posDelta, 'add', $indentation]; + $posDelta -= $indentationLen; + } + if ($separator === '') { + // Insert newline as separator + $code = \substr_replace($code, "\n", $separatorStart + $posDelta, 0); + $patches[] = [$separatorStart + $posDelta, 'remove', "\n"]; + $posDelta += 1; } - $attributes[$name] = $value; } - return $attributes; + return $code; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectReflector; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -use Throwable; -interface Exception extends Throwable +use PHPUnit\PhpParser\Lexer\Emulative; +final class FnTokenEmulator extends KeywordEmulator { + public function getPhpVersion() : string + { + return Emulative::PHP_7_4; + } + public function getKeywordString() : string + { + return 'fn'; + } + public function getKeywordToken() : int + { + return \T_FN; + } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectReflector; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class InvalidArgumentException extends \InvalidArgumentException implements Exception +abstract class KeywordEmulator extends TokenEmulator { -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Invoker; - -use const SIGALRM; -use function call_user_func_array; -use function function_exists; -use function pcntl_alarm; -use function pcntl_async_signals; -use function pcntl_signal; -use function sprintf; -use Throwable; -final class Invoker -{ - /** - * @var int - */ - private $timeout; - /** - * @throws Throwable - */ - public function invoke(callable $callable, array $arguments, int $timeout) + abstract function getKeywordString() : string; + abstract function getKeywordToken() : int; + public function isEmulationNeeded(string $code) : bool { - if (!$this->canInvokeWithTimeout()) { - throw new ProcessControlExtensionNotLoadedException('The pcntl (process control) extension for PHP is required'); - } - pcntl_signal(\SIGALRM, function () : void { - throw new TimeoutException(sprintf('Execution aborted after %d second%s', $this->timeout, $this->timeout === 1 ? '' : 's')); - }, \true); - $this->timeout = $timeout; - pcntl_async_signals(\true); - pcntl_alarm($timeout); - try { - return call_user_func_array($callable, $arguments); - } finally { - pcntl_alarm(0); - } + return \strpos(\strtolower($code), $this->getKeywordString()) !== \false; } - public function canInvokeWithTimeout() : bool + protected function isKeywordContext(array $tokens, int $pos) : bool { - return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); + $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); + return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR; + } + public function emulate(string $code, array $tokens) : array + { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token[0] === \T_STRING && \strtolower($token[1]) === $keywordString && $this->isKeywordContext($tokens, $i)) { + $tokens[$i][0] = $this->getKeywordToken(); + } + } + return $tokens; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Invoker; - -use RuntimeException; -final class ProcessControlExtensionNotLoadedException extends RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Invoker; - -use RuntimeException; -final class TimeoutException extends RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Invoker; - -use Throwable; -interface Exception extends Throwable -{ -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -class OrVersionConstraintGroup extends AbstractVersionConstraint -{ - /** @var VersionConstraint[] */ - private $constraints = []; /** - * @param string $originalValue - * @param VersionConstraint[] $constraints + * @param mixed[] $tokens + * @return array|string|null */ - public function __construct($originalValue, array $constraints) + private function getPreviousNonSpaceToken(array $tokens, int $start) { - parent::__construct($originalValue); - $this->constraints = $constraints; + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i][0] === \T_WHITESPACE) { + continue; + } + return $tokens[$i]; + } + return null; } - public function complies(Version $version) : bool + public function reverseEmulate(string $code, array $tokens) : array { - foreach ($this->constraints as $constraint) { - if ($constraint->complies($version)) { - return \true; + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $i => $token) { + if ($token[0] === $keywordToken) { + $tokens[$i][0] = \T_STRING; } } - return \false; + return $tokens; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -interface VersionConstraint -{ - public function complies(Version $version) : bool; - public function asString() : string; -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class ExactVersionConstraint extends AbstractVersionConstraint +use PHPUnit\PhpParser\Lexer\Emulative; +final class MatchTokenEmulator extends KeywordEmulator { - public function complies(Version $version) : bool + public function getPhpVersion() : string { - return $this->asString() === $version->getVersionString(); + return Emulative::PHP_8_0; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -class AnyVersionConstraint implements VersionConstraint -{ - public function complies(Version $version) : bool + public function getKeywordString() : string { - return \true; + return 'match'; } - public function asString() : string + public function getKeywordToken() : int { - return '*'; + return \T_MATCH; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class SpecificMajorVersionConstraint extends AbstractVersionConstraint +use PHPUnit\PhpParser\Lexer\Emulative; +final class NullsafeTokenEmulator extends TokenEmulator { - /** @var int */ - private $major; - public function __construct(string $originalValue, int $major) + public function getPhpVersion() : string { - parent::__construct($originalValue); - $this->major = $major; + return Emulative::PHP_8_0; } - public function complies(Version $version) : bool + public function isEmulationNeeded(string $code) : bool { - return $version->getMajor()->getValue() === $this->major; + return \strpos($code, '?->') !== \false; + } + public function emulate(string $code, array $tokens) : array + { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + $line = 1; + for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { + if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { + \array_splice($tokens, $i, 2, [[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]]); + $c--; + continue; + } + // Handle ?-> inside encapsed string. + if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) && $tokens[$i - 1][0] === \T_VARIABLE && \preg_match('/^\\?->([a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff]*)/', $tokens[$i][1], $matches)) { + $replacement = [[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line], [\T_STRING, $matches[1], $line]]; + if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) { + $replacement[] = [\T_ENCAPSED_AND_WHITESPACE, \substr($tokens[$i][1], \strlen($matches[0])), $line]; + } + \array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + if (\is_array($tokens[$i])) { + $line += \substr_count($tokens[$i][1], "\n"); + } + } + return $tokens; + } + public function reverseEmulate(string $code, array $tokens) : array + { + // ?-> was not valid code previously, don't bother. + return $tokens; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class SpecificMajorAndMinorVersionConstraint extends AbstractVersionConstraint +use PHPUnit\PhpParser\Lexer\Emulative; +final class NumericLiteralSeparatorEmulator extends TokenEmulator { - /** @var int */ - private $major; - /** @var int */ - private $minor; - public function __construct(string $originalValue, int $major, int $minor) + const BIN = '(?:0b[01]+(?:_[01]+)*)'; + const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)'; + const DEC = '(?:[0-9]+(?:_[0-9]+)*)'; + const SIMPLE_FLOAT = '(?:' . self::DEC . '\\.' . self::DEC . '?|\\.' . self::DEC . ')'; + const EXP = '(?:e[+-]?' . self::DEC . ')'; + const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')'; + const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA'; + public function getPhpVersion() : string { - parent::__construct($originalValue); - $this->major = $major; - $this->minor = $minor; + return Emulative::PHP_7_4; } - public function complies(Version $version) : bool + public function isEmulationNeeded(string $code) : bool { - if ($version->getMajor()->getValue() !== $this->major) { - return \false; + return \preg_match('~[0-9]_[0-9]~', $code) || \preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code); + } + public function emulate(string $code, array $tokens) : array + { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + $codeOffset = 0; + for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + $tokenLen = \strlen(\is_array($token) ? $token[1] : $token); + if ($token[0] !== \T_LNUMBER && $token[0] !== \T_DNUMBER) { + $codeOffset += $tokenLen; + continue; + } + $res = \preg_match(self::NUMBER, $code, $matches, 0, $codeOffset); + \assert($res, "No number at number token position"); + $match = $matches[0]; + $matchLen = \strlen($match); + if ($matchLen === $tokenLen) { + // Original token already holds the full number. + $codeOffset += $tokenLen; + continue; + } + $tokenKind = $this->resolveIntegerOrFloatToken($match); + $newTokens = [[$tokenKind, $match, $token[2]]]; + $numTokens = 1; + $len = $tokenLen; + while ($matchLen > $len) { + $nextToken = $tokens[$i + $numTokens]; + $nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken; + $nextTokenLen = \strlen($nextTokenText); + $numTokens++; + if ($matchLen < $len + $nextTokenLen) { + // Split trailing characters into a partial token. + \assert(\is_array($nextToken), "Partial token should be an array token"); + $partialText = \substr($nextTokenText, $matchLen - $len); + $newTokens[] = [$nextToken[0], $partialText, $nextToken[2]]; + break; + } + $len += $nextTokenLen; + } + \array_splice($tokens, $i, $numTokens, $newTokens); + $c -= $numTokens - \count($newTokens); + $codeOffset += $matchLen; } - return $version->getMinor()->getValue() === $this->minor; + return $tokens; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -class GreaterThanOrEqualToVersionConstraint extends AbstractVersionConstraint -{ - /** @var Version */ - private $minimalVersion; - public function __construct(string $originalValue, Version $minimalVersion) + private function resolveIntegerOrFloatToken(string $str) : int { - parent::__construct($originalValue); - $this->minimalVersion = $minimalVersion; + $str = \str_replace('_', '', $str); + if (\stripos($str, '0b') === 0) { + $num = \bindec($str); + } elseif (\stripos($str, '0x') === 0) { + $num = \hexdec($str); + } elseif (\stripos($str, '0') === 0 && \ctype_digit($str)) { + $num = \octdec($str); + } else { + $num = +$str; + } + return \is_float($num) ? \T_DNUMBER : \T_LNUMBER; } - public function complies(Version $version) : bool + public function reverseEmulate(string $code, array $tokens) : array { - return $version->getVersionString() === $this->minimalVersion->getVersionString() || $version->isGreaterThan($this->minimalVersion); + // Numeric separators were not legal code previously, don't bother. + return $tokens; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -abstract class AbstractVersionConstraint implements VersionConstraint +use PHPUnit\PhpParser\Lexer\Emulative; +final class ReadonlyTokenEmulator extends KeywordEmulator { - /** @var string */ - private $originalValue; - public function __construct(string $originalValue) + public function getPhpVersion() : string { - $this->originalValue = $originalValue; + return Emulative::PHP_8_1; } - public function asString() : string + public function getKeywordString() : string { - return $this->originalValue; + return 'readonly'; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -class AndVersionConstraintGroup extends AbstractVersionConstraint -{ - /** @var VersionConstraint[] */ - private $constraints = []; - /** - * @param VersionConstraint[] $constraints - */ - public function __construct(string $originalValue, array $constraints) + public function getKeywordToken() : int { - parent::__construct($originalValue); - $this->constraints = $constraints; + return \T_READONLY; } - public function complies(Version $version) : bool + protected function isKeywordContext(array $tokens, int $pos) : bool { - foreach ($this->constraints as $constraint) { - if (!$constraint->complies($version)) { - return \false; - } + if (!parent::isKeywordContext($tokens, $pos)) { + return \false; } - return \true; + // Support "function readonly(" + return !(isset($tokens[$pos + 1]) && ($tokens[$pos + 1][0] === '(' || $tokens[$pos + 1][0] === \T_WHITESPACE && isset($tokens[$pos + 2]) && $tokens[$pos + 2][0] === '(')); } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class VersionNumber +/** + * Reverses emulation direction of the inner emulator. + */ +final class ReverseEmulator extends TokenEmulator { - /** @var ?int */ - private $value; - public function __construct(?int $value) + /** @var TokenEmulator Inner emulator */ + private $emulator; + public function __construct(TokenEmulator $emulator) { - $this->value = $value; + $this->emulator = $emulator; } - public function isAny() : bool + public function getPhpVersion() : string { - return $this->value === null; + return $this->emulator->getPhpVersion(); } - public function getValue() : ?int + public function isEmulationNeeded(string $code) : bool { - return $this->value; + return $this->emulator->isEmulationNeeded($code); + } + public function emulate(string $code, array $tokens) : array + { + return $this->emulator->reverseEmulate($code, $tokens); + } + public function reverseEmulate(string $code, array $tokens) : array + { + return $this->emulator->emulate($code, $tokens); + } + public function preprocessCode(string $code, array &$patches) : string + { + return $code; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser\Lexer\TokenEmulator; -class VersionConstraintParser +/** @internal */ +abstract class TokenEmulator { + public abstract function getPhpVersion() : string; + public abstract function isEmulationNeeded(string $code) : bool; /** - * @throws UnsupportedVersionConstraintException + * @return array Modified Tokens */ - public function parse(string $value) : VersionConstraint + public abstract function emulate(string $code, array $tokens) : array; + /** + * @return array Modified Tokens + */ + public abstract function reverseEmulate(string $code, array $tokens) : array; + public function preprocessCode(string $code, array &$patches) : string { - if (\strpos($value, '||') !== \false) { - return $this->handleOrGroup($value); - } - if (!\preg_match('/^[\\^~*]?v?[\\d.*]+(?:-.*)?$/i', $value)) { - throw new UnsupportedVersionConstraintException(\sprintf('Version constraint %s is not supported.', $value)); - } - switch ($value[0]) { - case '~': - return $this->handleTildeOperator($value); - case '^': - return $this->handleCaretOperator($value); - } - $constraint = new VersionConstraintValue($value); - if ($constraint->getMajor()->isAny()) { - return new AnyVersionConstraint(); - } - if ($constraint->getMinor()->isAny()) { - return new SpecificMajorVersionConstraint($constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0); - } - if ($constraint->getPatch()->isAny()) { - return new SpecificMajorAndMinorVersionConstraint($constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0, $constraint->getMinor()->getValue() ?? 0); - } - return new ExactVersionConstraint($constraint->getVersionString()); - } - private function handleOrGroup(string $value) : OrVersionConstraintGroup - { - $constraints = []; - foreach (\explode('||', $value) as $groupSegment) { - $constraints[] = $this->parse(\trim($groupSegment)); - } - return new OrVersionConstraintGroup($value, $constraints); - } - private function handleTildeOperator(string $value) : AndVersionConstraintGroup - { - $constraintValue = new VersionConstraintValue(\substr($value, 1)); - if ($constraintValue->getPatch()->isAny()) { - return $this->handleCaretOperator($value); - } - $constraints = [new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1))), new SpecificMajorAndMinorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0)]; - return new AndVersionConstraintGroup($value, $constraints); - } - private function handleCaretOperator(string $value) : AndVersionConstraintGroup - { - $constraintValue = new VersionConstraintValue(\substr($value, 1)); - $constraints = [new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1)))]; - if ($constraintValue->getMajor()->getValue() === 0) { - $constraints[] = new SpecificMajorAndMinorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0); - } else { - $constraints[] = new SpecificMajorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0); - } - return new AndVersionConstraintGroup($value, $constraints); + return $code; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; +namespace PHPUnit\PhpParser; -class Version +use PHPUnit\PhpParser\Node\Name; +use PHPUnit\PhpParser\Node\Name\FullyQualified; +use PHPUnit\PhpParser\Node\Stmt; +class NameContext { - /** @var string */ - private $originalVersionString; - /** @var VersionNumber */ - private $major; - /** @var VersionNumber */ - private $minor; - /** @var VersionNumber */ - private $patch; - /** @var null|PreReleaseSuffix */ - private $preReleaseSuffix; - public function __construct(string $versionString) - { - $this->ensureVersionStringIsValid($versionString); - $this->originalVersionString = $versionString; - } - public function getPreReleaseSuffix() : PreReleaseSuffix + /** @var null|Name Current namespace */ + protected $namespace; + /** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */ + protected $aliases = []; + /** @var Name[][] Same as $aliases but preserving original case */ + protected $origAliases = []; + /** @var ErrorHandler Error handler */ + protected $errorHandler; + /** + * Create a name context. + * + * @param ErrorHandler $errorHandler Error handling used to report errors + */ + public function __construct(ErrorHandler $errorHandler) { - if ($this->preReleaseSuffix === null) { - throw new NoPreReleaseSuffixException('No pre-release suffix set'); - } - return $this->preReleaseSuffix; + $this->errorHandler = $errorHandler; } - public function getOriginalString() : string + /** + * Start a new namespace. + * + * This also resets the alias table. + * + * @param Name|null $namespace Null is the global namespace + */ + public function startNamespace(Name $namespace = null) { - return $this->originalVersionString; + $this->namespace = $namespace; + $this->origAliases = $this->aliases = [Stmt\Use_::TYPE_NORMAL => [], Stmt\Use_::TYPE_FUNCTION => [], Stmt\Use_::TYPE_CONSTANT => []]; } - public function getVersionString() : string + /** + * Add an alias / import. + * + * @param Name $name Original name + * @param string $aliasName Aliased name + * @param int $type One of Stmt\Use_::TYPE_* + * @param array $errorAttrs Attributes to use to report an error + */ + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) { - $str = \sprintf('%d.%d.%d', $this->getMajor()->getValue() ?? 0, $this->getMinor()->getValue() ?? 0, $this->getPatch()->getValue() ?? 0); - if (!$this->hasPreReleaseSuffix()) { - return $str; + // Constant names are case sensitive, everything else case insensitive + if ($type === Stmt\Use_::TYPE_CONSTANT) { + $aliasLookupName = $aliasName; + } else { + $aliasLookupName = \strtolower($aliasName); } - return $str . '-' . $this->getPreReleaseSuffix()->asString(); - } - public function hasPreReleaseSuffix() : bool - { - return $this->preReleaseSuffix !== null; + if (isset($this->aliases[$type][$aliasLookupName])) { + $typeStringMap = [Stmt\Use_::TYPE_NORMAL => '', Stmt\Use_::TYPE_FUNCTION => 'function ', Stmt\Use_::TYPE_CONSTANT => 'const ']; + $this->errorHandler->handleError(new Error(\sprintf('Cannot use %s%s as %s because the name is already in use', $typeStringMap[$type], $name, $aliasName), $errorAttrs)); + return; + } + $this->aliases[$type][$aliasLookupName] = $name; + $this->origAliases[$type][$aliasName] = $name; } - public function equals(Version $other) : bool + /** + * Get current namespace. + * + * @return null|Name Namespace (or null if global namespace) + */ + public function getNamespace() { - return $this->getVersionString() === $other->getVersionString(); + return $this->namespace; } - public function isGreaterThan(Version $version) : bool + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * + * @return null|Name Resolved name, or null if static resolution is not possible + */ + public function getResolvedName(Name $name, int $type) { - if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { - return \false; - } - if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { - return \true; - } - if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { - return \false; - } - if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { - return \true; - } - if ($version->getPatch()->getValue() > $this->getPatch()->getValue()) { - return \false; - } - if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { - return \true; + // don't resolve special class names + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { + if (!$name->isUnqualified()) { + $this->errorHandler->handleError(new Error(\sprintf("'\\%s' is an invalid class name", $name->toString()), $name->getAttributes())); + } + return $name; } - if (!$version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { - return \false; + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; } - if ($version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { - return \true; + // Try to resolve aliases + if (null !== ($resolvedName = $this->resolveAlias($name, $type))) { + return $resolvedName; } - if (!$version->hasPreReleaseSuffix() && $this->hasPreReleaseSuffix()) { - return \false; + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { + if (null === $this->namespace) { + // outside of a namespace unaliased unqualified is same as fully qualified + return new FullyQualified($name, $name->getAttributes()); + } + // Cannot resolve statically + return null; } - return $this->getPreReleaseSuffix()->isGreaterThan($version->getPreReleaseSuffix()); - } - public function getMajor() : VersionNumber - { - return $this->major; - } - public function getMinor() : VersionNumber - { - return $this->minor; - } - public function getPatch() : VersionNumber - { - return $this->patch; + // if no alias exists prepend current namespace + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); } /** - * @param string[] $matches + * Get resolved class name. * - * @throws InvalidPreReleaseSuffixException + * @param Name $name Class ame to resolve + * + * @return Name Resolved name */ - private function parseVersion(array $matches) : void + public function getResolvedClassName(Name $name) : Name { - $this->major = new VersionNumber((int) $matches['Major']); - $this->minor = new VersionNumber((int) $matches['Minor']); - $this->patch = isset($matches['Patch']) ? new VersionNumber((int) $matches['Patch']) : new VersionNumber(0); - if (isset($matches['PreReleaseSuffix'])) { - $this->preReleaseSuffix = new PreReleaseSuffix($matches['PreReleaseSuffix']); - } + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); } /** - * @param string $version + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). * - * @throws InvalidVersionException + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name[] Possible representations of the name */ - private function ensureVersionStringIsValid($version) : void + public function getPossibleNames(string $name, int $type) : array { - $regex = '/^v? - (?(0|(?:[1-9]\\d*))) - \\. - (?(0|(?:[1-9]\\d*))) - (\\. - (?(0|(?:[1-9]\\d*))) - )? - (?: - - - (?(?:(dev|beta|b|rc|alpha|a|patch|p)\\.?\\d*)) - )? - $/xi'; - if (\preg_match($regex, $version, $matches) !== 1) { - throw new InvalidVersionException(\sprintf("Version string '%s' does not follow SemVer semantics", $version)); + $lcName = \strtolower($name); + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } } - $this->parseVersion($matches); + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + if (null !== ($nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type))) { + // Make sure there is no alias that makes the normally namespace-relative name + // into something else + if (null === $this->resolveAlias($nsRelativeName, $type)) { + $possibleNames[] = $nsRelativeName; + } + } + // Check for relevant namespace use statements + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { + $lcOrig = $orig->toLowerString(); + if (0 === \strpos($lcName, $lcOrig . '\\')) { + $possibleNames[] = new Name($alias . \substr($name, \strlen($lcOrig))); + } + } + // Check for relevant type-specific use statements + foreach ($this->origAliases[$type] as $alias => $orig) { + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // Constants are are complicated-sensitive + $normalizedOrig = $this->normalizeConstName($orig->toString()); + if ($normalizedOrig === $this->normalizeConstName($name)) { + $possibleNames[] = new Name($alias); + } + } else { + // Everything else is case-insensitive + if ($orig->toLowerString() === $lcName) { + $possibleNames[] = new Name($alias); + } + } + } + return $possibleNames; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -final class UnsupportedVersionConstraintException extends \RuntimeException implements Exception -{ -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Version; - -use Throwable; -interface Exception extends Throwable -{ -} - 0, 'a' => 1, 'alpha' => 1, 'b' => 2, 'beta' => 2, 'rc' => 3, 'p' => 4, 'patch' => 4]; - /** @var string */ - private $value; - /** @var int */ - private $valueScore; - /** @var int */ - private $number = 0; - /** @var string */ - private $full; /** - * @throws InvalidPreReleaseSuffixException + * Get shortest representation of this fully-qualified name. + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name Shortest representation */ - public function __construct(string $value) - { - $this->parseValue($value); - } - public function asString() : string - { - return $this->full; - } - public function getValue() : string + public function getShortName(string $name, int $type) : Name { - return $this->value; + $possibleNames = $this->getPossibleNames($name, $type); + // Find shortest name + $shortestName = null; + $shortestLength = \INF; + foreach ($possibleNames as $possibleName) { + $length = \strlen($possibleName->toCodeString()); + if ($length < $shortestLength) { + $shortestName = $possibleName; + $shortestLength = $length; + } + } + return $shortestName; } - public function getNumber() : ?int + private function resolveAlias(Name $name, $type) { - return $this->number; + $firstPart = $name->getFirst(); + if ($name->isQualified()) { + // resolve aliases for qualified names, always against class alias table + $checkName = \strtolower($firstPart); + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); + } + } elseif ($name->isUnqualified()) { + // constant aliases are case-sensitive, function aliases case-insensitive + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : \strtolower($firstPart); + if (isset($this->aliases[$type][$checkName])) { + // resolve unqualified aliases + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); + } + } + // No applicable aliases + return null; } - public function isGreaterThan(PreReleaseSuffix $suffix) : bool + private function getNamespaceRelativeName(string $name, string $lcName, int $type) { - if ($this->valueScore > $suffix->valueScore) { - return \true; + if (null === $this->namespace) { + return new Name($name); } - if ($this->valueScore < $suffix->valueScore) { - return \false; + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } } - return $this->getNumber() > $suffix->getNumber(); - } - private function mapValueToScore(string $value) : int - { - $value = \strtolower($value); - if (\array_key_exists($value, self::valueScoreMap)) { - return self::valueScoreMap[$value]; + $namespacePrefix = \strtolower($this->namespace . '\\'); + if (0 === \strpos($lcName, $namespacePrefix)) { + return new Name(\substr($name, \strlen($namespacePrefix))); } - return 0; + return null; } - private function parseValue(string $value) : void + private function normalizeConstName(string $name) { - $regex = '/-?((dev|beta|b|rc|alpha|a|patch|p)\\.?(\\d*)).*$/i'; - if (\preg_match($regex, $value, $matches) !== 1) { - throw new InvalidPreReleaseSuffixException(\sprintf('Invalid label %s', $value)); - } - $this->full = $matches[1]; - $this->value = $matches[2]; - if ($matches[3] !== '') { - $this->number = (int) $matches[3]; + $nsSep = \strrpos($name, '\\'); + if (\false === $nsSep) { + return $name; } - $this->valueScore = $this->mapValueToScore($matches[2]); + // Constants have case-insensitive namespace and case-sensitive short-name + $ns = \substr($name, 0, $nsSep); + $shortName = \substr($name, $nsSep + 1); + return \strtolower($ns) . '\\' . $shortName; } } -phar-io/version - -Copyright (c) 2016-2017 Arne Blankerts , Sebastian Heuer and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Arne Blankerts nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - versionString = $versionString; - $this->parseVersion($versionString); - } - public function getLabel() : string - { - return $this->label; - } - public function getBuildMetaData() : string - { - return $this->buildMetaData; - } - public function getVersionString() : string + /** + * Gets the type of the node. + * + * @return string Type of the node + */ + public function getType() : string; + /** + * Gets the names of the sub nodes. + * + * @return array Names of sub nodes + */ + public function getSubNodeNames() : array; + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + */ + public function getLine() : int; + /** + * Gets line the node started in. + * + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int Start line (or -1 if not available) + */ + public function getStartLine() : int; + /** + * Gets the line the node ended in. + * + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int End line (or -1 if not available) + */ + public function getEndLine() : int; + /** + * Gets the token offset of the first token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) + */ + public function getStartTokenPos() : int; + /** + * Gets the token offset of the last token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token end position (or -1 if not available) + */ + public function getEndTokenPos() : int; + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos() : int; + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos() : int; + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments() : array; + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment(); + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment); + /** + * Sets an attribute on a node. + * + * @param string $key + * @param mixed $value + */ + public function setAttribute(string $key, $value); + /** + * Returns whether an attribute exists. + * + * @param string $key + * + * @return bool + */ + public function hasAttribute(string $key) : bool; + /** + * Returns the value of an attribute. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getAttribute(string $key, $default = null); + /** + * Returns all the attributes of this node. + * + * @return array + */ + public function getAttributes() : array; + /** + * Replaces all the attributes of this node. + * + * @param array $attributes + */ + public function setAttributes(array $attributes); +} +versionString; + $this->attributes = $attributes; + $this->name = $name; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; } - public function getMajor() : VersionNumber + public function getSubNodeNames() : array { - return $this->major; + return ['name', 'value', 'byRef', 'unpack']; } - public function getMinor() : VersionNumber + public function getType() : string { - return $this->minor; + return 'Arg'; } - public function getPatch() : VersionNumber +} +patch; + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; } - private function parseVersion(string $versionString) : void + public function getSubNodeNames() : array { - $this->extractBuildMetaData($versionString); - $this->extractLabel($versionString); - $this->stripPotentialVPrefix($versionString); - $versionSegments = \explode('.', $versionString); - $this->major = new VersionNumber(\is_numeric($versionSegments[0]) ? (int) $versionSegments[0] : null); - $minorValue = isset($versionSegments[1]) && \is_numeric($versionSegments[1]) ? (int) $versionSegments[1] : null; - $patchValue = isset($versionSegments[2]) && \is_numeric($versionSegments[2]) ? (int) $versionSegments[2] : null; - $this->minor = new VersionNumber($minorValue); - $this->patch = new VersionNumber($patchValue); + return ['name', 'args']; } - private function extractBuildMetaData(string &$versionString) : void + public function getType() : string { - if (\preg_match('/\\+(.*)/', $versionString, $matches) === 1) { - $this->buildMetaData = $matches[1]; - $versionString = \str_replace($matches[0], '', $versionString); - } + return 'Attribute'; } - private function extractLabel(string &$versionString) : void +} +label = $matches[1]; - $versionString = \str_replace($matches[0], '', $versionString); - } + $this->attributes = $attributes; + $this->attrs = $attrs; } - private function stripPotentialVPrefix(string &$versionString) : void + public function getSubNodeNames() : array { - if ($versionString[0] !== 'v') { - return; - } - $versionString = \substr($versionString, 1); + return ['attrs']; + } + public function getType() : string + { + return 'AttributeGroup'; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectEnumerator; +namespace PHPUnit\PhpParser\Node; -use function array_merge; -use function func_get_args; -use function is_array; -use function is_object; -use PHPUnit\SebastianBergmann\ObjectReflector\ObjectReflector; -use PHPUnit\SebastianBergmann\RecursionContext\Context; +use PHPUnit\PhpParser\NodeAbstract; /** - * Traverses array structures and object graphs - * to enumerate all referenced objects. + * This is a base class for complex types, including nullable types and union types. + * + * It does not provide any shared behavior and exists only for type-checking purposes. */ -class Enumerator +abstract class ComplexType extends NodeAbstract +{ +} +attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->value = $value; + } + public function getSubNodeNames() : array + { + return ['name', 'value']; + } + public function getType() : string + { + return 'Const'; + } +} +contains($variable)) { - return $objects; - } - $array = $variable; - $processed->add($variable); - if (is_array($variable)) { - foreach ($array as $element) { - if (!is_array($element) && !is_object($element)) { - continue; - } - $objects = array_merge($objects, $this->enumerate($element, $processed)); - } - } else { - $objects[] = $variable; - $reflector = new ObjectReflector(); - foreach ($reflector->getAttributes($variable) as $value) { - if (!is_array($value) && !is_object($value)) { - continue; - } - $objects = array_merge($objects, $this->enumerate($value, $processed)); - } - } - return $objects; + $this->attributes = $attributes; + $this->var = $var; + $this->dim = $dim; + } + public function getSubNodeNames() : array + { + return ['var', 'dim']; + } + public function getType() : string + { + return 'Expr_ArrayDimFetch'; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectEnumerator; +namespace PHPUnit\PhpParser\Node\Expr; -use Throwable; -interface Exception extends Throwable +use PHPUnit\PhpParser\Node\Expr; +class ArrayItem extends Expr { + /** @var null|Expr Key */ + public $key; + /** @var Expr Value */ + public $value; + /** @var bool Whether to assign by reference */ + public $byRef; + /** @var bool Whether to unpack the argument */ + public $unpack; + /** + * Constructs an array item node. + * + * @param Expr $value Value + * @param null|Expr $key Key + * @param bool $byRef Whether to assign by reference + * @param array $attributes Additional attributes + */ + public function __construct(Expr $value, Expr $key = null, bool $byRef = \false, array $attributes = [], bool $unpack = \false) + { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + public function getSubNodeNames() : array + { + return ['key', 'value', 'byRef', 'unpack']; + } + public function getType() : string + { + return 'Expr_ArrayItem'; + } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\ObjectEnumerator; +namespace PHPUnit\PhpParser\Node\Expr; -class InvalidArgumentException extends \InvalidArgumentException implements Exception +use PHPUnit\PhpParser\Node\Expr; +class Array_ extends Expr { + // For use in "kind" attribute + const KIND_LONG = 1; + // array() syntax + const KIND_SHORT = 2; + // [] syntax + /** @var (ArrayItem|null)[] Items */ + public $items; + /** + * Constructs an array node. + * + * @param (ArrayItem|null)[] $items Items of the array + * @param array $attributes Additional attributes + */ + public function __construct(array $items = [], array $attributes = []) + { + $this->attributes = $attributes; + $this->items = $items; + } + public function getSubNodeNames() : array + { + return ['items']; + } + public function getType() : string + { + return 'Expr_Array'; + } } - - - - - This Schema file defines the rules by which the XML configuration file of PHPUnit 9.5 may be structured. - - - - - - Root Element - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The main type specifying the document structure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -BSD 3-Clause License - -Copyright (c) 2011, Nikita Popov -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'expr' => Expr : Expression body + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes */ - public function __construct(array $options = []) + public function __construct(array $subNodes = [], array $attributes = []) { - $this->dumpComments = !empty($options['dumpComments']); - $this->dumpPositions = !empty($options['dumpPositions']); + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? \false; + $this->byRef = $subNodes['byRef'] ?? \false; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->expr = $subNodes['expr']; + $this->attrGroups = $subNodes['attrGroups'] ?? []; } - /** - * Dumps a node or array. - * - * @param array|Node $node Node or array to dump - * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if - * the dumpPositions option is enabled and the dumping of node offsets - * is desired. - * - * @return string Dumped value - */ - public function dump($node, string $code = null) : string + public function getSubNodeNames() : array { - $this->code = $code; - return $this->dumpRecursive($node); + return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; } - protected function dumpRecursive($node) + public function returnsByRef() : bool { - if ($node instanceof Node) { - $r = $node->getType(); - if ($this->dumpPositions && null !== ($p = $this->dumpPosition($node))) { - $r .= $p; - } - $r .= '('; - foreach ($node->getSubNodeNames() as $key) { - $r .= "\n " . $key . ': '; - $value = $node->{$key}; - if (null === $value) { - $r .= 'null'; - } elseif (\false === $value) { - $r .= 'false'; - } elseif (\true === $value) { - $r .= 'true'; - } elseif (\is_scalar($value)) { - if ('flags' === $key || 'newModifier' === $key) { - $r .= $this->dumpFlags($value); - } elseif ('type' === $key && $node instanceof Include_) { - $r .= $this->dumpIncludeType($value); - } elseif ('type' === $key && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) { - $r .= $this->dumpUseType($value); - } else { - $r .= $value; - } - } else { - $r .= \str_replace("\n", "\n ", $this->dumpRecursive($value)); - } - } - if ($this->dumpComments && ($comments = $node->getComments())) { - $r .= "\n comments: " . \str_replace("\n", "\n ", $this->dumpRecursive($comments)); - } - } elseif (\is_array($node)) { - $r = 'array('; - foreach ($node as $key => $value) { - $r .= "\n " . $key . ': '; - if (null === $value) { - $r .= 'null'; - } elseif (\false === $value) { - $r .= 'false'; - } elseif (\true === $value) { - $r .= 'true'; - } elseif (\is_scalar($value)) { - $r .= $value; - } else { - $r .= \str_replace("\n", "\n ", $this->dumpRecursive($value)); - } - } - } elseif ($node instanceof Comment) { - return $node->getReformattedText(); - } else { - throw new \InvalidArgumentException('Can only dump nodes and arrays.'); - } - return $r . "\n)"; + return $this->byRef; } - protected function dumpFlags($flags) + public function getParams() : array { - $strs = []; - if ($flags & Class_::MODIFIER_PUBLIC) { - $strs[] = 'MODIFIER_PUBLIC'; - } - if ($flags & Class_::MODIFIER_PROTECTED) { - $strs[] = 'MODIFIER_PROTECTED'; - } - if ($flags & Class_::MODIFIER_PRIVATE) { - $strs[] = 'MODIFIER_PRIVATE'; - } - if ($flags & Class_::MODIFIER_ABSTRACT) { - $strs[] = 'MODIFIER_ABSTRACT'; - } - if ($flags & Class_::MODIFIER_STATIC) { - $strs[] = 'MODIFIER_STATIC'; - } - if ($flags & Class_::MODIFIER_FINAL) { - $strs[] = 'MODIFIER_FINAL'; - } - if ($flags & Class_::MODIFIER_READONLY) { - $strs[] = 'MODIFIER_READONLY'; - } - if ($strs) { - return \implode(' | ', $strs) . ' (' . $flags . ')'; - } else { - return $flags; - } + return $this->params; } - protected function dumpIncludeType($type) + public function getReturnType() { - $map = [Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE']; - if (!isset($map[$type])) { - return $type; - } - return $map[$type] . ' (' . $type . ')'; + return $this->returnType; } - protected function dumpUseType($type) + public function getAttrGroups() : array { - $map = [Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', Use_::TYPE_NORMAL => 'TYPE_NORMAL', Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', Use_::TYPE_CONSTANT => 'TYPE_CONSTANT']; - if (!isset($map[$type])) { - return $type; - } - return $map[$type] . ' (' . $type . ')'; + return $this->attrGroups; } /** - * Dump node position, if possible. - * - * @param Node $node Node for which to dump position - * - * @return string|null Dump of position, or null if position information not available + * @return Node\Stmt\Return_[] */ - protected function dumpPosition(Node $node) + public function getStmts() : array { - if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { - return null; - } - $start = $node->getStartLine(); - $end = $node->getEndLine(); - if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') && null !== $this->code) { - $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); - $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); - } - return "[{$start} - {$end}]"; + return [new Node\Stmt\Return_($this->expr)]; } - // Copied from Error class - private function toColumn($code, $pos) + public function getType() : string { - if ($pos > \strlen($code)) { - throw new \RuntimeException('Invalid position information'); - } - $lineStartPos = \strrpos($code, "\n", $pos - \strlen($code)); - if (\false === $lineStartPos) { - $lineStartPos = -1; - } - return $pos - $lineStartPos; + return 'Expr_ArrowFunction'; } } tokens = $tokens; - $this->indentMap = $this->calcIndentMap(); + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; } - /** - * Whether the given position is immediately surrounded by parenthesis. - * - * @param int $startPos Start position - * @param int $endPos End position - * - * @return bool - */ - public function haveParens(int $startPos, int $endPos) : bool + public function getSubNodeNames() : array { - return $this->haveTokenImmediatelyBefore($startPos, '(') && $this->haveTokenImmediatelyAfter($endPos, ')'); + return ['var', 'expr']; } - /** - * Whether the given position is immediately surrounded by braces. - * - * @param int $startPos Start position - * @param int $endPos End position - * - * @return bool - */ - public function haveBraces(int $startPos, int $endPos) : bool + public function getType() : string { - return ($this->haveTokenImmediatelyBefore($startPos, '{') || $this->haveTokenImmediatelyBefore($startPos, \T_CURLY_OPEN)) && $this->haveTokenImmediatelyAfter($endPos, '}'); + return 'Expr_Assign'; } +} +tokens; - $pos--; - for (; $pos >= 0; $pos--) { - $tokenType = $tokens[$pos][0]; - if ($tokenType === $expectedTokenType) { - return \true; - } - if ($tokenType !== \T_WHITESPACE && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { - break; - } - } - return \false; + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; } - /** - * Check whether the position is directly followed by a certain token type. - * - * During this check whitespace and comments are skipped. - * - * @param int $pos Position after which the token should occur - * @param int|string $expectedTokenType Token to check for - * - * @return bool Whether the expected token was found - */ - public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool + public function getSubNodeNames() : array { - $tokens = $this->tokens; - $pos++; - for (; $pos < \count($tokens); $pos++) { - $tokenType = $tokens[$pos][0]; - if ($tokenType === $expectedTokenType) { - return \true; - } - if ($tokenType !== \T_WHITESPACE && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { - break; - } - } - return \false; + return ['var', 'expr']; } - public function skipLeft(int $pos, $skipTokenType) +} +tokens; - $pos = $this->skipLeftWhitespace($pos); - if ($skipTokenType === \T_WHITESPACE) { - return $pos; - } - if ($tokens[$pos][0] !== $skipTokenType) { - // Shouldn't happen. The skip token MUST be there - throw new \Exception('Encountered unexpected token'); - } - $pos--; - return $this->skipLeftWhitespace($pos); + return 'Expr_AssignOp_BitwiseAnd'; } - public function skipRight(int $pos, $skipTokenType) +} +tokens; - $pos = $this->skipRightWhitespace($pos); - if ($skipTokenType === \T_WHITESPACE) { - return $pos; - } - if ($tokens[$pos][0] !== $skipTokenType) { - // Shouldn't happen. The skip token MUST be there - throw new \Exception('Encountered unexpected token'); - } - $pos++; - return $this->skipRightWhitespace($pos); + return 'Expr_AssignOp_BitwiseOr'; } - /** - * Return first non-whitespace token position smaller or equal to passed position. - * - * @param int $pos Token position - * @return int Non-whitespace token position - */ - public function skipLeftWhitespace(int $pos) +} +tokens; - for (; $pos >= 0; $pos--) { - $type = $tokens[$pos][0]; - if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { - break; - } - } - return $pos; + return 'Expr_AssignOp_BitwiseXor'; } - /** - * Return first non-whitespace position greater or equal to passed position. - * - * @param int $pos Token position - * @return int Non-whitespace token position - */ - public function skipRightWhitespace(int $pos) +} +tokens; - for ($count = \count($tokens); $pos < $count; $pos++) { - $type = $tokens[$pos][0]; - if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { - break; - } - } - return $pos; + return 'Expr_AssignOp_Coalesce'; } - public function findRight(int $pos, $findTokenType) +} +tokens; - for ($count = \count($tokens); $pos < $count; $pos++) { - $type = $tokens[$pos][0]; - if ($type === $findTokenType) { - return $pos; - } - } - return -1; + return 'Expr_AssignOp_Concat'; } - /** - * Whether the given position range contains a certain token type. - * - * @param int $startPos Starting position (inclusive) - * @param int $endPos Ending position (exclusive) - * @param int|string $tokenType Token type to look for - * @return bool Whether the token occurs in the given range - */ - public function haveTokenInRange(int $startPos, int $endPos, $tokenType) +} +tokens; - for ($pos = $startPos; $pos < $endPos; $pos++) { - if ($tokens[$pos][0] === $tokenType) { - return \true; - } - } - return \false; + return 'Expr_AssignOp_Div'; } - public function haveBracesInRange(int $startPos, int $endPos) +} +haveTokenInRange($startPos, $endPos, '{') || $this->haveTokenInRange($startPos, $endPos, \T_CURLY_OPEN) || $this->haveTokenInRange($startPos, $endPos, '}'); + return 'Expr_AssignOp_Minus'; } - /** - * Get indentation before token position. - * - * @param int $pos Token position - * - * @return int Indentation depth (in spaces) - */ - public function getIndentationBefore(int $pos) : int +} +indentMap[$pos]; + return 'Expr_AssignOp_Mod'; } - /** - * Get the code corresponding to a token offset range, optionally adjusted for indentation. - * - * @param int $from Token start position (inclusive) - * @param int $to Token end position (exclusive) - * @param int $indent By how much the code should be indented (can be negative as well) - * - * @return string Code corresponding to token range, adjusted for indentation - */ - public function getTokenCode(int $from, int $to, int $indent) : string +} +tokens; - $result = ''; - for ($pos = $from; $pos < $to; $pos++) { - $token = $tokens[$pos]; - if (\is_array($token)) { - $type = $token[0]; - $content = $token[1]; - if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) { - $result .= $content; - } else { - // TODO Handle non-space indentation - if ($indent < 0) { - $result .= \str_replace("\n" . \str_repeat(" ", -$indent), "\n", $content); - } elseif ($indent > 0) { - $result .= \str_replace("\n", "\n" . \str_repeat(" ", $indent), $content); - } else { - $result .= $content; - } - } - } else { - $result .= $token; - } - } - return $result; + return 'Expr_AssignOp_Mul'; } - /** - * Precalculate the indentation at every token position. - * - * @return int[] Token position to indentation map - */ - private function calcIndentMap() +} +tokens as $token) { - $indentMap[] = $indent; - if ($token[0] === \T_WHITESPACE) { - $content = $token[1]; - $newlinePos = \strrpos($content, "\n"); - if (\false !== $newlinePos) { - $indent = \strlen($content) - $newlinePos - 1; - } - } - } - // Add a sentinel for one past end of the file - $indentMap[] = $indent; - return $indentMap; + return 'Expr_AssignOp_Plus'; } } type = $type; - $this->old = $old; - $this->new = $new; + return 'Expr_AssignOp_Pow'; } } isEqual = $isEqual; + return 'Expr_AssignOp_ShiftLeft'; + } +} +calculateTrace($old, $new); - return $this->extractDiff($trace, $x, $y, $old, $new); + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['var', 'expr']; + } + public function getType() : string + { + return 'Expr_AssignRef'; } +} +coalesceReplacements($this->diff($old, $new)); + $this->attributes = $attributes; + $this->left = $left; + $this->right = $right; } - private function calculateTrace(array $a, array $b) + public function getSubNodeNames() : array { - $n = \count($a); - $m = \count($b); - $max = $n + $m; - $v = [1 => 0]; - $trace = []; - for ($d = 0; $d <= $max; $d++) { - $trace[] = $v; - for ($k = -$d; $k <= $d; $k += 2) { - if ($k === -$d || $k !== $d && $v[$k - 1] < $v[$k + 1]) { - $x = $v[$k + 1]; - } else { - $x = $v[$k - 1] + 1; - } - $y = $x - $k; - while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) { - $x++; - $y++; - } - $v[$k] = $x; - if ($x >= $n && $y >= $m) { - return [$trace, $x, $y]; - } - } - } - throw new \Exception('Should not happen'); - } - private function extractDiff(array $trace, int $x, int $y, array $a, array $b) - { - $result = []; - for ($d = \count($trace) - 1; $d >= 0; $d--) { - $v = $trace[$d]; - $k = $x - $y; - if ($k === -$d || $k !== $d && $v[$k - 1] < $v[$k + 1]) { - $prevK = $k + 1; - } else { - $prevK = $k - 1; - } - $prevX = $v[$prevK]; - $prevY = $prevX - $prevK; - while ($x > $prevX && $y > $prevY) { - $result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x - 1], $b[$y - 1]); - $x--; - $y--; - } - if ($d === 0) { - break; - } - while ($x > $prevX) { - $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x - 1], null); - $x--; - } - while ($y > $prevY) { - $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y - 1]); - $y--; - } - } - return \array_reverse($result); + return ['left', 'right']; } /** - * Coalesce equal-length sequences of remove+add into a replace operation. + * Get the operator sigil for this binary operation. * - * @param DiffElem[] $diff - * @return DiffElem[] + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + * + * @return string */ - private function coalesceReplacements(array $diff) - { - $newDiff = []; - $c = \count($diff); - for ($i = 0; $i < $c; $i++) { - $diffType = $diff[$i]->type; - if ($diffType !== DiffElem::TYPE_REMOVE) { - $newDiff[] = $diff[$i]; - continue; - } - $j = $i; - while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { - $j++; - } - $k = $j; - while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { - $k++; - } - if ($j - $i === $k - $j) { - $len = $j - $i; - for ($n = 0; $n < $len; $n++) { - $newDiff[] = new DiffElem(DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new); - } - } else { - for (; $i < $k; $i++) { - $newDiff[] = $diff[$i]; - } - } - $i = $k - 1; - } - return $newDiff; - } + public abstract function getOperatorSigil() : string; } attrGroups = $attrGroups; - $this->args = $args; - $this->extends = $extends; - $this->implements = $implements; - $this->stmts = $stmts; + return '&'; } - public static function fromNewNode(Expr\New_ $newNode) + public function getType() : string { - $class = $newNode->class; - \assert($class instanceof Node\Stmt\Class_); - // We don't assert that $class->name is null here, to allow consumers to assign unique names - // to anonymous classes for their own purposes. We simplify ignore the name here. - return new self($class->attrGroups, $newNode->args, $class->extends, $class->implements, $class->stmts, $newNode->getAttributes()); + return 'Expr_BinaryOp_BitwiseAnd'; } - public function getType() : string +} + [0, 1], - Expr\BitwiseNot::class => [10, 1], - Expr\PreInc::class => [10, 1], - Expr\PreDec::class => [10, 1], - Expr\PostInc::class => [10, -1], - Expr\PostDec::class => [10, -1], - Expr\UnaryPlus::class => [10, 1], - Expr\UnaryMinus::class => [10, 1], - Cast\Int_::class => [10, 1], - Cast\Double::class => [10, 1], - Cast\String_::class => [10, 1], - Cast\Array_::class => [10, 1], - Cast\Object_::class => [10, 1], - Cast\Bool_::class => [10, 1], - Cast\Unset_::class => [10, 1], - Expr\ErrorSuppress::class => [10, 1], - Expr\Instanceof_::class => [20, 0], - Expr\BooleanNot::class => [30, 1], - BinaryOp\Mul::class => [40, -1], - BinaryOp\Div::class => [40, -1], - BinaryOp\Mod::class => [40, -1], - BinaryOp\Plus::class => [50, -1], - BinaryOp\Minus::class => [50, -1], - BinaryOp\Concat::class => [50, -1], - BinaryOp\ShiftLeft::class => [60, -1], - BinaryOp\ShiftRight::class => [60, -1], - BinaryOp\Smaller::class => [70, 0], - BinaryOp\SmallerOrEqual::class => [70, 0], - BinaryOp\Greater::class => [70, 0], - BinaryOp\GreaterOrEqual::class => [70, 0], - BinaryOp\Equal::class => [80, 0], - BinaryOp\NotEqual::class => [80, 0], - BinaryOp\Identical::class => [80, 0], - BinaryOp\NotIdentical::class => [80, 0], - BinaryOp\Spaceship::class => [80, 0], - BinaryOp\BitwiseAnd::class => [90, -1], - BinaryOp\BitwiseXor::class => [100, -1], - BinaryOp\BitwiseOr::class => [110, -1], - BinaryOp\BooleanAnd::class => [120, -1], - BinaryOp\BooleanOr::class => [130, -1], - BinaryOp\Coalesce::class => [140, 1], - Expr\Ternary::class => [150, 0], - // parser uses %left for assignments, but they really behave as %right - Expr\Assign::class => [160, 1], - Expr\AssignRef::class => [160, 1], - AssignOp\Plus::class => [160, 1], - AssignOp\Minus::class => [160, 1], - AssignOp\Mul::class => [160, 1], - AssignOp\Div::class => [160, 1], - AssignOp\Concat::class => [160, 1], - AssignOp\Mod::class => [160, 1], - AssignOp\BitwiseAnd::class => [160, 1], - AssignOp\BitwiseOr::class => [160, 1], - AssignOp\BitwiseXor::class => [160, 1], - AssignOp\ShiftLeft::class => [160, 1], - AssignOp\ShiftRight::class => [160, 1], - AssignOp\Pow::class => [160, 1], - AssignOp\Coalesce::class => [160, 1], - Expr\YieldFrom::class => [165, 1], - Expr\Print_::class => [168, 1], - BinaryOp\LogicalAnd::class => [170, -1], - BinaryOp\LogicalXor::class => [180, -1], - BinaryOp\LogicalOr::class => [190, -1], - Expr\Include_::class => [200, -1], - ]; - /** @var int Current indentation level. */ - protected $indentLevel; - /** @var string Newline including current indentation. */ - protected $nl; - /** @var string Token placed at end of doc string to ensure it is followed by a newline. */ - protected $docStringEndToken; - /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ - protected $canUseSemicolonNamespaces; - /** @var array Pretty printer options */ - protected $options; - /** @var TokenStream Original tokens for use in format-preserving pretty print */ - protected $origTokens; - /** @var Internal\Differ Differ for node lists */ - protected $nodeListDiffer; - /** @var bool[] Map determining whether a certain character is a label character */ - protected $labelCharMap; - /** - * @var int[][] Map from token classes and subnode names to FIXUP_* constants. This is used - * during format-preserving prints to place additional parens/braces if necessary. - */ - protected $fixupMap; - /** - * @var int[][] Map from "{$node->getType()}->{$subNode}" to ['left' => $l, 'right' => $r], - * where $l and $r specify the token type that needs to be stripped when removing - * this node. - */ - protected $removalMap; - /** - * @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. - * $find is an optional token after which the insertion occurs. $extraLeft/Right - * are optionally added before/after the main insertions. - */ - protected $insertionMap; - /** - * @var string[] Map From "{$node->getType()}->{$subNode}" to string that should be inserted - * between elements of this list subnode. - */ - protected $listInsertionMap; - protected $emptyListInsertionMap; - /** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers - * should be reprinted. */ - protected $modifierChangeMap; - /** - * Creates a pretty printer instance using the given options. - * - * Supported options: - * * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array - * syntax, if the node does not specify a format. - * - * @param array $options Dictionary of formatting options - */ - public function __construct(array $options = []) + public function getOperatorSigil() : string { - $this->docStringEndToken = '_DOC_STRING_END_' . \mt_rand(); - $defaultOptions = ['shortArraySyntax' => \false]; - $this->options = $options + $defaultOptions; + return '^'; } - /** - * Reset pretty printing state. - */ - protected function resetState() + public function getType() : string { - $this->indentLevel = 0; - $this->nl = "\n"; - $this->origTokens = null; + return 'Expr_BinaryOp_BitwiseXor'; } - /** - * Set indentation level - * - * @param int $level Level in number of spaces - */ - protected function setIndentLevel(int $level) +} +indentLevel = $level; - $this->nl = "\n" . \str_repeat(' ', $level); + return '&&'; } - /** - * Increase indentation level. - */ - protected function indent() + public function getType() : string { - $this->indentLevel += 4; - $this->nl .= ' '; + return 'Expr_BinaryOp_BooleanAnd'; } - /** - * Decrease indentation level. - */ - protected function outdent() +} +indentLevel >= 4); - $this->indentLevel -= 4; - $this->nl = "\n" . \str_repeat(' ', $this->indentLevel); + return '||'; } - /** - * Pretty prints an array of statements. - * - * @param Node[] $stmts Array of statements - * - * @return string Pretty printed statements - */ - public function prettyPrint(array $stmts) : string + public function getType() : string { - $this->resetState(); - $this->preprocessNodes($stmts); - return \ltrim($this->handleMagicTokens($this->pStmts($stmts, \false))); + return 'Expr_BinaryOp_BooleanOr'; } - /** - * Pretty prints an expression. - * - * @param Expr $node Expression node - * - * @return string Pretty printed node - */ - public function prettyPrintExpr(Expr $node) : string +} +resetState(); - return $this->handleMagicTokens($this->p($node)); + return '??'; } - /** - * Pretty prints a file of statements (includes the opening prettyPrint($stmts); - if ($stmts[0] instanceof Stmt\InlineHTML) { - $p = \preg_replace('/^<\\?php\\s+\\?>\\n?/', '', $p); - } - if ($stmts[\count($stmts) - 1] instanceof Stmt\InlineHTML) { - $p = \preg_replace('/<\\?php$/', '', \rtrim($p)); - } - return $p; + return 'Expr_BinaryOp_Coalesce'; } - /** - * Preprocesses the top-level nodes to initialize pretty printer state. - * - * @param Node[] $nodes Array of nodes - */ - protected function preprocessNodes(array $nodes) +} +canUseSemicolonNamespaces = \true; - foreach ($nodes as $node) { - if ($node instanceof Stmt\Namespace_ && null === $node->name) { - $this->canUseSemicolonNamespaces = \false; - break; - } - } + return '.'; } - /** - * Handles (and removes) no-indent and doc-string-end tokens. - * - * @param string $str - * @return string - */ - protected function handleMagicTokens(string $str) : string + public function getType() : string { - // Replace doc-string-end tokens with nothing or a newline - $str = \str_replace($this->docStringEndToken . ";\n", ";\n", $str); - $str = \str_replace($this->docStringEndToken, "\n", $str); - return $str; + return 'Expr_BinaryOp_Concat'; } - /** - * Pretty prints an array of nodes (statements) and indents them optionally. - * - * @param Node[] $nodes Array of nodes - * @param bool $indent Whether to indent the printed nodes - * - * @return string Pretty printed statements - */ - protected function pStmts(array $nodes, bool $indent = \true) : string +} +indent(); - } - $result = ''; - foreach ($nodes as $node) { - $comments = $node->getComments(); - if ($comments) { - $result .= $this->nl . $this->pComments($comments); - if ($node instanceof Stmt\Nop) { - continue; - } - } - $result .= $this->nl . $this->p($node); - } - if ($indent) { - $this->outdent(); - } - return $result; + return '/'; } - /** - * Pretty-print an infix operation while taking precedence into account. - * - * @param string $class Node class of operator - * @param Node $leftNode Left-hand side node - * @param string $operatorString String representation of the operator - * @param Node $rightNode Right-hand side node - * - * @return string Pretty printed infix operation - */ - protected function pInfixOp(string $class, Node $leftNode, string $operatorString, Node $rightNode) : string + public function getType() : string { - list($precedence, $associativity) = $this->precedenceMap[$class]; - return $this->pPrec($leftNode, $precedence, $associativity, -1) . $operatorString . $this->pPrec($rightNode, $precedence, $associativity, 1); + return 'Expr_BinaryOp_Div'; } - /** - * Pretty-print a prefix operation while taking precedence into account. - * - * @param string $class Node class of operator - * @param string $operatorString String representation of the operator - * @param Node $node Node - * - * @return string Pretty printed prefix operation - */ - protected function pPrefixOp(string $class, string $operatorString, Node $node) : string +} +precedenceMap[$class]; - return $operatorString . $this->pPrec($node, $precedence, $associativity, 1); + return '=='; } - /** - * Pretty-print a postfix operation while taking precedence into account. - * - * @param string $class Node class of operator - * @param string $operatorString String representation of the operator - * @param Node $node Node - * - * @return string Pretty printed postfix operation - */ - protected function pPostfixOp(string $class, Node $node, string $operatorString) : string + public function getType() : string { - list($precedence, $associativity) = $this->precedenceMap[$class]; - return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString; + return 'Expr_BinaryOp_Equal'; } - /** - * Prints an expression node with the least amount of parentheses necessary to preserve the meaning. - * - * @param Node $node Node to pretty print - * @param int $parentPrecedence Precedence of the parent operator - * @param int $parentAssociativity Associativity of parent operator - * (-1 is left, 0 is nonassoc, 1 is right) - * @param int $childPosition Position of the node relative to the operator - * (-1 is left, 1 is right) - * - * @return string The pretty printed node - */ - protected function pPrec(Node $node, int $parentPrecedence, int $parentAssociativity, int $childPosition) : string +} +precedenceMap[$class])) { - $childPrecedence = $this->precedenceMap[$class][0]; - if ($childPrecedence > $parentPrecedence || $parentPrecedence === $childPrecedence && $parentAssociativity !== $childPosition) { - return '(' . $this->p($node) . ')'; - } - } - return $this->p($node); + return '>'; + } + public function getType() : string + { + return 'Expr_BinaryOp_Greater'; + } +} +='; + } + public function getType() : string + { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} +>'; + } + public function getType() : string + { + return 'Expr_BinaryOp_ShiftRight'; + } +} +'; + } + public function getType() : string + { + return 'Expr_BinaryOp_Spaceship'; } +} +p($node); - } - } - return \implode($glue, $pNodes); + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Expr_BitwiseNot'; } +} +pImplode($nodes, ', '); + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Expr_BooleanNot'; } +} + */ - protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma) : string + public abstract function getRawArgs() : array; + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable() : bool { - $this->indent(); - $result = ''; - $lastIdx = \count($nodes) - 1; - foreach ($nodes as $idx => $node) { - if ($node !== null) { - $comments = $node->getComments(); - if ($comments) { - $result .= $this->nl . $this->pComments($comments); - } - $result .= $this->nl . $this->p($node); - } else { - $result .= $this->nl; - } - if ($trailingComma || $idx !== $lastIdx) { - $result .= ','; + foreach ($this->getRawArgs() as $arg) { + if ($arg instanceof VariadicPlaceholder) { + return \true; } } - $this->outdent(); - return $result; + return \false; } /** - * Prints reformatted text of the passed comments. - * - * @param Comment[] $comments List of comments + * Assert that this is not a first-class callable and return only ordinary Args. * - * @return string Reformatted text of comments + * @return Arg[] */ - protected function pComments(array $comments) : string + public function getArgs() : array { - $formattedComments = []; - foreach ($comments as $comment) { - $formattedComments[] = \str_replace("\n", $this->nl, $comment->getReformattedText()); - } - return \implode($this->nl, $formattedComments); + \assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); } +} +attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } +} +initializeNodeListDiffer(); - $this->initializeLabelCharMap(); - $this->initializeFixupMap(); - $this->initializeRemovalMap(); - $this->initializeInsertionMap(); - $this->initializeListInsertionMap(); - $this->initializeEmptyListInsertionMap(); - $this->initializeModifierChangeMap(); - $this->resetState(); - $this->origTokens = new TokenStream($origTokens); - $this->preprocessNodes($stmts); - $pos = 0; - $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); - if (null !== $result) { - $result .= $this->origTokens->getTokenCode($pos, \count($origTokens), 0); - } else { - // Fallback - // TODO Add pStmts($stmts, \false); - } - return \ltrim($this->handleMagicTokens($result)); + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; } - protected function pFallback(Node $node) + public function getSubNodeNames() : array { - return $this->{'p' . $node->getType()}($node); + return ['class', 'name']; } + public function getType() : string + { + return 'Expr_ClassConstFetch'; + } +} +attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Expr_Clone'; + } +} + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array(): Parameters + * 'uses' => array(): use()s + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attributes groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) + { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? \false; + $this->byRef = $subNodes['byRef'] ?? \false; + $this->params = $subNodes['params'] ?? []; + $this->uses = $subNodes['uses'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + public function getSubNodeNames() : array + { + return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; + } + public function returnsByRef() : bool + { + return $this->byRef; + } + public function getParams() : array + { + return $this->params; + } + public function getReturnType() + { + return $this->returnType; + } + /** @return Node\Stmt[] */ + public function getStmts() : array + { + return $this->stmts; + } + public function getAttrGroups() : array + { + return $this->attrGroups; + } + public function getType() : string + { + return 'Expr_Closure'; + } +} +origTokens) { - return $this->{'p' . $node->getType()}($node); - } - /** @var Node $origNode */ - $origNode = $node->getAttribute('origNode'); - if (null === $origNode) { - return $this->pFallback($node); - } - $class = \get_class($node); - \assert($class === \get_class($origNode)); - $startPos = $origNode->getStartTokenPos(); - $endPos = $origNode->getEndTokenPos(); - \assert($startPos >= 0 && $endPos >= 0); - $fallbackNode = $node; - if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { - // Normalize node structure of anonymous classes - $node = PrintableNewAnonClassNode::fromNewNode($node); - $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); - } - // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting - // is not preserved, then we need to use the fallback code to make sure the tags are - // printed. - if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { - return $this->pFallback($fallbackNode); - } - $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); - $type = $node->getType(); - $fixupInfo = $this->fixupMap[$class] ?? null; - $result = ''; - $pos = $startPos; - foreach ($node->getSubNodeNames() as $subNodeName) { - $subNode = $node->{$subNodeName}; - $origSubNode = $origNode->{$subNodeName}; - if (!$subNode instanceof Node && $subNode !== null || !$origSubNode instanceof Node && $origSubNode !== null) { - if ($subNode === $origSubNode) { - // Unchanged, can reuse old code - continue; - } - if (\is_array($subNode) && \is_array($origSubNode)) { - // Array subnode changed, we might be able to reconstruct it - $listResult = $this->pArray($subNode, $origSubNode, $pos, $indentAdjustment, $type, $subNodeName, $fixupInfo[$subNodeName] ?? null); - if (null === $listResult) { - return $this->pFallback($fallbackNode); - } - $result .= $listResult; - continue; - } - if (\is_int($subNode) && \is_int($origSubNode)) { - // Check if this is a modifier change - $key = $type . '->' . $subNodeName; - if (!isset($this->modifierChangeMap[$key])) { - return $this->pFallback($fallbackNode); - } - $findToken = $this->modifierChangeMap[$key]; - $result .= $this->pModifiers($subNode); - $pos = $this->origTokens->findRight($pos, $findToken); - continue; - } - // If a non-node, non-array subnode changed, we don't be able to do a partial - // reconstructions, as we don't have enough offset information. Pretty print the - // whole node instead. - return $this->pFallback($fallbackNode); - } - $extraLeft = ''; - $extraRight = ''; - if ($origSubNode !== null) { - $subStartPos = $origSubNode->getStartTokenPos(); - $subEndPos = $origSubNode->getEndTokenPos(); - \assert($subStartPos >= 0 && $subEndPos >= 0); - } else { - if ($subNode === null) { - // Both null, nothing to do - continue; - } - // A node has been inserted, check if we have insertion information for it - $key = $type . '->' . $subNodeName; - if (!isset($this->insertionMap[$key])) { - return $this->pFallback($fallbackNode); - } - list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; - if (null !== $findToken) { - $subStartPos = $this->origTokens->findRight($pos, $findToken) + (int) (!$beforeToken); - } else { - $subStartPos = $pos; - } - if (null === $extraLeft && null !== $extraRight) { - // If inserting on the right only, skipping whitespace looks better - $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); - } - $subEndPos = $subStartPos - 1; - } - if (null === $subNode) { - // A node has been removed, check if we have removal information for it - $key = $type . '->' . $subNodeName; - if (!isset($this->removalMap[$key])) { - return $this->pFallback($fallbackNode); - } - // Adjust positions to account for additional tokens that must be skipped - $removalInfo = $this->removalMap[$key]; - if (isset($removalInfo['left'])) { - $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; - } - if (isset($removalInfo['right'])) { - $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; - } - } - $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); - if (null !== $subNode) { - $result .= $extraLeft; - $origIndentLevel = $this->indentLevel; - $this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment); - // If it's the same node that was previously in this position, it certainly doesn't - // need fixup. It's important to check this here, because our fixup checks are more - // conservative than strictly necessary. - if (isset($fixupInfo[$subNodeName]) && $subNode->getAttribute('origNode') !== $origSubNode) { - $fixup = $fixupInfo[$subNodeName]; - $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); - } else { - $res = $this->p($subNode, \true); - } - $this->safeAppend($result, $res); - $this->setIndentLevel($origIndentLevel); - $result .= $extraRight; - } - $pos = $subEndPos + 1; - } - $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); - return $result; + $this->attributes = $attributes; + $this->var = $var; + $this->byRef = $byRef; + } + public function getSubNodeNames() : array + { + return ['var', 'byRef']; + } + public function getType() : string + { + return 'Expr_ClosureUse'; } +} +attributes = $attributes; + $this->name = $name; + } + public function getSubNodeNames() : array + { + return ['name']; + } + public function getType() : string + { + return 'Expr_ConstFetch'; + } +} +nodeListDiffer->diffWithReplacements($origNodes, $nodes); - $mapKey = $parentNodeType . '->' . $subNodeName; - $insertStr = $this->listInsertionMap[$mapKey] ?? null; - $isStmtList = $subNodeName === 'stmts'; - $beforeFirstKeepOrReplace = \true; - $skipRemovedNode = \false; - $delayedAdd = []; - $lastElemIndentLevel = $this->indentLevel; - $insertNewline = \false; - if ($insertStr === "\n") { - $insertStr = ''; - $insertNewline = \true; - } - if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { - $startPos = $origNodes[0]->getStartTokenPos(); - $endPos = $origNodes[0]->getEndTokenPos(); - \assert($startPos >= 0 && $endPos >= 0); - if (!$this->origTokens->haveBraces($startPos, $endPos)) { - // This was a single statement without braces, but either additional statements - // have been added, or the single statement has been removed. This requires the - // addition of braces. For now fall back. - // TODO: Try to preserve formatting - return null; - } - } - $result = ''; - foreach ($diff as $i => $diffElem) { - $diffType = $diffElem->type; - /** @var Node|null $arrItem */ - $arrItem = $diffElem->new; - /** @var Node|null $origArrItem */ - $origArrItem = $diffElem->old; - if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { - $beforeFirstKeepOrReplace = \false; - if ($origArrItem === null || $arrItem === null) { - // We can only handle the case where both are null - if ($origArrItem === $arrItem) { - continue; - } - return null; - } - if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { - // We can only deal with nodes. This can occur for Names, which use string arrays. - return null; - } - $itemStartPos = $origArrItem->getStartTokenPos(); - $itemEndPos = $origArrItem->getEndTokenPos(); - \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); - $origIndentLevel = $this->indentLevel; - $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; - $this->setIndentLevel($lastElemIndentLevel); - $comments = $arrItem->getComments(); - $origComments = $origArrItem->getComments(); - $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; - \assert($commentStartPos >= 0); - if ($commentStartPos < $pos) { - // Comments may be assigned to multiple nodes if they start at the same position. - // Make sure we don't try to print them multiple times. - $commentStartPos = $itemStartPos; - } - if ($skipRemovedNode) { - if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { - // We'd remove the brace of a code block. - // TODO: Preserve formatting. - $this->setIndentLevel($origIndentLevel); - return null; - } - } else { - $result .= $this->origTokens->getTokenCode($pos, $commentStartPos, $indentAdjustment); - } - if (!empty($delayedAdd)) { - /** @var Node $delayedAddNode */ - foreach ($delayedAdd as $delayedAddNode) { - if ($insertNewline) { - $delayedAddComments = $delayedAddNode->getComments(); - if ($delayedAddComments) { - $result .= $this->pComments($delayedAddComments) . $this->nl; - } - } - $this->safeAppend($result, $this->p($delayedAddNode, \true)); - if ($insertNewline) { - $result .= $insertStr . $this->nl; - } else { - $result .= $insertStr; - } - } - $delayedAdd = []; - } - if ($comments !== $origComments) { - if ($comments) { - $result .= $this->pComments($comments) . $this->nl; - } - } else { - $result .= $this->origTokens->getTokenCode($commentStartPos, $itemStartPos, $indentAdjustment); - } - // If we had to remove anything, we have done so now. - $skipRemovedNode = \false; - } elseif ($diffType === DiffElem::TYPE_ADD) { - if (null === $insertStr) { - // We don't have insertion information for this list type - return null; - } - // We go multiline if the original code was multiline, - // or if it's an array item with a comment above it. - if ($insertStr === ', ' && ($this->isMultiline($origNodes) || $arrItem->getComments())) { - $insertStr = ','; - $insertNewline = \true; - } - if ($beforeFirstKeepOrReplace) { - // Will be inserted at the next "replace" or "keep" element - $delayedAdd[] = $arrItem; - continue; - } - $itemStartPos = $pos; - $itemEndPos = $pos - 1; - $origIndentLevel = $this->indentLevel; - $this->setIndentLevel($lastElemIndentLevel); - if ($insertNewline) { - $result .= $insertStr . $this->nl; - $comments = $arrItem->getComments(); - if ($comments) { - $result .= $this->pComments($comments) . $this->nl; - } - } else { - $result .= $insertStr; - } - } elseif ($diffType === DiffElem::TYPE_REMOVE) { - if (!$origArrItem instanceof Node) { - // We only support removal for nodes - return null; - } - $itemStartPos = $origArrItem->getStartTokenPos(); - $itemEndPos = $origArrItem->getEndTokenPos(); - \assert($itemStartPos >= 0 && $itemEndPos >= 0); - // Consider comments part of the node. - $origComments = $origArrItem->getComments(); - if ($origComments) { - $itemStartPos = $origComments[0]->getStartTokenPos(); - } - if ($i === 0) { - // If we're removing from the start, keep the tokens before the node and drop those after it, - // instead of the other way around. - $result .= $this->origTokens->getTokenCode($pos, $itemStartPos, $indentAdjustment); - $skipRemovedNode = \true; - } else { - if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { - // We'd remove the brace of a code block. - // TODO: Preserve formatting. - return null; - } - } - $pos = $itemEndPos + 1; - continue; - } else { - throw new \Exception("Shouldn't happen"); - } - if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { - $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); - } else { - $res = $this->p($arrItem, \true); - } - $this->safeAppend($result, $res); - $this->setIndentLevel($origIndentLevel); - $pos = $itemEndPos + 1; - } - if ($skipRemovedNode) { - // TODO: Support removing single node. - return null; - } - if (!empty($delayedAdd)) { - if (!isset($this->emptyListInsertionMap[$mapKey])) { - return null; - } - list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; - if (null !== $findToken) { - $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; - $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); - $pos = $insertPos; - } - $first = \true; - $result .= $extraLeft; - foreach ($delayedAdd as $delayedAddNode) { - if (!$first) { - $result .= $insertStr; - } - $result .= $this->p($delayedAddNode, \true); - $first = \false; - } - $result .= $extraRight; - } - return $result; + $this->attributes = $attributes; + $this->expr = $expr; } - /** - * Print node with fixups. - * - * Fixups here refer to the addition of extra parentheses, braces or other characters, that - * are required to preserve program semantics in a certain context (e.g. to maintain precedence - * or because only certain expressions are allowed in certain places). - * - * @param int $fixup Fixup type - * @param Node $subNode Subnode to print - * @param string|null $parentClass Class of parent node - * @param int $subStartPos Original start pos of subnode - * @param int $subEndPos Original end pos of subnode - * - * @return string Result of fixed-up print of subnode - */ - protected function pFixup(int $fixup, Node $subNode, $parentClass, int $subStartPos, int $subEndPos) : string + public function getSubNodeNames() : array { - switch ($fixup) { - case self::FIXUP_PREC_LEFT: - case self::FIXUP_PREC_RIGHT: - if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { - list($precedence, $associativity) = $this->precedenceMap[$parentClass]; - return $this->pPrec($subNode, $precedence, $associativity, $fixup === self::FIXUP_PREC_LEFT ? -1 : 1); - } - break; - case self::FIXUP_CALL_LHS: - if ($this->callLhsRequiresParens($subNode) && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { - return '(' . $this->p($subNode) . ')'; - } - break; - case self::FIXUP_DEREF_LHS: - if ($this->dereferenceLhsRequiresParens($subNode) && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { - return '(' . $this->p($subNode) . ')'; - } - break; - case self::FIXUP_BRACED_NAME: - case self::FIXUP_VAR_BRACED_NAME: - if ($subNode instanceof Expr && !$this->origTokens->haveBraces($subStartPos, $subEndPos)) { - return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') . '{' . $this->p($subNode) . '}'; - } - break; - case self::FIXUP_ENCAPSED: - if (!$subNode instanceof Scalar\EncapsedStringPart && !$this->origTokens->haveBraces($subStartPos, $subEndPos)) { - return '{' . $this->p($subNode) . '}'; - } - break; - default: - throw new \Exception('Cannot happen'); - } - // Nothing special to do - return $this->p($subNode); + return ['expr']; } - /** - * Appends to a string, ensuring whitespace between label characters. - * - * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". - * Without safeAppend the result would be "echox", which does not preserve semantics. - * - * @param string $str - * @param string $append - */ - protected function safeAppend(string &$str, string $append) + public function getType() : string { - if ($str === "") { - $str = $append; - return; - } - if ($append === "") { - return; - } - if (!$this->labelCharMap[$append[0]] || !$this->labelCharMap[$str[\strlen($str) - 1]]) { - $str .= $append; - } else { - $str .= " " . $append; - } + return 'Expr_Empty'; } +} +attributes = $attributes; } - /** - * Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis. - * - * @param Node $node LHS of dereferencing operation - * - * @return bool Whether parentheses are required - */ - protected function dereferenceLhsRequiresParens(Node $node) : bool + public function getSubNodeNames() : array { - return !($node instanceof Expr\Variable || $node instanceof Node\Name || $node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch || $node instanceof Expr\NullsafePropertyFetch || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\FuncCall || $node instanceof Expr\MethodCall || $node instanceof Expr\NullsafeMethodCall || $node instanceof Expr\StaticCall || $node instanceof Expr\Array_ || $node instanceof Scalar\String_ || $node instanceof Expr\ConstFetch || $node instanceof Expr\ClassConstFetch); + return []; } - /** - * Print modifiers, including trailing whitespace. - * - * @param int $modifiers Modifier mask to print - * - * @return string Printed modifiers - */ - protected function pModifiers(int $modifiers) + public function getType() : string { - return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC ? 'public ' : '') . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '') . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '') . ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '') . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '') . ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '') . ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : ''); + return 'Expr_Error'; } +} +getEndTokenPos() + 1; - if ($pos >= 0) { - $text = $this->origTokens->getTokenCode($pos, $endPos, 0); - if (\false === \strpos($text, "\n")) { - // We require that a newline is present between *every* item. If the formatting - // is inconsistent, with only some items having newlines, we don't consider it - // as multiline - return \false; - } - } - $pos = $endPos; - } - return \true; + $this->attributes = $attributes; + $this->expr = $expr; } - /** - * Lazily initializes label char map. - * - * The label char map determines whether a certain character may occur in a label. - */ - protected function initializeLabelCharMap() + public function getSubNodeNames() : array { - if ($this->labelCharMap) { - return; - } - $this->labelCharMap = []; - for ($i = 0; $i < 256; $i++) { - // Since PHP 7.1 The lower range is 0x80. However, we also want to support code for - // older versions. - $chr = \chr($i); - $this->labelCharMap[$chr] = $i >= 0x7f || \ctype_alnum($chr); - } + return ['expr']; + } + public function getType() : string + { + return 'Expr_ErrorSuppress'; } +} +nodeListDiffer) { - return; - } - $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { - if ($a instanceof Node && $b instanceof Node) { - return $a === $b->getAttribute('origNode'); - } - // Can happen for array destructuring - return $a === null && $b === null; - }); + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; } + public function getType() : string + { + return 'Expr_Eval'; + } +} +fixupMap) { - return; - } - $this->fixupMap = [ - Expr\PreInc::class => ['var' => self::FIXUP_PREC_RIGHT], - Expr\PreDec::class => ['var' => self::FIXUP_PREC_RIGHT], - Expr\PostInc::class => ['var' => self::FIXUP_PREC_LEFT], - Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT], - Expr\Instanceof_::class => ['expr' => self::FIXUP_PREC_LEFT, 'class' => self::FIXUP_PREC_RIGHT], - Expr\Ternary::class => ['cond' => self::FIXUP_PREC_LEFT, 'else' => self::FIXUP_PREC_RIGHT], - Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], - Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS], - Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], - Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS], - Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], - // TODO: FIXUP_NEW_VARIABLE - Expr\MethodCall::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], - Expr\NullsafeMethodCall::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], - Expr\StaticPropertyFetch::class => ['class' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_VAR_BRACED_NAME], - Expr\PropertyFetch::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], - Expr\NullsafePropertyFetch::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], - Scalar\Encapsed::class => ['parts' => self::FIXUP_ENCAPSED], - ]; - $binaryOps = [BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class]; - foreach ($binaryOps as $binaryOp) { - $this->fixupMap[$binaryOp] = ['left' => self::FIXUP_PREC_LEFT, 'right' => self::FIXUP_PREC_RIGHT]; - } - $assignOps = [Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class]; - foreach ($assignOps as $assignOp) { - $this->fixupMap[$assignOp] = ['var' => self::FIXUP_PREC_LEFT, 'expr' => self::FIXUP_PREC_RIGHT]; - } - $prefixOps = [Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class]; - foreach ($prefixOps as $prefixOp) { - $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_RIGHT]; - } + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Expr_Exit'; } +} + Arguments */ + public $args; /** - * Lazily initializes the removal map. + * Constructs a function call node. * - * The removal map is used to determine which additional tokens should be removed when a - * certain node is replaced by null. + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes */ - protected function initializeRemovalMap() + public function __construct($name, array $args = [], array $attributes = []) { - if ($this->removalMap) { - return; - } - $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; - $stripLeft = ['left' => \T_WHITESPACE]; - $stripRight = ['right' => \T_WHITESPACE]; - $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; - $stripColon = ['left' => ':']; - $stripEquals = ['left' => '=']; - $this->removalMap = ['Expr_ArrayDimFetch->dim' => $stripBoth, 'Expr_ArrayItem->key' => $stripDoubleArrow, 'Expr_ArrowFunction->returnType' => $stripColon, 'Expr_Closure->returnType' => $stripColon, 'Expr_Exit->expr' => $stripBoth, 'Expr_Ternary->if' => $stripBoth, 'Expr_Yield->key' => $stripDoubleArrow, 'Expr_Yield->value' => $stripBoth, 'Param->type' => $stripRight, 'Param->default' => $stripEquals, 'Stmt_Break->num' => $stripBoth, 'Stmt_Catch->var' => $stripLeft, 'Stmt_ClassMethod->returnType' => $stripColon, 'Stmt_Class->extends' => ['left' => \T_EXTENDS], 'Stmt_Enum->scalarType' => $stripColon, 'Stmt_EnumCase->expr' => $stripEquals, 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], 'Stmt_Continue->num' => $stripBoth, 'Stmt_Foreach->keyVar' => $stripDoubleArrow, 'Stmt_Function->returnType' => $stripColon, 'Stmt_If->else' => $stripLeft, 'Stmt_Namespace->name' => $stripLeft, 'Stmt_Property->type' => $stripRight, 'Stmt_PropertyProperty->default' => $stripEquals, 'Stmt_Return->expr' => $stripBoth, 'Stmt_StaticVar->default' => $stripEquals, 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, 'Stmt_TryCatch->finally' => $stripLeft]; + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; } - protected function initializeInsertionMap() + public function getSubNodeNames() : array { - if ($this->insertionMap) { - return; - } - // TODO: "yield" where both key and value are inserted doesn't work - // [$find, $beforeToken, $extraLeft, $extraRight] - $this->insertionMap = [ - 'Expr_ArrayDimFetch->dim' => ['[', \false, null, null], - 'Expr_ArrayItem->key' => [null, \false, null, ' => '], - 'Expr_ArrowFunction->returnType' => [')', \false, ' : ', null], - 'Expr_Closure->returnType' => [')', \false, ' : ', null], - 'Expr_Ternary->if' => ['?', \false, ' ', ' '], - 'Expr_Yield->key' => [\T_YIELD, \false, null, ' => '], - 'Expr_Yield->value' => [\T_YIELD, \false, ' ', null], - 'Param->type' => [null, \false, null, ' '], - 'Param->default' => [null, \false, ' = ', null], - 'Stmt_Break->num' => [\T_BREAK, \false, ' ', null], - 'Stmt_Catch->var' => [null, \false, ' ', null], - 'Stmt_ClassMethod->returnType' => [')', \false, ' : ', null], - 'Stmt_Class->extends' => [null, \false, ' extends ', null], - 'Stmt_Enum->scalarType' => [null, \false, ' : ', null], - 'Stmt_EnumCase->expr' => [null, \false, ' = ', null], - 'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null], - 'Stmt_Continue->num' => [\T_CONTINUE, \false, ' ', null], - 'Stmt_Foreach->keyVar' => [\T_AS, \false, null, ' => '], - 'Stmt_Function->returnType' => [')', \false, ' : ', null], - 'Stmt_If->else' => [null, \false, ' ', null], - 'Stmt_Namespace->name' => [\T_NAMESPACE, \false, ' ', null], - 'Stmt_Property->type' => [\T_VARIABLE, \true, null, ' '], - 'Stmt_PropertyProperty->default' => [null, \false, ' = ', null], - 'Stmt_Return->expr' => [\T_RETURN, \false, ' ', null], - 'Stmt_StaticVar->default' => [null, \false, ' = ', null], - //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO - 'Stmt_TryCatch->finally' => [null, \false, ' ', null], - ]; - } - protected function initializeListInsertionMap() - { - if ($this->listInsertionMap) { - return; - } - $this->listInsertionMap = [ - // special - //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully - //'Scalar_Encapsed->parts' => '', - 'Stmt_Catch->types' => '|', - 'UnionType->types' => '|', - 'IntersectionType->types' => '&', - 'Stmt_If->elseifs' => ' ', - 'Stmt_TryCatch->catches' => ' ', - // comma-separated lists - 'Expr_Array->items' => ', ', - 'Expr_ArrowFunction->params' => ', ', - 'Expr_Closure->params' => ', ', - 'Expr_Closure->uses' => ', ', - 'Expr_FuncCall->args' => ', ', - 'Expr_Isset->vars' => ', ', - 'Expr_List->items' => ', ', - 'Expr_MethodCall->args' => ', ', - 'Expr_NullsafeMethodCall->args' => ', ', - 'Expr_New->args' => ', ', - 'Expr_PrintableNewAnonClass->args' => ', ', - 'Expr_StaticCall->args' => ', ', - 'Stmt_ClassConst->consts' => ', ', - 'Stmt_ClassMethod->params' => ', ', - 'Stmt_Class->implements' => ', ', - 'Stmt_Enum->implements' => ', ', - 'Expr_PrintableNewAnonClass->implements' => ', ', - 'Stmt_Const->consts' => ', ', - 'Stmt_Declare->declares' => ', ', - 'Stmt_Echo->exprs' => ', ', - 'Stmt_For->init' => ', ', - 'Stmt_For->cond' => ', ', - 'Stmt_For->loop' => ', ', - 'Stmt_Function->params' => ', ', - 'Stmt_Global->vars' => ', ', - 'Stmt_GroupUse->uses' => ', ', - 'Stmt_Interface->extends' => ', ', - 'Stmt_Match->arms' => ', ', - 'Stmt_Property->props' => ', ', - 'Stmt_StaticVar->vars' => ', ', - 'Stmt_TraitUse->traits' => ', ', - 'Stmt_TraitUseAdaptation_Precedence->insteadof' => ', ', - 'Stmt_Unset->vars' => ', ', - 'Stmt_Use->uses' => ', ', - 'MatchArm->conds' => ', ', - 'AttributeGroup->attrs' => ', ', - // statement lists - 'Expr_Closure->stmts' => "\n", - 'Stmt_Case->stmts' => "\n", - 'Stmt_Catch->stmts' => "\n", - 'Stmt_Class->stmts' => "\n", - 'Stmt_Enum->stmts' => "\n", - 'Expr_PrintableNewAnonClass->stmts' => "\n", - 'Stmt_Interface->stmts' => "\n", - 'Stmt_Trait->stmts' => "\n", - 'Stmt_ClassMethod->stmts' => "\n", - 'Stmt_Declare->stmts' => "\n", - 'Stmt_Do->stmts' => "\n", - 'Stmt_ElseIf->stmts' => "\n", - 'Stmt_Else->stmts' => "\n", - 'Stmt_Finally->stmts' => "\n", - 'Stmt_Foreach->stmts' => "\n", - 'Stmt_For->stmts' => "\n", - 'Stmt_Function->stmts' => "\n", - 'Stmt_If->stmts' => "\n", - 'Stmt_Namespace->stmts' => "\n", - 'Stmt_Class->attrGroups' => "\n", - 'Stmt_Enum->attrGroups' => "\n", - 'Stmt_EnumCase->attrGroups' => "\n", - 'Stmt_Interface->attrGroups' => "\n", - 'Stmt_Trait->attrGroups' => "\n", - 'Stmt_Function->attrGroups' => "\n", - 'Stmt_ClassMethod->attrGroups' => "\n", - 'Stmt_ClassConst->attrGroups' => "\n", - 'Stmt_Property->attrGroups' => "\n", - 'Expr_PrintableNewAnonClass->attrGroups' => ' ', - 'Expr_Closure->attrGroups' => ' ', - 'Expr_ArrowFunction->attrGroups' => ' ', - 'Param->attrGroups' => ' ', - 'Stmt_Switch->cases' => "\n", - 'Stmt_TraitUse->adaptations' => "\n", - 'Stmt_TryCatch->stmts' => "\n", - 'Stmt_While->stmts' => "\n", - // dummy for top-level context - 'File->stmts' => "\n", - ]; + return ['name', 'args']; } - protected function initializeEmptyListInsertionMap() + public function getType() : string { - if ($this->emptyListInsertionMap) { - return; - } - // TODO Insertion into empty statement lists. - // [$find, $extraLeft, $extraRight] - $this->emptyListInsertionMap = ['Expr_ArrowFunction->params' => ['(', '', ''], 'Expr_Closure->uses' => [')', ' use(', ')'], 'Expr_Closure->params' => ['(', '', ''], 'Expr_FuncCall->args' => ['(', '', ''], 'Expr_MethodCall->args' => ['(', '', ''], 'Expr_NullsafeMethodCall->args' => ['(', '', ''], 'Expr_New->args' => ['(', '', ''], 'Expr_PrintableNewAnonClass->args' => ['(', '', ''], 'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''], 'Expr_StaticCall->args' => ['(', '', ''], 'Stmt_Class->implements' => [null, ' implements ', ''], 'Stmt_Enum->implements' => [null, ' implements ', ''], 'Stmt_ClassMethod->params' => ['(', '', ''], 'Stmt_Interface->extends' => [null, ' extends ', ''], 'Stmt_Function->params' => ['(', '', '']]; + return 'Expr_FuncCall'; } - protected function initializeModifierChangeMap() + public function getRawArgs() : array { - if ($this->modifierChangeMap) { - return; - } - $this->modifierChangeMap = ['Stmt_ClassConst->flags' => \T_CONST, 'Stmt_ClassMethod->flags' => \T_FUNCTION, 'Stmt_Class->flags' => \T_CLASS, 'Stmt_Property->flags' => \T_VARIABLE, 'Param->flags' => \T_VARIABLE]; - // List of integer subnodes that are not modifiers: - // Expr_Include->type - // Stmt_GroupUse->type - // Stmt_Use->type - // Stmt_UseUse->type + return $this->args; } } name = $name; - } + const TYPE_INCLUDE = 1; + const TYPE_INCLUDE_ONCE = 2; + const TYPE_REQUIRE = 3; + const TYPE_REQUIRE_ONCE = 4; + /** @var Expr Expression */ + public $expr; + /** @var int Type of include */ + public $type; /** - * Adds a statement. - * - * @param Node|PhpParser\Builder $stmt The statement to add + * Constructs an include node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $expr Expression + * @param int $type Type of include + * @param array $attributes Additional attributes */ - public function addStmt($stmt) + public function __construct(Expr $expr, int $type, array $attributes = []) { - $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); - return $this; + $this->attributes = $attributes; + $this->expr = $expr; + $this->type = $type; } - /** - * Adds an attribute group. - * - * @param Node\Attribute|Node\AttributeGroup $attribute - * - * @return $this The builder instance (for fluid interface) - */ - public function addAttribute($attribute) + public function getSubNodeNames() : array { - $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + return ['expr', 'type']; } - /** - * Returns the built function node. - * - * @return Stmt\Function_ The built function node - */ - public function getNode() : Node + public function getType() : string { - return new Stmt\Function_($this->name, ['byRef' => $this->returnByRef, 'params' => $this->params, 'returnType' => $this->returnType, 'stmts' => $this->stmts, 'attrGroups' => $this->attributeGroups], $this->attributes); + return 'Expr_Include'; } } name = $name; + $this->attributes = $attributes; + $this->expr = $expr; + $this->class = $class; } - /** - * Extends one or more interfaces. - * - * @param Name|string ...$interfaces Names of interfaces to extend - * - * @return $this The builder instance (for fluid interface) - */ - public function extend(...$interfaces) + public function getSubNodeNames() : array { - foreach ($interfaces as $interface) { - $this->extends[] = BuilderHelpers::normalizeName($interface); - } - return $this; + return ['expr', 'class']; } - /** - * Adds a statement. - * - * @param Stmt|PhpParser\Builder $stmt The statement to add - * - * @return $this The builder instance (for fluid interface) - */ - public function addStmt($stmt) + public function getType() : string { - $stmt = BuilderHelpers::normalizeNode($stmt); - if ($stmt instanceof Stmt\ClassConst) { - $this->constants[] = $stmt; - } elseif ($stmt instanceof Stmt\ClassMethod) { - // we erase all statements in the body of an interface method - $stmt->stmts = null; - $this->methods[] = $stmt; - } else { - throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); - } - return $this; + return 'Expr_Instanceof'; } +} +attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + $this->attributes = $attributes; + $this->vars = $vars; } - /** - * Returns the built interface node. - * - * @return Stmt\Interface_ The built interface node - */ - public function getNode() : PhpParser\Node + public function getSubNodeNames() : array { - return new Stmt\Interface_($this->name, ['extends' => $this->extends, 'stmts' => \array_merge($this->constants, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); + return ['vars']; + } + public function getType() : string + { + return 'Expr_Isset'; } } name = $name; + $this->attributes = $attributes; + $this->items = $items; } - /** - * Makes the method public. - * - * @return $this The builder instance (for fluid interface) - */ - public function makePublic() + public function getSubNodeNames() : array { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); - return $this; + return ['items']; } - /** - * Makes the method protected. - * - * @return $this The builder instance (for fluid interface) - */ - public function makeProtected() + public function getType() : string { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); - return $this; + return 'Expr_List'; } +} +flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); - return $this; + $this->attributes = $attributes; + $this->cond = $cond; + $this->arms = $arms; } - /** - * Makes the method static. - * - * @return $this The builder instance (for fluid interface) - */ - public function makeStatic() + public function getSubNodeNames() : array { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); - return $this; + return ['cond', 'arms']; } - /** - * Makes the method abstract. - * - * @return $this The builder instance (for fluid interface) - */ - public function makeAbstract() + public function getType() : string { - if (!empty($this->stmts)) { - throw new \LogicException('Cannot make method with statements abstract'); - } - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); - $this->stmts = null; - // abstract methods don't have statements - return $this; + return 'Expr_Match'; } +} + Arguments */ + public $args; /** - * Makes the method final. + * Constructs a function call node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ - public function makeFinal() + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); - return $this; + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; } - /** - * Adds a statement. - * - * @param Node|PhpParser\Builder $stmt The statement to add - * - * @return $this The builder instance (for fluid interface) - */ - public function addStmt($stmt) + public function getSubNodeNames() : array { - if (null === $this->stmts) { - throw new \LogicException('Cannot add statements to an abstract method'); - } - $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); - return $this; + return ['var', 'name', 'args']; } - /** - * Adds an attribute group. - * - * @param Node\Attribute|Node\AttributeGroup $attribute - * - * @return $this The builder instance (for fluid interface) - */ - public function addAttribute($attribute) + public function getType() : string { - $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + return 'Expr_MethodCall'; } - /** - * Returns the built method node. - * - * @return Stmt\ClassMethod The built method node - */ - public function getNode() : Node + public function getRawArgs() : array { - return new Stmt\ClassMethod($this->name, ['flags' => $this->flags, 'byRef' => $this->returnByRef, 'params' => $this->params, 'returnType' => $this->returnType, 'stmts' => $this->stmts, 'attrGroups' => $this->attributeGroups], $this->attributes); + return $this->args; } } Arguments */ + public $args; /** - * Creates a class builder. + * Constructs a function call node. * - * @param string $name Name of the class + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param array $args Arguments + * @param array $attributes Additional attributes */ - public function __construct(string $name) + public function __construct($class, array $args = [], array $attributes = []) { - $this->name = $name; + $this->attributes = $attributes; + $this->class = $class; + $this->args = $args; } - /** - * Extends a class. - * - * @param Name|string $class Name of class to extend - * - * @return $this The builder instance (for fluid interface) - */ - public function extend($class) + public function getSubNodeNames() : array { - $this->extends = BuilderHelpers::normalizeName($class); - return $this; + return ['class', 'args']; } - /** - * Implements one or more interfaces. - * - * @param Name|string ...$interfaces Names of interfaces to implement - * - * @return $this The builder instance (for fluid interface) - */ - public function implement(...$interfaces) + public function getType() : string { - foreach ($interfaces as $interface) { - $this->implements[] = BuilderHelpers::normalizeName($interface); - } - return $this; + return 'Expr_New'; } - /** - * Makes the class abstract. - * - * @return $this The builder instance (for fluid interface) - */ - public function makeAbstract() + public function getRawArgs() : array { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); - return $this; + return $this->args; } +} + Arguments */ + public $args; /** - * Makes the class final. + * Constructs a nullsafe method call node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ - public function makeFinal() + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); - return $this; + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; } - /** - * Adds a statement. - * - * @param Stmt|PhpParser\Builder $stmt The statement to add - * - * @return $this The builder instance (for fluid interface) - */ - public function addStmt($stmt) + public function getSubNodeNames() : array { - $stmt = BuilderHelpers::normalizeNode($stmt); - $targets = [Stmt\TraitUse::class => &$this->uses, Stmt\ClassConst::class => &$this->constants, Stmt\Property::class => &$this->properties, Stmt\ClassMethod::class => &$this->methods]; - $class = \get_class($stmt); - if (!isset($targets[$class])) { - throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); - } - $targets[$class][] = $stmt; - return $this; + return ['var', 'name', 'args']; } - /** - * Adds an attribute group. - * - * @param Node\Attribute|Node\AttributeGroup $attribute - * - * @return $this The builder instance (for fluid interface) - */ - public function addAttribute($attribute) + public function getType() : string { - $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + return 'Expr_NullsafeMethodCall'; } - /** - * Returns the built class node. - * - * @return Stmt\Class_ The built class node - */ - public function getNode() : PhpParser\Node + public function getRawArgs() : array { - return new Stmt\Class_($this->name, ['flags' => $this->flags, 'extends' => $this->extends, 'implements' => $this->implements, 'stmts' => \array_merge($this->uses, $this->constants, $this->properties, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); + return $this->args; } } name = $name; - } + /** @var Expr Variable holding object */ + public $var; + /** @var Identifier|Expr Property name */ + public $name; /** - * Adds a statement. - * - * @param Stmt|PhpParser\Builder $stmt The statement to add + * Constructs a nullsafe property fetch node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + * @param array $attributes Additional attributes */ - public function addStmt($stmt) + public function __construct(Expr $var, $name, array $attributes = []) { - $stmt = BuilderHelpers::normalizeNode($stmt); - if ($stmt instanceof Stmt\Property) { - $this->properties[] = $stmt; - } elseif ($stmt instanceof Stmt\ClassMethod) { - $this->methods[] = $stmt; - } elseif ($stmt instanceof Stmt\TraitUse) { - $this->uses[] = $stmt; - } else { - throw new \LogicException(\sprintf('Unexpected node of type "%s"', $stmt->getType())); - } - return $this; + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; } - /** - * Adds an attribute group. - * - * @param Node\Attribute|Node\AttributeGroup $attribute - * - * @return $this The builder instance (for fluid interface) - */ - public function addAttribute($attribute) + public function getSubNodeNames() : array { - $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + return ['var', 'name']; } - /** - * Returns the built trait node. - * - * @return Stmt\Trait_ The built interface node - */ - public function getNode() : PhpParser\Node + public function getType() : string { - return new Stmt\Trait_($this->name, ['stmts' => \array_merge($this->uses, $this->properties, $this->methods), 'attrGroups' => $this->attributeGroups], $this->attributes); + return 'Expr_NullsafePropertyFetch'; } } returnByRef = \true; - return $this; - } + /** @var Expr Variable */ + public $var; /** - * Adds a parameter. - * - * @param Node\Param|Param $param The parameter to add + * Constructs a post decrement node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $var Variable + * @param array $attributes Additional attributes */ - public function addParam($param) + public function __construct(Expr $var, array $attributes = []) { - $param = BuilderHelpers::normalizeNode($param); - if (!$param instanceof Node\Param) { - throw new \LogicException(\sprintf('Expected parameter node, got "%s"', $param->getType())); - } - $this->params[] = $param; - return $this; + $this->attributes = $attributes; + $this->var = $var; } - /** - * Adds multiple parameters. - * - * @param array $params The parameters to add - * - * @return $this The builder instance (for fluid interface) - */ - public function addParams(array $params) + public function getSubNodeNames() : array { - foreach ($params as $param) { - $this->addParam($param); - } - return $this; + return ['var']; } - /** - * Sets the return type for PHP 7. - * - * @param string|Node\Name|Node\Identifier|Node\ComplexType $type - * - * @return $this The builder instance (for fluid interface) - */ - public function setReturnType($type) + public function getType() : string { - $this->returnType = BuilderHelpers::normalizeType($type); - return $this; + return 'Expr_PostDec'; } } and($trait); - } - } + /** @var Expr Variable */ + public $var; /** - * Adds used trait. - * - * @param Node\Name|string $trait Trait name + * Constructs a post increment node. * - * @return $this The builder instance (for fluid interface) + * @param Expr $var Variable + * @param array $attributes Additional attributes */ - public function and($trait) + public function __construct(Expr $var, array $attributes = []) { - $this->traits[] = BuilderHelpers::normalizeName($trait); - return $this; + $this->attributes = $attributes; + $this->var = $var; } - /** - * Adds trait adaptation. - * - * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation - * - * @return $this The builder instance (for fluid interface) - */ - public function with($adaptation) + public function getSubNodeNames() : array { - $adaptation = BuilderHelpers::normalizeNode($adaptation); - if (!$adaptation instanceof Stmt\TraitUseAdaptation) { - throw new \LogicException('Adaptation must have type TraitUseAdaptation'); - } - $this->adaptations[] = $adaptation; - return $this; + return ['var']; } - /** - * Returns the built node. - * - * @return Node The built node - */ - public function getNode() : Node + public function getType() : string { - return new Stmt\TraitUse($this->traits, $this->adaptations); + return 'Expr_PostInc'; } } addStmt($stmt); - } - return $this; + $this->attributes = $attributes; + $this->var = $var; } - /** - * Sets doc comment for the declaration. - * - * @param PhpParser\Comment\Doc|string $docComment Doc comment to set - * - * @return $this The builder instance (for fluid interface) - */ - public function setDocComment($docComment) + public function getSubNodeNames() : array { - $this->attributes['comments'] = [BuilderHelpers::normalizeDocComment($docComment)]; - return $this; + return ['var']; + } + public function getType() : string + { + return 'Expr_PreDec'; } } name = $name; + $this->attributes = $attributes; + $this->var = $var; } - /** - * Sets default value for the parameter. - * - * @param mixed $value Default value to use - * - * @return $this The builder instance (for fluid interface) - */ - public function setDefault($value) + public function getSubNodeNames() : array { - $this->default = BuilderHelpers::normalizeValue($value); - return $this; + return ['var']; } - /** - * Sets type for the parameter. - * - * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type - * - * @return $this The builder instance (for fluid interface) - */ - public function setType($type) + public function getType() : string { - $this->type = BuilderHelpers::normalizeType($type); - if ($this->type == 'void') { - throw new \LogicException('Parameter type cannot be void'); - } - return $this; + return 'Expr_PreInc'; } +} +setType($type); + $this->attributes = $attributes; + $this->expr = $expr; } - /** - * Make the parameter accept the value by reference. - * - * @return $this The builder instance (for fluid interface) - */ - public function makeByRef() + public function getSubNodeNames() : array { - $this->byRef = \true; - return $this; + return ['expr']; } - /** - * Make the parameter variadic - * - * @return $this The builder instance (for fluid interface) - */ - public function makeVariadic() + public function getType() : string { - $this->variadic = \true; - return $this; + return 'Expr_Print'; } +} +attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; } - /** - * Returns the built parameter node. - * - * @return Node\Param The built parameter node - */ - public function getNode() : Node + public function getSubNodeNames() : array { - return new Node\Param(new Node\Expr\Variable($this->name), $this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups); + return ['var', 'name']; + } + public function getType() : string + { + return 'Expr_PropertyFetch'; } } name = BuilderHelpers::normalizeName($name); - $this->type = $type; + $this->attributes = $attributes; + $this->parts = $parts; } - /** - * Sets alias for used name. - * - * @param string $alias Alias to use (last component of full name by default) - * - * @return $this The builder instance (for fluid interface) - */ - public function as(string $alias) + public function getSubNodeNames() : array { - $this->alias = $alias; - return $this; + return ['parts']; } - /** - * Returns the built node. - * - * @return Stmt\Use_ The built node - */ - public function getNode() : Node + public function getType() : string { - return new Stmt\Use_([new Stmt\UseUse($this->name, $this->alias)], $this->type); + return 'Expr_ShellExec'; } } Arguments */ + public $args; /** - * Creates a namespace builder. + * Constructs a static method call node. * - * @param Node\Name|string|null $name Name of the namespace + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ - public function __construct($name) + public function __construct($class, $name, array $args = [], array $attributes = []) { - $this->name = null !== $name ? BuilderHelpers::normalizeName($name) : null; + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + public function getSubNodeNames() : array + { + return ['class', 'name', 'args']; + } + public function getType() : string + { + return 'Expr_StaticCall'; + } + public function getRawArgs() : array + { + return $this->args; } +} +stmts[] = BuilderHelpers::normalizeStmt($stmt); - return $this; + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; + } + public function getSubNodeNames() : array + { + return ['class', 'name']; + } + public function getType() : string + { + return 'Expr_StaticPropertyFetch'; } +} +name, $this->stmts, $this->attributes); + $this->attributes = $attributes; + $this->cond = $cond; + $this->if = $if; + $this->else = $else; + } + public function getSubNodeNames() : array + { + return ['cond', 'if', 'else']; + } + public function getType() : string + { + return 'Expr_Ternary'; } } constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; + $this->attributes = $attributes; + $this->expr = $expr; } - /** - * Add another constant to const group - * - * @param string|Identifier $name Name - * @param Node\Expr|bool|null|int|float|string|array $value Value - * - * @return $this The builder instance (for fluid interface) - */ - public function addConst($name, $value) + public function getSubNodeNames() : array { - $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); - return $this; + return ['expr']; } - /** - * Makes the constant public. - * - * @return $this The builder instance (for fluid interface) - */ - public function makePublic() + public function getType() : string { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); - return $this; + return 'Expr_Throw'; } +} +flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); - return $this; + $this->attributes = $attributes; + $this->expr = $expr; } - /** - * Makes the constant private. - * - * @return $this The builder instance (for fluid interface) - */ - public function makePrivate() + public function getSubNodeNames() : array { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); - return $this; + return ['expr']; + } + public function getType() : string + { + return 'Expr_UnaryMinus'; } +} +flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); - return $this; + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; } + public function getType() : string + { + return 'Expr_UnaryPlus'; + } +} +attributes = ['comments' => [BuilderHelpers::normalizeDocComment($docComment)]]; - return $this; + $this->attributes = $attributes; + $this->name = $name; + } + public function getSubNodeNames() : array + { + return ['name']; } + public function getType() : string + { + return 'Expr_Variable'; + } +} +attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; } + public function getType() : string + { + return 'Expr_YieldFrom'; + } +} +constants, $this->flags, $this->attributes, $this->attributeGroups); + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + } + public function getSubNodeNames() : array + { + return ['key', 'value']; + } + public function getType() : string + { + return 'Expr_Yield'; } } name = $name; - } + public function returnsByRef() : bool; /** - * Makes the property public. + * List of parameters * - * @return $this The builder instance (for fluid interface) + * @return Param[] */ - public function makePublic() - { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); - return $this; - } + public function getParams() : array; /** - * Makes the property protected. + * Get the declared return type or null * - * @return $this The builder instance (for fluid interface) + * @return null|Identifier|Name|ComplexType */ - public function makeProtected() - { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); - return $this; - } + public function getReturnType(); /** - * Makes the property private. + * The function body * - * @return $this The builder instance (for fluid interface) + * @return Stmt[]|null */ - public function makePrivate() - { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); - return $this; - } + public function getStmts(); /** - * Makes the property static. + * Get PHP attribute groups. * - * @return $this The builder instance (for fluid interface) + * @return AttributeGroup[] */ - public function makeStatic() - { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); - return $this; - } + public function getAttrGroups() : array; +} + \true, 'parent' => \true, 'static' => \true]; /** - * Makes the property readonly. + * Constructs an identifier node. * - * @return $this The builder instance (for fluid interface) + * @param string $name Identifier as string + * @param array $attributes Additional attributes */ - public function makeReadonly() + public function __construct(string $name, array $attributes = []) { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); - return $this; + $this->attributes = $attributes; + $this->name = $name; + } + public function getSubNodeNames() : array + { + return ['name']; } /** - * Sets default value for the property. - * - * @param mixed $value Default value to use + * Get identifier as string. * - * @return $this The builder instance (for fluid interface) + * @return string Identifier as string. */ - public function setDefault($value) + public function toString() : string { - $this->default = BuilderHelpers::normalizeValue($value); - return $this; + return $this->name; } /** - * Sets doc comment for the property. - * - * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * Get lowercased identifier as string. * - * @return $this The builder instance (for fluid interface) + * @return string Lowercased identifier as string */ - public function setDocComment($docComment) + public function toLowerString() : string { - $this->attributes = ['comments' => [BuilderHelpers::normalizeDocComment($docComment)]]; - return $this; + return \strtolower($this->name); } /** - * Sets the property type for PHP 7.4+. - * - * @param string|Name|Identifier|ComplexType $type + * Checks whether the identifier is a special class name (self, parent or static). * - * @return $this + * @return bool Whether identifier is a special class name */ - public function setType($type) + public function isSpecialClassName() : bool { - $this->type = BuilderHelpers::normalizeType($type); - return $this; + return isset(self::$specialClassNames[\strtolower($this->name)]); } /** - * Adds an attribute group. - * - * @param Node\Attribute|Node\AttributeGroup $attribute + * Get identifier as string. * - * @return $this The builder instance (for fluid interface) + * @return string Identifier as string */ - public function addAttribute($attribute) + public function __toString() : string { - $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); - return $this; + return $this->name; + } + public function getType() : string + { + return 'Identifier'; } +} +flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC, [new Stmt\PropertyProperty($this->name, $this->default)], $this->attributes, $this->type, $this->attributeGroups); + $this->attributes = $attributes; + $this->types = $types; + } + public function getSubNodeNames() : array + { + return ['types']; + } + public function getType() : string + { + return 'IntersectionType'; } } type = self::TYPE_UNDEFINED; - $this->trait = \is_null($trait) ? null : BuilderHelpers::normalizeName($trait); - $this->method = BuilderHelpers::normalizeIdentifier($method); + $this->conds = $conds; + $this->body = $body; + $this->attributes = $attributes; } - /** - * Sets alias of method. - * - * @param Node\Identifier|string $alias Alias for adaptated method - * - * @return $this The builder instance (for fluid interface) - */ - public function as($alias) + public function getSubNodeNames() : array { - if ($this->type === self::TYPE_UNDEFINED) { - $this->type = self::TYPE_ALIAS; - } - if ($this->type !== self::TYPE_ALIAS) { - throw new \LogicException('Cannot set alias for not alias adaptation buider'); - } - $this->alias = $alias; - return $this; + return ['conds', 'body']; } + public function getType() : string + { + return 'MatchArm'; + } +} + \true, 'parent' => \true, 'static' => \true]; /** - * Sets adaptated method public. + * Constructs a name node. * - * @return $this The builder instance (for fluid interface) + * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) + * @param array $attributes Additional attributes */ - public function makePublic() + public function __construct($name, array $attributes = []) { - $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC); - return $this; + $this->attributes = $attributes; + $this->parts = self::prepareName($name); + } + public function getSubNodeNames() : array + { + return ['parts']; } /** - * Sets adaptated method protected. + * Gets the first part of the name, i.e. everything before the first namespace separator. * - * @return $this The builder instance (for fluid interface) + * @return string First part of the name */ - public function makeProtected() + public function getFirst() : string { - $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED); - return $this; + return $this->parts[0]; } /** - * Sets adaptated method private. + * Gets the last part of the name, i.e. everything after the last namespace separator. * - * @return $this The builder instance (for fluid interface) + * @return string Last part of the name */ - public function makePrivate() + public function getLast() : string { - $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE); - return $this; + return $this->parts[\count($this->parts) - 1]; } /** - * Adds overwritten traits. - * - * @param Node\Name|string ...$traits Traits for overwrite + * Checks whether the name is unqualified. (E.g. Name) * - * @return $this The builder instance (for fluid interface) + * @return bool Whether the name is unqualified */ - public function insteadof(...$traits) - { - if ($this->type === self::TYPE_UNDEFINED) { - if (\is_null($this->trait)) { - throw new \LogicException('Precedence adaptation must have trait'); - } - $this->type = self::TYPE_PRECEDENCE; - } - if ($this->type !== self::TYPE_PRECEDENCE) { - throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); - } - foreach ($traits as $trait) { - $this->insteadof[] = BuilderHelpers::normalizeName($trait); - } - return $this; - } - protected function setModifier(int $modifier) + public function isUnqualified() : bool { - if ($this->type === self::TYPE_UNDEFINED) { - $this->type = self::TYPE_ALIAS; - } - if ($this->type !== self::TYPE_ALIAS) { - throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); - } - if (\is_null($this->modifier)) { - $this->modifier = $modifier; - } else { - throw new \LogicException('Multiple access type modifiers are not allowed'); - } + return 1 === \count($this->parts); } /** - * Returns the built node. + * Checks whether the name is qualified. (E.g. Name\Name) * - * @return Node The built node + * @return bool Whether the name is qualified */ - public function getNode() : Node + public function isQualified() : bool { - switch ($this->type) { - case self::TYPE_ALIAS: - return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); - case self::TYPE_PRECEDENCE: - return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); - default: - throw new \LogicException('Type of adaptation is not defined'); - } + return 1 < \count($this->parts); } -} -rawMessage = $message; - if (\is_array($attributes)) { - $this->attributes = $attributes; - } else { - $this->attributes = ['startLine' => $attributes]; - } - $this->updateMessage(); + return \false; } /** - * Gets the error message + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) * - * @return string Error message + * @return bool Whether the name is relative */ - public function getRawMessage() : string + public function isRelative() : bool { - return $this->rawMessage; + return \false; } /** - * Gets the line the error starts in. + * Returns a string representation of the name itself, without taking the name type into + * account (e.g., not including a leading backslash for fully qualified names). * - * @return int Error start line + * @return string String representation */ - public function getStartLine() : int + public function toString() : string { - return $this->attributes['startLine'] ?? -1; + return \implode('\\', $this->parts); } /** - * Gets the line the error ends in. + * Returns a string representation of the name as it would occur in code (e.g., including + * leading backslash for fully qualified names. * - * @return int Error end line + * @return string String representation */ - public function getEndLine() : int + public function toCodeString() : string { - return $this->attributes['endLine'] ?? -1; + return $this->toString(); } /** - * Gets the attributes of the node/token the error occurred at. + * Returns lowercased string representation of the name, without taking the name type into + * account (e.g., no leading backslash for fully qualified names). * - * @return array + * @return string Lowercased string representation */ - public function getAttributes() : array + public function toLowerString() : string { - return $this->attributes; + return \strtolower(\implode('\\', $this->parts)); } /** - * Sets the attributes of the node/token the error occurred at. + * Checks whether the identifier is a special class name (self, parent or static). * - * @param array $attributes + * @return bool Whether identifier is a special class name */ - public function setAttributes(array $attributes) + public function isSpecialClassName() : bool { - $this->attributes = $attributes; - $this->updateMessage(); + return \count($this->parts) === 1 && isset(self::$specialClassNames[\strtolower($this->parts[0])]); } /** - * Sets the line of the PHP file the error occurred in. + * Returns a string representation of the name by imploding the namespace parts with the + * namespace separator. * - * @param string $message Error message + * @return string String representation */ - public function setRawMessage(string $message) + public function __toString() : string { - $this->rawMessage = $message; - $this->updateMessage(); + return \implode('\\', $this->parts); } /** - * Sets the line the error starts in. + * Gets a slice of a name (similar to array_slice). * - * @param int $line Error start line + * This method returns a new instance of the same type as the original and with the same + * attributes. + * + * If the slice is empty, null is returned. The null value will be correctly handled in + * concatenations using concat(). + * + * Offset and length have the same meaning as in array_slice(). + * + * @param int $offset Offset to start the slice at (may be negative) + * @param int|null $length Length of the slice (may be negative) + * + * @return static|null Sliced name */ - public function setStartLine(int $line) + public function slice(int $offset, int $length = null) { - $this->attributes['startLine'] = $line; - $this->updateMessage(); + $numParts = \count($this->parts); + $realOffset = $offset < 0 ? $offset + $numParts : $offset; + if ($realOffset < 0 || $realOffset > $numParts) { + throw new \OutOfBoundsException(\sprintf('Offset %d is out of bounds', $offset)); + } + if (null === $length) { + $realLength = $numParts - $realOffset; + } else { + $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; + if ($realLength < 0 || $realLength > $numParts - $realOffset) { + throw new \OutOfBoundsException(\sprintf('Length %d is out of bounds', $length)); + } + } + if ($realLength === 0) { + // Empty slice is represented as null + return null; + } + return new static(\array_slice($this->parts, $realOffset, $realLength), $this->attributes); } /** - * Returns whether the error has start and end column information. + * Concatenate two names, yielding a new Name instance. * - * For column information enable the startFilePos and endFilePos in the lexer options. + * The type of the generated instance depends on which class this method is called on, for + * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. * - * @return bool + * If one of the arguments is null, a new instance of the other name will be returned. If both + * arguments are null, null will be returned. As such, writing + * Name::concat($namespace, $shortName) + * where $namespace is a Name node or null will work as expected. + * + * @param string|string[]|self|null $name1 The first name + * @param string|string[]|self|null $name2 The second name + * @param array $attributes Attributes to assign to concatenated name + * + * @return static|null Concatenated name */ - public function hasColumnInfo() : bool + public static function concat($name1, $name2, array $attributes = []) { - return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); + if (null === $name1 && null === $name2) { + return null; + } elseif (null === $name1) { + return new static(self::prepareName($name2), $attributes); + } elseif (null === $name2) { + return new static(self::prepareName($name1), $attributes); + } else { + return new static(\array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes); + } } /** - * Gets the start column (1-based) into the line where the error started. + * Prepares a (string, array or Name node) name for use in name changing methods by converting + * it to an array. * - * @param string $code Source code of the file - * @return int + * @param string|string[]|self $name Name to prepare + * + * @return string[] Prepared name */ - public function getStartColumn(string $code) : int + private static function prepareName($name) : array { - if (!$this->hasColumnInfo()) { - throw new \RuntimeException('Error does not have column information'); + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + return \explode('\\', $name); + } elseif (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + return $name; + } elseif ($name instanceof self) { + return $name->parts; } - return $this->toColumn($code, $this->attributes['startFilePos']); + throw new \InvalidArgumentException('Expected string, array of parts or Name instance'); + } + public function getType() : string + { + return 'Name'; } +} +hasColumnInfo()) { - throw new \RuntimeException('Error does not have column information'); - } - return $this->toColumn($code, $this->attributes['endFilePos']); + return \false; } /** - * Formats message including line and column information. - * - * @param string $code Source code associated with the error, for calculation of the columns + * Checks whether the name is qualified. (E.g. Name\Name) * - * @return string Formatted message + * @return bool Whether the name is qualified */ - public function getMessageWithColumnInfo(string $code) : string + public function isQualified() : bool { - return \sprintf('%s from %d:%d to %d:%d', $this->getRawMessage(), $this->getStartLine(), $this->getStartColumn($code), $this->getEndLine(), $this->getEndColumn($code)); + return \false; } /** - * Converts a file offset into a column. - * - * @param string $code Source code that $pos indexes into - * @param int $pos 0-based position in $code + * Checks whether the name is fully qualified. (E.g. \Name) * - * @return int 1-based column (relative to start of line) + * @return bool Whether the name is fully qualified */ - private function toColumn(string $code, int $pos) : int + public function isFullyQualified() : bool { - if ($pos > \strlen($code)) { - throw new \RuntimeException('Invalid position information'); - } - $lineStartPos = \strrpos($code, "\n", $pos - \strlen($code)); - if (\false === $lineStartPos) { - $lineStartPos = -1; - } - return $pos - $lineStartPos; + return \true; } /** - * Updates the exception message after a change to rawMessage or rawLine. + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative */ - protected function updateMessage() + public function isRelative() : bool { - $this->message = $this->rawMessage; - if (-1 === $this->getStartLine()) { - $this->message .= ' on unknown line'; - } else { - $this->message .= ' on line ' . $this->getStartLine(); - } + return \false; + } + public function toCodeString() : string + { + return '\\' . $this->toString(); + } + public function getType() : string + { + return 'Name_FullyQualified'; } } nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing()); - $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? \false; - $this->replaceNodes = $options['replaceNodes'] ?? \true; + return \false; } /** - * Get name resolution context. + * Checks whether the name is fully qualified. (E.g. \Name) * - * @return NameContext + * @return bool Whether the name is fully qualified */ - public function getNameContext() : NameContext + public function isFullyQualified() : bool { - return $this->nameContext; + return \false; } - public function beforeTraverse(array $nodes) + /** + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative + */ + public function isRelative() : bool { - $this->nameContext->startNamespace(); - return null; + return \true; } - public function enterNode(Node $node) + public function toCodeString() : string { - if ($node instanceof Stmt\Namespace_) { - $this->nameContext->startNamespace($node->name); - } elseif ($node instanceof Stmt\Use_) { - foreach ($node->uses as $use) { - $this->addAlias($use, $node->type, null); - } - } elseif ($node instanceof Stmt\GroupUse) { - foreach ($node->uses as $use) { - $this->addAlias($use, $node->type, $node->prefix); - } - } elseif ($node instanceof Stmt\Class_) { - if (null !== $node->extends) { - $node->extends = $this->resolveClassName($node->extends); - } - foreach ($node->implements as &$interface) { - $interface = $this->resolveClassName($interface); - } - $this->resolveAttrGroups($node); - if (null !== $node->name) { - $this->addNamespacedName($node); - } - } elseif ($node instanceof Stmt\Interface_) { - foreach ($node->extends as &$interface) { - $interface = $this->resolveClassName($interface); - } - $this->resolveAttrGroups($node); - $this->addNamespacedName($node); - } elseif ($node instanceof Stmt\Enum_) { - foreach ($node->implements as &$interface) { - $interface = $this->resolveClassName($interface); - } - $this->resolveAttrGroups($node); - if (null !== $node->name) { - $this->addNamespacedName($node); - } - } elseif ($node instanceof Stmt\Trait_) { - $this->resolveAttrGroups($node); - $this->addNamespacedName($node); - } elseif ($node instanceof Stmt\Function_) { - $this->resolveSignature($node); - $this->resolveAttrGroups($node); - $this->addNamespacedName($node); - } elseif ($node instanceof Stmt\ClassMethod || $node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) { - $this->resolveSignature($node); - $this->resolveAttrGroups($node); - } elseif ($node instanceof Stmt\Property) { - if (null !== $node->type) { - $node->type = $this->resolveType($node->type); - } - $this->resolveAttrGroups($node); - } elseif ($node instanceof Stmt\Const_) { - foreach ($node->consts as $const) { - $this->addNamespacedName($const); - } - } else { - if ($node instanceof Stmt\ClassConst) { - $this->resolveAttrGroups($node); - } else { - if ($node instanceof Stmt\EnumCase) { - $this->resolveAttrGroups($node); - } elseif ($node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\ClassConstFetch || $node instanceof Expr\New_ || $node instanceof Expr\Instanceof_) { - if ($node->class instanceof Name) { - $node->class = $this->resolveClassName($node->class); - } - } elseif ($node instanceof Stmt\Catch_) { - foreach ($node->types as &$type) { - $type = $this->resolveClassName($type); - } - } elseif ($node instanceof Expr\FuncCall) { - if ($node->name instanceof Name) { - $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); - } - } elseif ($node instanceof Expr\ConstFetch) { - $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); - } elseif ($node instanceof Stmt\TraitUse) { - foreach ($node->traits as &$trait) { - $trait = $this->resolveClassName($trait); - } - foreach ($node->adaptations as $adaptation) { - if (null !== $adaptation->trait) { - $adaptation->trait = $this->resolveClassName($adaptation->trait); - } - if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { - foreach ($adaptation->insteadof as &$insteadof) { - $insteadof = $this->resolveClassName($insteadof); - } - } - } - } - } - } - return null; - } - private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) - { - // Add prefix for group uses - $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; - // Type is determined either by individual element or whole use declaration - $type |= $use->type; - $this->nameContext->addAlias($name, (string) $use->getAlias(), $type, $use->getAttributes()); - } - /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ - private function resolveSignature($node) - { - foreach ($node->params as $param) { - $param->type = $this->resolveType($param->type); - $this->resolveAttrGroups($param); - } - $node->returnType = $this->resolveType($node->returnType); + return 'namespace\\' . $this->toString(); } - private function resolveType($node) + public function getType() : string { - if ($node instanceof Name) { - return $this->resolveClassName($node); - } - if ($node instanceof Node\NullableType) { - $node->type = $this->resolveType($node->type); - return $node; - } - if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { - foreach ($node->types as &$type) { - $type = $this->resolveType($type); - } - return $node; - } - return $node; + return 'Name_Relative'; } +} +replaceNodes) { - $resolvedName = $this->nameContext->getResolvedName($name, $type); - if (null !== $resolvedName) { - $name->setAttribute('resolvedName', $resolvedName); - } else { - $name->setAttribute('namespacedName', FullyQualified::concat($this->nameContext->getNamespace(), $name, $name->getAttributes())); - } - return $name; - } - if ($this->preserveOriginalNames) { - // Save the original name - $originalName = $name; - $name = clone $originalName; - $name->setAttribute('originalName', $originalName); - } - $resolvedName = $this->nameContext->getResolvedName($name, $type); - if (null !== $resolvedName) { - return $resolvedName; - } - // unqualified names inside a namespace cannot be resolved at compile-time - // add the namespaced version of the name as an attribute - $name->setAttribute('namespacedName', FullyQualified::concat($this->nameContext->getNamespace(), $name, $name->getAttributes())); - return $name; - } - protected function resolveClassName(Name $name) + public function __construct($type, array $attributes = []) { - return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + $this->attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($type) : $type; } - protected function addNamespacedName(Node $node) + public function getSubNodeNames() : array { - $node->namespacedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name); + return ['type']; } - protected function resolveAttrGroups(Node $node) + public function getType() : string { - foreach ($node->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - $attr->name = $this->resolveClassName($attr->name); - } - } + return 'NullableType'; } } $node->getAttribute('parent'). - */ -final class ParentConnectingVisitor extends NodeVisitorAbstract +use PHPUnit\PhpParser\NodeAbstract; +class Param extends NodeAbstract { + /** @var null|Identifier|Name|ComplexType Type declaration */ + public $type; + /** @var bool Whether parameter is passed by reference */ + public $byRef; + /** @var bool Whether this is a variadic argument */ + public $variadic; + /** @var Expr\Variable|Expr\Error Parameter variable */ + public $var; + /** @var null|Expr Default value */ + public $default; + /** @var int */ + public $flags; + /** @var AttributeGroup[] PHP attribute groups */ + public $attrGroups; /** - * @var Node[] + * Constructs a parameter node. + * + * @param Expr\Variable|Expr\Error $var Parameter variable + * @param null|Expr $default Default value + * @param null|string|Identifier|Name|ComplexType $type Type declaration + * @param bool $byRef Whether is passed by reference + * @param bool $variadic Whether this is a variadic argument + * @param array $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param AttributeGroup[] $attrGroups PHP attribute groups */ - private $stack = []; - public function beforeTraverse(array $nodes) + public function __construct($var, Expr $default = null, $type = null, bool $byRef = \false, bool $variadic = \false, array $attributes = [], int $flags = 0, array $attrGroups = []) { - $this->stack = []; + $this->attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($type) : $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->var = $var; + $this->default = $default; + $this->flags = $flags; + $this->attrGroups = $attrGroups; } - public function enterNode(Node $node) + public function getSubNodeNames() : array { - if (!empty($this->stack)) { - $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); - } - $this->stack[] = $node; + return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default']; } - public function leaveNode(Node $node) + public function getType() : string { - array_pop($this->stack); + return 'Param'; } } setAttribute('origNode', $origNode); - return $node; - } } filterCallback = $filterCallback; - } + /** @var float Number value */ + public $value; /** - * Get found nodes satisfying the filter callback. - * - * Nodes are returned in pre-order. + * Constructs a float number scalar node. * - * @return Node[] Found nodes + * @param float $value Value of the number + * @param array $attributes Additional attributes */ - public function getFoundNodes() : array + public function __construct(float $value, array $attributes = []) { - return $this->foundNodes; + $this->attributes = $attributes; + $this->value = $value; } - public function beforeTraverse(array $nodes) + public function getSubNodeNames() : array { - $this->foundNodes = []; - return null; + return ['value']; } - public function enterNode(Node $node) + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []) : DNumber { - $filterCallback = $this->filterCallback; - if ($filterCallback($node)) { - $this->foundNodes[] = $node; + $attributes['rawValue'] = $str; + $float = self::parse($str); + return new DNumber($float, $attributes); + } + /** + * @internal + * + * Parses a DNUMBER token like PHP would. + * + * @param string $str A string number + * + * @return float The parsed number + */ + public static function parse(string $str) : float + { + $str = \str_replace('_', '', $str); + // if string contains any of .eE just cast it to float + if (\false !== \strpbrk($str, '.eE')) { + return (float) $str; } - return null; + // otherwise it's an integer notation that overflowed into a float + // if it starts with 0 it's one of the special integer notations + if ('0' === $str[0]) { + // hex + if ('x' === $str[1] || 'X' === $str[1]) { + return \hexdec($str); + } + // bin + if ('b' === $str[1] || 'B' === $str[1]) { + return \bindec($str); + } + // oct + // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9) + // so that only the digits before that are used + return \octdec(\substr($str, 0, \strcspn($str, '89'))); + } + // dec + return (float) $str; + } + public function getType() : string + { + return 'Scalar_DNumber'; } } filterCallback = $filterCallback; - } + /** @var Expr[] list of string parts */ + public $parts; /** - * Get found node satisfying the filter callback. - * - * Returns null if no node satisfies the filter callback. + * Constructs an encapsed string node. * - * @return null|Node Found node (or null if not found) + * @param Expr[] $parts Encaps list + * @param array $attributes Additional attributes */ - public function getFoundNode() + public function __construct(array $parts, array $attributes = []) { - return $this->foundNode; + $this->attributes = $attributes; + $this->parts = $parts; } - public function beforeTraverse(array $nodes) + public function getSubNodeNames() : array { - $this->foundNode = null; - return null; + return ['parts']; } - public function enterNode(Node $node) + public function getType() : string { - $filterCallback = $this->filterCallback; - if ($filterCallback($node)) { - $this->foundNode = $node; - return NodeTraverser::STOP_TRAVERSAL; - } - return null; + return 'Scalar_Encapsed'; } } $node->getAttribute('parent'), the previous - * node can be accessed through $node->getAttribute('previous'), - * and the next node can be accessed through $node->getAttribute('next'). - */ -final class NodeConnectingVisitor extends NodeVisitorAbstract +use PHPUnit\PhpParser\Node\Scalar; +class EncapsedStringPart extends Scalar { + /** @var string String value */ + public $value; /** - * @var Node[] - */ - private $stack = []; - /** - * @var ?Node + * Constructs a node representing a string part of an encapsed string. + * + * @param string $value String value + * @param array $attributes Additional attributes */ - private $previous; - public function beforeTraverse(array $nodes) + public function __construct(string $value, array $attributes = []) { - $this->stack = []; - $this->previous = null; + $this->attributes = $attributes; + $this->value = $value; } - public function enterNode(Node $node) + public function getSubNodeNames() : array { - if (!empty($this->stack)) { - $node->setAttribute('parent', $this->stack[\count($this->stack) - 1]); - } - if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { - $node->setAttribute('previous', $this->previous); - $this->previous->setAttribute('next', $node); - } - $this->stack[] = $node; + return ['value']; } - public function leaveNode(Node $node) + public function getType() : string { - $this->previous = $node; - \array_pop($this->stack); + return 'Scalar_EncapsedStringPart'; } } attributes = $attributes; + $this->value = $value; + } + public function getSubNodeNames() : array + { + return ['value']; + } + /** + * Constructs an LNumber node from a string number literal. * - * @return Parser The parser instance + * @param string $str String number literal (decimal, octal, hex or binary) + * @param array $attributes Additional attributes + * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) + * + * @return LNumber The constructed LNumber, including kind attribute */ - public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = \false) : LNumber { - if (null === $lexer) { - $lexer = new Lexer\Emulative(); + $attributes['rawValue'] = $str; + $str = \str_replace('_', '', $str); + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = LNumber::KIND_DEC; + return new LNumber((int) $str, $attributes); } - switch ($kind) { - case self::PREFER_PHP7: - return new Parser\Multiple([new Parser\Php7($lexer, $parserOptions), new Parser\Php5($lexer, $parserOptions)]); - case self::PREFER_PHP5: - return new Parser\Multiple([new Parser\Php5($lexer, $parserOptions), new Parser\Php7($lexer, $parserOptions)]); - case self::ONLY_PHP7: - return new Parser\Php7($lexer, $parserOptions); - case self::ONLY_PHP5: - return new Parser\Php5($lexer, $parserOptions); - default: - throw new \LogicException('Kind must be one of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5'); + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = LNumber::KIND_HEX; + return new LNumber(\hexdec($str), $attributes); } + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = LNumber::KIND_BIN; + return new LNumber(\bindec($str), $attributes); + } + if (!$allowInvalidOctal && \strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = \substr($str, 2); + } + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = LNumber::KIND_OCT; + return new LNumber(\intval($str, 8), $attributes); } -} -fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) { - throw new ConstExprEvaluationException("Expression of type {$expr->getType()} cannot be evaluated"); - }; + $this->attributes = $attributes; } - /** - * Silently evaluates a constant expression into a PHP value. - * - * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. - * The original source of the exception is available through getPrevious(). - * - * If some part of the expression cannot be evaluated, the fallback evaluator passed to the - * constructor will be invoked. By default, if no fallback is provided, an exception of type - * ConstExprEvaluationException is thrown. - * - * See class doc comment for caveats and limitations. - * - * @param Expr $expr Constant expression to evaluate - * @return mixed Result of evaluation - * - * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred - */ - public function evaluateSilently(Expr $expr) + public function getSubNodeNames() : array { - \set_error_handler(function ($num, $str, $file, $line) { - throw new \ErrorException($str, 0, $num, $file, $line); - }); - try { - return $this->evaluate($expr); - } catch (\Throwable $e) { - if (!$e instanceof ConstExprEvaluationException) { - $e = new ConstExprEvaluationException("An error occurred during constant expression evaluation", 0, $e); - } - throw $e; - } finally { - \restore_error_handler(); - } + return []; } /** - * Directly evaluates a constant expression into a PHP value. - * - * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these - * into a ConstExprEvaluationException. - * - * If some part of the expression cannot be evaluated, the fallback evaluator passed to the - * constructor will be invoked. By default, if no fallback is provided, an exception of type - * ConstExprEvaluationException is thrown. - * - * See class doc comment for caveats and limitations. - * - * @param Expr $expr Constant expression to evaluate - * @return mixed Result of evaluation + * Get name of magic constant. * - * @throws ConstExprEvaluationException if the expression cannot be evaluated + * @return string Name of magic constant */ - public function evaluateDirectly(Expr $expr) - { - return $this->evaluate($expr); - } - private function evaluate(Expr $expr) - { - if ($expr instanceof Scalar\LNumber || $expr instanceof Scalar\DNumber || $expr instanceof Scalar\String_) { - return $expr->value; - } - if ($expr instanceof Expr\Array_) { - return $this->evaluateArray($expr); - } - // Unary operators - if ($expr instanceof Expr\UnaryPlus) { - return +$this->evaluate($expr->expr); - } - if ($expr instanceof Expr\UnaryMinus) { - return -$this->evaluate($expr->expr); - } - if ($expr instanceof Expr\BooleanNot) { - return !$this->evaluate($expr->expr); - } - if ($expr instanceof Expr\BitwiseNot) { - return ~$this->evaluate($expr->expr); - } - if ($expr instanceof Expr\BinaryOp) { - return $this->evaluateBinaryOp($expr); - } - if ($expr instanceof Expr\Ternary) { - return $this->evaluateTernary($expr); - } - if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { - return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; - } - if ($expr instanceof Expr\ConstFetch) { - return $this->evaluateConstFetch($expr); - } - return ($this->fallbackEvaluator)($expr); - } - private function evaluateArray(Expr\Array_ $expr) - { - $array = []; - foreach ($expr->items as $item) { - if (null !== $item->key) { - $array[$this->evaluate($item->key)] = $this->evaluate($item->value); - } else { - $array[] = $this->evaluate($item->value); - } - } - return $array; - } - private function evaluateTernary(Expr\Ternary $expr) - { - if (null === $expr->if) { - return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); - } - return $this->evaluate($expr->cond) ? $this->evaluate($expr->if) : $this->evaluate($expr->else); - } - private function evaluateBinaryOp(Expr\BinaryOp $expr) - { - if ($expr instanceof Expr\BinaryOp\Coalesce && $expr->left instanceof Expr\ArrayDimFetch) { - // This needs to be special cased to respect BP_VAR_IS fetch semantics - return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] ?? $this->evaluate($expr->right); - } - // The evaluate() calls are repeated in each branch, because some of the operators are - // short-circuiting and evaluating the RHS in advance may be illegal in that case - $l = $expr->left; - $r = $expr->right; - switch ($expr->getOperatorSigil()) { - case '&': - return $this->evaluate($l) & $this->evaluate($r); - case '|': - return $this->evaluate($l) | $this->evaluate($r); - case '^': - return $this->evaluate($l) ^ $this->evaluate($r); - case '&&': - return $this->evaluate($l) && $this->evaluate($r); - case '||': - return $this->evaluate($l) || $this->evaluate($r); - case '??': - return $this->evaluate($l) ?? $this->evaluate($r); - case '.': - return $this->evaluate($l) . $this->evaluate($r); - case '/': - return $this->evaluate($l) / $this->evaluate($r); - case '==': - return $this->evaluate($l) == $this->evaluate($r); - case '>': - return $this->evaluate($l) > $this->evaluate($r); - case '>=': - return $this->evaluate($l) >= $this->evaluate($r); - case '===': - return $this->evaluate($l) === $this->evaluate($r); - case 'and': - return $this->evaluate($l) and $this->evaluate($r); - case 'or': - return $this->evaluate($l) or $this->evaluate($r); - case 'xor': - return $this->evaluate($l) xor $this->evaluate($r); - case '-': - return $this->evaluate($l) - $this->evaluate($r); - case '%': - return $this->evaluate($l) % $this->evaluate($r); - case '*': - return $this->evaluate($l) * $this->evaluate($r); - case '!=': - return $this->evaluate($l) != $this->evaluate($r); - case '!==': - return $this->evaluate($l) !== $this->evaluate($r); - case '+': - return $this->evaluate($l) + $this->evaluate($r); - case '**': - return $this->evaluate($l) ** $this->evaluate($r); - case '<<': - return $this->evaluate($l) << $this->evaluate($r); - case '>>': - return $this->evaluate($l) >> $this->evaluate($r); - case '<': - return $this->evaluate($l) < $this->evaluate($r); - case '<=': - return $this->evaluate($l) <= $this->evaluate($r); - case '<=>': - return $this->evaluate($l) <=> $this->evaluate($r); - } - throw new \Exception('Should not happen'); - } - private function evaluateConstFetch(Expr\ConstFetch $expr) - { - $name = $expr->name->toLowerString(); - switch ($name) { - case 'null': - return null; - case 'false': - return \false; - case 'true': - return \true; - } - return ($this->fallbackEvaluator)($expr); - } + public abstract function getName() : string; } resolveIntegerOrFloatToken($tokens[$i + 1][1]); - \array_splice($tokens, $i, 2, [[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]]]); - $c--; - } - } - return $tokens; - } - private function resolveIntegerOrFloatToken(string $str) : int + public function getName() : string { - $str = \substr($str, 1); - $str = \str_replace('_', '', $str); - $num = \octdec($str); - return \is_float($num) ? \T_DNUMBER : \T_LNUMBER; + return '__CLASS__'; } - public function reverseEmulate(string $code, array $tokens) : array + public function getType() : string { - // Explicit octals were not legal code previously, don't bother. - return $tokens; + return 'Scalar_MagicConst_Class'; } } emulator = $emulator; - } - public function getPhpVersion() : string - { - return $this->emulator->getPhpVersion(); - } - public function isEmulationNeeded(string $code) : bool - { - return $this->emulator->isEmulationNeeded($code); - } - public function emulate(string $code, array $tokens) : array - { - return $this->emulator->reverseEmulate($code, $tokens); - } - public function reverseEmulate(string $code, array $tokens) : array + public function getName() : string { - return $this->emulator->emulate($code, $tokens); + return '__METHOD__'; } - public function preprocessCode(string $code, array &$patches) : string + public function getType() : string { - return $code; + return 'Scalar_MagicConst_Method'; } } '\\', '$' => '$', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\x1b"]; + /** + * Constructs a string scalar node. + * + * @param string $value Value of the string + * @param array $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { - return Emulative::PHP_8_0; + $this->attributes = $attributes; + $this->value = $value; } - public function getKeywordString() : string + public function getSubNodeNames() : array { - return 'match'; + return ['value']; } - public function getKeywordToken() : int + /** + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = \true) : self { - return \T_MATCH; + $attributes['kind'] = $str[0] === "'" || $str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B') ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED; + $attributes['rawValue'] = $str; + $string = self::parse($str, $parseUnicodeEscape); + return new self($string, $attributes); } -} -\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?(?:;?[\r\n])?)/x -REGEX; - public function getPhpVersion() : string - { - return Emulative::PHP_7_3; - } - public function isEmulationNeeded(string $code) : bool - { - return \strpos($code, '<<<') !== \false; - } - public function emulate(string $code, array $tokens) : array + /** + * @internal + * + * Parses a string token. + * + * @param string $str String token content + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string The parsed string + */ + public static function parse(string $str, bool $parseUnicodeEscape = \true) : string { - // Handled by preprocessing + fixup. - return $tokens; + $bLength = 0; + if ('b' === $str[0] || 'B' === $str[0]) { + $bLength = 1; + } + if ('\'' === $str[$bLength]) { + return \str_replace(['\\\\', '\\\''], ['\\', '\''], \substr($str, $bLength + 1, -1)); + } else { + return self::parseEscapeSequences(\substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape); + } } - public function reverseEmulate(string $code, array $tokens) : array + /** + * @internal + * + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param null|string $quote Quote type + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = \true) : string { - // Not supported. - return $tokens; + if (null !== $quote) { + $str = \str_replace('\\' . $quote, $quote, $str); + } + $extra = ''; + if ($parseUnicodeEscape) { + $extra = '|u\\{([0-9a-fA-F]+)\\}'; + } + return \preg_replace_callback('~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', function ($matches) { + $str = $matches[1]; + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(\hexdec(\substr($str, 1))); + } elseif ('u' === $str[0]) { + return self::codePointToUtf8(\hexdec($matches[2])); + } else { + return \chr(\octdec($str)); + } + }, $str); } - public function preprocessCode(string $code, array &$patches) : string + /** + * Converts a Unicode code point to its UTF-8 encoded representation. + * + * @param int $num Code point + * + * @return string UTF-8 representation of code point + */ + private static function codePointToUtf8(int $num) : string { - if (!\preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE)) { - // No heredoc/nowdoc found - return $code; + if ($num <= 0x7f) { + return \chr($num); } - // Keep track of how much we need to adjust string offsets due to the modifications we - // already made - $posDelta = 0; - foreach ($matches as $match) { - $indentation = $match['indentation'][0]; - $indentationStart = $match['indentation'][1]; - $separator = $match['separator'][0]; - $separatorStart = $match['separator'][1]; - if ($indentation === '' && $separator !== '') { - // Ordinary heredoc/nowdoc - continue; - } - if ($indentation !== '') { - // Remove indentation - $indentationLen = \strlen($indentation); - $code = \substr_replace($code, '', $indentationStart + $posDelta, $indentationLen); - $patches[] = [$indentationStart + $posDelta, 'add', $indentation]; - $posDelta -= $indentationLen; - } - if ($separator === '') { - // Insert newline as separator - $code = \substr_replace($code, "\n", $separatorStart + $posDelta, 0); - $patches[] = [$separatorStart + $posDelta, 'remove', "\n"]; - $posDelta += 1; - } + if ($num <= 0x7ff) { + return \chr(($num >> 6) + 0xc0) . \chr(($num & 0x3f) + 0x80); } - return $code; + if ($num <= 0xffff) { + return \chr(($num >> 12) + 0xe0) . \chr(($num >> 6 & 0x3f) + 0x80) . \chr(($num & 0x3f) + 0x80); + } + if ($num <= 0x1fffff) { + return \chr(($num >> 18) + 0xf0) . \chr(($num >> 12 & 0x3f) + 0x80) . \chr(($num >> 6 & 0x3f) + 0x80) . \chr(($num & 0x3f) + 0x80); + } + throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); + } + public function getType() : string + { + return 'Scalar_String'; } } resolveIntegerOrFloatToken($match); - $newTokens = [[$tokenKind, $match, $token[2]]]; - $numTokens = 1; - $len = $tokenLen; - while ($matchLen > $len) { - $nextToken = $tokens[$i + $numTokens]; - $nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken; - $nextTokenLen = \strlen($nextTokenText); - $numTokens++; - if ($matchLen < $len + $nextTokenLen) { - // Split trailing characters into a partial token. - \assert(\is_array($nextToken), "Partial token should be an array token"); - $partialText = \substr($nextTokenText, $matchLen - $len); - $newTokens[] = [$nextToken[0], $partialText, $nextToken[2]]; - break; - } - $len += $nextTokenLen; - } - \array_splice($tokens, $i, $numTokens, $newTokens); - $c -= $numTokens - \count($newTokens); - $codeOffset += $matchLen; - } - return $tokens; + $this->attributes = $attributes; + $this->num = $num; } - private function resolveIntegerOrFloatToken(string $str) : int + public function getSubNodeNames() : array { - $str = \str_replace('_', '', $str); - if (\stripos($str, '0b') === 0) { - $num = \bindec($str); - } elseif (\stripos($str, '0x') === 0) { - $num = \hexdec($str); - } elseif (\stripos($str, '0') === 0 && \ctype_digit($str)) { - $num = \octdec($str); - } else { - $num = +$str; - } - return \is_float($num) ? \T_DNUMBER : \T_LNUMBER; + return ['num']; } - public function reverseEmulate(string $code, array $tokens) : array + public function getType() : string { - // Numeric separators were not legal code previously, don't bother. - return $tokens; + return 'Stmt_Break'; } } getKeywordString()) !== \false; + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; } - protected function isKeywordContext(array $tokens, int $pos) : bool + public function getSubNodeNames() : array { - $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); - return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR; + return ['cond', 'stmts']; } - public function emulate(string $code, array $tokens) : array + public function getType() : string { - $keywordString = $this->getKeywordString(); - foreach ($tokens as $i => $token) { - if ($token[0] === \T_STRING && \strtolower($token[1]) === $keywordString && $this->isKeywordContext($tokens, $i)) { - $tokens[$i][0] = $this->getKeywordToken(); - } - } - return $tokens; + return 'Stmt_Case'; } +} += 0; --$i) { - if ($tokens[$i][0] === \T_WHITESPACE) { - continue; - } - return $tokens[$i]; - } - return null; + $this->attributes = $attributes; + $this->types = $types; + $this->var = $var; + $this->stmts = $stmts; } - public function reverseEmulate(string $code, array $tokens) : array + public function getSubNodeNames() : array { - $keywordToken = $this->getKeywordToken(); - foreach ($tokens as $i => $token) { - if ($token[0] === $keywordToken) { - $tokens[$i][0] = \T_STRING; - } - } - return $tokens; + return ['types', 'var', 'stmts']; + } + public function getType() : string + { + return 'Stmt_Catch'; } } attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; } - public function isEmulationNeeded(string $code) : bool + public function getSubNodeNames() : array { - return \strpos($code, '?->') !== \false; + return ['attrGroups', 'flags', 'consts']; } - public function emulate(string $code, array $tokens) : array + /** + * Whether constant is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { - // We need to manually iterate and manage a count because we'll change - // the tokens array on the way - $line = 1; - for ($i = 0, $c = \count($tokens); $i < $c; ++$i) { - if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { - \array_splice($tokens, $i, 2, [[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]]); - $c--; - continue; - } - // Handle ?-> inside encapsed string. - if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) && $tokens[$i - 1][0] === \T_VARIABLE && \preg_match('/^\\?->([a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff]*)/', $tokens[$i][1], $matches)) { - $replacement = [[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line], [\T_STRING, $matches[1], $line]]; - if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) { - $replacement[] = [\T_ENCAPSED_AND_WHITESPACE, \substr($tokens[$i][1], \strlen($matches[0])), $line]; - } - \array_splice($tokens, $i, 1, $replacement); - $c += \count($replacement) - 1; - continue; - } - if (\is_array($tokens[$i])) { - $line += \substr_count($tokens[$i][1], "\n"); - } - } - return $tokens; + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; } - public function reverseEmulate(string $code, array $tokens) : array + /** + * Whether constant is protected. + * + * @return bool + */ + public function isProtected() : bool { - // ?-> was not valid code previously, don't bother. - return $tokens; + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + /** + * Whether constant is private. + * + * @return bool + */ + public function isPrivate() : bool + { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + /** + * Whether constant is final. + * + * @return bool + */ + public function isFinal() : bool + { + return (bool) ($this->flags & Class_::MODIFIER_FINAL); + } + public function getType() : string + { + return 'Stmt_ClassConst'; } } targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1; - unset($options['phpVersion']); - parent::__construct($options); - $emulators = [new FlexibleDocStringEmulator(), new FnTokenEmulator(), new MatchTokenEmulator(), new CoaleseEqualTokenEmulator(), new NumericLiteralSeparatorEmulator(), new NullsafeTokenEmulator(), new AttributeEmulator(), new EnumTokenEmulator(), new ReadonlyTokenEmulator(), new ExplicitOctalEmulator()]; - // Collect emulators that are relevant for the PHP version we're running - // and the PHP version we're targeting for emulation. - foreach ($emulators as $emulator) { - $emulatorPhpVersion = $emulator->getPhpVersion(); - if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { - $this->emulators[] = $emulator; - } else { - if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { - $this->emulators[] = new ReverseEmulator($emulator); - } + $traitUses = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof TraitUse) { + $traitUses[] = $stmt; } } + return $traitUses; } - public function startLexing(string $code, ErrorHandler $errorHandler = null) + /** + * @return ClassConst[] + */ + public function getConstants() : array { - $emulators = \array_filter($this->emulators, function ($emulator) use($code) { - return $emulator->isEmulationNeeded($code); - }); - if (empty($emulators)) { - // Nothing to emulate, yay - parent::startLexing($code, $errorHandler); - return; - } - $this->patches = []; - foreach ($emulators as $emulator) { - $code = $emulator->preprocessCode($code, $this->patches); - } - $collector = new ErrorHandler\Collecting(); - parent::startLexing($code, $collector); - $this->sortPatches(); - $this->fixupTokens(); - $errors = $collector->getErrors(); - if (!empty($errors)) { - $this->fixupErrors($errors); - foreach ($errors as $error) { - $errorHandler->handleError($error); + $constants = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + $constants[] = $stmt; } } - foreach ($emulators as $emulator) { - $this->tokens = $emulator->emulate($code, $this->tokens); - } + return $constants; } - private function isForwardEmulationNeeded(string $emulatorPhpVersion) : bool + /** + * @return Property[] + */ + public function getProperties() : array { - return \version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') && \version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>='); - } - private function isReverseEmulationNeeded(string $emulatorPhpVersion) : bool - { - return \version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=') && \version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<'); - } - private function sortPatches() - { - // Patches may be contributed by different emulators. - // Make sure they are sorted by increasing patch position. - \usort($this->patches, function ($p1, $p2) { - return $p1[0] <=> $p2[0]; - }); + $properties = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + $properties[] = $stmt; + } + } + return $properties; } - private function fixupTokens() + /** + * Gets property with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the property + * + * @return Property|null Property node or null if the property does not exist + */ + public function getProperty(string $name) { - if (\count($this->patches) === 0) { - return; - } - // Load first patch - $patchIdx = 0; - list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; - // We use a manual loop over the tokens, because we modify the array on the fly - $pos = 0; - for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { - $token = $this->tokens[$i]; - if (\is_string($token)) { - if ($patchPos === $pos) { - // Only support replacement for string tokens. - \assert($patchType === 'replace'); - $this->tokens[$i] = $patchText; - // Fetch the next patch - $patchIdx++; - if ($patchIdx >= \count($this->patches)) { - // No more patches, we're done - return; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyProperty && $name === $prop->name->toString()) { + return $stmt; } - list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; } - $pos += \strlen($token); - continue; } - $len = \strlen($token[1]); - $posDelta = 0; - while ($patchPos >= $pos && $patchPos < $pos + $len) { - $patchTextLen = \strlen($patchText); - if ($patchType === 'remove') { - if ($patchPos === $pos && $patchTextLen === $len) { - // Remove token entirely - \array_splice($this->tokens, $i, 1, []); - $i--; - $c--; - } else { - // Remove from token string - $this->tokens[$i][1] = \substr_replace($token[1], '', $patchPos - $pos + $posDelta, $patchTextLen); - $posDelta -= $patchTextLen; - } - } elseif ($patchType === 'add') { - // Insert into the token string - $this->tokens[$i][1] = \substr_replace($token[1], $patchText, $patchPos - $pos + $posDelta, 0); - $posDelta += $patchTextLen; - } else { - if ($patchType === 'replace') { - // Replace inside the token string - $this->tokens[$i][1] = \substr_replace($token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen); - } else { - \assert(\false); - } - } - // Fetch the next patch - $patchIdx++; - if ($patchIdx >= \count($this->patches)) { - // No more patches, we're done - return; - } - list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; - // Multiple patches may apply to the same token. Reload the current one to check - // If the new patch applies - $token = $this->tokens[$i]; + } + return null; + } + /** + * Gets all methods defined directly in this class/interface/trait + * + * @return ClassMethod[] + */ + public function getMethods() : array + { + $methods = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $methods[] = $stmt; } - $pos += $len; } - // A patch did not apply - \assert(\false); + return $methods; } /** - * Fixup line and position information in errors. + * Gets method with the given name defined directly in this class/interface/trait. * - * @param Error[] $errors + * @param string $name Name of the method (compared case-insensitively) + * + * @return ClassMethod|null Method node or null if the method does not exist */ - private function fixupErrors(array $errors) + public function getMethod(string $name) { - foreach ($errors as $error) { - $attrs = $error->getAttributes(); - $posDelta = 0; - $lineDelta = 0; - foreach ($this->patches as $patch) { - list($patchPos, $patchType, $patchText) = $patch; - if ($patchPos >= $attrs['startFilePos']) { - // No longer relevant - break; - } - if ($patchType === 'add') { - $posDelta += \strlen($patchText); - $lineDelta += \substr_count($patchText, "\n"); - } else { - if ($patchType === 'remove') { - $posDelta -= \strlen($patchText); - $lineDelta -= \substr_count($patchText, "\n"); - } - } + $lowerName = \strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; } - $attrs['startFilePos'] += $posDelta; - $attrs['endFilePos'] += $posDelta; - $attrs['startLine'] += $lineDelta; - $attrs['endLine'] += $lineDelta; - $error->setAttributes($attrs); } + return null; } } [aliasName => originalName]] */ - protected $aliases = []; - /** @var Name[][] Same as $aliases but preserving original case */ - protected $origAliases = []; - /** @var ErrorHandler Error handler */ - protected $errorHandler; + /** @var int Flags */ + public $flags; + /** @var bool Whether to return by reference */ + public $byRef; + /** @var Node\Identifier Name */ + public $name; + /** @var Node\Param[] Parameters */ + public $params; + /** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */ + public $returnType; + /** @var Node\Stmt[]|null Statements */ + public $stmts; + /** @var Node\AttributeGroup[] PHP attribute groups */ + public $attrGroups; + private static $magicNames = ['__construct' => \true, '__destruct' => \true, '__call' => \true, '__callstatic' => \true, '__get' => \true, '__set' => \true, '__isset' => \true, '__unset' => \true, '__sleep' => \true, '__wakeup' => \true, '__tostring' => \true, '__set_state' => \true, '__clone' => \true, '__invoke' => \true, '__debuginfo' => \true, '__serialize' => \true, '__unserialize' => \true]; /** - * Create a name context. + * Constructs a class method node. * - * @param ErrorHandler $errorHandler Error handling used to report errors + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'flags => MODIFIER_PUBLIC: Flags + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes */ - public function __construct(ErrorHandler $errorHandler) + public function __construct($name, array $subNodes = [], array $attributes = []) { - $this->errorHandler = $errorHandler; + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? \false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = \array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + public function getSubNodeNames() : array + { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + public function returnsByRef() : bool + { + return $this->byRef; + } + public function getParams() : array + { + return $this->params; + } + public function getReturnType() + { + return $this->returnType; + } + public function getStmts() + { + return $this->stmts; + } + public function getAttrGroups() : array + { + return $this->attrGroups; } /** - * Start a new namespace. - * - * This also resets the alias table. + * Whether the method is explicitly or implicitly public. * - * @param Name|null $namespace Null is the global namespace + * @return bool */ - public function startNamespace(Name $namespace = null) + public function isPublic() : bool { - $this->namespace = $namespace; - $this->origAliases = $this->aliases = [Stmt\Use_::TYPE_NORMAL => [], Stmt\Use_::TYPE_FUNCTION => [], Stmt\Use_::TYPE_CONSTANT => []]; + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; } /** - * Add an alias / import. + * Whether the method is protected. * - * @param Name $name Original name - * @param string $aliasName Aliased name - * @param int $type One of Stmt\Use_::TYPE_* - * @param array $errorAttrs Attributes to use to report an error + * @return bool */ - public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) + public function isProtected() : bool { - // Constant names are case sensitive, everything else case insensitive - if ($type === Stmt\Use_::TYPE_CONSTANT) { - $aliasLookupName = $aliasName; - } else { - $aliasLookupName = \strtolower($aliasName); - } - if (isset($this->aliases[$type][$aliasLookupName])) { - $typeStringMap = [Stmt\Use_::TYPE_NORMAL => '', Stmt\Use_::TYPE_FUNCTION => 'function ', Stmt\Use_::TYPE_CONSTANT => 'const ']; - $this->errorHandler->handleError(new Error(\sprintf('Cannot use %s%s as %s because the name is already in use', $typeStringMap[$type], $name, $aliasName), $errorAttrs)); - return; - } - $this->aliases[$type][$aliasLookupName] = $name; - $this->origAliases[$type][$aliasName] = $name; + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); } /** - * Get current namespace. + * Whether the method is private. * - * @return null|Name Namespace (or null if global namespace) + * @return bool */ - public function getNamespace() + public function isPrivate() : bool { - return $this->namespace; + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); } /** - * Get resolved name. - * - * @param Name $name Name to resolve - * @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * Whether the method is abstract. * - * @return null|Name Resolved name, or null if static resolution is not possible + * @return bool */ - public function getResolvedName(Name $name, int $type) + public function isAbstract() : bool { - // don't resolve special class names - if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { - if (!$name->isUnqualified()) { - $this->errorHandler->handleError(new Error(\sprintf("'\\%s' is an invalid class name", $name->toString()), $name->getAttributes())); - } - return $name; - } - // fully qualified names are already resolved - if ($name->isFullyQualified()) { - return $name; - } - // Try to resolve aliases - if (null !== ($resolvedName = $this->resolveAlias($name, $type))) { - return $resolvedName; - } - if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { - if (null === $this->namespace) { - // outside of a namespace unaliased unqualified is same as fully qualified - return new FullyQualified($name, $name->getAttributes()); - } - // Cannot resolve statically - return null; - } - // if no alias exists prepend current namespace - return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); + return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT); } /** - * Get resolved class name. - * - * @param Name $name Class ame to resolve + * Whether the method is final. * - * @return Name Resolved name + * @return bool */ - public function getResolvedClassName(Name $name) : Name + public function isFinal() : bool { - return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); + return (bool) ($this->flags & Class_::MODIFIER_FINAL); } /** - * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). - * - * @param string $name Fully-qualified name (without leading namespace separator) - * @param int $type One of Stmt\Use_::TYPE_* + * Whether the method is static. * - * @return Name[] Possible representations of the name + * @return bool */ - public function getPossibleNames(string $name, int $type) : array + public function isStatic() : bool { - $lcName = \strtolower($name); - if ($type === Stmt\Use_::TYPE_NORMAL) { - // self, parent and static must always be unqualified - if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { - return [new Name($name)]; - } - } - // Collect possible ways to write this name, starting with the fully-qualified name - $possibleNames = [new FullyQualified($name)]; - if (null !== ($nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type))) { - // Make sure there is no alias that makes the normally namespace-relative name - // into something else - if (null === $this->resolveAlias($nsRelativeName, $type)) { - $possibleNames[] = $nsRelativeName; - } - } - // Check for relevant namespace use statements - foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { - $lcOrig = $orig->toLowerString(); - if (0 === \strpos($lcName, $lcOrig . '\\')) { - $possibleNames[] = new Name($alias . \substr($name, \strlen($lcOrig))); - } - } - // Check for relevant type-specific use statements - foreach ($this->origAliases[$type] as $alias => $orig) { - if ($type === Stmt\Use_::TYPE_CONSTANT) { - // Constants are are complicated-sensitive - $normalizedOrig = $this->normalizeConstName($orig->toString()); - if ($normalizedOrig === $this->normalizeConstName($name)) { - $possibleNames[] = new Name($alias); - } - } else { - // Everything else is case-insensitive - if ($orig->toLowerString() === $lcName) { - $possibleNames[] = new Name($alias); - } - } - } - return $possibleNames; + return (bool) ($this->flags & Class_::MODIFIER_STATIC); } /** - * Get shortest representation of this fully-qualified name. - * - * @param string $name Fully-qualified name (without leading namespace separator) - * @param int $type One of Stmt\Use_::TYPE_* + * Whether the method is magic. * - * @return Name Shortest representation + * @return bool */ - public function getShortName(string $name, int $type) : Name - { - $possibleNames = $this->getPossibleNames($name, $type); - // Find shortest name - $shortestName = null; - $shortestLength = \INF; - foreach ($possibleNames as $possibleName) { - $length = \strlen($possibleName->toCodeString()); - if ($length < $shortestLength) { - $shortestName = $possibleName; - $shortestLength = $length; - } - } - return $shortestName; - } - private function resolveAlias(Name $name, $type) - { - $firstPart = $name->getFirst(); - if ($name->isQualified()) { - // resolve aliases for qualified names, always against class alias table - $checkName = \strtolower($firstPart); - if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { - $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; - return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); - } - } elseif ($name->isUnqualified()) { - // constant aliases are case-sensitive, function aliases case-insensitive - $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : \strtolower($firstPart); - if (isset($this->aliases[$type][$checkName])) { - // resolve unqualified aliases - return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); - } - } - // No applicable aliases - return null; - } - private function getNamespaceRelativeName(string $name, string $lcName, int $type) + public function isMagic() : bool { - if (null === $this->namespace) { - return new Name($name); - } - if ($type === Stmt\Use_::TYPE_CONSTANT) { - // The constants true/false/null always resolve to the global symbols, even inside a - // namespace, so they may be used without qualification - if ($lcName === "true" || $lcName === "false" || $lcName === "null") { - return new Name($name); - } - } - $namespacePrefix = \strtolower($this->namespace . '\\'); - if (0 === \strpos($lcName, $namespacePrefix)) { - return new Name(\substr($name, \strlen($namespacePrefix))); - } - return null; + return isset(self::$magicNames[$this->name->toLowerString()]); } - private function normalizeConstName(string $name) + public function getType() : string { - $nsSep = \strrpos($name, '\\'); - if (\false === $nsSep) { - return $name; - } - // Constants have case-insensitive namespace and case-sensitive short-name - $ns = \substr($name, 0, $nsSep); - $shortName = \substr($name, $nsSep + 1); - return \strtolower($ns) . '\\' . $shortName; + return 'Stmt_ClassMethod'; } } 0 : Flags + * 'extends' => null : Name of extended class + * 'implements' => array(): Names of implemented interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes */ - public function __construct(string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1, int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1) + public function __construct($name, array $subNodes = [], array $attributes = []) { - $this->text = $text; - $this->startLine = $startLine; - $this->startFilePos = $startFilePos; - $this->startTokenPos = $startTokenPos; - $this->endLine = $endLine; - $this->endFilePos = $endFilePos; - $this->endTokenPos = $endTokenPos; + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + public function getSubNodeNames() : array + { + return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; } /** - * Gets the comment text. + * Whether the class is explicitly abstract. * - * @return string The comment text (including comment delimiters like /*) + * @return bool */ - public function getText() : string + public function isAbstract() : bool { - return $this->text; + return (bool) ($this->flags & self::MODIFIER_ABSTRACT); } /** - * Gets the line number the comment started on. + * Whether the class is final. * - * @return int Line number (or -1 if not available) + * @return bool */ - public function getStartLine() : int + public function isFinal() : bool { - return $this->startLine; + return (bool) ($this->flags & self::MODIFIER_FINAL); } - /** - * Gets the file offset the comment started on. - * - * @return int File offset (or -1 if not available) - */ - public function getStartFilePos() : int + public function isReadonly() : bool { - return $this->startFilePos; + return (bool) ($this->flags & self::MODIFIER_READONLY); } /** - * Gets the token offset the comment started on. + * Whether the class is anonymous. * - * @return int Token offset (or -1 if not available) + * @return bool */ - public function getStartTokenPos() : int + public function isAnonymous() : bool { - return $this->startTokenPos; + return null === $this->name; } /** - * Gets the line number the comment ends on. - * - * @return int Line number (or -1 if not available) + * @internal */ - public function getEndLine() : int + public static function verifyClassModifier($a, $b) { - return $this->endLine; + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { + throw new Error('Multiple readonly modifiers are not allowed'); + } + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class'); + } } /** - * Gets the file offset the comment ends on. - * - * @return int File offset (or -1 if not available) + * @internal */ - public function getEndFilePos() : int + public static function verifyModifier($a, $b) { - return $this->endFilePos; + if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) { + throw new Error('Multiple access type modifiers are not allowed'); + } + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) { + throw new Error('Multiple static modifiers are not allowed'); + } + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { + throw new Error('Multiple readonly modifiers are not allowed'); + } + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class member'); + } } - /** - * Gets the token offset the comment ends on. - * - * @return int Token offset (or -1 if not available) - */ - public function getEndTokenPos() : int + public function getType() : string { - return $this->endTokenPos; + return 'Stmt_Class'; } +} +startLine; + $this->attributes = $attributes; + $this->consts = $consts; + } + public function getSubNodeNames() : array + { + return ['consts']; } + public function getType() : string + { + return 'Stmt_Const'; + } +} +startFilePos; + $this->attributes = $attributes; + $this->num = $num; + } + public function getSubNodeNames() : array + { + return ['num']; + } + public function getType() : string + { + return 'Stmt_Continue'; } +} +value pair node. * - * @return int Token offset + * @param string|Node\Identifier $key Key + * @param Node\Expr $value Value + * @param array $attributes Additional attributes */ - public function getTokenPos() : int + public function __construct($key, Node\Expr $value, array $attributes = []) { - return $this->startTokenPos; + $this->attributes = $attributes; + $this->key = \is_string($key) ? new Node\Identifier($key) : $key; + $this->value = $value; + } + public function getSubNodeNames() : array + { + return ['key', 'value']; + } + public function getType() : string + { + return 'Stmt_DeclareDeclare'; } +} +text; + $this->attributes = $attributes; + $this->declares = $declares; + $this->stmts = $stmts; + } + public function getSubNodeNames() : array + { + return ['declares', 'stmts']; + } + public function getType() : string + { + return 'Stmt_Declare'; } +} +text); - $newlinePos = \strpos($text, "\n"); - if (\false === $newlinePos) { - // Single line comments don't need further processing - return $text; - } elseif (\preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\\R\\s+\\*.*)+$)', $text)) { - // Multi line comment of the type - // - // /* - // * Some text. - // * Some more text. - // */ - // - // is handled by replacing the whitespace sequences before the * by a single space - return \preg_replace('(^\\s+\\*)m', ' *', $this->text); - } elseif (\preg_match('(^/\\*\\*?\\s*[\\r\\n])', $text) && \preg_match('(\\n(\\s*)\\*/$)', $text, $matches)) { - // Multi line comment of the type - // - // /* - // Some text. - // Some more text. - // */ - // - // is handled by removing the whitespace sequence on the line before the closing - // */ on all lines. So if the last line is " */", then " " is removed at the - // start of all lines. - return \preg_replace('(^' . \preg_quote($matches[1]) . ')m', '', $text); - } elseif (\preg_match('(^/\\*\\*?\\s*(?!\\s))', $text, $matches)) { - // Multi line comment of the type - // - // /* Some text. - // Some more text. - // Indented text. - // Even more text. */ - // - // is handled by removing the difference between the shortest whitespace prefix on all - // lines and the length of the "/* " opening sequence. - $prefixLen = $this->getShortestWhitespacePrefixLen(\substr($text, $newlinePos + 1)); - $removeLen = $prefixLen - \strlen($matches[0]); - return \preg_replace('(^\\s{' . $removeLen . '})m', '', $text); - } - // No idea how to format this comment, so simply return as is - return $text; + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + public function getSubNodeNames() : array + { + return ['stmts', 'cond']; + } + public function getType() : string + { + return 'Stmt_Do'; } +} +attributes = $attributes; + $this->exprs = $exprs; } - /** - * @return array - * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} - */ - public function jsonSerialize() : array + public function getSubNodeNames() : array { - // Technically not a node, but we make it look like one anyway - $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; - return [ - 'nodeType' => $type, - 'text' => $this->text, - // TODO: Rename these to include "start". - 'line' => $this->startLine, - 'filePos' => $this->startFilePos, - 'tokenPos' => $this->startTokenPos, - 'endLine' => $this->endLine, - 'endFilePos' => $this->endFilePos, - 'endTokenPos' => $this->endTokenPos, - ]; + return ['exprs']; + } + public function getType() : string + { + return 'Stmt_Echo'; } } lexer = $lexer; - if (isset($options['throwOnError'])) { - throw new \LogicException('"throwOnError" is no longer supported, use "errorHandler" instead'); - } - $this->initReduceCallbacks(); + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + public function getSubNodeNames() : array + { + return ['cond', 'stmts']; + } + public function getType() : string + { + return 'Stmt_ElseIf'; } +} +errorHandler = $errorHandler ?: new ErrorHandler\Throwing(); - $this->lexer->startLexing($code, $this->errorHandler); - $result = $this->doParse(); - // Clear out some of the interior state, so we don't hold onto unnecessary - // memory between uses of the parser - $this->startAttributeStack = []; - $this->endAttributeStack = []; - $this->semStack = []; - $this->semValue = null; - return $result; + $this->attributes = $attributes; + $this->stmts = $stmts; } - protected function doParse() + public function getSubNodeNames() : array { - // We start off with no lookahead-token - $symbol = self::SYMBOL_NONE; - // The attributes for a node are taken from the first and last token of the node. - // From the first token only the startAttributes are taken and from the last only - // the endAttributes. Both are merged using the array union operator (+). - $startAttributes = []; - $endAttributes = []; - $this->endAttributes = $endAttributes; - // Keep stack of start and end attributes - $this->startAttributeStack = []; - $this->endAttributeStack = [$endAttributes]; - // Start off in the initial state and keep a stack of previous states - $state = 0; - $stateStack = [$state]; - // Semantic value stack (contains values of tokens and semantic action results) - $this->semStack = []; - // Current position in the stack(s) - $stackPos = 0; - $this->errorState = 0; - for (;;) { - //$this->traceNewState($state, $symbol); - if ($this->actionBase[$state] === 0) { - $rule = $this->actionDefault[$state]; - } else { - if ($symbol === self::SYMBOL_NONE) { - // Fetch the next token id from the lexer and fetch additional info by-ref. - // The end attributes are fetched into a temporary variable and only set once the token is really - // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is - // reduced after a token was read but not yet shifted. - $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes); - // map the lexer token id to the internally used symbols - $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize ? $this->tokenToSymbol[$tokenId] : $this->invalidSymbol; - if ($symbol === $this->invalidSymbol) { - throw new \RangeException(\sprintf('The lexer returned an invalid token (id=%d, value=%s)', $tokenId, $tokenValue)); - } - // Allow productions to access the start attributes of the lookahead token. - $this->lookaheadStartAttributes = $startAttributes; - //$this->traceRead($symbol); - } - $idx = $this->actionBase[$state] + $symbol; - if (($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) && ($action = $this->action[$idx]) !== $this->defaultAction) { - /* - * >= numNonLeafStates: shift and reduce - * > 0: shift - * = 0: accept - * < 0: reduce - * = -YYUNEXPECTED: error - */ - if ($action > 0) { - /* shift */ - //$this->traceShift($symbol); - ++$stackPos; - $stateStack[$stackPos] = $state = $action; - $this->semStack[$stackPos] = $tokenValue; - $this->startAttributeStack[$stackPos] = $startAttributes; - $this->endAttributeStack[$stackPos] = $endAttributes; - $this->endAttributes = $endAttributes; - $symbol = self::SYMBOL_NONE; - if ($this->errorState) { - --$this->errorState; - } - if ($action < $this->numNonLeafStates) { - continue; - } - /* $yyn >= numNonLeafStates means shift-and-reduce */ - $rule = $action - $this->numNonLeafStates; - } else { - $rule = -$action; - } - } else { - $rule = $this->actionDefault[$state]; - } - } - for (;;) { - if ($rule === 0) { - /* accept */ - //$this->traceAccept(); - return $this->semValue; - } elseif ($rule !== $this->unexpectedTokenRule) { - /* reduce */ - //$this->traceReduce($rule); - try { - $this->reduceCallbacks[$rule]($stackPos); - } catch (Error $e) { - if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) { - $e->setStartLine($startAttributes['startLine']); - } - $this->emitError($e); - // Can't recover from this type of error - return null; - } - /* Goto - shift nonterminal */ - $lastEndAttributes = $this->endAttributeStack[$stackPos]; - $ruleLength = $this->ruleToLength[$rule]; - $stackPos -= $ruleLength; - $nonTerminal = $this->ruleToNonTerminal[$rule]; - $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; - if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { - $state = $this->goto[$idx]; - } else { - $state = $this->gotoDefault[$nonTerminal]; - } - ++$stackPos; - $stateStack[$stackPos] = $state; - $this->semStack[$stackPos] = $this->semValue; - $this->endAttributeStack[$stackPos] = $lastEndAttributes; - if ($ruleLength === 0) { - // Empty productions use the start attributes of the lookahead token. - $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; - } - } else { - /* error */ - switch ($this->errorState) { - case 0: - $msg = $this->getErrorMessage($symbol, $state); - $this->emitError(new Error($msg, $startAttributes + $endAttributes)); - // Break missing intentionally - case 1: - case 2: - $this->errorState = 3; - // Pop until error-expecting state uncovered - while (!(($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) || ($action = $this->action[$idx]) === $this->defaultAction) { - // Not totally sure about this - if ($stackPos <= 0) { - // Could not recover from error - return null; - } - $state = $stateStack[--$stackPos]; - //$this->tracePop($state); - } - //$this->traceShift($this->errorSymbol); - ++$stackPos; - $stateStack[$stackPos] = $state = $action; - // We treat the error symbol as being empty, so we reset the end attributes - // to the end attributes of the last non-error symbol - $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; - $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1]; - $this->endAttributes = $this->endAttributeStack[$stackPos - 1]; - break; - case 3: - if ($symbol === 0) { - // Reached EOF without recovering from error - return null; - } - //$this->traceDiscard($symbol); - $symbol = self::SYMBOL_NONE; - break 2; - } - } - if ($state < $this->numNonLeafStates) { - break; - } - /* >= numNonLeafStates means shift-and-reduce */ - $rule = $state - $this->numNonLeafStates; - } - } - throw new \RuntimeException('Reached end of parser loop'); + return ['stmts']; } - protected function emitError(Error $error) + public function getType() : string { - $this->errorHandler->handleError($error); + return 'Stmt_Else'; } +} +getExpectedTokens($state)) { - $expectedString = ', expecting ' . \implode(' or ', $expected); - } - return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + parent::__construct($attributes); + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->expr = $expr; + $this->attrGroups = $attrGroups; } - /** - * Get limited number of expected tokens in given state. - * - * @param int $state State - * - * @return string[] Expected tokens. If too many, an empty array is returned. - */ - protected function getExpectedTokens(int $state) : array + public function getSubNodeNames() : array { - $expected = []; - $base = $this->actionBase[$state]; - foreach ($this->symbolToName as $symbol => $name) { - $idx = $base + $symbol; - if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) { - if ($this->action[$idx] !== $this->unexpectedTokenRule && $this->action[$idx] !== $this->defaultAction && $symbol !== $this->errorSymbol) { - if (\count($expected) === 4) { - /* Too many expected tokens */ - return []; - } - $expected[] = $name; - } - } - } - return $expected; - } - /* - * Tracing functions used for debugging the parser. - */ - /* - protected function traceNewState($state, $symbol) { - echo '% State ' . $state - . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; - } - - protected function traceRead($symbol) { - echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; - } - - protected function traceShift($symbol) { - echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; - } - - protected function traceAccept() { - echo "% Accepted.\n"; - } - - protected function traceReduce($n) { - echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; - } - - protected function tracePop($state) { - echo '% Recovering, uncovered state ' . $state . "\n"; + return ['attrGroups', 'name', 'expr']; } - - protected function traceDiscard($symbol) { - echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + public function getType() : string + { + return 'Stmt_EnumCase'; } - */ - /* - * Helper functions invoked by semantic actions - */ +} +stmts and checks various error conditions. - * - * @param Node\Stmt[] $stmts - * @return Node\Stmt[] + * @param string|Node\Identifier|null $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'scalarType' => null : Scalar type + * 'implements' => array() : Names of implemented interfaces + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes */ - protected function handleNamespaces(array $stmts) : array + public function __construct($name, array $subNodes = [], array $attributes = []) { - $hasErrored = \false; - $style = $this->getNamespacingStyle($stmts); - if (null === $style) { - // not namespaced, nothing to do - return $stmts; - } elseif ('brace' === $style) { - // For braced namespaces we only have to check that there are no invalid statements between the namespaces - $afterFirstNamespace = \false; - foreach ($stmts as $stmt) { - if ($stmt instanceof Node\Stmt\Namespace_) { - $afterFirstNamespace = \true; - } elseif (!$stmt instanceof Node\Stmt\HaltCompiler && !$stmt instanceof Node\Stmt\Nop && $afterFirstNamespace && !$hasErrored) { - $this->emitError(new Error('No code may exist outside of namespace {}', $stmt->getAttributes())); - $hasErrored = \true; - // Avoid one error for every statement - } - } - return $stmts; - } else { - // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts - $resultStmts = []; - $targetStmts =& $resultStmts; - $lastNs = null; - foreach ($stmts as $stmt) { - if ($stmt instanceof Node\Stmt\Namespace_) { - if ($lastNs !== null) { - $this->fixupNamespaceAttributes($lastNs); - } - if ($stmt->stmts === null) { - $stmt->stmts = []; - $targetStmts =& $stmt->stmts; - $resultStmts[] = $stmt; - } else { - // This handles the invalid case of mixed style namespaces - $resultStmts[] = $stmt; - $targetStmts =& $resultStmts; - } - $lastNs = $stmt; - } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { - // __halt_compiler() is not moved into the namespace - $resultStmts[] = $stmt; - } else { - $targetStmts[] = $stmt; - } - } - if ($lastNs !== null) { - $this->fixupNamespaceAttributes($lastNs); - } - return $resultStmts; - } + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->scalarType = $subNodes['scalarType'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + parent::__construct($attributes); } - private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) + public function getSubNodeNames() : array { - // We moved the statements into the namespace node, as such the end of the namespace node - // needs to be extended to the end of the statements. - if (empty($stmt->stmts)) { - return; - } - // We only move the builtin end attributes here. This is the best we can do with the - // knowledge we have. - $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; - $lastStmt = $stmt->stmts[\count($stmt->stmts) - 1]; - foreach ($endAttributes as $endAttribute) { - if ($lastStmt->hasAttribute($endAttribute)) { - $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); - } - } + return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; } - /** - * Determine namespacing style (semicolon or brace) - * - * @param Node[] $stmts Top-level statements. - * - * @return null|string One of "semicolon", "brace" or null (no namespaces) - */ - private function getNamespacingStyle(array $stmts) + public function getType() : string { - $style = null; - $hasNotAllowedStmts = \false; - foreach ($stmts as $i => $stmt) { - if ($stmt instanceof Node\Stmt\Namespace_) { - $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; - if (null === $style) { - $style = $currentStyle; - if ($hasNotAllowedStmts) { - $this->emitError(new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine())); - } - } elseif ($style !== $currentStyle) { - $this->emitError(new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine())); - // Treat like semicolon style for namespace normalization - return 'semicolon'; - } - continue; - } - /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ - if ($stmt instanceof Node\Stmt\Declare_ || $stmt instanceof Node\Stmt\HaltCompiler || $stmt instanceof Node\Stmt\Nop) { - continue; - } - /* There may be a hashbang line at the very start of the file */ - if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && \preg_match('/\\A#!.*\\r?\\n\\z/', $stmt->value)) { - continue; - } - /* Everything else if forbidden before namespace declarations */ - $hasNotAllowedStmts = \true; - } - return $style; + return 'Stmt_Enum'; } +} +name instanceof VarLikeIdentifier ? $prop->name->toString() : $prop->name; - $var = new Expr\Variable($name, $prop->name->getAttributes()); - return new Expr\StaticCall($prop->class, $var, $args, $attributes); - } elseif ($prop instanceof Node\Expr\ArrayDimFetch) { - $tmp = $prop; - while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { - $tmp = $tmp->var; - } - /** @var Expr\StaticPropertyFetch $staticProp */ - $staticProp = $tmp->var; - // Set start attributes to attributes of innermost node - $tmp = $prop; - $this->fixupStartAttributes($tmp, $staticProp->name); - while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { - $tmp = $tmp->var; - $this->fixupStartAttributes($tmp, $staticProp->name); - } - $name = $staticProp->name instanceof VarLikeIdentifier ? $staticProp->name->toString() : $staticProp->name; - $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes()); - return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes); - } else { - throw new \Exception(); - } + $this->attributes = $attributes; + $this->expr = $expr; } - protected function fixupStartAttributes(Node $to, Node $from) + public function getSubNodeNames() : array { - $startAttributes = ['startLine', 'startFilePos', 'startTokenPos']; - foreach ($startAttributes as $startAttribute) { - if ($from->hasAttribute($startAttribute)) { - $to->setAttribute($startAttribute, $from->getAttribute($startAttribute)); - } - } + return ['expr']; } - protected function handleBuiltinTypes(Name $name) + public function getType() : string { - $builtinTypes = ['bool' => \true, 'int' => \true, 'float' => \true, 'string' => \true, 'iterable' => \true, 'void' => \true, 'object' => \true, 'null' => \true, 'false' => \true, 'mixed' => \true, 'never' => \true]; - if (!$name->isUnqualified()) { - return $name; - } - $lowerName = $name->toLowerString(); - if (!isset($builtinTypes[$lowerName])) { - return $name; - } - return new Node\Identifier($lowerName, $name->getAttributes()); + return 'Stmt_Expression'; } +} +startAttributeStack[$pos] + $this->endAttributeStack[$pos]; + $this->attributes = $attributes; + $this->stmts = $stmts; } - protected function getFloatCastKind(string $cast) : int + public function getSubNodeNames() : array { - $cast = \strtolower($cast); - if (\strpos($cast, 'float') !== \false) { - return Double::KIND_FLOAT; - } - if (\strpos($cast, 'real') !== \false) { - return Double::KIND_REAL; - } - return Double::KIND_DOUBLE; + return ['stmts']; } - protected function parseLNumber($str, $attributes, $allowInvalidOctal = \false) + public function getType() : string { - try { - return LNumber::fromString($str, $attributes, $allowInvalidOctal); - } catch (Error $error) { - $this->emitError($error); - // Use dummy value - return new LNumber(0, $attributes); - } + return 'Stmt_Finally'; } +} + array(): Init expressions + * 'cond' => array(): Loop conditions + * 'loop' => array(): Loop expressions + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes */ - protected function parseNumString(string $str, array $attributes) + public function __construct(array $subNodes = [], array $attributes = []) { - if (!\preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { - return new String_($str, $attributes); - } - $num = +$str; - if (!\is_int($num)) { - return new String_($str, $attributes); - } - return new LNumber($num, $attributes); + $this->attributes = $attributes; + $this->init = $subNodes['init'] ?? []; + $this->cond = $subNodes['cond'] ?? []; + $this->loop = $subNodes['loop'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; } - protected function stripIndentation(string $string, int $indentLen, string $indentChar, bool $newlineAtStart, bool $newlineAtEnd, array $attributes) + public function getSubNodeNames() : array { - if ($indentLen === 0) { - return $string; - } - $start = $newlineAtStart ? '(?:(?<=\\n)|\\A)' : '(?<=\\n)'; - $end = $newlineAtEnd ? '(?:(?=[\\r\\n])|\\z)' : '(?=[\\r\\n])'; - $regex = '/' . $start . '([ \\t]*)(' . $end . ')?/'; - return \preg_replace_callback($regex, function ($matches) use($indentLen, $indentChar, $attributes) { - $prefix = \substr($matches[1], 0, $indentLen); - if (\false !== \strpos($prefix, $indentChar === " " ? "\t" : " ")) { - $this->emitError(new Error('Invalid indentation - tabs and spaces cannot be mixed', $attributes)); - } elseif (\strlen($prefix) < $indentLen && !isset($matches[2])) { - $this->emitError(new Error('Invalid body indentation level ' . '(expecting an indentation level of at least ' . $indentLen . ')', $attributes)); - } - return \substr($matches[0], \strlen($prefix)); - }, $string); + return ['init', 'cond', 'loop', 'stmts']; } - protected function parseDocString(string $startToken, $contents, string $endToken, array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape) + public function getType() : string { - $kind = \strpos($startToken, "'") === \false ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; - $regex = '/\\A[bB]?<<<[ \\t]*[\'"]?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)[\'"]?(?:\\r\\n|\\n|\\r)\\z/'; - $result = \preg_match($regex, $startToken, $matches); - \assert($result === 1); - $label = $matches[1]; - $result = \preg_match('/\\A[ \\t]*/', $endToken, $matches); - \assert($result === 1); - $indentation = $matches[0]; - $attributes['kind'] = $kind; - $attributes['docLabel'] = $label; - $attributes['docIndentation'] = $indentation; - $indentHasSpaces = \false !== \strpos($indentation, " "); - $indentHasTabs = \false !== \strpos($indentation, "\t"); - if ($indentHasSpaces && $indentHasTabs) { - $this->emitError(new Error('Invalid indentation - tabs and spaces cannot be mixed', $endTokenAttributes)); - // Proceed processing as if this doc string is not indented - $indentation = ''; - } - $indentLen = \strlen($indentation); - $indentChar = $indentHasSpaces ? " " : "\t"; - if (\is_string($contents)) { - if ($contents === '') { - return new String_('', $attributes); - } - $contents = $this->stripIndentation($contents, $indentLen, $indentChar, \true, \true, $attributes); - $contents = \preg_replace('~(\\r\\n|\\n|\\r)\\z~', '', $contents); - if ($kind === String_::KIND_HEREDOC) { - $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); - } - return new String_($contents, $attributes); - } else { - \assert(\count($contents) > 0); - if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) { - // If there is no leading encapsed string part, pretend there is an empty one - $this->stripIndentation('', $indentLen, $indentChar, \true, \false, $contents[0]->getAttributes()); - } - $newContents = []; - foreach ($contents as $i => $part) { - if ($part instanceof Node\Scalar\EncapsedStringPart) { - $isLast = $i === \count($contents) - 1; - $part->value = $this->stripIndentation($part->value, $indentLen, $indentChar, $i === 0, $isLast, $part->getAttributes()); - $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); - if ($isLast) { - $part->value = \preg_replace('~(\\r\\n|\\n|\\r)\\z~', '', $part->value); - } - if ('' === $part->value) { - continue; - } - } - $newContents[] = $part; - } - return new Encapsed($newContents, $attributes); - } + return 'Stmt_For'; } +} + null : Variable to assign key to + * 'byRef' => false : Whether to assign value by reference + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes */ - protected function createCommentNopAttributes(array $comments) + public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { - $comment = $comments[\count($comments) - 1]; - $commentEndLine = $comment->getEndLine(); - $commentEndFilePos = $comment->getEndFilePos(); - $commentEndTokenPos = $comment->getEndTokenPos(); - $attributes = ['comments' => $comments]; - if (-1 !== $commentEndLine) { - $attributes['startLine'] = $commentEndLine; - $attributes['endLine'] = $commentEndLine; - } - if (-1 !== $commentEndFilePos) { - $attributes['startFilePos'] = $commentEndFilePos + 1; - $attributes['endFilePos'] = $commentEndFilePos; - } - if (-1 !== $commentEndTokenPos) { - $attributes['startTokenPos'] = $commentEndTokenPos + 1; - $attributes['endTokenPos'] = $commentEndTokenPos; - } - return $attributes; + $this->attributes = $attributes; + $this->expr = $expr; + $this->keyVar = $subNodes['keyVar'] ?? null; + $this->byRef = $subNodes['byRef'] ?? \false; + $this->valueVar = $valueVar; + $this->stmts = $subNodes['stmts'] ?? []; } - protected function checkModifier($a, $b, $modifierPos) + public function getSubNodeNames() : array { - // Jumping through some hoops here because verifyModifier() is also used elsewhere - try { - Class_::verifyModifier($a, $b); - } catch (Error $error) { - $error->setAttributes($this->getAttributesAt($modifierPos)); - $this->emitError($error); - } + return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; } - protected function checkParam(Param $node) + public function getType() : string { - if ($node->variadic && null !== $node->default) { - $this->emitError(new Error('Variadic parameter cannot have a default value', $node->default->getAttributes())); - } + return 'Stmt_Foreach'; } - protected function checkTryCatch(TryCatch $node) +} + false : Whether to return by reference + * 'params' => array(): Parameters + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { - if (empty($node->catches) && null === $node->finally) { - $this->emitError(new Error('Cannot use try without catch or finally', $node->getAttributes())); - } + $this->attributes = $attributes; + $this->byRef = $subNodes['byRef'] ?? \false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; } - protected function checkNamespace(Namespace_ $node) + public function getSubNodeNames() : array { - if (null !== $node->stmts) { - foreach ($node->stmts as $stmt) { - if ($stmt instanceof Namespace_) { - $this->emitError(new Error('Namespace declarations cannot be nested', $stmt->getAttributes())); - } - } - } + return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; } - private function checkClassName($name, $namePos) + public function returnsByRef() : bool { - if (null !== $name && $name->isSpecialClassName()) { - $this->emitError(new Error(\sprintf('Cannot use \'%s\' as class name as it is reserved', $name), $this->getAttributesAt($namePos))); - } + return $this->byRef; } - private function checkImplementedInterfaces(array $interfaces) + public function getParams() : array { - foreach ($interfaces as $interface) { - if ($interface->isSpecialClassName()) { - $this->emitError(new Error(\sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), $interface->getAttributes())); - } - } + return $this->params; } - protected function checkClass(Class_ $node, $namePos) + public function getReturnType() { - $this->checkClassName($node->name, $namePos); - if ($node->extends && $node->extends->isSpecialClassName()) { - $this->emitError(new Error(\sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), $node->extends->getAttributes())); - } - $this->checkImplementedInterfaces($node->implements); + return $this->returnType; } - protected function checkInterface(Interface_ $node, $namePos) + public function getAttrGroups() : array { - $this->checkClassName($node->name, $namePos); - $this->checkImplementedInterfaces($node->extends); + return $this->attrGroups; } - protected function checkEnum(Enum_ $node, $namePos) + /** @return Node\Stmt[] */ + public function getStmts() : array { - $this->checkClassName($node->name, $namePos); - $this->checkImplementedInterfaces($node->implements); + return $this->stmts; } - protected function checkClassMethod(ClassMethod $node, $modifierPos) + public function getType() : string { - if ($node->flags & Class_::MODIFIER_STATIC) { - switch ($node->name->toLowerString()) { - case '__construct': - $this->emitError(new Error(\sprintf('Constructor %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); - break; - case '__destruct': - $this->emitError(new Error(\sprintf('Destructor %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); - break; - case '__clone': - $this->emitError(new Error(\sprintf('Clone method %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); - break; - } - } - if ($node->flags & Class_::MODIFIER_READONLY) { - $this->emitError(new Error(\sprintf('Method %s() cannot be readonly', $node->name), $this->getAttributesAt($modifierPos))); - } + return 'Stmt_Function'; } - protected function checkClassConst(ClassConst $node, $modifierPos) +} +flags & Class_::MODIFIER_STATIC) { - $this->emitError(new Error("Cannot use 'static' as constant modifier", $this->getAttributesAt($modifierPos))); - } - if ($node->flags & Class_::MODIFIER_ABSTRACT) { - $this->emitError(new Error("Cannot use 'abstract' as constant modifier", $this->getAttributesAt($modifierPos))); - } - if ($node->flags & Class_::MODIFIER_READONLY) { - $this->emitError(new Error("Cannot use 'readonly' as constant modifier", $this->getAttributesAt($modifierPos))); - } + $this->attributes = $attributes; + $this->vars = $vars; } - protected function checkProperty(Property $node, $modifierPos) + public function getSubNodeNames() : array { - if ($node->flags & Class_::MODIFIER_ABSTRACT) { - $this->emitError(new Error('Properties cannot be declared abstract', $this->getAttributesAt($modifierPos))); - } - if ($node->flags & Class_::MODIFIER_FINAL) { - $this->emitError(new Error('Properties cannot be declared final', $this->getAttributesAt($modifierPos))); - } + return ['vars']; } - protected function checkUseUse(UseUse $node, $namePos) + public function getType() : string { - if ($node->alias && $node->alias->isSpecialClassName()) { - $this->emitError(new Error(\sprintf('Cannot use %s as %s because \'%2$s\' is a special class name', $node->name, $node->alias), $this->getAttributesAt($namePos))); - } + return 'Stmt_Global'; } } attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; } - public function leaveNode(Node $node) + public function getSubNodeNames() : array { - return null; + return ['name']; } - public function afterTraverse(array $nodes) + public function getType() : string { - return null; + return 'Stmt_Goto'; } } args($args)); - } - /** - * Creates a namespace builder. - * - * @param null|string|Node\Name $name Name of the namespace + * Constructs a group use node. * - * @return Builder\Namespace_ The created namespace builder + * @param Name $prefix Prefix for uses + * @param UseUse[] $uses Uses + * @param int $type Type of group use + * @param array $attributes Additional attributes */ - public function namespace($name) : Builder\Namespace_ + public function __construct(Name $prefix, array $uses, int $type = Use_::TYPE_NORMAL, array $attributes = []) { - return new Builder\Namespace_($name); + $this->attributes = $attributes; + $this->type = $type; + $this->prefix = $prefix; + $this->uses = $uses; } - /** - * Creates a class builder. - * - * @param string $name Name of the class - * - * @return Builder\Class_ The created class builder - */ - public function class(string $name) : Builder\Class_ + public function getSubNodeNames() : array { - return new Builder\Class_($name); + return ['type', 'prefix', 'uses']; } - /** - * Creates an interface builder. - * - * @param string $name Name of the interface - * - * @return Builder\Interface_ The created interface builder - */ - public function interface(string $name) : Builder\Interface_ + public function getType() : string { - return new Builder\Interface_($name); + return 'Stmt_GroupUse'; } +} +attributes = $attributes; + $this->remaining = $remaining; } - /** - * Creates a trait use builder. - * - * @param Node\Name|string ...$traits Trait names - * - * @return Builder\TraitUse The create trait use builder - */ - public function useTrait(...$traits) : Builder\TraitUse + public function getSubNodeNames() : array { - return new Builder\TraitUse(...$traits); + return ['remaining']; } - /** - * Creates a trait use adaptation builder. - * - * @param Node\Name|string|null $trait Trait name - * @param Node\Identifier|string $method Method name - * - * @return Builder\TraitUseAdaptation The create trait use adaptation builder - */ - public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation + public function getType() : string { - if ($method === null) { - $method = $trait; - $trait = null; - } - return new Builder\TraitUseAdaptation($trait, $method); + return 'Stmt_HaltCompiler'; } +} + array(): Statements + * 'elseifs' => array(): Elseif clauses + * 'else' => null : Else clause + * @param array $attributes Additional attributes */ - public function method(string $name) : Builder\Method + public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) { - return new Builder\Method($name); + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $subNodes['stmts'] ?? []; + $this->elseifs = $subNodes['elseifs'] ?? []; + $this->else = $subNodes['else'] ?? null; } - /** - * Creates a parameter builder. - * - * @param string $name Name of the parameter - * - * @return Builder\Param The created parameter builder - */ - public function param(string $name) : Builder\Param + public function getSubNodeNames() : array { - return new Builder\Param($name); + return ['cond', 'stmts', 'elseifs', 'else']; } - /** - * Creates a property builder. - * - * @param string $name Name of the property - * - * @return Builder\Property The created property builder - */ - public function property(string $name) : Builder\Property + public function getType() : string { - return new Builder\Property($name); + return 'Stmt_If'; } +} +attributes = $attributes; + $this->value = $value; } - /** - * Creates a namespace/class use builder. - * - * @param Node\Name|string $name Name of the entity (namespace or class) to alias - * - * @return Builder\Use_ The created use builder - */ - public function use($name) : Builder\Use_ + public function getSubNodeNames() : array { - return new Builder\Use_($name, Use_::TYPE_NORMAL); + return ['value']; } - /** - * Creates a function use builder. - * - * @param Node\Name|string $name Name of the function to alias - * - * @return Builder\Use_ The created use function builder - */ - public function useFunction($name) : Builder\Use_ + public function getType() : string { - return new Builder\Use_($name, Use_::TYPE_FUNCTION); + return 'Stmt_InlineHTML'; } +} + array(): Name of extended interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes */ - public function useConst($name) : Builder\Use_ + public function __construct($name, array $subNodes = [], array $attributes = []) { - return new Builder\Use_($name, Use_::TYPE_CONSTANT); + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; } - /** - * Creates a class constant builder. - * - * @param string|Identifier $name Name - * @param Node\Expr|bool|null|int|float|string|array $value Value - * - * @return Builder\ClassConst The created use const builder - */ - public function classConst($name, $value) : Builder\ClassConst + public function getSubNodeNames() : array { - return new Builder\ClassConst($name, $value); + return ['attrGroups', 'name', 'extends', 'stmts']; } - /** - * Creates node a for a literal value. - * - * @param Expr|bool|null|int|float|string|array $value $value - * - * @return Expr - */ - public function val($value) : Expr + public function getType() : string { - return BuilderHelpers::normalizeValue($value); + return 'Stmt_Interface'; } +} +attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; } - /** - * Normalizes an argument list. - * - * Creates Arg nodes for all arguments and converts literal values to expressions. - * - * @param array $args List of arguments to normalize - * - * @return Arg[] - */ - public function args(array $args) : array + public function getSubNodeNames() : array { - $normalizedArgs = []; - foreach ($args as $key => $arg) { - if (!$arg instanceof Arg) { - $arg = new Arg(BuilderHelpers::normalizeValue($arg)); - } - if (\is_string($key)) { - $arg->name = BuilderHelpers::normalizeIdentifier($key); - } - $normalizedArgs[] = $arg; - } - return $normalizedArgs; + return ['name']; } - /** - * Creates a function call node. - * - * @param string|Name|Expr $name Function name - * @param array $args Function arguments - * - * @return Expr\FuncCall - */ - public function funcCall($name, array $args = []) : Expr\FuncCall + public function getType() : string { - return new Expr\FuncCall(BuilderHelpers::normalizeNameOrExpr($name), $this->args($args)); + return 'Stmt_Label'; } +} +args($args)); + $this->attributes = $attributes; + $this->name = $name; + $this->stmts = $stmts; + } + public function getSubNodeNames() : array + { + return ['name', 'stmts']; + } + public function getType() : string + { + return 'Stmt_Namespace'; + } +} +args($args)); + $this->attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = \is_string($type) ? new Identifier($type) : $type; + $this->attrGroups = $attrGroups; + } + public function getSubNodeNames() : array + { + return ['attrGroups', 'flags', 'type', 'props']; } /** - * Creates an object creation node. - * - * @param string|Name|Expr $class Class name - * @param array $args Constructor arguments + * Whether the property is explicitly or implicitly public. * - * @return Expr\New_ + * @return bool */ - public function new($class, array $args = []) : Expr\New_ + public function isPublic() : bool { - return new Expr\New_(BuilderHelpers::normalizeNameOrExpr($class), $this->args($args)); + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; } /** - * Creates a constant fetch node. - * - * @param string|Name $name Constant name + * Whether the property is protected. * - * @return Expr\ConstFetch + * @return bool */ - public function constFetch($name) : Expr\ConstFetch + public function isProtected() : bool { - return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); } /** - * Creates a property fetch node. - * - * @param Expr $var Variable holding object - * @param string|Identifier|Expr $name Property name + * Whether the property is private. * - * @return Expr\PropertyFetch + * @return bool */ - public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch + public function isPrivate() : bool { - return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); } /** - * Creates a class constant fetch node. - * - * @param string|Name|Expr $class Class name - * @param string|Identifier $name Constant name + * Whether the property is static. * - * @return Expr\ClassConstFetch + * @return bool */ - public function classConstFetch($class, $name) : Expr\ClassConstFetch + public function isStatic() : bool { - return new Expr\ClassConstFetch(BuilderHelpers::normalizeNameOrExpr($class), BuilderHelpers::normalizeIdentifier($name)); + return (bool) ($this->flags & Class_::MODIFIER_STATIC); } /** - * Creates nested Concat nodes from a list of expressions. - * - * @param Expr|string ...$exprs Expressions or literal strings + * Whether the property is readonly. * - * @return Concat + * @return bool */ - public function concat(...$exprs) : Concat + public function isReadonly() : bool { - $numExprs = \count($exprs); - if ($numExprs < 2) { - throw new \LogicException('Expected at least two expressions'); - } - $lastConcat = $this->normalizeStringExpr($exprs[0]); - for ($i = 1; $i < $numExprs; $i++) { - $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); - } - return $lastConcat; + return (bool) ($this->flags & Class_::MODIFIER_READONLY); } - /** - * @param string|Expr $expr - * @return Expr - */ - private function normalizeStringExpr($expr) : Expr + public function getType() : string { - if ($expr instanceof Expr) { - return $expr; - } - if (\is_string($expr)) { - return new String_($expr); - } - throw new \LogicException('Expected string or Expr'); + return 'Stmt_Property'; } } attributes = $attributes; + $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; + $this->default = $default; + } + public function getSubNodeNames() : array + { + return ['name', 'default']; + } + public function getType() : string + { + return 'Stmt_PropertyProperty'; + } } attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Stmt_Return'; + } } attributes = $attributes; + $this->var = $var; + $this->default = $default; + } + public function getSubNodeNames() : array + { + return ['var', 'default']; + } + public function getType() : string + { + return 'Stmt_StaticVar'; } } errors[] = $error; - } + /** @var StaticVar[] Variable definitions */ + public $vars; /** - * Get collected errors. + * Constructs a static variables list node. * - * @return Error[] + * @param StaticVar[] $vars Variable definitions + * @param array $attributes Additional attributes */ - public function getErrors() : array + public function __construct(array $vars, array $attributes = []) { - return $this->errors; + $this->attributes = $attributes; + $this->vars = $vars; } - /** - * Check whether there are any errors. - * - * @return bool - */ - public function hasErrors() : bool + public function getSubNodeNames() : array { - return !empty($this->errors); + return ['vars']; } - /** - * Reset/clear collected errors. - */ - public function clearErrors() + public function getType() : string { - $this->errors = []; + return 'Stmt_Static'; } } addVisitor($visitor); - $traverser->traverse($nodes); - return $visitor->getFoundNodes(); + $this->attributes = $attributes; + $this->cond = $cond; + $this->cases = $cases; } - /** - * Find all nodes that are instances of a certain class. - * - * @param Node|Node[] $nodes Single node or array of nodes to search in - * @param string $class Class name - * - * @return Node[] Found nodes (all instances of $class) - */ - public function findInstanceOf($nodes, string $class) : array + public function getSubNodeNames() : array { - return $this->find($nodes, function ($node) use($class) { - return $node instanceof $class; - }); + return ['cond', 'cases']; } - /** - * Find first node satisfying a filter callback. - * - * @param Node|Node[] $nodes Single node or array of nodes to search in - * @param callable $filter Filter callback: function(Node $node) : bool - * - * @return null|Node Found node (or null if none found) - */ - public function findFirst($nodes, callable $filter) + public function getType() : string { - if (!\is_array($nodes)) { - $nodes = [$nodes]; - } - $visitor = new FirstFindingVisitor($filter); - $traverser = new NodeTraverser(); - $traverser->addVisitor($visitor); - $traverser->traverse($nodes); - return $visitor->getFoundNode(); + return 'Stmt_Switch'; } +} +findFirst($nodes, function ($node) use($class) { - return $node instanceof $class; - }); + $this->attributes = $attributes; + $this->expr = $expr; + } + public function getSubNodeNames() : array + { + return ['expr']; + } + public function getType() : string + { + return 'Stmt_Throw'; } } attributes = $attributes; + $this->traits = $traits; + $this->adaptations = $adaptations; + } + public function getSubNodeNames() : array + { + return ['traits', 'adaptations']; + } + public function getType() : string + { + return 'Stmt_TraitUse'; + } +} +attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->newModifier = $newModifier; + $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; + } + public function getSubNodeNames() : array + { + return ['trait', 'method', 'newModifier', 'newName']; + } + public function getType() : string + { + return 'Stmt_TraitUseAdaptation_Alias'; + } +} +attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->insteadof = $insteadof; + } + public function getSubNodeNames() : array + { + return ['trait', 'method', 'insteadof']; + } + public function getType() : string + { + return 'Stmt_TraitUseAdaptation_Precedence'; + } +} + array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes */ - public function getAttribute(string $key, $default = null); + public function __construct($name, array $subNodes = [], array $attributes = []) + { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + public function getSubNodeNames() : array + { + return ['attrGroups', 'name', 'stmts']; + } + public function getType() : string + { + return 'Stmt_Trait'; + } +} +attributes = $attributes; + $this->stmts = $stmts; + $this->catches = $catches; + $this->finally = $finally; + } + public function getSubNodeNames() : array + { + return ['stmts', 'catches', 'finally']; + } + public function getType() : string + { + return 'Stmt_TryCatch'; + } +} +attributes = $attributes; + $this->vars = $vars; + } + public function getSubNodeNames() : array + { + return ['vars']; + } + public function getType() : string + { + return 'Stmt_Unset'; + } } getNode(); - } - if ($node instanceof Node) { - return $node; - } - throw new \LogicException('Expected node or builder object'); + $this->attributes = $attributes; + $this->type = $type; + $this->name = $name; + $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; + } + public function getSubNodeNames() : array + { + return ['type', 'name', 'alias']; } /** - * Normalizes a node to a statement. - * - * Expressions are wrapped in a Stmt\Expression node. - * - * @param Node|Builder $node The node to normalize + * Get alias. If not explicitly given this is the last component of the used name. * - * @return Stmt The normalized statement node + * @return Identifier */ - public static function normalizeStmt($node) : Stmt + public function getAlias() : Identifier { - $node = self::normalizeNode($node); - if ($node instanceof Stmt) { - return $node; - } - if ($node instanceof Expr) { - return new Stmt\Expression($node); + if (null !== $this->alias) { + return $this->alias; } - throw new \LogicException('Expected statement or expression node'); + return new Identifier($this->name->getLast()); + } + public function getType() : string + { + return 'Stmt_UseUse'; } +} +attributes = $attributes; + $this->type = $type; + $this->uses = $uses; + } + public function getSubNodeNames() : array + { + return ['type', 'uses']; + } + public function getType() : string + { + return 'Stmt_Use'; } +} +attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + public function getSubNodeNames() : array + { + return ['cond', 'stmts']; + } + public function getType() : string + { + return 'Stmt_While'; } +} +attributes = $attributes; + $this->types = $types; + } + public function getSubNodeNames() : array + { + return ['types']; + } + public function getType() : string + { + return 'UnionType'; + } +} +attributes = $attributes; + } + public function getType() : string + { + return 'VariadicPlaceholder'; + } + public function getSubNodeNames() : array + { + return []; } +} + 0 && $type[0] === '?') { - $nullable = \true; - $type = \substr($type, 1); - } - $builtinTypes = ['array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never']; - $lowerType = \strtolower($type); - if (\in_array($lowerType, $builtinTypes)) { - $type = new Identifier($lowerType); - } else { - $type = self::normalizeName($type); - } - $notNullableTypes = ['void', 'mixed', 'never']; - if ($nullable && \in_array((string) $type, $notNullableTypes)) { - throw new \LogicException(\sprintf('%s type cannot be nullable', $type)); - } - return $nullable ? new NullableType($type) : $type; + $this->attributes = $attributes; } /** - * Normalizes a value: Converts nulls, booleans, integers, - * floats, strings and arrays into their respective nodes - * - * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize + * Gets line the node started in (alias of getStartLine). * - * @return Expr The normalized value + * @return int Start line (or -1 if not available) */ - public static function normalizeValue($value) : Expr + public function getLine() : int { - if ($value instanceof Node\Expr) { - return $value; - } - if (\is_null($value)) { - return new Expr\ConstFetch(new Name('null')); - } - if (\is_bool($value)) { - return new Expr\ConstFetch(new Name($value ? 'true' : 'false')); - } - if (\is_int($value)) { - return new Scalar\LNumber($value); - } - if (\is_float($value)) { - return new Scalar\DNumber($value); - } - if (\is_string($value)) { - return new Scalar\String_($value); - } - if (\is_array($value)) { - $items = []; - $lastKey = -1; - foreach ($value as $itemKey => $itemValue) { - // for consecutive, numeric keys don't generate keys - if (null !== $lastKey && ++$lastKey === $itemKey) { - $items[] = new Expr\ArrayItem(self::normalizeValue($itemValue)); - } else { - $lastKey = null; - $items[] = new Expr\ArrayItem(self::normalizeValue($itemValue), self::normalizeValue($itemKey)); - } - } - return new Expr\Array_($items); - } - throw new \LogicException('Invalid value'); + return $this->attributes['startLine'] ?? -1; } /** - * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. + * Gets line the node started in. * - * @param Comment\Doc|string $docComment The doc comment to normalize + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). * - * @return Comment\Doc The normalized doc comment + * @return int Start line (or -1 if not available) */ - public static function normalizeDocComment($docComment) : Comment\Doc + public function getStartLine() : int { - if ($docComment instanceof Comment\Doc) { - return $docComment; - } - if (\is_string($docComment)) { - return new Comment\Doc($docComment); - } - throw new \LogicException('PHPUnit\\Doc comment must be a string or an instance of PhpParser\\Comment\\Doc'); + return $this->attributes['startLine'] ?? -1; } /** - * Normalizes a attribute: Converts attribute to the Attribute Group if needed. + * Gets the line the node ended in. * - * @param Node\Attribute|Node\AttributeGroup $attribute + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). * - * @return Node\AttributeGroup The Attribute Group + * @return int End line (or -1 if not available) */ - public static function normalizeAttribute($attribute) : Node\AttributeGroup + public function getEndLine() : int { - if ($attribute instanceof Node\AttributeGroup) { - return $attribute; - } - if (!$attribute instanceof Node\Attribute) { - throw new \LogicException('PHPUnit\\Attribute must be an instance of PhpParser\\Node\\Attribute or PhpParser\\Node\\AttributeGroup'); - } - return new Node\AttributeGroup([$attribute]); + return $this->attributes['endLine'] ?? -1; } /** - * Adds a modifier and returns new modifier bitmask. + * Gets the token offset of the first token that is part of this node. * - * @param int $modifiers Existing modifiers - * @param int $modifier Modifier to set + * The offset is an index into the array returned by Lexer::getTokens(). * - * @return int New modifiers + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) */ - public static function addModifier(int $modifiers, int $modifier) : int + public function getStartTokenPos() : int { - Stmt\Class_::verifyModifier($modifiers, $modifier); - return $modifiers | $modifier; + return $this->attributes['startTokenPos'] ?? -1; } -} -pAttrGroups($node->attrGroups, \true) . $this->pModifiers($node->flags) . ($node->type ? $this->p($node->type) . ' ' : '') . ($node->byRef ? '&' : '') . ($node->variadic ? '...' : '') . $this->p($node->var) . ($node->default ? ' = ' . $this->p($node->default) : ''); + return $this->attributes['endTokenPos'] ?? -1; } - protected function pArg(Node\Arg $node) + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos() : int { - return ($node->name ? $node->name->toString() . ': ' : '') . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value); + return $this->attributes['startFilePos'] ?? -1; } - protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos() : int { - return '...'; + return $this->attributes['endFilePos'] ?? -1; } - protected function pConst(Node\Const_ $node) + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments() : array { - return $node->name . ' = ' . $this->p($node->value); + return $this->attributes['comments'] ?? []; } - protected function pNullableType(Node\NullableType $node) + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment() { - return '?' . $this->p($node->type); + $comments = $this->getComments(); + for ($i = \count($comments) - 1; $i >= 0; $i--) { + $comment = $comments[$i]; + if ($comment instanceof Comment\Doc) { + return $comment; + } + } + return null; } - protected function pUnionType(Node\UnionType $node) + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment) { - return $this->pImplode($node->types, '|'); + $comments = $this->getComments(); + for ($i = \count($comments) - 1; $i >= 0; $i--) { + if ($comments[$i] instanceof Comment\Doc) { + // Replace existing doc comment. + $comments[$i] = $docComment; + $this->setAttribute('comments', $comments); + return; + } + } + // Append new doc comment. + $comments[] = $docComment; + $this->setAttribute('comments', $comments); } - protected function pIntersectionType(Node\IntersectionType $node) + public function setAttribute(string $key, $value) { - return $this->pImplode($node->types, '&'); + $this->attributes[$key] = $value; } - protected function pIdentifier(Node\Identifier $node) + public function hasAttribute(string $key) : bool { - return $node->name; + return \array_key_exists($key, $this->attributes); } - protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) + public function getAttribute(string $key, $default = null) { - return '$' . $node->name; + if (\array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + return $default; } - protected function pAttribute(Node\Attribute $node) + public function getAttributes() : array { - return $this->p($node->name) . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + return $this->attributes; } - protected function pAttributeGroup(Node\AttributeGroup $node) + public function setAttributes(array $attributes) { - return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + $this->attributes = $attributes; } - // Names - protected function pName(Name $node) + /** + * @return array + */ + public function jsonSerialize() : array { - return \implode('\\', $node->parts); + return ['nodeType' => $this->getType()] + \get_object_vars($this); } - protected function pName_FullyQualified(Name\FullyQualified $node) +} +parts); + $this->dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); } - protected function pName_Relative(Name\Relative $node) + /** + * Dumps a node or array. + * + * @param array|Node $node Node or array to dump + * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if + * the dumpPositions option is enabled and the dumping of node offsets + * is desired. + * + * @return string Dumped value + */ + public function dump($node, string $code = null) : string { - return 'namespace\\' . \implode('\\', $node->parts); + $this->code = $code; + return $this->dumpRecursive($node); } - // Magic Constants - protected function pScalar_MagicConst_Class(MagicConst\Class_ $node) + protected function dumpRecursive($node) { - return '__CLASS__'; - } - protected function pScalar_MagicConst_Dir(MagicConst\Dir $node) - { - return '__DIR__'; - } - protected function pScalar_MagicConst_File(MagicConst\File $node) - { - return '__FILE__'; - } - protected function pScalar_MagicConst_Function(MagicConst\Function_ $node) - { - return '__FUNCTION__'; - } - protected function pScalar_MagicConst_Line(MagicConst\Line $node) - { - return '__LINE__'; - } - protected function pScalar_MagicConst_Method(MagicConst\Method $node) - { - return '__METHOD__'; - } - protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) - { - return '__NAMESPACE__'; - } - protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) - { - return '__TRAIT__'; - } - // Scalars - protected function pScalar_String(Scalar\String_ $node) - { - $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); - switch ($kind) { - case Scalar\String_::KIND_NOWDOC: - $label = $node->getAttribute('docLabel'); - if ($label && !$this->containsEndLabel($node->value, $label)) { - if ($node->value === '') { - return "<<<'{$label}'\n{$label}" . $this->docStringEndToken; - } - return "<<<'{$label}'\n{$node->value}\n{$label}" . $this->docStringEndToken; - } - /* break missing intentionally */ - case Scalar\String_::KIND_SINGLE_QUOTED: - return $this->pSingleQuotedString($node->value); - case Scalar\String_::KIND_HEREDOC: - $label = $node->getAttribute('docLabel'); - if ($label && !$this->containsEndLabel($node->value, $label)) { - if ($node->value === '') { - return "<<<{$label}\n{$label}" . $this->docStringEndToken; + if ($node instanceof Node) { + $r = $node->getType(); + if ($this->dumpPositions && null !== ($p = $this->dumpPosition($node))) { + $r .= $p; + } + $r .= '('; + foreach ($node->getSubNodeNames() as $key) { + $r .= "\n " . $key . ': '; + $value = $node->{$key}; + if (null === $value) { + $r .= 'null'; + } elseif (\false === $value) { + $r .= 'false'; + } elseif (\true === $value) { + $r .= 'true'; + } elseif (\is_scalar($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $r .= $this->dumpFlags($value); + } elseif ('type' === $key && $node instanceof Include_) { + $r .= $this->dumpIncludeType($value); + } elseif ('type' === $key && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) { + $r .= $this->dumpUseType($value); + } else { + $r .= $value; } - $escaped = $this->escapeString($node->value, null); - return "<<<{$label}\n" . $escaped . "\n{$label}" . $this->docStringEndToken; + } else { + $r .= \str_replace("\n", "\n ", $this->dumpRecursive($value)); } - /* break missing intentionally */ - case Scalar\String_::KIND_DOUBLE_QUOTED: - return '"' . $this->escapeString($node->value, '"') . '"'; - } - throw new \Exception('Invalid string kind'); - } - protected function pScalar_Encapsed(Scalar\Encapsed $node) - { - if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { - $label = $node->getAttribute('docLabel'); - if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { - if (\count($node->parts) === 1 && $node->parts[0] instanceof Scalar\EncapsedStringPart && $node->parts[0]->value === '') { - return "<<<{$label}\n{$label}" . $this->docStringEndToken; + } + if ($this->dumpComments && ($comments = $node->getComments())) { + $r .= "\n comments: " . \str_replace("\n", "\n ", $this->dumpRecursive($comments)); + } + } elseif (\is_array($node)) { + $r = 'array('; + foreach ($node as $key => $value) { + $r .= "\n " . $key . ': '; + if (null === $value) { + $r .= 'null'; + } elseif (\false === $value) { + $r .= 'false'; + } elseif (\true === $value) { + $r .= 'true'; + } elseif (\is_scalar($value)) { + $r .= $value; + } else { + $r .= \str_replace("\n", "\n ", $this->dumpRecursive($value)); } - return "<<<{$label}\n" . $this->pEncapsList($node->parts, null) . "\n{$label}" . $this->docStringEndToken; } + } elseif ($node instanceof Comment) { + return $node->getReformattedText(); + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); } - return '"' . $this->pEncapsList($node->parts, '"') . '"'; + return $r . "\n)"; } - protected function pScalar_LNumber(Scalar\LNumber $node) + protected function dumpFlags($flags) { - if ($node->value === -\PHP_INT_MAX - 1) { - // PHP_INT_MIN cannot be represented as a literal, - // because the sign is not part of the literal - return '(-' . \PHP_INT_MAX . '-1)'; + $strs = []; + if ($flags & Class_::MODIFIER_PUBLIC) { + $strs[] = 'MODIFIER_PUBLIC'; } - $kind = $node->getAttribute('kind', Scalar\LNumber::KIND_DEC); - if (Scalar\LNumber::KIND_DEC === $kind) { - return (string) $node->value; + if ($flags & Class_::MODIFIER_PROTECTED) { + $strs[] = 'MODIFIER_PROTECTED'; } - if ($node->value < 0) { - $sign = '-'; - $str = (string) -$node->value; - } else { - $sign = ''; - $str = (string) $node->value; + if ($flags & Class_::MODIFIER_PRIVATE) { + $strs[] = 'MODIFIER_PRIVATE'; } - switch ($kind) { - case Scalar\LNumber::KIND_BIN: - return $sign . '0b' . \base_convert($str, 10, 2); - case Scalar\LNumber::KIND_OCT: - return $sign . '0' . \base_convert($str, 10, 8); - case Scalar\LNumber::KIND_HEX: - return $sign . '0x' . \base_convert($str, 10, 16); + if ($flags & Class_::MODIFIER_ABSTRACT) { + $strs[] = 'MODIFIER_ABSTRACT'; } - throw new \Exception('Invalid number kind'); - } - protected function pScalar_DNumber(Scalar\DNumber $node) - { - if (!\is_finite($node->value)) { - if ($node->value === \INF) { - return '\\INF'; - } elseif ($node->value === -\INF) { - return '-\\INF'; - } else { - return '\\NAN'; - } + if ($flags & Class_::MODIFIER_STATIC) { + $strs[] = 'MODIFIER_STATIC'; } - // Try to find a short full-precision representation - $stringValue = \sprintf('%.16G', $node->value); - if ($node->value !== (double) $stringValue) { - $stringValue = \sprintf('%.17G', $node->value); + if ($flags & Class_::MODIFIER_FINAL) { + $strs[] = 'MODIFIER_FINAL'; + } + if ($flags & Class_::MODIFIER_READONLY) { + $strs[] = 'MODIFIER_READONLY'; + } + if ($strs) { + return \implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return $flags; } - // %G is locale dependent and there exists no locale-independent alternative. We don't want - // mess with switching locales here, so let's assume that a comma is the only non-standard - // decimal separator we may encounter... - $stringValue = \str_replace(',', '.', $stringValue); - // ensure that number is really printed as float - return \preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; - } - protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) - { - throw new \LogicException('Cannot directly print EncapsedStringPart'); - } - // Assignments - protected function pExpr_Assign(Expr\Assign $node) - { - return $this->pInfixOp(Expr\Assign::class, $node->var, ' = ', $node->expr); - } - protected function pExpr_AssignRef(Expr\AssignRef $node) - { - return $this->pInfixOp(Expr\AssignRef::class, $node->var, ' =& ', $node->expr); - } - protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) - { - return $this->pInfixOp(AssignOp\Plus::class, $node->var, ' += ', $node->expr); - } - protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) - { - return $this->pInfixOp(AssignOp\Minus::class, $node->var, ' -= ', $node->expr); - } - protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) - { - return $this->pInfixOp(AssignOp\Mul::class, $node->var, ' *= ', $node->expr); - } - protected function pExpr_AssignOp_Div(AssignOp\Div $node) - { - return $this->pInfixOp(AssignOp\Div::class, $node->var, ' /= ', $node->expr); - } - protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) - { - return $this->pInfixOp(AssignOp\Concat::class, $node->var, ' .= ', $node->expr); } - protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) + protected function dumpIncludeType($type) { - return $this->pInfixOp(AssignOp\Mod::class, $node->var, ' %= ', $node->expr); + $map = [Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE']; + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; } - protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) + protected function dumpUseType($type) { - return $this->pInfixOp(AssignOp\BitwiseAnd::class, $node->var, ' &= ', $node->expr); + $map = [Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', Use_::TYPE_NORMAL => 'TYPE_NORMAL', Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', Use_::TYPE_CONSTANT => 'TYPE_CONSTANT']; + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; } - protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) + /** + * Dump node position, if possible. + * + * @param Node $node Node for which to dump position + * + * @return string|null Dump of position, or null if position information not available + */ + protected function dumpPosition(Node $node) { - return $this->pInfixOp(AssignOp\BitwiseOr::class, $node->var, ' |= ', $node->expr); + if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { + return null; + } + $start = $node->getStartLine(); + $end = $node->getEndLine(); + if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') && null !== $this->code) { + $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); + $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); + } + return "[{$start} - {$end}]"; } - protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) + // Copied from Error class + private function toColumn($code, $pos) { - return $this->pInfixOp(AssignOp\BitwiseXor::class, $node->var, ' ^= ', $node->expr); + if ($pos > \strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + $lineStartPos = \strrpos($code, "\n", $pos - \strlen($code)); + if (\false === $lineStartPos) { + $lineStartPos = -1; + } + return $pos - $lineStartPos; } - protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) +} +pInfixOp(AssignOp\ShiftLeft::class, $node->var, ' <<= ', $node->expr); + if (!\is_array($nodes)) { + $nodes = [$nodes]; + } + $visitor = new FindingVisitor($filter); + $traverser = new NodeTraverser(); + $traverser->addVisitor($visitor); + $traverser->traverse($nodes); + return $visitor->getFoundNodes(); } - protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) + /** + * Find all nodes that are instances of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return Node[] Found nodes (all instances of $class) + */ + public function findInstanceOf($nodes, string $class) : array { - return $this->pInfixOp(AssignOp\ShiftRight::class, $node->var, ' >>= ', $node->expr); + return $this->find($nodes, function ($node) use($class) { + return $node instanceof $class; + }); } - protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) + /** + * Find first node satisfying a filter callback. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param callable $filter Filter callback: function(Node $node) : bool + * + * @return null|Node Found node (or null if none found) + */ + public function findFirst($nodes, callable $filter) { - return $this->pInfixOp(AssignOp\Pow::class, $node->var, ' **= ', $node->expr); + if (!\is_array($nodes)) { + $nodes = [$nodes]; + } + $visitor = new FirstFindingVisitor($filter); + $traverser = new NodeTraverser(); + $traverser->addVisitor($visitor); + $traverser->traverse($nodes); + return $visitor->getFoundNode(); } - protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node) + /** + * Find first node that is an instance of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return null|Node Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class) { - return $this->pInfixOp(AssignOp\Coalesce::class, $node->var, ' ??= ', $node->expr); + return $this->findFirst($nodes, function ($node) use($class) { + return $node instanceof $class; + }); } - // Binary expressions - protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) +} +pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right); + // for BC } - protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor) { - return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right); + $this->visitors[] = $visitor; } - protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) + /** + * Removes an added visitor. + * + * @param NodeVisitor $visitor + */ + public function removeVisitor(NodeVisitor $visitor) { - return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right); + foreach ($this->visitors as $index => $storedVisitor) { + if ($storedVisitor === $visitor) { + unset($this->visitors[$index]); + break; + } + } } - protected function pExpr_BinaryOp_Div(BinaryOp\Div $node) + /** + * Traverses an array of nodes using the registered visitors. + * + * @param Node[] $nodes Array of nodes + * + * @return Node[] Traversed array of nodes + */ + public function traverse(array $nodes) : array { - return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right); + $this->stopTraversal = \false; + foreach ($this->visitors as $visitor) { + if (null !== ($return = $visitor->beforeTraverse($nodes))) { + $nodes = $return; + } + } + $nodes = $this->traverseArray($nodes); + foreach ($this->visitors as $visitor) { + if (null !== ($return = $visitor->afterTraverse($nodes))) { + $nodes = $return; + } + } + return $nodes; } - protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + * + * @return Node Result of traversal (may be original node or new one) + */ + protected function traverseNode(Node $node) : Node { - return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right); + foreach ($node->getSubNodeNames() as $name) { + $subNode =& $node->{$name}; + if (\is_array($subNode)) { + $subNode = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = \true; + $breakVisitorIndex = null; + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = \false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = \false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = \true; + break 2; + } else { + throw new \LogicException('enterNode() returned invalid value of type ' . \gettype($return)); + } + } + } + if ($traverseChildren) { + $subNode = $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = \true; + break 2; + } elseif (\is_array($return)) { + throw new \LogicException('leaveNode() may only return an array ' . 'if the parent structure is an array'); + } else { + throw new \LogicException('leaveNode() returned invalid value of type ' . \gettype($return)); + } + } + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } + } + return $node; } - protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) + /** + * Recursively traverse array (usually of nodes). + * + * @param array $nodes Array to traverse + * + * @return array Result of traversal (may be original array or changed one) + */ + protected function traverseArray(array $nodes) : array { - return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right); - } - protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) - { - return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right); + $doNodes = []; + foreach ($nodes as $i => &$node) { + if ($node instanceof Node) { + $traverseChildren = \true; + $breakVisitorIndex = null; + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = \false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = \false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = \true; + break 2; + } else { + throw new \LogicException('enterNode() returned invalid value of type ' . \gettype($return)); + } + } + } + if ($traverseChildren) { + $node = $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (self::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = \true; + break 2; + } elseif (\false === $return) { + throw new \LogicException('bool(false) return from leaveNode() no longer supported. ' . 'Return NodeTraverser::REMOVE_NODE instead'); + } else { + throw new \LogicException('leaveNode() returned invalid value of type ' . \gettype($return)); + } + } + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } elseif (\is_array($node)) { + throw new \LogicException('Invalid node structure: Contains nested arrays'); + } + } + if (!empty($doNodes)) { + while (list($i, $replace) = \array_pop($doNodes)) { + \array_splice($nodes, $i, 1, $replace); + } + } + return $nodes; } - protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) + private function ensureReplacementReasonable($old, $new) { - return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right); + if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { + throw new \LogicException("Trying to replace statement ({$old->getType()}) " . "with expression ({$new->getType()}). Are you missing a " . "Stmt_Expression wrapper?"); + } + if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { + throw new \LogicException("Trying to replace expression ({$old->getType()}) " . "with statement ({$new->getType()})"); + } } - protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) +} + $node stays as-is + * * NodeTraverser::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeTraverser::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node Replacement node (or special return value) + */ + public function enterNode(Node $node); + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeTraverser::REMOVE_NODE + * => $node is removed from the parent array + * * NodeTraverser::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function leaveNode(Node $node); + /** + * Called once after traversal. + * + * Return value semantics: + * * null: $nodes stays as-is + * * otherwise: $nodes is set to the return value + * + * @param Node[] $nodes Array of nodes + * + * @return null|Node[] Array of nodes + */ + public function afterTraverse(array $nodes); +} +pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right); + $node = clone $origNode; + $node->setAttribute('origNode', $origNode); + return $node; } - protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) +} +pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right); + $this->filterCallback = $filterCallback; } - protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) + /** + * Get found nodes satisfying the filter callback. + * + * Nodes are returned in pre-order. + * + * @return Node[] Found nodes + */ + public function getFoundNodes() : array { - return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right); + return $this->foundNodes; } - protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) + public function beforeTraverse(array $nodes) { - return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right); + $this->foundNodes = []; + return null; } - protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) + public function enterNode(Node $node) { - return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right); + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + return null; } - protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) +} +pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right); + $this->filterCallback = $filterCallback; } - protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) + /** + * Get found node satisfying the filter callback. + * + * Returns null if no node satisfies the filter callback. + * + * @return null|Node Found node (or null if not found) + */ + public function getFoundNode() { - return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right); + return $this->foundNode; } - protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) + public function beforeTraverse(array $nodes) { - return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right); + $this->foundNode = null; + return null; } - protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) + public function enterNode(Node $node) { - return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right); + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeTraverser::STOP_TRAVERSAL; + } + return null; } - protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) +} +pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right); + $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing()); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? \false; + $this->replaceNodes = $options['replaceNodes'] ?? \true; } - protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) + /** + * Get name resolution context. + * + * @return NameContext + */ + public function getNameContext() : NameContext { - return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right); + return $this->nameContext; } - protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) + public function beforeTraverse(array $nodes) { - return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right); + $this->nameContext->startNamespace(); + return null; } - protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) + public function enterNode(Node $node) { - return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right); + if ($node instanceof Stmt\Namespace_) { + $this->nameContext->startNamespace($node->name); + } elseif ($node instanceof Stmt\Use_) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, null); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, $node->prefix); + } + } elseif ($node instanceof Stmt\Class_) { + if (null !== $node->extends) { + $node->extends = $this->resolveClassName($node->extends); + } + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends as &$interface) { + $interface = $this->resolveClassName($interface); + } + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Enum_) { + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } + } elseif ($node instanceof Stmt\Trait_) { + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Function_) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\ClassMethod || $node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Property) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $this->addNamespacedName($const); + } + } else { + if ($node instanceof Stmt\ClassConst) { + $this->resolveAttrGroups($node); + } else { + if ($node instanceof Stmt\EnumCase) { + $this->resolveAttrGroups($node); + } elseif ($node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\ClassConstFetch || $node instanceof Expr\New_ || $node instanceof Expr\Instanceof_) { + if ($node->class instanceof Name) { + $node->class = $this->resolveClassName($node->class); + } + } elseif ($node instanceof Stmt\Catch_) { + foreach ($node->types as &$type) { + $type = $this->resolveClassName($type); + } + } elseif ($node instanceof Expr\FuncCall) { + if ($node->name instanceof Name) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); + } + } elseif ($node instanceof Expr\ConstFetch) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); + } elseif ($node instanceof Stmt\TraitUse) { + foreach ($node->traits as &$trait) { + $trait = $this->resolveClassName($trait); + } + foreach ($node->adaptations as $adaptation) { + if (null !== $adaptation->trait) { + $adaptation->trait = $this->resolveClassName($adaptation->trait); + } + if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + foreach ($adaptation->insteadof as &$insteadof) { + $insteadof = $this->resolveClassName($insteadof); + } + } + } + } + } + } + return null; } - protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) + private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) { - return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right); + // Add prefix for group uses + $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; + // Type is determined either by individual element or whole use declaration + $type |= $use->type; + $this->nameContext->addAlias($name, (string) $use->getAlias(), $type, $use->getAttributes()); } - protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) + /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ + private function resolveSignature($node) { - return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right); + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); } - protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) + private function resolveType($node) { - return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right); + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; } - protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) + /** + * Resolve name, according to name resolver options. + * + * @param Name $name Function or constant name to resolve + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name Resolved name, or original name with attribute + */ + protected function resolveName(Name $name, int $type) : Name { - return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right); + if (!$this->replaceNodes) { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName); + } else { + $name->setAttribute('namespacedName', FullyQualified::concat($this->nameContext->getNamespace(), $name, $name->getAttributes())); + } + return $name; + } + if ($this->preserveOriginalNames) { + // Save the original name + $originalName = $name; + $name = clone $originalName; + $name->setAttribute('originalName', $originalName); + } + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + return $resolvedName; + } + // unqualified names inside a namespace cannot be resolved at compile-time + // add the namespaced version of the name as an attribute + $name->setAttribute('namespacedName', FullyQualified::concat($this->nameContext->getNamespace(), $name, $name->getAttributes())); + return $name; } - protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) + protected function resolveClassName(Name $name) { - return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right); - } - protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) - { - return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right); + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); } - protected function pExpr_Instanceof(Expr\Instanceof_ $node) + protected function addNamespacedName(Node $node) { - list($precedence, $associativity) = $this->precedenceMap[Expr\Instanceof_::class]; - return $this->pPrec($node->expr, $precedence, $associativity, -1) . ' instanceof ' . $this->pNewVariable($node->class); + $node->namespacedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name); } - // Unary expressions - protected function pExpr_BooleanNot(Expr\BooleanNot $node) + protected function resolveAttrGroups(Node $node) { - return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr); + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } } - protected function pExpr_BitwiseNot(Expr\BitwiseNot $node) +} +$node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + /** + * @var ?Node + */ + private $previous; + public function beforeTraverse(array $nodes) { - return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr); + $this->stack = []; + $this->previous = null; } - protected function pExpr_UnaryMinus(Expr\UnaryMinus $node) + public function enterNode(Node $node) { - if ($node->expr instanceof Expr\UnaryMinus || $node->expr instanceof Expr\PreDec) { - // Enforce -(-$expr) instead of --$expr - return '-(' . $this->p($node->expr) . ')'; + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[\count($this->stack) - 1]); } - return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr); - } - protected function pExpr_UnaryPlus(Expr\UnaryPlus $node) - { - if ($node->expr instanceof Expr\UnaryPlus || $node->expr instanceof Expr\PreInc) { - // Enforce +(+$expr) instead of ++$expr - return '+(' . $this->p($node->expr) . ')'; + if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); } - return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr); - } - protected function pExpr_PreInc(Expr\PreInc $node) - { - return $this->pPrefixOp(Expr\PreInc::class, '++', $node->var); - } - protected function pExpr_PreDec(Expr\PreDec $node) - { - return $this->pPrefixOp(Expr\PreDec::class, '--', $node->var); - } - protected function pExpr_PostInc(Expr\PostInc $node) - { - return $this->pPostfixOp(Expr\PostInc::class, $node->var, '++'); - } - protected function pExpr_PostDec(Expr\PostDec $node) - { - return $this->pPostfixOp(Expr\PostDec::class, $node->var, '--'); - } - protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) - { - return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr); - } - protected function pExpr_YieldFrom(Expr\YieldFrom $node) - { - return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr); + $this->stack[] = $node; } - protected function pExpr_Print(Expr\Print_ $node) + public function leaveNode(Node $node) { - return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr); + $this->previous = $node; + \array_pop($this->stack); } - // Casts - protected function pExpr_Cast_Int(Cast\Int_ $node) +} +$node->getAttribute('parent'). + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + public function beforeTraverse(array $nodes) { - return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr); + $this->stack = []; } - protected function pExpr_Cast_Double(Cast\Double $node) + public function enterNode(Node $node) { - $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); - if ($kind === Cast\Double::KIND_DOUBLE) { - $cast = '(double)'; - } elseif ($kind === Cast\Double::KIND_FLOAT) { - $cast = '(float)'; - } elseif ($kind === Cast\Double::KIND_REAL) { - $cast = '(real)'; + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); } - return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr); - } - protected function pExpr_Cast_String(Cast\String_ $node) - { - return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr); - } - protected function pExpr_Cast_Array(Cast\Array_ $node) - { - return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr); - } - protected function pExpr_Cast_Object(Cast\Object_ $node) - { - return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr); - } - protected function pExpr_Cast_Bool(Cast\Bool_ $node) - { - return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr); - } - protected function pExpr_Cast_Unset(Cast\Unset_ $node) - { - return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr); - } - // Function calls and similar constructs - protected function pExpr_FuncCall(Expr\FuncCall $node) - { - return $this->pCallLhs($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; - } - protected function pExpr_MethodCall(Expr\MethodCall $node) - { - return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; - } - protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node) - { - return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; - } - protected function pExpr_StaticCall(Expr\StaticCall $node) - { - return $this->pDereferenceLhs($node->class) . '::' . ($node->name instanceof Expr ? $node->name instanceof Expr\Variable ? $this->p($node->name) : '{' . $this->p($node->name) . '}' : $node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; - } - protected function pExpr_Empty(Expr\Empty_ $node) - { - return 'empty(' . $this->p($node->expr) . ')'; + $this->stack[] = $node; } - protected function pExpr_Isset(Expr\Isset_ $node) + public function leaveNode(Node $node) { - return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + array_pop($this->stack); } - protected function pExpr_Eval(Expr\Eval_ $node) +} +p($node->expr) . ')'; + return null; } - protected function pExpr_Include(Expr\Include_ $node) + public function enterNode(Node $node) { - static $map = [Expr\Include_::TYPE_INCLUDE => 'include', Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', Expr\Include_::TYPE_REQUIRE => 'require', Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once']; - return $map[$node->type] . ' ' . $this->p($node->expr); + return null; } - protected function pExpr_List(Expr\List_ $node) + public function leaveNode(Node $node) { - return 'list(' . $this->pCommaSeparated($node->items) . ')'; + return null; } - // Other - protected function pExpr_Error(Expr\Error $node) + public function afterTraverse(array $nodes) { - throw new \LogicException('Cannot pretty-print AST with Error nodes'); + return null; } - protected function pExpr_Variable(Expr\Variable $node) +} +name instanceof Expr) { - return '${' . $this->p($node->name) . '}'; - } else { - return '$' . $node->name; - } + $this->parsers = $parsers; } - protected function pExpr_Array(Expr\Array_ $node) + public function parse(string $code, ErrorHandler $errorHandler = null) { - $syntax = $node->getAttribute('kind', $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); - if ($syntax === Expr\Array_::KIND_SHORT) { - return '[' . $this->pMaybeMultiline($node->items, \true) . ']'; - } else { - return 'array(' . $this->pMaybeMultiline($node->items, \true) . ')'; + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing(); } - } - protected function pExpr_ArrayItem(Expr\ArrayItem $node) - { - return (null !== $node->key ? $this->p($node->key) . ' => ' : '') . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value); - } - protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) - { - return $this->pDereferenceLhs($node->var) . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; - } - protected function pExpr_ConstFetch(Expr\ConstFetch $node) - { - return $this->p($node->name); - } - protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) - { - return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name); - } - protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) - { - return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); - } - protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node) - { - return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); - } - protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) - { - return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); - } - protected function pExpr_ShellExec(Expr\ShellExec $node) - { - return '`' . $this->pEncapsList($node->parts, '`') . '`'; - } - protected function pExpr_Closure(Expr\Closure $node) - { - return $this->pAttrGroups($node->attrGroups, \true) . ($node->static ? 'static ' : '') . 'function ' . ($node->byRef ? '&' : '') . '(' . $this->pCommaSeparated($node->params) . ')' . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '') . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pExpr_Match(Expr\Match_ $node) - { - return 'match (' . $this->p($node->cond) . ') {' . $this->pCommaSeparatedMultiline($node->arms, \true) . $this->nl . '}'; - } - protected function pMatchArm(Node\MatchArm $node) - { - return ($node->conds ? $this->pCommaSeparated($node->conds) : 'default') . ' => ' . $this->p($node->body); - } - protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) - { - return $this->pAttrGroups($node->attrGroups, \true) . ($node->static ? 'static ' : '') . 'fn' . ($node->byRef ? '&' : '') . '(' . $this->pCommaSeparated($node->params) . ')' . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . ' => ' . $this->p($node->expr); - } - protected function pExpr_ClosureUse(Expr\ClosureUse $node) - { - return ($node->byRef ? '&' : '') . $this->p($node->var); - } - protected function pExpr_New(Expr\New_ $node) - { - if ($node->class instanceof Stmt\Class_) { - $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; - return 'new ' . $this->pClassCommon($node->class, $args); + list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code); + if ($firstError === null) { + return $firstStmts; } - return 'new ' . $this->pNewVariable($node->class) . '(' . $this->pMaybeMultiline($node->args) . ')'; - } - protected function pExpr_Clone(Expr\Clone_ $node) - { - return 'clone ' . $this->p($node->expr); - } - protected function pExpr_Ternary(Expr\Ternary $node) - { - // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. - // this is okay because the part between ? and : never needs parentheses. - return $this->pInfixOp(Expr\Ternary::class, $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else); - } - protected function pExpr_Exit(Expr\Exit_ $node) - { - $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); - return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); - } - protected function pExpr_Throw(Expr\Throw_ $node) - { - return 'throw ' . $this->p($node->expr); - } - protected function pExpr_Yield(Expr\Yield_ $node) - { - if ($node->value === null) { - return 'yield'; - } else { - // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary - return '(yield ' . ($node->key !== null ? $this->p($node->key) . ' => ' : '') . $this->p($node->value) . ')'; + for ($i = 1, $c = \count($this->parsers); $i < $c; ++$i) { + list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code); + if ($error === null) { + return $stmts; + } } + throw $firstError; } - // Declarations - protected function pStmt_Namespace(Stmt\Namespace_ $node) + private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) { - if ($this->canUseSemicolonNamespaces) { - return 'namespace ' . $this->p($node->name) . ';' . $this->nl . $this->pStmts($node->stmts, \false); - } else { - return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + $stmts = null; + $error = null; + try { + $stmts = $parser->parse($code, $errorHandler); + } catch (Error $error) { } + return [$stmts, $error]; } - protected function pStmt_Use(Stmt\Use_ $node) - { - return 'use ' . $this->pUseType($node->type) . $this->pCommaSeparated($node->uses) . ';'; - } - protected function pStmt_GroupUse(Stmt\GroupUse $node) - { - return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) . '\\{' . $this->pCommaSeparated($node->uses) . '};'; - } - protected function pStmt_UseUse(Stmt\UseUse $node) - { - return $this->pUseType($node->type) . $this->p($node->name) . (null !== $node->alias ? ' as ' . $node->alias : ''); - } - protected function pUseType($type) - { - return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); - } - protected function pStmt_Interface(Stmt\Interface_ $node) - { - return $this->pAttrGroups($node->attrGroups) . 'interface ' . $node->name . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Enum(Stmt\Enum_ $node) - { - return $this->pAttrGroups($node->attrGroups) . 'enum ' . $node->name . ($node->scalarType ? " : {$node->scalarType}" : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Class(Stmt\Class_ $node) - { - return $this->pClassCommon($node, ' ' . $node->name); - } - protected function pStmt_Trait(Stmt\Trait_ $node) - { - return $this->pAttrGroups($node->attrGroups) . 'trait ' . $node->name . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_EnumCase(Stmt\EnumCase $node) - { - return $this->pAttrGroups($node->attrGroups) . 'case ' . $node->name . ($node->expr ? ' = ' . $this->p($node->expr) : '') . ';'; - } - protected function pStmt_TraitUse(Stmt\TraitUse $node) - { - return 'use ' . $this->pCommaSeparated($node->traits) . (empty($node->adaptations) ? ';' : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); - } - protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) - { - return $this->p($node->trait) . '::' . $node->method . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; - } - protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) - { - return (null !== $node->trait ? $this->p($node->trait) . '::' : '') . $node->method . ' as' . (null !== $node->newModifier ? ' ' . \rtrim($this->pModifiers($node->newModifier), ' ') : '') . (null !== $node->newName ? ' ' . $node->newName : '') . ';'; - } - protected function pStmt_Property(Stmt\Property $node) - { - return $this->pAttrGroups($node->attrGroups) . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) . ($node->type ? $this->p($node->type) . ' ' : '') . $this->pCommaSeparated($node->props) . ';'; - } - protected function pStmt_PropertyProperty(Stmt\PropertyProperty $node) - { - return '$' . $node->name . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); - } - protected function pStmt_ClassMethod(Stmt\ClassMethod $node) - { - return $this->pAttrGroups($node->attrGroups) . $this->pModifiers($node->flags) . 'function ' . ($node->byRef ? '&' : '') . $node->name . '(' . $this->pMaybeMultiline($node->params) . ')' . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . (null !== $node->stmts ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); - } - protected function pStmt_ClassConst(Stmt\ClassConst $node) - { - return $this->pAttrGroups($node->attrGroups) . $this->pModifiers($node->flags) . 'const ' . $this->pCommaSeparated($node->consts) . ';'; - } - protected function pStmt_Function(Stmt\Function_ $node) - { - return $this->pAttrGroups($node->attrGroups) . 'function ' . ($node->byRef ? '&' : '') . $node->name . '(' . $this->pCommaSeparated($node->params) . ')' . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Const(Stmt\Const_ $node) - { - return 'const ' . $this->pCommaSeparated($node->consts) . ';'; - } - protected function pStmt_Declare(Stmt\Declare_ $node) - { - return 'declare (' . $this->pCommaSeparated($node->declares) . ')' . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); - } - protected function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) - { - return $node->key . '=' . $this->p($node->value); - } - // Control flow - protected function pStmt_If(Stmt\If_ $node) - { - return 'if (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}' . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') . (null !== $node->else ? ' ' . $this->p($node->else) : ''); - } - protected function pStmt_ElseIf(Stmt\ElseIf_ $node) - { - return 'elseif (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Else(Stmt\Else_ $node) +} +'", "T_IS_GREATER_OR_EQUAL", "T_SL", "T_SR", "'+'", "'-'", "'.'", "'*'", "'/'", "'%'", "'!'", "T_INSTANCEOF", "'~'", "T_INC", "T_DEC", "T_INT_CAST", "T_DOUBLE_CAST", "T_STRING_CAST", "T_ARRAY_CAST", "T_OBJECT_CAST", "T_BOOL_CAST", "T_UNSET_CAST", "'@'", "T_POW", "'['", "T_NEW", "T_CLONE", "T_EXIT", "T_IF", "T_ELSEIF", "T_ELSE", "T_ENDIF", "T_LNUMBER", "T_DNUMBER", "T_STRING", "T_STRING_VARNAME", "T_VARIABLE", "T_NUM_STRING", "T_INLINE_HTML", "T_ENCAPSED_AND_WHITESPACE", "T_CONSTANT_ENCAPSED_STRING", "T_ECHO", "T_DO", "T_WHILE", "T_ENDWHILE", "T_FOR", "T_ENDFOR", "T_FOREACH", "T_ENDFOREACH", "T_DECLARE", "T_ENDDECLARE", "T_AS", "T_SWITCH", "T_MATCH", "T_ENDSWITCH", "T_CASE", "T_DEFAULT", "T_BREAK", "T_CONTINUE", "T_GOTO", "T_FUNCTION", "T_FN", "T_CONST", "T_RETURN", "T_TRY", "T_CATCH", "T_FINALLY", "T_USE", "T_INSTEADOF", "T_GLOBAL", "T_STATIC", "T_ABSTRACT", "T_FINAL", "T_PRIVATE", "T_PROTECTED", "T_PUBLIC", "T_VAR", "T_UNSET", "T_ISSET", "T_EMPTY", "T_HALT_COMPILER", "T_CLASS", "T_TRAIT", "T_INTERFACE", "T_EXTENDS", "T_IMPLEMENTS", "T_OBJECT_OPERATOR", "T_LIST", "T_ARRAY", "T_CALLABLE", "T_CLASS_C", "T_TRAIT_C", "T_METHOD_C", "T_FUNC_C", "T_LINE", "T_FILE", "T_START_HEREDOC", "T_END_HEREDOC", "T_DOLLAR_OPEN_CURLY_BRACES", "T_CURLY_OPEN", "T_PAAMAYIM_NEKUDOTAYIM", "T_NAMESPACE", "T_NS_C", "T_DIR", "T_NS_SEPARATOR", "T_ELLIPSIS", "T_NAME_FULLY_QUALIFIED", "T_NAME_QUALIFIED", "T_NAME_RELATIVE", "';'", "'{'", "'}'", "'('", "')'", "'\$'", "'`'", "']'", "'\"'", "T_READONLY", "T_ENUM", "T_NULLSAFE_OBJECT_OPERATOR", "T_ATTRIBUTE"); + protected $tokenToSymbol = array(0, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 56, 163, 168, 160, 55, 168, 168, 158, 159, 53, 50, 8, 51, 52, 54, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 31, 155, 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 70, 168, 162, 36, 168, 161, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 156, 35, 157, 58, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 164, 122, 123, 124, 125, 126, 127, 128, 129, 165, 130, 131, 132, 166, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 167); + protected $action = array(699, 669, 670, 671, 672, 673, 286, 674, 675, 676, 712, 713, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 0, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, 245, 246, 242, 243, 244, -32766, -32766, 677, -32766, 750, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 1224, 245, 246, 1225, 678, 679, 680, 681, 682, 683, 684, -32766, 48, 746, -32766, -32766, -32766, -32766, -32766, -32766, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 715, 738, 716, 717, 718, 719, 707, 708, 709, 737, 710, 711, 696, 697, 698, 700, 701, 702, 740, 741, 742, 743, 744, 745, 703, 704, 705, 706, 736, 727, 725, 726, 722, 723, 751, 714, 720, 721, 728, 729, 731, 730, 732, 733, 55, 56, 425, 57, 58, 724, 735, 734, 1073, 59, 60, -224, 61, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 121, -32767, -32767, -32767, -32767, 29, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 1043, 766, 1071, 767, 580, 62, 63, -32766, -32766, -32766, -32766, 64, 516, 65, 294, 295, 66, 67, 68, 69, 70, 71, 72, 73, 822, 25, 302, 74, 418, 981, 983, 1043, 1181, 1095, 1096, 1073, 748, 754, 1075, 1074, 1076, 469, -32766, -32766, -32766, 337, 823, 54, -32767, -32767, -32767, -32767, 98, 99, 100, 101, 102, 220, 221, 222, 78, 361, 1107, -32766, 341, -32766, -32766, -32766, -32766, -32766, 1107, 492, 949, 950, 951, 948, 947, 946, 207, 477, 478, 949, 950, 951, 948, 947, 946, 1043, 479, 480, 52, 1101, 1102, 1103, 1104, 1098, 1099, 319, 872, 668, 667, 27, -511, 1105, 1100, -32766, 130, 1075, 1074, 1076, 345, 668, 667, 41, 126, 341, 334, 369, 336, 426, -128, -128, -128, 896, 897, 468, 220, 221, 222, 811, 1195, 619, 40, 21, 427, -128, 470, -128, 471, -128, 472, -128, 802, 428, -4, 823, 54, 207, 33, 34, 429, 360, 317, 28, 35, 473, -32766, -32766, -32766, 211, 356, 357, 474, 475, -32766, -32766, -32766, 754, 476, 49, 313, 794, 843, 430, 431, 289, 125, -32766, 813, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, -32767, -32766, -32766, -32766, 769, 103, 104, 105, 327, 307, 825, 633, -128, 1075, 1074, 1076, 221, 222, 927, 748, 1146, 106, -32766, 129, -32766, -32766, -32766, -32766, 426, 823, 54, 902, 873, 302, 468, 75, 207, 359, 811, 668, 667, 40, 21, 427, 754, 470, 754, 471, 423, 472, 1043, 127, 428, 435, 1043, 341, 1043, 33, 34, 429, 360, 1181, 415, 35, 473, 122, 10, 315, 128, 356, 357, 474, 475, -32766, -32766, -32766, 768, 476, 668, 667, 758, 843, 430, 431, 754, 1043, 1147, -32766, -32766, -32766, 754, 419, 342, 1215, -32766, 131, -32766, -32766, -32766, 341, 363, 346, 426, 823, 54, 100, 101, 102, 468, 825, 633, -4, 811, 442, 903, 40, 21, 427, 754, 470, 435, 471, 341, 472, 341, 766, 428, 767, -209, -209, -209, 33, 34, 429, 360, 479, 1196, 35, 473, 345, -32766, -32766, -32766, 356, 357, 474, 475, 220, 221, 222, 421, 476, 32, 297, 794, 843, 430, 431, 754, 754, 435, -32766, 341, -32766, -32766, 9, 300, 51, 207, 249, 324, 753, 120, 220, 221, 222, 426, 30, 247, 941, 422, 424, 468, 825, 633, -209, 811, 1043, 1061, 40, 21, 427, 129, 470, 207, 471, 341, 472, 804, 20, 428, 124, -208, -208, -208, 33, 34, 429, 360, 479, 212, 35, 473, 923, -259, 823, 54, 356, 357, 474, 475, -32766, -32766, -32766, 1043, 476, 213, 806, 794, 843, 430, 431, -32766, -32766, 435, 435, 341, 341, 443, 79, 80, 81, -32766, 668, 667, 636, 344, 808, 668, 667, 239, 240, 241, 123, 214, 538, 250, 825, 633, -208, 36, 251, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 252, 307, 426, 220, 221, 222, 823, 54, 468, -32766, 222, 765, 811, 106, 134, 40, 21, 427, 571, 470, 207, 471, 445, 472, 207, -32766, 428, 896, 897, 207, 307, 33, 34, 429, 245, 246, 637, 35, 473, 452, 22, 809, 922, 356, 357, 457, 588, 135, 374, 595, 596, 476, -228, 759, 639, 938, 653, 926, 661, -86, 823, 54, 314, 644, 647, 821, 133, 836, 43, 106, 603, 44, 45, 46, 47, 748, 50, 53, 132, 426, 302, -32766, 520, 825, 633, 468, -84, 607, 577, 811, 641, 362, 40, 21, 427, -278, 470, 754, 471, 954, 472, 441, 627, 428, 823, 54, 574, 844, 33, 34, 429, 11, 615, 845, 35, 473, 444, 461, 285, -511, 356, 357, 592, -419, 593, 1106, 1153, -410, 476, 368, 838, 38, 658, 426, 645, 795, 1052, 0, 325, 468, 0, -32766, 0, 811, 0, 0, 40, 21, 427, 0, 470, 0, 471, 0, 472, 0, 322, 428, 823, 54, 825, 633, 33, 34, 429, 0, 326, 0, 35, 473, 323, 0, 316, 318, 356, 357, -512, 426, 0, 753, 531, 0, 476, 468, 6, 0, 0, 811, 650, 7, 40, 21, 427, 12, 470, 14, 471, 373, 472, -420, 562, 428, 823, 54, 78, -225, 33, 34, 429, 39, 656, 657, 35, 473, 859, 633, 764, 812, 356, 357, 820, 799, 814, 875, 866, 867, 476, 797, 860, 857, 855, 426, 933, 934, 931, 819, 803, 468, 805, 807, 810, 811, 930, 762, 40, 21, 427, 763, 470, 932, 471, 335, 472, 358, 634, 428, 638, 640, 825, 633, 33, 34, 429, 642, 643, 646, 35, 473, 648, 649, 651, 652, 356, 357, 635, 426, 1221, 1223, 761, 842, 476, 468, 248, 760, 841, 811, 1222, 840, 40, 21, 427, 1057, 470, 830, 471, 1045, 472, 839, 1046, 428, 828, 215, 216, 939, 33, 34, 429, 217, 864, 218, 35, 473, 825, 633, 24, 865, 356, 357, 456, 1220, 1189, 209, 1187, 1172, 476, 1185, 215, 216, 1086, 1095, 1096, 914, 217, 1193, 218, 1183, -224, 1097, 26, 31, 37, 42, 76, 77, 210, 288, 209, 292, 293, 308, 309, 310, 311, 339, 1095, 1096, 825, 633, 355, 291, 416, 1152, 1097, 16, 17, 18, 393, 453, 460, 462, 466, 552, 624, 1048, 1051, 904, 1111, 1047, 1023, 563, 1022, 1088, 0, 0, -429, 558, 1041, 1101, 1102, 1103, 1104, 1098, 1099, 398, 1054, 1053, 1056, 1055, 1070, 1105, 1100, 1186, 1171, 1167, 1184, 1085, 1218, 1112, 1166, 219, 558, 599, 1101, 1102, 1103, 1104, 1098, 1099, 398, 0, 0, 0, 0, 0, 1105, 1100, 0, 0, 0, 0, 0, 0, 0, 0, 219); + protected $actionCheck = array(2, 3, 4, 5, 6, 7, 14, 9, 10, 11, 12, 13, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 0, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 9, 10, 11, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 69, 70, 53, 54, 55, 9, 10, 57, 30, 80, 32, 33, 34, 35, 36, 37, 38, 80, 69, 70, 83, 71, 72, 73, 74, 75, 76, 77, 9, 70, 80, 33, 34, 35, 36, 37, 38, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 153, 133, 134, 135, 136, 137, 138, 139, 140, 141, 3, 4, 5, 6, 7, 147, 148, 149, 80, 12, 13, 159, 15, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 156, 44, 45, 46, 47, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 13, 106, 116, 108, 85, 50, 51, 33, 34, 35, 36, 56, 85, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 1, 70, 71, 72, 73, 59, 60, 13, 82, 78, 79, 80, 80, 82, 152, 153, 154, 86, 9, 10, 11, 8, 1, 2, 44, 45, 46, 47, 48, 49, 50, 51, 52, 9, 10, 11, 156, 106, 143, 30, 160, 32, 33, 34, 35, 36, 143, 116, 116, 117, 118, 119, 120, 121, 30, 124, 125, 116, 117, 118, 119, 120, 121, 13, 133, 134, 70, 136, 137, 138, 139, 140, 141, 142, 31, 37, 38, 8, 132, 148, 149, 116, 156, 152, 153, 154, 160, 37, 38, 158, 8, 160, 161, 8, 163, 74, 75, 76, 77, 134, 135, 80, 9, 10, 11, 84, 1, 80, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 155, 98, 0, 1, 2, 30, 103, 104, 105, 106, 132, 8, 109, 110, 9, 10, 11, 8, 115, 116, 117, 118, 9, 10, 11, 82, 123, 70, 8, 126, 127, 128, 129, 8, 156, 30, 155, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 9, 10, 11, 157, 53, 54, 55, 8, 57, 155, 156, 157, 152, 153, 154, 10, 11, 157, 80, 162, 69, 30, 151, 32, 33, 34, 35, 74, 1, 2, 159, 155, 71, 80, 151, 30, 8, 84, 37, 38, 87, 88, 89, 82, 91, 82, 93, 8, 95, 13, 156, 98, 158, 13, 160, 13, 103, 104, 105, 106, 82, 108, 109, 110, 156, 8, 113, 31, 115, 116, 117, 118, 9, 10, 11, 157, 123, 37, 38, 126, 127, 128, 129, 82, 13, 159, 33, 34, 35, 82, 127, 8, 85, 30, 156, 32, 33, 34, 160, 8, 147, 74, 1, 2, 50, 51, 52, 80, 155, 156, 157, 84, 31, 159, 87, 88, 89, 82, 91, 158, 93, 160, 95, 160, 106, 98, 108, 100, 101, 102, 103, 104, 105, 106, 133, 159, 109, 110, 160, 9, 10, 11, 115, 116, 117, 118, 9, 10, 11, 8, 123, 144, 145, 126, 127, 128, 129, 82, 82, 158, 30, 160, 32, 33, 108, 8, 70, 30, 31, 113, 152, 16, 9, 10, 11, 74, 14, 14, 122, 8, 8, 80, 155, 156, 157, 84, 13, 159, 87, 88, 89, 151, 91, 30, 93, 160, 95, 155, 159, 98, 14, 100, 101, 102, 103, 104, 105, 106, 133, 16, 109, 110, 155, 157, 1, 2, 115, 116, 117, 118, 9, 10, 11, 13, 123, 16, 155, 126, 127, 128, 129, 33, 34, 158, 158, 160, 160, 156, 9, 10, 11, 30, 37, 38, 31, 70, 155, 37, 38, 50, 51, 52, 156, 16, 81, 16, 155, 156, 157, 30, 16, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 16, 57, 74, 9, 10, 11, 1, 2, 80, 116, 11, 155, 84, 69, 156, 87, 88, 89, 160, 91, 30, 93, 132, 95, 30, 33, 98, 134, 135, 30, 57, 103, 104, 105, 69, 70, 31, 109, 110, 75, 76, 155, 155, 115, 116, 75, 76, 101, 102, 111, 112, 123, 159, 155, 156, 155, 156, 155, 156, 31, 1, 2, 31, 31, 31, 31, 31, 38, 70, 69, 77, 70, 70, 70, 70, 80, 70, 70, 70, 74, 71, 85, 85, 155, 156, 80, 97, 96, 100, 84, 31, 106, 87, 88, 89, 82, 91, 82, 93, 82, 95, 89, 92, 98, 1, 2, 90, 127, 103, 104, 105, 97, 94, 127, 109, 110, 97, 97, 97, 132, 115, 116, 100, 146, 113, 143, 143, 146, 123, 106, 151, 155, 157, 74, 31, 157, 162, -1, 114, 80, -1, 116, -1, 84, -1, -1, 87, 88, 89, -1, 91, -1, 93, -1, 95, -1, 130, 98, 1, 2, 155, 156, 103, 104, 105, -1, 130, -1, 109, 110, 131, -1, 132, 132, 115, 116, 132, 74, -1, 152, 150, -1, 123, 80, 146, -1, -1, 84, 31, 146, 87, 88, 89, 146, 91, 146, 93, 146, 95, 146, 150, 98, 1, 2, 156, 159, 103, 104, 105, 155, 155, 155, 109, 110, 155, 156, 155, 155, 115, 116, 155, 155, 155, 155, 155, 155, 123, 155, 155, 155, 155, 74, 155, 155, 155, 155, 155, 80, 155, 155, 155, 84, 155, 155, 87, 88, 89, 155, 91, 155, 93, 156, 95, 156, 156, 98, 156, 156, 155, 156, 103, 104, 105, 156, 156, 156, 109, 110, 156, 156, 156, 156, 115, 116, 156, 74, 157, 157, 157, 157, 123, 80, 31, 157, 157, 84, 157, 157, 87, 88, 89, 157, 91, 157, 93, 157, 95, 157, 157, 98, 157, 50, 51, 157, 103, 104, 105, 56, 157, 58, 109, 110, 155, 156, 158, 157, 115, 116, 157, 157, 157, 70, 157, 157, 123, 157, 50, 51, 157, 78, 79, 157, 56, 157, 58, 157, 159, 86, 158, 158, 158, 158, 158, 158, 158, 158, 70, 158, 158, 158, 158, 158, 158, 158, 78, 79, 155, 156, 158, 160, 158, 163, 86, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, -1, -1, 161, 134, 161, 136, 137, 138, 139, 140, 141, 142, 162, 162, 162, 162, 162, 148, 149, 162, 162, 162, 162, 162, 162, 162, 162, 158, 134, 162, 136, 137, 138, 139, 140, 141, 142, -1, -1, -1, -1, -1, 148, 149, -1, -1, -1, -1, -1, -1, -1, -1, 158); + protected $actionBase = array(0, 227, 326, 400, 474, 233, 132, 132, 752, -2, -2, 138, -2, -2, -2, 663, 761, 815, 761, 586, 717, 859, 859, 859, 244, 256, 256, 256, 413, 583, 583, 880, 546, 169, 415, 444, 409, 200, 200, 200, 200, 137, 137, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 249, 205, 738, 559, 535, 739, 741, 742, 876, 679, 877, 820, 821, 693, 823, 824, 826, 829, 832, 819, 834, 907, 836, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 67, 536, 299, 510, 230, 44, 652, 652, 652, 652, 652, 652, 652, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 378, 584, 584, 584, 657, 909, 648, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 503, -21, -21, 436, 650, 364, 571, 215, 426, 156, 26, 26, 329, 329, 329, 329, 329, 46, 46, 5, 5, 5, 5, 152, 186, 186, 186, 186, 120, 120, 120, 120, 374, 374, 429, 448, 448, 334, 267, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 336, 427, 427, 572, 572, 408, 551, 551, 551, 551, 671, 171, 171, 391, 311, 311, 311, 109, 641, 856, 68, 68, 68, 68, 68, 68, 324, 324, 324, -3, -3, -3, 655, 77, 380, 77, 380, 683, 685, 86, 685, 654, -15, 516, 776, 281, 646, 809, 680, 816, 560, 711, 202, 578, 857, 643, -23, 578, 578, 578, 578, 857, 622, 628, 596, -23, 578, -23, 639, 454, 849, 351, 249, 558, 469, 631, 743, 514, 688, 746, 464, 544, 548, 556, 7, 412, 708, 750, 878, 879, 349, 702, 631, 631, 631, 327, 101, 7, -8, 623, 623, 623, 623, 219, 623, 623, 623, 623, 291, 430, 545, 401, 745, 653, 653, 675, 839, 814, 814, 653, 673, 653, 675, 841, 841, 841, 841, 653, 653, 653, 653, 814, 814, 667, 814, 275, 684, 694, 694, 841, 713, 714, 653, 653, 697, 814, 814, 814, 697, 687, 841, 669, 637, 333, 814, 841, 689, 673, 689, 653, 669, 689, 673, 673, 689, 22, 686, 656, 840, 842, 860, 756, 638, 644, 847, 848, 843, 845, 838, 692, 719, 720, 528, 659, 660, 661, 662, 696, 664, 698, 643, 658, 658, 658, 645, 701, 645, 658, 658, 658, 658, 658, 658, 658, 658, 632, 635, 709, 699, 670, 723, 566, 582, 758, 640, 636, 872, 865, 881, 883, 849, 870, 645, 890, 634, 288, 610, 850, 633, 753, 645, 851, 645, 759, 645, 873, 777, 666, 778, 779, 658, 874, 891, 892, 893, 894, 897, 898, 899, 900, 665, 901, 724, 674, 866, 344, 844, 639, 705, 677, 755, 725, 780, 372, 902, 784, 645, 645, 765, 706, 645, 766, 726, 712, 862, 727, 867, 903, 640, 678, 868, 645, 681, 785, 904, 372, 690, 651, 704, 649, 728, 858, 875, 853, 767, 612, 617, 787, 788, 792, 691, 730, 863, 864, 835, 731, 770, 642, 771, 676, 794, 772, 852, 732, 796, 798, 871, 647, 707, 682, 672, 668, 773, 799, 869, 733, 735, 736, 801, 737, 804, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, 137, 137, 137, -2, -2, -2, -2, 0, 0, -2, 0, 0, 0, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 0, 0, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 602, -21, -21, -21, -21, 602, -21, -21, -21, -21, -21, -21, -21, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, -21, 602, 602, 602, -21, 68, -21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 602, 0, 0, 602, -21, 602, -21, 602, -21, -21, 602, 602, 602, 602, 602, 602, 602, -21, -21, -21, -21, -21, -21, 0, 324, 324, 324, 324, -21, -21, -21, -21, 68, 68, 147, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 324, 324, -3, -3, 68, 68, 68, 68, 68, 147, 68, 68, -23, 673, 673, 673, 380, 380, 380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 380, -23, 0, -23, 0, 68, -23, 673, -23, 380, 673, 673, -23, 814, 604, 604, 604, 604, 372, 7, 0, 0, 673, 673, 0, 0, 0, 0, 0, 673, 0, 0, 0, 0, 0, 0, 814, 0, 653, 0, 0, 0, 0, 658, 288, 0, 677, 456, 0, 0, 0, 0, 0, 0, 677, 456, 530, 530, 0, 665, 658, 658, 658, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 372); + protected $actionDefault = array(3, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 540, 540, 495, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 297, 297, 297, 32767, 32767, 32767, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 32767, 32767, 32767, 32767, 32767, 32767, 381, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 387, 545, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 362, 363, 365, 366, 296, 548, 529, 245, 388, 544, 295, 247, 325, 499, 32767, 32767, 32767, 327, 122, 256, 201, 498, 125, 294, 232, 380, 382, 326, 301, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 300, 454, 359, 358, 357, 456, 32767, 455, 492, 492, 495, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 323, 483, 482, 324, 452, 328, 453, 331, 457, 460, 329, 330, 347, 348, 345, 346, 349, 458, 459, 476, 477, 474, 475, 299, 350, 351, 352, 353, 478, 479, 480, 481, 32767, 32767, 280, 539, 539, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 338, 339, 467, 468, 32767, 236, 236, 236, 236, 281, 236, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 333, 334, 332, 462, 463, 461, 428, 32767, 32767, 32767, 430, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 500, 32767, 32767, 32767, 32767, 32767, 513, 417, 171, 32767, 409, 32767, 171, 171, 171, 171, 32767, 220, 222, 167, 32767, 171, 32767, 486, 32767, 32767, 32767, 32767, 32767, 518, 343, 32767, 32767, 116, 32767, 32767, 32767, 555, 32767, 513, 32767, 116, 32767, 32767, 32767, 32767, 356, 335, 336, 337, 32767, 32767, 517, 511, 470, 471, 472, 473, 32767, 464, 465, 466, 469, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 425, 431, 431, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 516, 515, 32767, 410, 494, 186, 184, 184, 32767, 206, 206, 32767, 32767, 188, 487, 506, 32767, 188, 173, 32767, 398, 175, 494, 32767, 32767, 238, 32767, 238, 32767, 398, 238, 32767, 32767, 238, 32767, 411, 435, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 377, 378, 489, 502, 32767, 503, 32767, 409, 341, 342, 344, 320, 32767, 322, 367, 368, 369, 370, 371, 372, 373, 375, 32767, 415, 32767, 418, 32767, 32767, 32767, 255, 32767, 553, 32767, 32767, 304, 553, 32767, 32767, 32767, 547, 32767, 32767, 298, 32767, 32767, 32767, 32767, 251, 32767, 169, 32767, 537, 32767, 554, 32767, 511, 32767, 340, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 512, 32767, 32767, 32767, 32767, 227, 32767, 448, 32767, 116, 32767, 32767, 32767, 187, 32767, 32767, 302, 246, 32767, 32767, 546, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 114, 32767, 170, 32767, 32767, 32767, 189, 32767, 32767, 511, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 293, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 511, 32767, 32767, 231, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 411, 32767, 274, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 127, 127, 3, 127, 127, 258, 3, 258, 127, 258, 258, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 214, 217, 206, 206, 164, 127, 127, 266); + protected $goto = array(166, 140, 140, 140, 166, 187, 168, 144, 147, 141, 142, 143, 149, 163, 163, 163, 163, 144, 144, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 138, 159, 160, 161, 162, 184, 139, 185, 493, 494, 377, 495, 499, 500, 501, 502, 503, 504, 505, 506, 967, 164, 145, 146, 148, 171, 176, 186, 203, 253, 256, 258, 260, 263, 264, 265, 266, 267, 268, 269, 277, 278, 279, 280, 303, 304, 328, 329, 330, 394, 395, 396, 542, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 150, 151, 152, 167, 153, 169, 154, 204, 170, 155, 156, 157, 205, 158, 136, 620, 560, 756, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 1108, 628, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 757, 888, 888, 508, 1200, 1200, 400, 606, 508, 536, 536, 568, 532, 534, 534, 496, 498, 524, 540, 569, 572, 583, 590, 852, 852, 852, 852, 847, 853, 174, 585, 519, 600, 601, 177, 178, 179, 401, 402, 403, 404, 173, 202, 206, 208, 257, 259, 261, 262, 270, 271, 272, 273, 274, 275, 281, 282, 283, 284, 305, 306, 331, 332, 333, 406, 407, 408, 409, 175, 180, 254, 255, 181, 182, 183, 497, 497, 785, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 509, 578, 582, 626, 749, 509, 544, 545, 546, 547, 548, 549, 550, 551, 553, 586, 338, 559, 321, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 530, 349, 655, 555, 587, 352, 414, 591, 575, 604, 885, 611, 612, 881, 616, 617, 623, 625, 630, 632, 298, 296, 296, 296, 298, 290, 299, 944, 610, 816, 1170, 613, 436, 436, 375, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 1072, 1084, 1083, 945, 1065, 1072, 895, 895, 895, 895, 1178, 895, 895, 1212, 1212, 1178, 388, 858, 561, 755, 1072, 1072, 1072, 1072, 1072, 1072, 3, 4, 384, 384, 384, 1212, 874, 856, 854, 856, 654, 465, 511, 883, 878, 1089, 541, 384, 537, 384, 567, 384, 1026, 19, 15, 371, 384, 1226, 510, 1204, 1192, 1192, 1192, 510, 906, 372, 522, 533, 554, 912, 514, 1068, 1069, 13, 1065, 378, 912, 1158, 594, 23, 965, 386, 386, 386, 602, 1066, 1169, 1066, 937, 447, 449, 631, 752, 1177, 1067, 1109, 614, 935, 1177, 605, 1197, 391, 1211, 1211, 543, 892, 386, 1194, 1194, 1194, 399, 518, 1016, 901, 389, 771, 529, 752, 340, 752, 1211, 518, 518, 385, 781, 1214, 770, 772, 1063, 910, 774, 1058, 1176, 659, 953, 514, 782, 862, 915, 450, 573, 1155, 0, 463, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 513, 528, 0, 0, 0, 0, 513, 0, 528, 0, 350, 351, 0, 609, 512, 515, 438, 439, 1064, 618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 779, 1219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 301); + protected $gotoCheck = array(43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 57, 68, 15, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 126, 9, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 16, 76, 76, 68, 76, 76, 51, 51, 68, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 68, 68, 68, 68, 68, 68, 27, 66, 101, 66, 66, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 117, 117, 29, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 61, 61, 61, 6, 117, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 125, 57, 125, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 32, 71, 32, 32, 69, 69, 69, 32, 40, 40, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 5, 5, 5, 5, 5, 5, 97, 62, 50, 81, 62, 57, 57, 62, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 124, 124, 97, 81, 57, 57, 57, 57, 57, 118, 57, 57, 142, 142, 118, 12, 33, 12, 14, 57, 57, 57, 57, 57, 57, 30, 30, 13, 13, 13, 142, 14, 14, 14, 14, 14, 57, 14, 14, 14, 34, 2, 13, 109, 13, 2, 13, 34, 34, 34, 34, 13, 13, 122, 140, 9, 9, 9, 122, 83, 58, 58, 58, 34, 13, 13, 81, 81, 58, 81, 46, 13, 131, 127, 34, 101, 123, 123, 123, 34, 81, 81, 81, 8, 8, 8, 8, 11, 119, 81, 8, 8, 8, 119, 49, 138, 48, 141, 141, 47, 78, 123, 119, 119, 119, 123, 47, 102, 80, 17, 23, 9, 11, 18, 11, 141, 47, 47, 11, 23, 141, 23, 24, 115, 84, 25, 113, 119, 73, 99, 13, 26, 70, 85, 64, 65, 130, -1, 108, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, -1, -1, -1, -1, 9, -1, 9, -1, 71, 71, -1, 13, 9, 9, 9, 9, 13, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, 5); + protected $gotoBase = array(0, 0, -184, 0, 0, 356, 290, 0, 488, 149, 0, 182, 85, 118, 426, 112, 203, 179, 208, 0, 0, 0, 0, 162, 190, 198, 120, 27, 0, 272, -224, 0, -274, 406, 32, 0, 0, 0, 0, 0, 330, 0, 0, -24, 0, 0, 440, 485, 213, 218, 371, -74, 0, 0, 0, 0, 0, 107, 110, 0, 0, -11, -72, 0, 104, 95, -405, 0, -94, 41, 119, -82, 0, 164, 0, 0, -79, 0, 197, 0, 204, 43, 0, 441, 171, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 115, 0, 195, 210, 0, 0, 0, 0, 0, 86, 427, 259, 0, 0, 116, 0, 174, 0, -5, 117, 196, 0, 0, 161, 170, 93, -21, -48, 273, 0, 0, 91, 271, 0, 0, 0, 0, 0, 0, 216, 0, 437, 187, 102, 0, 0); + protected $gotoDefault = array(-32768, 467, 663, 2, 664, 834, 739, 747, 597, 481, 629, 581, 380, 1188, 791, 792, 793, 381, 367, 482, 379, 410, 405, 780, 773, 775, 783, 172, 411, 786, 1, 788, 517, 824, 1017, 364, 796, 365, 589, 798, 526, 800, 801, 137, 382, 383, 527, 483, 390, 576, 815, 276, 387, 817, 366, 818, 827, 370, 464, 454, 459, 556, 608, 432, 446, 570, 564, 535, 1081, 565, 861, 348, 869, 660, 877, 880, 484, 557, 891, 451, 899, 1094, 397, 905, 911, 916, 287, 919, 417, 412, 584, 924, 925, 5, 929, 621, 622, 8, 312, 952, 598, 966, 420, 1036, 1038, 485, 486, 521, 458, 507, 525, 487, 1059, 440, 413, 1062, 488, 489, 433, 434, 1078, 354, 1163, 353, 448, 320, 1150, 579, 1113, 455, 1203, 1159, 347, 490, 491, 376, 1182, 392, 1198, 437, 1205, 1213, 343, 539, 566); + protected $ruleToNonTerminal = array(0, 1, 3, 3, 2, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, 11, 12, 12, 13, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 18, 18, 19, 19, 21, 21, 17, 17, 22, 22, 23, 23, 24, 24, 25, 25, 20, 20, 26, 28, 28, 29, 30, 30, 32, 31, 31, 31, 31, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 14, 14, 54, 54, 56, 55, 55, 48, 48, 58, 58, 59, 59, 60, 60, 15, 16, 16, 16, 63, 63, 63, 64, 64, 67, 67, 65, 65, 69, 69, 41, 41, 50, 50, 53, 53, 53, 52, 52, 70, 42, 42, 42, 42, 71, 71, 72, 72, 73, 73, 39, 39, 35, 35, 74, 37, 37, 75, 36, 36, 38, 38, 49, 49, 49, 61, 61, 77, 77, 78, 78, 80, 80, 80, 79, 79, 62, 62, 81, 81, 81, 82, 82, 83, 83, 83, 44, 44, 84, 84, 84, 45, 45, 85, 85, 86, 86, 66, 87, 87, 87, 87, 92, 92, 93, 93, 94, 94, 94, 94, 94, 95, 96, 96, 91, 91, 88, 88, 90, 90, 98, 98, 97, 97, 97, 97, 97, 97, 89, 89, 100, 99, 99, 46, 46, 40, 40, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 34, 34, 47, 47, 105, 105, 106, 106, 106, 106, 112, 101, 101, 108, 108, 114, 114, 115, 116, 116, 116, 116, 116, 116, 68, 68, 57, 57, 57, 57, 102, 102, 120, 120, 117, 117, 121, 121, 121, 121, 103, 103, 103, 107, 107, 107, 113, 113, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 27, 27, 27, 27, 27, 27, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 111, 111, 104, 104, 104, 104, 127, 127, 130, 130, 129, 129, 131, 131, 51, 51, 51, 51, 133, 133, 132, 132, 132, 132, 132, 134, 134, 119, 119, 122, 122, 118, 118, 136, 135, 135, 135, 135, 123, 123, 123, 123, 110, 110, 124, 124, 124, 124, 76, 137, 137, 138, 138, 138, 109, 109, 139, 139, 140, 140, 140, 140, 140, 125, 125, 125, 125, 142, 143, 141, 141, 141, 141, 141, 141, 141, 144, 144, 144); + protected $ruleToLength = array(1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 4, 3, 4, 2, 3, 1, 1, 7, 6, 3, 1, 3, 1, 3, 1, 1, 3, 1, 3, 1, 2, 3, 1, 3, 3, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 5, 8, 3, 5, 9, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 1, 2, 2, 5, 7, 9, 5, 6, 3, 3, 2, 2, 1, 1, 1, 0, 2, 8, 0, 4, 1, 3, 0, 1, 0, 1, 0, 1, 10, 7, 6, 5, 1, 2, 2, 0, 2, 0, 2, 0, 2, 1, 3, 1, 4, 1, 4, 1, 1, 4, 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, 3, 1, 1, 1, 4, 0, 2, 3, 0, 2, 4, 0, 2, 0, 3, 1, 2, 1, 1, 0, 1, 3, 4, 6, 1, 1, 1, 0, 1, 0, 2, 2, 3, 3, 1, 3, 1, 2, 2, 3, 1, 1, 2, 4, 3, 1, 1, 3, 2, 0, 1, 3, 3, 9, 3, 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, 1, 1, 3, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 3, 1, 0, 1, 1, 3, 3, 4, 4, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 5, 4, 3, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 3, 2, 1, 2, 10, 11, 3, 3, 2, 4, 4, 3, 4, 4, 4, 4, 7, 3, 2, 0, 4, 1, 3, 2, 2, 4, 6, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 4, 4, 0, 2, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 3, 1, 4, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 4, 3, 1, 3, 1, 1, 3, 3, 0, 2, 0, 1, 3, 1, 3, 1, 1, 1, 1, 1, 6, 4, 3, 4, 2, 4, 4, 1, 3, 1, 2, 1, 1, 4, 1, 1, 3, 6, 4, 4, 4, 4, 1, 4, 0, 1, 1, 3, 1, 1, 4, 3, 1, 1, 1, 0, 0, 2, 3, 1, 3, 1, 4, 2, 2, 2, 2, 1, 2, 1, 1, 1, 4, 3, 3, 3, 6, 3, 1, 1, 1); + protected function initReduceCallbacks() { - return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_For(Stmt\For_ $node) - { - return 'for (' . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') . $this->pCommaSeparated($node->loop) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Foreach(Stmt\Foreach_ $node) - { - return 'foreach (' . $this->p($node->expr) . ' as ' . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_While(Stmt\While_ $node) - { - return 'while (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Do(Stmt\Do_ $node) - { - return 'do {' . $this->pStmts($node->stmts) . $this->nl . '} while (' . $this->p($node->cond) . ');'; - } - protected function pStmt_Switch(Stmt\Switch_ $node) - { - return 'switch (' . $this->p($node->cond) . ') {' . $this->pStmts($node->cases) . $this->nl . '}'; - } - protected function pStmt_Case(Stmt\Case_ $node) - { - return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' . $this->pStmts($node->stmts); - } - protected function pStmt_TryCatch(Stmt\TryCatch $node) - { - return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); - } - protected function pStmt_Catch(Stmt\Catch_ $node) - { - return 'catch (' . $this->pImplode($node->types, '|') . ($node->var !== null ? ' ' . $this->p($node->var) : '') . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Finally(Stmt\Finally_ $node) - { - return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pStmt_Break(Stmt\Break_ $node) - { - return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; - } - protected function pStmt_Continue(Stmt\Continue_ $node) - { - return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; - } - protected function pStmt_Return(Stmt\Return_ $node) - { - return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; - } - protected function pStmt_Throw(Stmt\Throw_ $node) - { - return 'throw ' . $this->p($node->expr) . ';'; - } - protected function pStmt_Label(Stmt\Label $node) - { - return $node->name . ':'; - } - protected function pStmt_Goto(Stmt\Goto_ $node) - { - return 'goto ' . $node->name . ';'; - } - // Other - protected function pStmt_Expression(Stmt\Expression $node) - { - return $this->p($node->expr) . ';'; - } - protected function pStmt_Echo(Stmt\Echo_ $node) - { - return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; - } - protected function pStmt_Static(Stmt\Static_ $node) - { - return 'static ' . $this->pCommaSeparated($node->vars) . ';'; - } - protected function pStmt_Global(Stmt\Global_ $node) - { - return 'global ' . $this->pCommaSeparated($node->vars) . ';'; - } - protected function pStmt_StaticVar(Stmt\StaticVar $node) - { - return $this->p($node->var) . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); - } - protected function pStmt_Unset(Stmt\Unset_ $node) - { - return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; - } - protected function pStmt_InlineHTML(Stmt\InlineHTML $node) - { - $newline = $node->getAttribute('hasLeadingNewline', \true) ? "\n" : ''; - return '?>' . $newline . $node->value . 'remaining; - } - protected function pStmt_Nop(Stmt\Nop $node) - { - return ''; - } - // Helpers - protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) - { - return $this->pAttrGroups($node->attrGroups, $node->name === null) . $this->pModifiers($node->flags) . 'class' . $afterClassToken . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; - } - protected function pObjectProperty($node) - { - if ($node instanceof Expr) { - return '{' . $this->p($node) . '}'; - } else { - return $node; - } - } - protected function pEncapsList(array $encapsList, $quote) - { - $return = ''; - foreach ($encapsList as $element) { - if ($element instanceof Scalar\EncapsedStringPart) { - $return .= $this->escapeString($element->value, $quote); + $this->reduceCallbacks = [0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos - (1 - 1)]); + }, 2 => function ($stackPos) { + if (\is_array($this->semStack[$stackPos - (2 - 2)])) { + $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); } else { - $return .= '{' . $this->p($element) . '}'; + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; } - } - return $return; - } - protected function pSingleQuotedString(string $string) - { - return '\'' . \addcslashes($string, '\'\\') . '\''; - } - protected function escapeString($string, $quote) - { - if (null === $quote) { - // For doc strings, don't escape newlines - $escaped = \addcslashes($string, "\t\f\v\$\\"); - } else { - $escaped = \addcslashes($string, "\n\r\t\f\v\$" . $quote . "\\"); - } - // Escape control characters and non-UTF-8 characters. - // Regex based on https://stackoverflow.com/a/11709412/385378. - $regex = '/( - [\\x00-\\x08\\x0E-\\x1F] # Control characters - | [\\xC0-\\xC1] # Invalid UTF-8 Bytes - | [\\xF5-\\xFF] # Invalid UTF-8 Bytes - | \\xE0(?=[\\x80-\\x9F]) # Overlong encoding of prior code point - | \\xF0(?=[\\x80-\\x8F]) # Overlong encoding of prior code point - | [\\xC2-\\xDF](?![\\x80-\\xBF]) # Invalid UTF-8 Sequence Start - | [\\xE0-\\xEF](?![\\x80-\\xBF]{2}) # Invalid UTF-8 Sequence Start - | [\\xF0-\\xF4](?![\\x80-\\xBF]{3}) # Invalid UTF-8 Sequence Start - | (?<=[\\x00-\\x7F\\xF5-\\xFF])[\\x80-\\xBF] # Invalid UTF-8 Sequence Middle - | (? $part) { - $atStart = $i === 0; - $atEnd = $i === \count($parts) - 1; - if ($part instanceof Scalar\EncapsedStringPart && $this->containsEndLabel($part->value, $label, $atStart, $atEnd)) { - return \true; + }, 3 => function ($stackPos) { + $this->semValue = array(); + }, 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; } - } - return \false; - } - protected function pDereferenceLhs(Node $node) - { - if (!$this->dereferenceLhsRequiresParens($node)) { - return $this->p($node); - } else { - return '(' . $this->p($node) . ')'; - } - } - protected function pCallLhs(Node $node) - { - if (!$this->callLhsRequiresParens($node)) { - return $this->p($node); - } else { - return '(' . $this->p($node) . ')'; - } - } - protected function pNewVariable(Node $node) - { - // TODO: This is not fully accurate. - return $this->pDereferenceLhs($node); - } - /** - * @param Node[] $nodes - * @return bool - */ - protected function hasNodeWithComments(array $nodes) - { - foreach ($nodes as $node) { - if ($node && $node->getComments()) { - return \true; + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; } - } - return \false; - } - protected function pMaybeMultiline(array $nodes, bool $trailingComma = \false) - { - if (!$this->hasNodeWithComments($nodes)) { - return $this->pCommaSeparated($nodes); - } else { - return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; - } - } - protected function pAttrGroups(array $nodes, bool $inline = \false) : string - { - $result = ''; - $sep = $inline ? ' ' : $this->nl; - foreach ($nodes as $node) { - $result .= $this->p($node) . $sep; - } - return $result; - } -} -attributes = $attributes; - $this->name = $name; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['name', 'args']; - } - public function getType() : string - { - return 'Attribute'; - } -} -attributes = $attributes; - $this->name = $name; - $this->value = $value; - $this->byRef = $byRef; - $this->unpack = $unpack; - } - public function getSubNodeNames() : array - { - return ['name', 'value', 'byRef', 'unpack']; - } - public function getType() : string - { - return 'Arg'; - } -} -attributes = $attributes; - $this->types = $types; - } - public function getSubNodeNames() : array - { - return ['types']; - } - public function getType() : string - { - return 'UnionType'; - } -} -attributes = $attributes; - $this->remaining = $remaining; - } - public function getSubNodeNames() : array - { - return ['remaining']; - } - public function getType() : string - { - return 'Stmt_HaltCompiler'; - } -} -attributes = $attributes; - $this->num = $num; - } - public function getSubNodeNames() : array - { - return ['num']; - } - public function getType() : string - { - return 'Stmt_Break'; - } -} - array(): Statements - * 'elseifs' => array(): Elseif clauses - * 'else' => null : Else clause - * @param array $attributes Additional attributes - */ - public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->cond = $cond; - $this->stmts = $subNodes['stmts'] ?? []; - $this->elseifs = $subNodes['elseifs'] ?? []; - $this->else = $subNodes['else'] ?? null; - } - public function getSubNodeNames() : array - { - return ['cond', 'stmts', 'elseifs', 'else']; - } - public function getType() : string - { - return 'Stmt_If'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['cond', 'stmts']; - } - public function getType() : string - { - return 'Stmt_While'; - } -} - false : Whether to return by reference - * 'params' => array(): Parameters - * 'returnType' => null : Return type - * 'stmts' => array(): Statements - * 'attrGroups' => array(): PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->byRef = $subNodes['byRef'] ?? \false; - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->params = $subNodes['params'] ?? []; - $returnType = $subNodes['returnType'] ?? null; - $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; - } - public function returnsByRef() : bool - { - return $this->byRef; - } - public function getParams() : array - { - return $this->params; - } - public function getReturnType() - { - return $this->returnType; - } - public function getAttrGroups() : array - { - return $this->attrGroups; - } - /** @return Node\Stmt[] */ - public function getStmts() : array - { - return $this->stmts; - } - public function getType() : string - { - return 'Stmt_Function'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['cond', 'stmts']; - } - public function getType() : string - { - return 'Stmt_ElseIf'; - } -} - array(): Name of extended interfaces - * 'stmts' => array(): Statements - * 'attrGroups' => array(): PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->extends = $subNodes['extends'] ?? []; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'name', 'extends', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Interface'; - } -} - 0 : Flags - * 'extends' => null : Name of extended class - * 'implements' => array(): Names of implemented interfaces - * 'stmts' => array(): Statements - * 'attrGroups' => array(): PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->extends = $subNodes['extends'] ?? null; - $this->implements = $subNodes['implements'] ?? []; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; - } - /** - * Whether the class is explicitly abstract. - * - * @return bool - */ - public function isAbstract() : bool - { - return (bool) ($this->flags & self::MODIFIER_ABSTRACT); - } - /** - * Whether the class is final. - * - * @return bool - */ - public function isFinal() : bool - { - return (bool) ($this->flags & self::MODIFIER_FINAL); - } - /** - * Whether the class is anonymous. - * - * @return bool - */ - public function isAnonymous() : bool - { - return null === $this->name; - } - /** - * @internal - */ - public static function verifyModifier($a, $b) - { - if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) { - throw new Error('Multiple access type modifiers are not allowed'); - } - if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { - throw new Error('Multiple abstract modifiers are not allowed'); - } - if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) { - throw new Error('Multiple static modifiers are not allowed'); - } - if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { - throw new Error('Multiple final modifiers are not allowed'); - } - if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { - throw new Error('Multiple readonly modifiers are not allowed'); - } - if ($a & 48 && $b & 48) { - throw new Error('Cannot use the final modifier on an abstract class member'); - } - } - public function getType() : string - { - return 'Stmt_Class'; - } -} -attributes = $attributes; - $this->type = $type; - $this->prefix = $prefix; - $this->uses = $uses; - } - public function getSubNodeNames() : array - { - return ['type', 'prefix', 'uses']; - } - public function getType() : string - { - return 'Stmt_GroupUse'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Stmt_Throw'; - } -} -attributes = $attributes; - $this->num = $num; - } - public function getSubNodeNames() : array - { - return ['num']; - } - public function getType() : string - { - return 'Stmt_Continue'; - } -} -attributes = $attributes; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['stmts']; - } - public function getType() : string - { - return 'Stmt_Else'; - } -} - array(): Statements - * 'attrGroups' => array(): PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'name', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Trait'; - } -} -attributes = $attributes; - $this->vars = $vars; - } - public function getSubNodeNames() : array - { - return ['vars']; - } - public function getType() : string - { - return 'Stmt_Unset'; - } -} -attributes = $attributes; - $this->exprs = $exprs; - } - public function getSubNodeNames() : array - { - return ['exprs']; - } - public function getType() : string - { - return 'Stmt_Echo'; - } -} -attributes = $attributes; - $this->name = \is_string($name) ? new Identifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['name']; - } - public function getType() : string - { - return 'Stmt_Label'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['stmts', 'cond']; - } - public function getType() : string - { - return 'Stmt_Do'; - } -} -value pair node. - * - * @param string|Node\Identifier $key Key - * @param Node\Expr $value Value - * @param array $attributes Additional attributes - */ - public function __construct($key, Node\Expr $value, array $attributes = []) - { - $this->attributes = $attributes; - $this->key = \is_string($key) ? new Node\Identifier($key) : $key; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['key', 'value']; - } - public function getType() : string - { - return 'Stmt_DeclareDeclare'; - } -} -attributes = $attributes; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['stmts']; - } - public function getType() : string - { - return 'Stmt_Finally'; - } -} -attributes = $attributes; - $this->trait = $trait; - $this->method = \is_string($method) ? new Node\Identifier($method) : $method; - $this->newModifier = $newModifier; - $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; - } - public function getSubNodeNames() : array - { - return ['trait', 'method', 'newModifier', 'newName']; - } - public function getType() : string - { - return 'Stmt_TraitUseAdaptation_Alias'; - } -} -attributes = $attributes; - $this->trait = $trait; - $this->method = \is_string($method) ? new Node\Identifier($method) : $method; - $this->insteadof = $insteadof; - } - public function getSubNodeNames() : array - { - return ['trait', 'method', 'insteadof']; - } - public function getType() : string - { - return 'Stmt_TraitUseAdaptation_Precedence'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->cases = $cases; - } - public function getSubNodeNames() : array - { - return ['cond', 'cases']; - } - public function getType() : string - { - return 'Stmt_Switch'; - } -} -attributes = $attributes; - $this->traits = $traits; - $this->adaptations = $adaptations; - } - public function getSubNodeNames() : array - { - return ['traits', 'adaptations']; - } - public function getType() : string - { - return 'Stmt_TraitUse'; - } -} -attributes = $attributes; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['value']; - } - public function getType() : string - { - return 'Stmt_InlineHTML'; - } -} -stmts as $stmt) { - if ($stmt instanceof TraitUse) { - $traitUses[] = $stmt; - } - } - return $traitUses; - } - /** - * @return ClassConst[] - */ - public function getConstants() : array - { - $constants = []; - foreach ($this->stmts as $stmt) { - if ($stmt instanceof ClassConst) { - $constants[] = $stmt; - } - } - return $constants; - } - /** - * @return Property[] - */ - public function getProperties() : array - { - $properties = []; - foreach ($this->stmts as $stmt) { - if ($stmt instanceof Property) { - $properties[] = $stmt; - } - } - return $properties; - } - /** - * Gets property with the given name defined directly in this class/interface/trait. - * - * @param string $name Name of the property - * - * @return Property|null Property node or null if the property does not exist - */ - public function getProperty(string $name) - { - foreach ($this->stmts as $stmt) { - if ($stmt instanceof Property) { - foreach ($stmt->props as $prop) { - if ($prop instanceof PropertyProperty && $name === $prop->name->toString()) { - return $stmt; - } - } - } - } - return null; - } - /** - * Gets all methods defined directly in this class/interface/trait - * - * @return ClassMethod[] - */ - public function getMethods() : array - { - $methods = []; - foreach ($this->stmts as $stmt) { - if ($stmt instanceof ClassMethod) { - $methods[] = $stmt; - } - } - return $methods; - } - /** - * Gets method with the given name defined directly in this class/interface/trait. - * - * @param string $name Name of the method (compared case-insensitively) - * - * @return ClassMethod|null Method node or null if the method does not exist - */ - public function getMethod(string $name) - { - $lowerName = \strtolower($name); - foreach ($this->stmts as $stmt) { - if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { - return $stmt; - } - } - return null; - } -} -attributes = $attributes; - $this->name = \is_string($name) ? new Identifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['name']; - } - public function getType() : string - { - return 'Stmt_Goto'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Stmt_Return'; - } -} -attributes = $attributes; - $this->declares = $declares; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['declares', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Declare'; - } -} -attributes = $attributes; - $this->var = $var; - $this->default = $default; - } - public function getSubNodeNames() : array - { - return ['var', 'default']; - } - public function getType() : string - { - return 'Stmt_StaticVar'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['cond', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Case'; - } -} -attributes = $attributes; - $this->consts = $consts; - } - public function getSubNodeNames() : array - { - return ['consts']; - } - public function getType() : string - { - return 'Stmt_Const'; - } -} -attributes = $attributes; - $this->vars = $vars; - } - public function getSubNodeNames() : array - { - return ['vars']; - } - public function getType() : string - { - return 'Stmt_Static'; - } -} -attributes = $attributes; - $this->stmts = $stmts; - $this->catches = $catches; - $this->finally = $finally; - } - public function getSubNodeNames() : array - { - return ['stmts', 'catches', 'finally']; - } - public function getType() : string - { - return 'Stmt_TryCatch'; - } -} -name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->expr = $expr; - $this->attrGroups = $attrGroups; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'name', 'expr']; - } - public function getType() : string - { - return 'Stmt_EnumCase'; - } -} - null : Variable to assign key to - * 'byRef' => false : Whether to assign value by reference - * 'stmts' => array(): Statements - * @param array $attributes Additional attributes - */ - public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->expr = $expr; - $this->keyVar = $subNodes['keyVar'] ?? null; - $this->byRef = $subNodes['byRef'] ?? \false; - $this->valueVar = $valueVar; - $this->stmts = $subNodes['stmts'] ?? []; - } - public function getSubNodeNames() : array - { - return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Foreach'; - } -} -attributes = $attributes; - $this->type = $type; - $this->name = $name; - $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; - } - public function getSubNodeNames() : array - { - return ['type', 'name', 'alias']; - } - /** - * Get alias. If not explicitly given this is the last component of the used name. - * - * @return Identifier - */ - public function getAlias() : Identifier - { - if (null !== $this->alias) { - return $this->alias; - } - return new Identifier($this->name->getLast()); - } - public function getType() : string - { - return 'Stmt_UseUse'; - } -} - null : Scalar type - * 'implements' => array() : Names of implemented interfaces - * 'stmts' => array() : Statements - * 'attrGroups' => array() : PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->scalarType = $subNodes['scalarType'] ?? null; - $this->implements = $subNodes['implements'] ?? []; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - parent::__construct($attributes); - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Enum'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Stmt_Expression'; - } -} - \true, '__destruct' => \true, '__call' => \true, '__callstatic' => \true, '__get' => \true, '__set' => \true, '__isset' => \true, '__unset' => \true, '__sleep' => \true, '__wakeup' => \true, '__tostring' => \true, '__set_state' => \true, '__clone' => \true, '__invoke' => \true, '__debuginfo' => \true]; - /** - * Constructs a class method node. - * - * @param string|Node\Identifier $name Name - * @param array $subNodes Array of the following optional subnodes: - * 'flags => MODIFIER_PUBLIC: Flags - * 'byRef' => false : Whether to return by reference - * 'params' => array() : Parameters - * 'returnType' => null : Return type - * 'stmts' => array() : Statements - * 'attrGroups' => array() : PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct($name, array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; - $this->byRef = $subNodes['byRef'] ?? \false; - $this->name = \is_string($name) ? new Node\Identifier($name) : $name; - $this->params = $subNodes['params'] ?? []; - $returnType = $subNodes['returnType'] ?? null; - $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; - $this->stmts = \array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; - } - public function returnsByRef() : bool - { - return $this->byRef; - } - public function getParams() : array - { - return $this->params; - } - public function getReturnType() - { - return $this->returnType; - } - public function getStmts() - { - return $this->stmts; - } - public function getAttrGroups() : array - { - return $this->attrGroups; - } - /** - * Whether the method is explicitly or implicitly public. - * - * @return bool - */ - public function isPublic() : bool - { - return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; - } - /** - * Whether the method is protected. - * - * @return bool - */ - public function isProtected() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); - } - /** - * Whether the method is private. - * - * @return bool - */ - public function isPrivate() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); - } - /** - * Whether the method is abstract. - * - * @return bool - */ - public function isAbstract() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT); - } - /** - * Whether the method is final. - * - * @return bool - */ - public function isFinal() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_FINAL); - } - /** - * Whether the method is static. - * - * @return bool - */ - public function isStatic() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_STATIC); - } - /** - * Whether the method is magic. - * - * @return bool - */ - public function isMagic() : bool - { - return isset(self::$magicNames[$this->name->toLowerString()]); - } - public function getType() : string - { - return 'Stmt_ClassMethod'; - } -} -attributes = $attributes; - $this->types = $types; - $this->var = $var; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['types', 'var', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Catch'; - } -} -attributes = $attributes; - $this->vars = $vars; - } - public function getSubNodeNames() : array - { - return ['vars']; - } - public function getType() : string - { - return 'Stmt_Global'; - } -} -attributes = $attributes; - $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; - $this->default = $default; - } - public function getSubNodeNames() : array - { - return ['name', 'default']; - } - public function getType() : string - { - return 'Stmt_PropertyProperty'; - } -} - array(): Init expressions - * 'cond' => array(): Loop conditions - * 'loop' => array(): Loop expressions - * 'stmts' => array(): Statements - * @param array $attributes Additional attributes - */ - public function __construct(array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->init = $subNodes['init'] ?? []; - $this->cond = $subNodes['cond'] ?? []; - $this->loop = $subNodes['loop'] ?? []; - $this->stmts = $subNodes['stmts'] ?? []; - } - public function getSubNodeNames() : array - { - return ['init', 'cond', 'loop', 'stmts']; - } - public function getType() : string - { - return 'Stmt_For'; - } -} -attributes = $attributes; - $this->type = $type; - $this->uses = $uses; - } - public function getSubNodeNames() : array - { - return ['type', 'uses']; - } - public function getType() : string - { - return 'Stmt_Use'; - } -} -attributes = $attributes; - $this->name = $name; - $this->stmts = $stmts; - } - public function getSubNodeNames() : array - { - return ['name', 'stmts']; - } - public function getType() : string - { - return 'Stmt_Namespace'; - } -} -attributes = $attributes; - $this->flags = $flags; - $this->consts = $consts; - $this->attrGroups = $attrGroups; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'flags', 'consts']; - } - /** - * Whether constant is explicitly or implicitly public. - * - * @return bool - */ - public function isPublic() : bool - { - return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; - } - /** - * Whether constant is protected. - * - * @return bool - */ - public function isProtected() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); - } - /** - * Whether constant is private. - * - * @return bool - */ - public function isPrivate() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); - } - /** - * Whether constant is final. - * - * @return bool - */ - public function isFinal() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_FINAL); - } - public function getType() : string - { - return 'Stmt_ClassConst'; - } -} -attributes = $attributes; - $this->flags = $flags; - $this->props = $props; - $this->type = \is_string($type) ? new Identifier($type) : $type; - $this->attrGroups = $attrGroups; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'flags', 'type', 'props']; - } - /** - * Whether the property is explicitly or implicitly public. - * - * @return bool - */ - public function isPublic() : bool - { - return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; - } - /** - * Whether the property is protected. - * - * @return bool - */ - public function isProtected() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); - } - /** - * Whether the property is private. - * - * @return bool - */ - public function isPrivate() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); - } - /** - * Whether the property is static. - * - * @return bool - */ - public function isStatic() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_STATIC); - } - /** - * Whether the property is readonly. - * - * @return bool - */ - public function isReadonly() : bool - { - return (bool) ($this->flags & Class_::MODIFIER_READONLY); - } - public function getType() : string - { - return 'Stmt_Property'; - } -} -attributes = $attributes; - $this->var = $var; - $this->name = \is_string($name) ? new Identifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['var', 'name']; - } - public function getType() : string - { - return 'Expr_NullsafePropertyFetch'; - } -} -attributes = $attributes; - $this->key = $key; - $this->value = $value; - $this->byRef = $byRef; - $this->unpack = $unpack; - } - public function getSubNodeNames() : array - { - return ['key', 'value', 'byRef', 'unpack']; - } - public function getType() : string - { - return 'Expr_ArrayItem'; - } -} -attributes = $attributes; - $this->var = $var; - } - public function getSubNodeNames() : array - { - return ['var']; - } - public function getType() : string - { - return 'Expr_PreDec'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Clone'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_UnaryPlus'; - } -} -attributes = $attributes; - $this->var = $var; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['var', 'expr']; - } -} - - */ - public abstract function getRawArgs() : array; - /** - * Returns whether this call expression is actually a first class callable. - */ - public function isFirstClassCallable() : bool - { - foreach ($this->getRawArgs() as $arg) { - if ($arg instanceof VariadicPlaceholder) { - return \true; - } - } - return \false; - } - /** - * Assert that this is not a first-class callable and return only ordinary Args. - * - * @return Arg[] - */ - public function getArgs() : array - { - \assert(!$this->isFirstClassCallable()); - return $this->getRawArgs(); - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Throw'; - } -} -attributes = $attributes; - $this->key = $key; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['key', 'value']; - } - public function getType() : string - { - return 'Expr_Yield'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->arms = $arms; - } - public function getSubNodeNames() : array - { - return ['cond', 'arms']; - } - public function getType() : string - { - return 'Expr_Match'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Print'; - } -} -attributes = $attributes; - $this->name = $name; - } - public function getSubNodeNames() : array - { - return ['name']; - } - public function getType() : string - { - return 'Expr_Variable'; - } -} -attributes = $attributes; - } - public function getSubNodeNames() : array - { - return []; - } - public function getType() : string - { - return 'Expr_Error'; - } -} -attributes = $attributes; - $this->var = $var; - } - public function getSubNodeNames() : array - { - return ['var']; - } - public function getType() : string - { - return 'Expr_PostDec'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Eval'; - } -} - Arguments */ - public $args; - /** - * Constructs a function call node. - * - * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) - * @param array $args Arguments - * @param array $attributes Additional attributes - */ - public function __construct($class, array $args = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->class = $class; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['class', 'args']; - } - public function getType() : string - { - return 'Expr_New'; - } - public function getRawArgs() : array - { - return $this->args; - } -} - false : Whether the closure is static - * 'byRef' => false : Whether to return by reference - * 'params' => array() : Parameters - * 'returnType' => null : Return type - * 'expr' => Expr : Expression body - * 'attrGroups' => array() : PHP attribute groups - * @param array $attributes Additional attributes - */ - public function __construct(array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->static = $subNodes['static'] ?? \false; - $this->byRef = $subNodes['byRef'] ?? \false; - $this->params = $subNodes['params'] ?? []; - $returnType = $subNodes['returnType'] ?? null; - $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; - $this->expr = $subNodes['expr'] ?? null; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; - } - public function returnsByRef() : bool - { - return $this->byRef; - } - public function getParams() : array - { - return $this->params; - } - public function getReturnType() - { - return $this->returnType; - } - public function getAttrGroups() : array - { - return $this->attrGroups; - } - /** - * @return Node\Stmt\Return_[] - */ - public function getStmts() : array - { - return [new Node\Stmt\Return_($this->expr)]; - } - public function getType() : string - { - return 'Expr_ArrowFunction'; - } -} -'; - } - public function getType() : string - { - return 'Expr_BinaryOp_Spaceship'; - } -} -'; - } - public function getType() : string - { - return 'Expr_BinaryOp_Greater'; - } -} ->'; - } - public function getType() : string - { - return 'Expr_BinaryOp_ShiftRight'; - } -} -='; - } - public function getType() : string - { - return 'Expr_BinaryOp_GreaterOrEqual'; - } -} -attributes = $attributes; - $this->var = $var; - } - public function getSubNodeNames() : array - { - return ['var']; - } - public function getType() : string - { - return 'Expr_PostInc'; - } -} -attributes = $attributes; - $this->cond = $cond; - $this->if = $if; - $this->else = $else; - } - public function getSubNodeNames() : array - { - return ['cond', 'if', 'else']; - } - public function getType() : string - { - return 'Expr_Ternary'; - } -} -attributes = $attributes; - $this->var = $var; - $this->name = \is_string($name) ? new Identifier($name) : $name; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['var', 'name', 'args']; - } - public function getType() : string - { - return 'Expr_NullsafeMethodCall'; - } -} -attributes = $attributes; - $this->var = $var; - $this->name = \is_string($name) ? new Identifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['var', 'name']; - } - public function getType() : string - { - return 'Expr_PropertyFetch'; - } -} -attributes = $attributes; - $this->expr = $expr; - $this->class = $class; - } - public function getSubNodeNames() : array - { - return ['expr', 'class']; - } - public function getType() : string - { - return 'Expr_Instanceof'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Empty'; - } -} -attributes = $attributes; - $this->var = $var; - $this->dim = $dim; - } - public function getSubNodeNames() : array - { - return ['var', 'dim']; - } - public function getType() : string - { - return 'Expr_ArrayDimFetch'; - } -} -attributes = $attributes; - $this->class = $class; - $this->name = \is_string($name) ? new Identifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['class', 'name']; - } - public function getType() : string - { - return 'Expr_ClassConstFetch'; - } -} -attributes = $attributes; - $this->name = $name; - } - public function getSubNodeNames() : array - { - return ['name']; - } - public function getType() : string - { - return 'Expr_ConstFetch'; - } -} - Arguments */ - public $args; - /** - * Constructs a static method call node. - * - * @param Node\Name|Expr $class Class name - * @param string|Identifier|Expr $name Method name - * @param array $args Arguments - * @param array $attributes Additional attributes - */ - public function __construct($class, $name, array $args = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->class = $class; - $this->name = \is_string($name) ? new Identifier($name) : $name; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['class', 'name', 'args']; - } - public function getType() : string - { - return 'Expr_StaticCall'; - } - public function getRawArgs() : array - { - return $this->args; - } -} - Arguments */ - public $args; - /** - * Constructs a function call node. - * - * @param Node\Name|Expr $name Function name - * @param array $args Arguments - * @param array $attributes Additional attributes - */ - public function __construct($name, array $args = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->name = $name; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['name', 'args']; - } - public function getType() : string - { - return 'Expr_FuncCall'; - } - public function getRawArgs() : array - { - return $this->args; - } -} -attributes = $attributes; - $this->items = $items; - } - public function getSubNodeNames() : array - { - return ['items']; - } - public function getType() : string - { - return 'Expr_List'; - } -} -attributes = $attributes; - $this->vars = $vars; - } - public function getSubNodeNames() : array - { - return ['vars']; - } - public function getType() : string - { - return 'Expr_Isset'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_ErrorSuppress'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_BooleanNot'; - } -} -attributes = $attributes; - $this->items = $items; - } - public function getSubNodeNames() : array - { - return ['items']; - } - public function getType() : string - { - return 'Expr_Array'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_UnaryMinus'; - } -} - false : Whether the closure is static - * 'byRef' => false : Whether to return by reference - * 'params' => array(): Parameters - * 'uses' => array(): use()s - * 'returnType' => null : Return type - * 'stmts' => array(): Statements - * 'attrGroups' => array(): PHP attributes groups - * @param array $attributes Additional attributes - */ - public function __construct(array $subNodes = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->static = $subNodes['static'] ?? \false; - $this->byRef = $subNodes['byRef'] ?? \false; - $this->params = $subNodes['params'] ?? []; - $this->uses = $subNodes['uses'] ?? []; - $returnType = $subNodes['returnType'] ?? null; - $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; - $this->stmts = $subNodes['stmts'] ?? []; - $this->attrGroups = $subNodes['attrGroups'] ?? []; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; - } - public function returnsByRef() : bool - { - return $this->byRef; - } - public function getParams() : array - { - return $this->params; - } - public function getReturnType() - { - return $this->returnType; - } - /** @return Node\Stmt[] */ - public function getStmts() : array - { - return $this->stmts; - } - public function getAttrGroups() : array - { - return $this->attrGroups; - } - public function getType() : string - { - return 'Expr_Closure'; - } -} -attributes = $attributes; - $this->var = $var; - } - public function getSubNodeNames() : array - { - return ['var']; - } - public function getType() : string - { - return 'Expr_PreInc'; - } -} -attributes = $attributes; - $this->var = $var; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['var', 'expr']; - } - public function getType() : string - { - return 'Expr_Assign'; - } -} -attributes = $attributes; - $this->left = $left; - $this->right = $right; - } - public function getSubNodeNames() : array - { - return ['left', 'right']; - } - /** - * Get the operator sigil for this binary operation. - * - * In the case there are multiple possible sigils for an operator, this method does not - * necessarily return the one used in the parsed code. - * - * @return string - */ - public abstract function getOperatorSigil() : string; -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_YieldFrom'; - } -} -attributes = $attributes; - $this->class = $class; - $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; - } - public function getSubNodeNames() : array - { - return ['class', 'name']; - } - public function getType() : string - { - return 'Expr_StaticPropertyFetch'; - } -} -attributes = $attributes; - $this->var = $var; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['var', 'expr']; - } - public function getType() : string - { - return 'Expr_AssignRef'; - } -} - Arguments */ - public $args; - /** - * Constructs a function call node. - * - * @param Expr $var Variable holding object - * @param string|Identifier|Expr $name Method name - * @param array $args Arguments - * @param array $attributes Additional attributes - */ - public function __construct(Expr $var, $name, array $args = [], array $attributes = []) - { - $this->attributes = $attributes; - $this->var = $var; - $this->name = \is_string($name) ? new Identifier($name) : $name; - $this->args = $args; - } - public function getSubNodeNames() : array - { - return ['var', 'name', 'args']; - } - public function getType() : string - { - return 'Expr_MethodCall'; - } - public function getRawArgs() : array - { - return $this->args; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_BitwiseNot'; - } -} -attributes = $attributes; - $this->var = $var; - $this->byRef = $byRef; - } - public function getSubNodeNames() : array - { - return ['var', 'byRef']; - } - public function getType() : string - { - return 'Expr_ClosureUse'; - } -} -attributes = $attributes; - $this->parts = $parts; - } - public function getSubNodeNames() : array - { - return ['parts']; - } - public function getType() : string - { - return 'Expr_ShellExec'; - } -} -attributes = $attributes; - $this->expr = $expr; - } - public function getSubNodeNames() : array - { - return ['expr']; - } - public function getType() : string - { - return 'Expr_Exit'; - } -} -attributes = $attributes; - $this->expr = $expr; - $this->type = $type; - } - public function getSubNodeNames() : array - { - return ['expr', 'type']; - } - public function getType() : string - { - return 'Expr_Include'; - } -} -attributes = $attributes; - $this->type = \is_string($type) ? new Identifier($type) : $type; - } - public function getSubNodeNames() : array - { - return ['type']; - } - public function getType() : string - { - return 'NullableType'; - } -} -attributes = $attributes; - } - public function getSubNodeNames() : array - { - return []; - } - /** - * Get name of magic constant. - * - * @return string Name of magic constant - */ - public abstract function getName() : string; -} -attributes = $attributes; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['value']; - } - public function getType() : string - { - return 'Scalar_EncapsedStringPart'; - } -} -attributes = $attributes; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['value']; - } - /** - * Constructs an LNumber node from a string number literal. - * - * @param string $str String number literal (decimal, octal, hex or binary) - * @param array $attributes Additional attributes - * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) - * - * @return LNumber The constructed LNumber, including kind attribute - */ - public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = \false) : LNumber - { - $str = \str_replace('_', '', $str); - if ('0' !== $str[0] || '0' === $str) { - $attributes['kind'] = LNumber::KIND_DEC; - return new LNumber((int) $str, $attributes); - } - if ('x' === $str[1] || 'X' === $str[1]) { - $attributes['kind'] = LNumber::KIND_HEX; - return new LNumber(\hexdec($str), $attributes); - } - if ('b' === $str[1] || 'B' === $str[1]) { - $attributes['kind'] = LNumber::KIND_BIN; - return new LNumber(\bindec($str), $attributes); - } - if (!$allowInvalidOctal && \strpbrk($str, '89')) { - throw new Error('Invalid numeric literal', $attributes); - } - // Strip optional explicit octal prefix. - if ('o' === $str[1] || 'O' === $str[1]) { - $str = \substr($str, 2); - } - // use intval instead of octdec to get proper cutting behavior with malformed numbers - $attributes['kind'] = LNumber::KIND_OCT; - return new LNumber(\intval($str, 8), $attributes); - } - public function getType() : string - { - return 'Scalar_LNumber'; - } -} - '\\', '$' => '$', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\33"]; - /** - * Constructs a string scalar node. - * - * @param string $value Value of the string - * @param array $attributes Additional attributes - */ - public function __construct(string $value, array $attributes = []) - { - $this->attributes = $attributes; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['value']; - } - /** - * @internal - * - * Parses a string token. - * - * @param string $str String token content - * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes - * - * @return string The parsed string - */ - public static function parse(string $str, bool $parseUnicodeEscape = \true) : string - { - $bLength = 0; - if ('b' === $str[0] || 'B' === $str[0]) { - $bLength = 1; - } - if ('\'' === $str[$bLength]) { - return \str_replace(['\\\\', '\\\''], ['\\', '\''], \substr($str, $bLength + 1, -1)); - } else { - return self::parseEscapeSequences(\substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape); - } - } - /** - * @internal - * - * Parses escape sequences in strings (all string types apart from single quoted). - * - * @param string $str String without quotes - * @param null|string $quote Quote type - * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes - * - * @return string String with escape sequences parsed - */ - public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = \true) : string - { - if (null !== $quote) { - $str = \str_replace('\\' . $quote, $quote, $str); - } - $extra = ''; - if ($parseUnicodeEscape) { - $extra = '|u\\{([0-9a-fA-F]+)\\}'; - } - return \preg_replace_callback('~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', function ($matches) { - $str = $matches[1]; - if (isset(self::$replacements[$str])) { - return self::$replacements[$str]; - } elseif ('x' === $str[0] || 'X' === $str[0]) { - return \chr(\hexdec(\substr($str, 1))); - } elseif ('u' === $str[0]) { - return self::codePointToUtf8(\hexdec($matches[2])); - } else { - return \chr(\octdec($str)); - } - }, $str); - } - /** - * Converts a Unicode code point to its UTF-8 encoded representation. - * - * @param int $num Code point - * - * @return string UTF-8 representation of code point - */ - private static function codePointToUtf8(int $num) : string - { - if ($num <= 0x7f) { - return \chr($num); - } - if ($num <= 0x7ff) { - return \chr(($num >> 6) + 0xc0) . \chr(($num & 0x3f) + 0x80); - } - if ($num <= 0xffff) { - return \chr(($num >> 12) + 0xe0) . \chr(($num >> 6 & 0x3f) + 0x80) . \chr(($num & 0x3f) + 0x80); - } - if ($num <= 0x1fffff) { - return \chr(($num >> 18) + 0xf0) . \chr(($num >> 12 & 0x3f) + 0x80) . \chr(($num >> 6 & 0x3f) + 0x80) . \chr(($num & 0x3f) + 0x80); - } - throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); - } - public function getType() : string - { - return 'Scalar_String'; - } -} -attributes = $attributes; - $this->parts = $parts; - } - public function getSubNodeNames() : array - { - return ['parts']; - } - public function getType() : string - { - return 'Scalar_Encapsed'; - } -} -attributes = $attributes; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['value']; - } - /** - * @internal - * - * Parses a DNUMBER token like PHP would. - * - * @param string $str A string number - * - * @return float The parsed number - */ - public static function parse(string $str) : float - { - $str = \str_replace('_', '', $str); - // if string contains any of .eE just cast it to float - if (\false !== \strpbrk($str, '.eE')) { - return (float) $str; - } - // otherwise it's an integer notation that overflowed into a float - // if it starts with 0 it's one of the special integer notations - if ('0' === $str[0]) { - // hex - if ('x' === $str[1] || 'X' === $str[1]) { - return \hexdec($str); - } - // bin - if ('b' === $str[1] || 'B' === $str[1]) { - return \bindec($str); - } - // oct - // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9) - // so that only the digits before that are used - return \octdec(\substr($str, 0, \strcspn($str, '89'))); - } - // dec - return (float) $str; - } - public function getType() : string - { - return 'Scalar_DNumber'; - } -} -attributes = $attributes; - } - public function getType() : string - { - return 'VariadicPlaceholder'; - } - public function getSubNodeNames() : array - { - return []; - } -} - \true, 'parent' => \true, 'static' => \true]; - /** - * Constructs a name node. - * - * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) - * @param array $attributes Additional attributes - */ - public function __construct($name, array $attributes = []) - { - $this->attributes = $attributes; - $this->parts = self::prepareName($name); - } - public function getSubNodeNames() : array - { - return ['parts']; - } - /** - * Gets the first part of the name, i.e. everything before the first namespace separator. - * - * @return string First part of the name - */ - public function getFirst() : string - { - return $this->parts[0]; - } - /** - * Gets the last part of the name, i.e. everything after the last namespace separator. - * - * @return string Last part of the name - */ - public function getLast() : string - { - return $this->parts[\count($this->parts) - 1]; - } - /** - * Checks whether the name is unqualified. (E.g. Name) - * - * @return bool Whether the name is unqualified - */ - public function isUnqualified() : bool - { - return 1 === \count($this->parts); - } - /** - * Checks whether the name is qualified. (E.g. Name\Name) - * - * @return bool Whether the name is qualified - */ - public function isQualified() : bool - { - return 1 < \count($this->parts); - } - /** - * Checks whether the name is fully qualified. (E.g. \Name) - * - * @return bool Whether the name is fully qualified - */ - public function isFullyQualified() : bool - { - return \false; - } - /** - * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) - * - * @return bool Whether the name is relative - */ - public function isRelative() : bool - { - return \false; - } - /** - * Returns a string representation of the name itself, without taking the name type into - * account (e.g., not including a leading backslash for fully qualified names). - * - * @return string String representation - */ - public function toString() : string - { - return \implode('\\', $this->parts); - } - /** - * Returns a string representation of the name as it would occur in code (e.g., including - * leading backslash for fully qualified names. - * - * @return string String representation - */ - public function toCodeString() : string - { - return $this->toString(); - } - /** - * Returns lowercased string representation of the name, without taking the name type into - * account (e.g., no leading backslash for fully qualified names). - * - * @return string Lowercased string representation - */ - public function toLowerString() : string - { - return \strtolower(\implode('\\', $this->parts)); - } - /** - * Checks whether the identifier is a special class name (self, parent or static). - * - * @return bool Whether identifier is a special class name - */ - public function isSpecialClassName() : bool - { - return \count($this->parts) === 1 && isset(self::$specialClassNames[\strtolower($this->parts[0])]); - } - /** - * Returns a string representation of the name by imploding the namespace parts with the - * namespace separator. - * - * @return string String representation - */ - public function __toString() : string - { - return \implode('\\', $this->parts); - } - /** - * Gets a slice of a name (similar to array_slice). - * - * This method returns a new instance of the same type as the original and with the same - * attributes. - * - * If the slice is empty, null is returned. The null value will be correctly handled in - * concatenations using concat(). - * - * Offset and length have the same meaning as in array_slice(). - * - * @param int $offset Offset to start the slice at (may be negative) - * @param int|null $length Length of the slice (may be negative) - * - * @return static|null Sliced name - */ - public function slice(int $offset, int $length = null) - { - $numParts = \count($this->parts); - $realOffset = $offset < 0 ? $offset + $numParts : $offset; - if ($realOffset < 0 || $realOffset > $numParts) { - throw new \OutOfBoundsException(\sprintf('Offset %d is out of bounds', $offset)); - } - if (null === $length) { - $realLength = $numParts - $realOffset; - } else { - $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; - if ($realLength < 0 || $realLength > $numParts) { - throw new \OutOfBoundsException(\sprintf('Length %d is out of bounds', $length)); - } - } - if ($realLength === 0) { - // Empty slice is represented as null - return null; - } - return new static(\array_slice($this->parts, $realOffset, $realLength), $this->attributes); - } - /** - * Concatenate two names, yielding a new Name instance. - * - * The type of the generated instance depends on which class this method is called on, for - * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. - * - * If one of the arguments is null, a new instance of the other name will be returned. If both - * arguments are null, null will be returned. As such, writing - * Name::concat($namespace, $shortName) - * where $namespace is a Name node or null will work as expected. - * - * @param string|string[]|self|null $name1 The first name - * @param string|string[]|self|null $name2 The second name - * @param array $attributes Attributes to assign to concatenated name - * - * @return static|null Concatenated name - */ - public static function concat($name1, $name2, array $attributes = []) - { - if (null === $name1 && null === $name2) { - return null; - } elseif (null === $name1) { - return new static(self::prepareName($name2), $attributes); - } elseif (null === $name2) { - return new static(self::prepareName($name1), $attributes); - } else { - return new static(\array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes); - } - } - /** - * Prepares a (string, array or Name node) name for use in name changing methods by converting - * it to an array. - * - * @param string|string[]|self $name Name to prepare - * - * @return string[] Prepared name - */ - private static function prepareName($name) : array - { - if (\is_string($name)) { - if ('' === $name) { - throw new \InvalidArgumentException('Name cannot be empty'); - } - return \explode('\\', $name); - } elseif (\is_array($name)) { - if (empty($name)) { - throw new \InvalidArgumentException('Name cannot be empty'); - } - return $name; - } elseif ($name instanceof self) { - return $name->parts; - } - throw new \InvalidArgumentException('Expected string, array of parts or Name instance'); - } - public function getType() : string - { - return 'Name'; - } -} -attributes = $attributes; - $this->attrs = $attrs; - } - public function getSubNodeNames() : array - { - return ['attrs']; - } - public function getType() : string - { - return 'AttributeGroup'; - } -} -attributes = $attributes; - $this->name = \is_string($name) ? new Identifier($name) : $name; - $this->value = $value; - } - public function getSubNodeNames() : array - { - return ['name', 'value']; - } - public function getType() : string - { - return 'Const'; - } -} -toString(); - } - public function getType() : string - { - return 'Name_Relative'; - } -} -toString(); - } - public function getType() : string - { - return 'Name_FullyQualified'; - } -} - \true, 'parent' => \true, 'static' => \true]; - /** - * Constructs an identifier node. - * - * @param string $name Identifier as string - * @param array $attributes Additional attributes - */ - public function __construct(string $name, array $attributes = []) - { - $this->attributes = $attributes; - $this->name = $name; - } - public function getSubNodeNames() : array - { - return ['name']; - } - /** - * Get identifier as string. - * - * @return string Identifier as string. - */ - public function toString() : string - { - return $this->name; - } - /** - * Get lowercased identifier as string. - * - * @return string Lowercased identifier as string - */ - public function toLowerString() : string - { - return \strtolower($this->name); - } - /** - * Checks whether the identifier is a special class name (self, parent or static). - * - * @return bool Whether identifier is a special class name - */ - public function isSpecialClassName() : bool - { - return isset(self::$specialClassNames[\strtolower($this->name)]); - } - /** - * Get identifier as string. - * - * @return string Identifier as string - */ - public function __toString() : string - { - return $this->name; - } - public function getType() : string - { - return 'Identifier'; - } -} -attributes = $attributes; - $this->type = \is_string($type) ? new Identifier($type) : $type; - $this->byRef = $byRef; - $this->variadic = $variadic; - $this->var = $var; - $this->default = $default; - $this->flags = $flags; - $this->attrGroups = $attrGroups; - } - public function getSubNodeNames() : array - { - return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default']; - } - public function getType() : string - { - return 'Param'; - } -} -conds = $conds; - $this->body = $body; - $this->attributes = $attributes; - } - public function getSubNodeNames() : array - { - return ['conds', 'body']; - } - public function getType() : string - { - return 'MatchArm'; - } -} -attributes = $attributes; - $this->types = $types; - } - public function getSubNodeNames() : array - { - return ['types']; - } - public function getType() : string - { - return 'IntersectionType'; - } -} -visitors[] = $visitor; - } - /** - * Removes an added visitor. - * - * @param NodeVisitor $visitor - */ - public function removeVisitor(NodeVisitor $visitor) - { - foreach ($this->visitors as $index => $storedVisitor) { - if ($storedVisitor === $visitor) { - unset($this->visitors[$index]); - break; - } - } - } - /** - * Traverses an array of nodes using the registered visitors. - * - * @param Node[] $nodes Array of nodes - * - * @return Node[] Traversed array of nodes - */ - public function traverse(array $nodes) : array - { - $this->stopTraversal = \false; - foreach ($this->visitors as $visitor) { - if (null !== ($return = $visitor->beforeTraverse($nodes))) { - $nodes = $return; - } - } - $nodes = $this->traverseArray($nodes); - foreach ($this->visitors as $visitor) { - if (null !== ($return = $visitor->afterTraverse($nodes))) { - $nodes = $return; - } - } - return $nodes; - } - /** - * Recursively traverse a node. - * - * @param Node $node Node to traverse. - * - * @return Node Result of traversal (may be original node or new one) - */ - protected function traverseNode(Node $node) : Node - { - foreach ($node->getSubNodeNames() as $name) { - $subNode =& $node->{$name}; - if (\is_array($subNode)) { - $subNode = $this->traverseArray($subNode); - if ($this->stopTraversal) { - break; - } - } elseif ($subNode instanceof Node) { - $traverseChildren = \true; - $breakVisitorIndex = null; - foreach ($this->visitors as $visitorIndex => $visitor) { - $return = $visitor->enterNode($subNode); - if (null !== $return) { - if ($return instanceof Node) { - $this->ensureReplacementReasonable($subNode, $return); - $subNode = $return; - } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { - $traverseChildren = \false; - } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { - $traverseChildren = \false; - $breakVisitorIndex = $visitorIndex; - break; - } elseif (self::STOP_TRAVERSAL === $return) { - $this->stopTraversal = \true; - break 2; - } else { - throw new \LogicException('enterNode() returned invalid value of type ' . \gettype($return)); - } - } - } - if ($traverseChildren) { - $subNode = $this->traverseNode($subNode); - if ($this->stopTraversal) { - break; - } - } - foreach ($this->visitors as $visitorIndex => $visitor) { - $return = $visitor->leaveNode($subNode); - if (null !== $return) { - if ($return instanceof Node) { - $this->ensureReplacementReasonable($subNode, $return); - $subNode = $return; - } elseif (self::STOP_TRAVERSAL === $return) { - $this->stopTraversal = \true; - break 2; - } elseif (\is_array($return)) { - throw new \LogicException('leaveNode() may only return an array ' . 'if the parent structure is an array'); - } else { - throw new \LogicException('leaveNode() returned invalid value of type ' . \gettype($return)); - } - } - if ($breakVisitorIndex === $visitorIndex) { - break; - } - } - } - } - return $node; - } - /** - * Recursively traverse array (usually of nodes). - * - * @param array $nodes Array to traverse - * - * @return array Result of traversal (may be original array or changed one) - */ - protected function traverseArray(array $nodes) : array - { - $doNodes = []; - foreach ($nodes as $i => &$node) { - if ($node instanceof Node) { - $traverseChildren = \true; - $breakVisitorIndex = null; - foreach ($this->visitors as $visitorIndex => $visitor) { - $return = $visitor->enterNode($node); - if (null !== $return) { - if ($return instanceof Node) { - $this->ensureReplacementReasonable($node, $return); - $node = $return; - } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { - $traverseChildren = \false; - } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { - $traverseChildren = \false; - $breakVisitorIndex = $visitorIndex; - break; - } elseif (self::STOP_TRAVERSAL === $return) { - $this->stopTraversal = \true; - break 2; - } else { - throw new \LogicException('enterNode() returned invalid value of type ' . \gettype($return)); - } - } - } - if ($traverseChildren) { - $node = $this->traverseNode($node); - if ($this->stopTraversal) { - break; - } - } - foreach ($this->visitors as $visitorIndex => $visitor) { - $return = $visitor->leaveNode($node); - if (null !== $return) { - if ($return instanceof Node) { - $this->ensureReplacementReasonable($node, $return); - $node = $return; - } elseif (\is_array($return)) { - $doNodes[] = [$i, $return]; - break; - } elseif (self::REMOVE_NODE === $return) { - $doNodes[] = [$i, []]; - break; - } elseif (self::STOP_TRAVERSAL === $return) { - $this->stopTraversal = \true; - break 2; - } elseif (\false === $return) { - throw new \LogicException('bool(false) return from leaveNode() no longer supported. ' . 'Return NodeTraverser::REMOVE_NODE instead'); - } else { - throw new \LogicException('leaveNode() returned invalid value of type ' . \gettype($return)); - } - } - if ($breakVisitorIndex === $visitorIndex) { - break; - } - } - } elseif (\is_array($node)) { - throw new \LogicException('Invalid node structure: Contains nested arrays'); - } - } - if (!empty($doNodes)) { - while (list($i, $replace) = \array_pop($doNodes)) { - \array_splice($nodes, $i, 1, $replace); - } - } - return $nodes; - } - private function ensureReplacementReasonable($old, $new) - { - if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { - throw new \LogicException("Trying to replace statement ({$old->getType()}) " . "with expression ({$new->getType()}). Are you missing a " . "Stmt_Expression wrapper?"); - } - if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { - throw new \LogicException("Trying to replace expression ({$old->getType()}) " . "with statement ({$new->getType()})"); - } - } -} - $node stays as-is - * * NodeTraverser::DONT_TRAVERSE_CHILDREN - * => Children of $node are not traversed. $node stays as-is - * * NodeTraverser::STOP_TRAVERSAL - * => Traversal is aborted. $node stays as-is - * * otherwise - * => $node is set to the return value - * - * @param Node $node Node - * - * @return null|int|Node Replacement node (or special return value) - */ - public function enterNode(Node $node); - /** - * Called when leaving a node. - * - * Return value semantics: - * * null - * => $node stays as-is - * * NodeTraverser::REMOVE_NODE - * => $node is removed from the parent array - * * NodeTraverser::STOP_TRAVERSAL - * => Traversal is aborted. $node stays as-is - * * array (of Nodes) - * => The return value is merged into the parent array (at the position of the $node) - * * otherwise - * => $node is set to the return value - * - * @param Node $node Node - * - * @return null|int|Node|Node[] Replacement node (or special return value) - */ - public function leaveNode(Node $node); - /** - * Called once after traversal. - * - * Return value semantics: - * * null: $nodes stays as-is - * * otherwise: $nodes is set to the return value - * - * @param Node[] $nodes Array of nodes - * - * @return null|Node[] Array of nodes - */ - public function afterTraverse(array $nodes); -} -decodeRecursive($value); - } - private function decodeRecursive($value) - { - if (\is_array($value)) { - if (isset($value['nodeType'])) { - if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { - return $this->decodeComment($value); - } - return $this->decodeNode($value); - } - return $this->decodeArray($value); - } - return $value; - } - private function decodeArray(array $array) : array - { - $decodedArray = []; - foreach ($array as $key => $value) { - $decodedArray[$key] = $this->decodeRecursive($value); - } - return $decodedArray; - } - private function decodeNode(array $value) : Node - { - $nodeType = $value['nodeType']; - if (!\is_string($nodeType)) { - throw new \RuntimeException('Node type must be a string'); - } - $reflectionClass = $this->reflectionClassFromNodeType($nodeType); - /** @var Node $node */ - $node = $reflectionClass->newInstanceWithoutConstructor(); - if (isset($value['attributes'])) { - if (!\is_array($value['attributes'])) { - throw new \RuntimeException('Attributes must be an array'); - } - $node->setAttributes($this->decodeArray($value['attributes'])); - } - foreach ($value as $name => $subNode) { - if ($name === 'nodeType' || $name === 'attributes') { - continue; - } - $node->{$name} = $this->decodeRecursive($subNode); - } - return $node; - } - private function decodeComment(array $value) : Comment - { - $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; - if (!isset($value['text'])) { - throw new \RuntimeException('Comment must have text'); - } - return new $className($value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1); - } - private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass - { - if (!isset($this->reflectionClassCache[$nodeType])) { - $className = $this->classNameFromNodeType($nodeType); - $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); - } - return $this->reflectionClassCache[$nodeType]; - } - private function classNameFromNodeType(string $nodeType) : string - { - $className = 'PhpParser\\Node\\' . \strtr($nodeType, '_', '\\'); - if (\class_exists($className)) { - return $className; - } - $className .= '_'; - if (\class_exists($className)) { - return $className; - } - throw new \RuntimeException("Unknown node type \"{$nodeType}\""); - } -} -attributes = $attributes; - } - /** - * Gets line the node started in (alias of getStartLine). - * - * @return int Start line (or -1 if not available) - */ - public function getLine() : int - { - return $this->attributes['startLine'] ?? -1; - } - /** - * Gets line the node started in. - * - * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). - * - * @return int Start line (or -1 if not available) - */ - public function getStartLine() : int - { - return $this->attributes['startLine'] ?? -1; - } - /** - * Gets the line the node ended in. - * - * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). - * - * @return int End line (or -1 if not available) - */ - public function getEndLine() : int - { - return $this->attributes['endLine'] ?? -1; - } - /** - * Gets the token offset of the first token that is part of this node. - * - * The offset is an index into the array returned by Lexer::getTokens(). - * - * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). - * - * @return int Token start position (or -1 if not available) - */ - public function getStartTokenPos() : int - { - return $this->attributes['startTokenPos'] ?? -1; - } - /** - * Gets the token offset of the last token that is part of this node. - * - * The offset is an index into the array returned by Lexer::getTokens(). - * - * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). - * - * @return int Token end position (or -1 if not available) - */ - public function getEndTokenPos() : int - { - return $this->attributes['endTokenPos'] ?? -1; - } - /** - * Gets the file offset of the first character that is part of this node. - * - * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). - * - * @return int File start position (or -1 if not available) - */ - public function getStartFilePos() : int - { - return $this->attributes['startFilePos'] ?? -1; - } - /** - * Gets the file offset of the last character that is part of this node. - * - * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). - * - * @return int File end position (or -1 if not available) - */ - public function getEndFilePos() : int - { - return $this->attributes['endFilePos'] ?? -1; - } - /** - * Gets all comments directly preceding this node. - * - * The comments are also available through the "comments" attribute. - * - * @return Comment[] - */ - public function getComments() : array - { - return $this->attributes['comments'] ?? []; - } - /** - * Gets the doc comment of the node. - * - * @return null|Comment\Doc Doc comment object or null - */ - public function getDocComment() - { - $comments = $this->getComments(); - for ($i = \count($comments) - 1; $i >= 0; $i--) { - $comment = $comments[$i]; - if ($comment instanceof Comment\Doc) { - return $comment; - } - } - return null; - } - /** - * Sets the doc comment of the node. - * - * This will either replace an existing doc comment or add it to the comments array. - * - * @param Comment\Doc $docComment Doc comment to set - */ - public function setDocComment(Comment\Doc $docComment) - { - $comments = $this->getComments(); - for ($i = \count($comments) - 1; $i >= 0; $i--) { - if ($comments[$i] instanceof Comment\Doc) { - // Replace existing doc comment. - $comments[$i] = $docComment; - $this->setAttribute('comments', $comments); - return; - } - } - // Append new doc comment. - $comments[] = $docComment; - $this->setAttribute('comments', $comments); - } - public function setAttribute(string $key, $value) - { - $this->attributes[$key] = $value; - } - public function hasAttribute(string $key) : bool - { - return \array_key_exists($key, $this->attributes); - } - public function getAttribute(string $key, $default = null) - { - if (\array_key_exists($key, $this->attributes)) { - return $this->attributes[$key]; - } - return $default; - } - public function getAttributes() : array - { - return $this->attributes; - } - public function setAttributes(array $attributes) - { - $this->attributes = $attributes; - } - /** - * @return array - */ - public function jsonSerialize() : array - { - return ['nodeType' => $this->getType()] + \get_object_vars($this); - } -} -defineCompatibilityTokens(); - $this->tokenMap = $this->createTokenMap(); - $this->identifierTokens = $this->createIdentifierTokenMap(); - // map of tokens to drop while lexing (the map is only used for isset lookup, - // that's why the value is simply set to 1; the value is never actually used.) - $this->dropTokens = \array_fill_keys([\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1); - $defaultAttributes = ['comments', 'startLine', 'endLine']; - $usedAttributes = \array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, \true); - // Create individual boolean properties to make these checks faster. - $this->attributeStartLineUsed = isset($usedAttributes['startLine']); - $this->attributeEndLineUsed = isset($usedAttributes['endLine']); - $this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']); - $this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']); - $this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']); - $this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']); - $this->attributeCommentsUsed = isset($usedAttributes['comments']); - } - /** - * Initializes the lexer for lexing the provided source code. - * - * This function does not throw if lexing errors occur. Instead, errors may be retrieved using - * the getErrors() method. - * - * @param string $code The source code to lex - * @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to - * ErrorHandler\Throwing - */ - public function startLexing(string $code, ErrorHandler $errorHandler = null) - { - if (null === $errorHandler) { - $errorHandler = new ErrorHandler\Throwing(); - } - $this->code = $code; - // keep the code around for __halt_compiler() handling - $this->pos = -1; - $this->line = 1; - $this->filePos = 0; - // If inline HTML occurs without preceding code, treat it as if it had a leading newline. - // This ensures proper composability, because having a newline is the "safe" assumption. - $this->prevCloseTagHasNewline = \true; - $scream = \ini_set('xdebug.scream', '0'); - $this->tokens = @\token_get_all($code); - $this->postprocessTokens($errorHandler); - if (\false !== $scream) { - \ini_set('xdebug.scream', $scream); - } - } - private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) - { - $tokens = []; - for ($i = $start; $i < $end; $i++) { - $chr = $this->code[$i]; - if ($chr === "\0") { - // PHP cuts error message after null byte, so need special case - $errorMsg = 'Unexpected null byte'; - } else { - $errorMsg = \sprintf('Unexpected character "%s" (ASCII %d)', $chr, \ord($chr)); - } - $tokens[] = [\T_BAD_CHARACTER, $chr, $line]; - $errorHandler->handleError(new Error($errorMsg, ['startLine' => $line, 'endLine' => $line, 'startFilePos' => $i, 'endFilePos' => $i])); - } - return $tokens; - } - /** - * Check whether comment token is unterminated. - * - * @return bool - */ - private function isUnterminatedComment($token) : bool - { - return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT) && \substr($token[1], 0, 2) === '/*' && \substr($token[1], -2) !== '*/'; - } - protected function postprocessTokens(ErrorHandler $errorHandler) - { - // PHP's error handling for token_get_all() is rather bad, so if we want detailed - // error information we need to compute it ourselves. Invalid character errors are - // detected by finding "gaps" in the token array. Unterminated comments are detected - // by checking if a trailing comment has a "*/" at the end. - // - // Additionally, we perform a number of canonicalizations here: - // * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore. - // * Use PHP 8.0 T_NAME_* tokens. - // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and - // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. - $filePos = 0; - $line = 1; - $numTokens = \count($this->tokens); - for ($i = 0; $i < $numTokens; $i++) { - $token = $this->tokens[$i]; - // Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token. - // In this case we only need to emit an error. - if ($token[0] === \T_BAD_CHARACTER) { - $this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler); - } - if ($token[0] === \T_COMMENT && \substr($token[1], 0, 2) !== '/*' && \preg_match('/(\\r\\n|\\n|\\r)$/D', $token[1], $matches)) { - $trailingNewline = $matches[0]; - $token[1] = \substr($token[1], 0, -\strlen($trailingNewline)); - $this->tokens[$i] = $token; - if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) { - // Move trailing newline into following T_WHITESPACE token, if it already exists. - $this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1]; - $this->tokens[$i + 1][2]--; - } else { - // Otherwise, we need to create a new T_WHITESPACE token. - \array_splice($this->tokens, $i + 1, 0, [[\T_WHITESPACE, $trailingNewline, $line]]); - $numTokens++; - } - } - // Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING - // into a single token. - if (\is_array($token) && ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) { - $lastWasSeparator = $token[0] === \T_NS_SEPARATOR; - $text = $token[1]; - for ($j = $i + 1; isset($this->tokens[$j]); $j++) { - if ($lastWasSeparator) { - if (!isset($this->identifierTokens[$this->tokens[$j][0]])) { - break; - } - $lastWasSeparator = \false; - } else { - if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) { - break; - } - $lastWasSeparator = \true; - } - $text .= $this->tokens[$j][1]; - } - if ($lastWasSeparator) { - // Trailing separator is not part of the name. - $j--; - $text = \substr($text, 0, -1); - } - if ($j > $i + 1) { - if ($token[0] === \T_NS_SEPARATOR) { - $type = \T_NAME_FULLY_QUALIFIED; - } else { - if ($token[0] === \T_NAMESPACE) { - $type = \T_NAME_RELATIVE; - } else { - $type = \T_NAME_QUALIFIED; - } - } - $token = [$type, $text, $line]; - \array_splice($this->tokens, $i, $j - $i, [$token]); - $numTokens -= $j - $i - 1; - } - } - if ($token === '&') { - $next = $i + 1; - while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) { - $next++; - } - $followedByVarOrVarArg = isset($this->tokens[$next]) && ($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS); - $this->tokens[$i] = $token = [$followedByVarOrVarArg ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&', $line]; - } - $tokenValue = \is_string($token) ? $token : $token[1]; - $tokenLen = \strlen($tokenValue); - if (\substr($this->code, $filePos, $tokenLen) !== $tokenValue) { - // Something is missing, must be an invalid character - $nextFilePos = \strpos($this->code, $tokenValue, $filePos); - $badCharTokens = $this->handleInvalidCharacterRange($filePos, $nextFilePos, $line, $errorHandler); - $filePos = (int) $nextFilePos; - \array_splice($this->tokens, $i, 0, $badCharTokens); - $numTokens += \count($badCharTokens); - $i += \count($badCharTokens); - } - $filePos += $tokenLen; - $line += \substr_count($tokenValue, "\n"); - } - if ($filePos !== \strlen($this->code)) { - if (\substr($this->code, $filePos, 2) === '/*') { - // Unlike PHP, HHVM will drop unterminated comments entirely - $comment = \substr($this->code, $filePos); - $errorHandler->handleError(new Error('Unterminated comment', ['startLine' => $line, 'endLine' => $line + \substr_count($comment, "\n"), 'startFilePos' => $filePos, 'endFilePos' => $filePos + \strlen($comment)])); - // Emulate the PHP behavior - $isDocComment = isset($comment[3]) && $comment[3] === '*'; - $this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line]; - } else { - // Invalid characters at the end of the input - $badCharTokens = $this->handleInvalidCharacterRange($filePos, \strlen($this->code), $line, $errorHandler); - $this->tokens = \array_merge($this->tokens, $badCharTokens); - } - return; - } - if (\count($this->tokens) > 0) { - // Check for unterminated comment - $lastToken = $this->tokens[\count($this->tokens) - 1]; - if ($this->isUnterminatedComment($lastToken)) { - $errorHandler->handleError(new Error('Unterminated comment', ['startLine' => $line - \substr_count($lastToken[1], "\n"), 'endLine' => $line, 'startFilePos' => $filePos - \strlen($lastToken[1]), 'endFilePos' => $filePos])); - } - } - } - /** - * Fetches the next token. - * - * The available attributes are determined by the 'usedAttributes' option, which can - * be specified in the constructor. The following attributes are supported: - * - * * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances, - * representing all comments that occurred between the previous - * non-discarded token and the current one. - * * 'startLine' => Line in which the node starts. - * * 'endLine' => Line in which the node ends. - * * 'startTokenPos' => Offset into the token array of the first token in the node. - * * 'endTokenPos' => Offset into the token array of the last token in the node. - * * 'startFilePos' => Offset into the code string of the first character that is part of the node. - * * 'endFilePos' => Offset into the code string of the last character that is part of the node. - * - * @param mixed $value Variable to store token content in - * @param mixed $startAttributes Variable to store start attributes in - * @param mixed $endAttributes Variable to store end attributes in - * - * @return int Token id - */ - public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int - { - $startAttributes = []; - $endAttributes = []; - while (1) { - if (isset($this->tokens[++$this->pos])) { - $token = $this->tokens[$this->pos]; - } else { - // EOF token with ID 0 - $token = "\0"; - } - if ($this->attributeStartLineUsed) { - $startAttributes['startLine'] = $this->line; - } - if ($this->attributeStartTokenPosUsed) { - $startAttributes['startTokenPos'] = $this->pos; - } - if ($this->attributeStartFilePosUsed) { - $startAttributes['startFilePos'] = $this->filePos; - } - if (\is_string($token)) { - $value = $token; - if (isset($token[1])) { - // bug in token_get_all - $this->filePos += 2; - $id = \ord('"'); - } else { - $this->filePos += 1; - $id = \ord($token); - } - } elseif (!isset($this->dropTokens[$token[0]])) { - $value = $token[1]; - $id = $this->tokenMap[$token[0]]; - if (\T_CLOSE_TAG === $token[0]) { - $this->prevCloseTagHasNewline = \false !== \strpos($token[1], "\n") || \false !== \strpos($token[1], "\r"); - } elseif (\T_INLINE_HTML === $token[0]) { - $startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline; - } - $this->line += \substr_count($value, "\n"); - $this->filePos += \strlen($value); - } else { - $origLine = $this->line; - $origFilePos = $this->filePos; - $this->line += \substr_count($token[1], "\n"); - $this->filePos += \strlen($token[1]); - if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) { - if ($this->attributeCommentsUsed) { - $comment = \T_DOC_COMMENT === $token[0] ? new Comment\Doc($token[1], $origLine, $origFilePos, $this->pos, $this->line, $this->filePos - 1, $this->pos) : new Comment($token[1], $origLine, $origFilePos, $this->pos, $this->line, $this->filePos - 1, $this->pos); - $startAttributes['comments'][] = $comment; - } - } - continue; - } - if ($this->attributeEndLineUsed) { - $endAttributes['endLine'] = $this->line; - } - if ($this->attributeEndTokenPosUsed) { - $endAttributes['endTokenPos'] = $this->pos; - } - if ($this->attributeEndFilePosUsed) { - $endAttributes['endFilePos'] = $this->filePos - 1; - } - return $id; - } - throw new \RuntimeException('Reached end of lexer loop'); - } - /** - * Returns the token array for current code. - * - * The token array is in the same format as provided by the - * token_get_all() function and does not discard tokens (i.e. - * whitespace and comments are included). The token position - * attributes are against this token array. - * - * @return array Array of tokens in token_get_all() format - */ - public function getTokens() : array - { - return $this->tokens; - } - /** - * Handles __halt_compiler() by returning the text after it. - * - * @return string Remaining text - */ - public function handleHaltCompiler() : string - { - // text after T_HALT_COMPILER, still including (); - $textAfter = \substr($this->code, $this->filePos); - // ensure that it is followed by (); - // this simplifies the situation, by not allowing any comments - // in between of the tokens. - if (!\preg_match('~^\\s*\\(\\s*\\)\\s*(?:;|\\?>\\r?\\n?)~', $textAfter, $matches)) { - throw new Error('__HALT_COMPILER must be followed by "();"'); - } - // prevent the lexer from returning any further tokens - $this->pos = \count($this->tokens); - // return with (); removed - return \substr($textAfter, \strlen($matches[0])); - } - private function defineCompatibilityTokens() - { - static $compatTokensDefined = \false; - if ($compatTokensDefined) { - return; - } - $compatTokens = [ - // PHP 7.4 - 'T_BAD_CHARACTER', - 'T_FN', - 'T_COALESCE_EQUAL', - // PHP 8.0 - 'T_NAME_QUALIFIED', - 'T_NAME_FULLY_QUALIFIED', - 'T_NAME_RELATIVE', - 'T_MATCH', - 'T_NULLSAFE_OBJECT_OPERATOR', - 'T_ATTRIBUTE', - // PHP 8.1 - 'T_ENUM', - 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', - 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', - 'T_READONLY', - ]; - // PHP-Parser might be used together with another library that also emulates some or all - // of these tokens. Perform a sanity-check that all already defined tokens have been - // assigned a unique ID. - $usedTokenIds = []; - foreach ($compatTokens as $token) { - if (\defined($token)) { - $tokenId = \constant($token); - $clashingToken = $usedTokenIds[$tokenId] ?? null; - if ($clashingToken !== null) { - throw new \Error(\sprintf('Token %s has same ID as token %s, ' . 'you may be using a library with broken token emulation', $token, $clashingToken)); - } - $usedTokenIds[$tokenId] = $token; - } - } - // Now define any tokens that have not yet been emulated. Try to assign IDs from -1 - // downwards, but skip any IDs that may already be in use. - $newTokenId = -1; - foreach ($compatTokens as $token) { - if (!\defined($token)) { - while (isset($usedTokenIds[$newTokenId])) { - $newTokenId--; - } - \define($token, $newTokenId); - $newTokenId--; - } - } - $compatTokensDefined = \true; - } - /** - * Creates the token map. - * - * The token map maps the PHP internal token identifiers - * to the identifiers used by the Parser. Additionally it - * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'. - * - * @return array The token map - */ - protected function createTokenMap() : array - { - $tokenMap = []; - // 256 is the minimum possible token number, as everything below - // it is an ASCII value - for ($i = 256; $i < 1000; ++$i) { - if (\T_DOUBLE_COLON === $i) { - // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM - $tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM; - } elseif (\T_OPEN_TAG_WITH_ECHO === $i) { - // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO - $tokenMap[$i] = Tokens::T_ECHO; - } elseif (\T_CLOSE_TAG === $i) { - // T_CLOSE_TAG is equivalent to ';' - $tokenMap[$i] = \ord(';'); - } elseif ('UNKNOWN' !== ($name = \token_name($i))) { - if ('T_HASHBANG' === $name) { - // HHVM uses a special token for #! hashbang lines - $tokenMap[$i] = Tokens::T_INLINE_HTML; - } elseif (\defined($name = Tokens::class . '::' . $name)) { - // Other tokens can be mapped directly - $tokenMap[$i] = \constant($name); - } - } - } - // HHVM uses a special token for numbers that overflow to double - if (\defined('T_ONUMBER')) { - $tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER; - } - // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant - if (\defined('T_COMPILER_HALT_OFFSET')) { - $tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING; - } - // Assign tokens for which we define compatibility constants, as token_name() does not know them. - $tokenMap[\T_FN] = Tokens::T_FN; - $tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL; - $tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED; - $tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED; - $tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE; - $tokenMap[\T_MATCH] = Tokens::T_MATCH; - $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR; - $tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE; - $tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; - $tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; - $tokenMap[\T_ENUM] = Tokens::T_ENUM; - $tokenMap[\T_READONLY] = Tokens::T_READONLY; - return $tokenMap; - } - private function createIdentifierTokenMap() : array - { - // Based on semi_reserved production. - return \array_fill_keys([\T_STRING, \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, \T_MATCH], \true); - } -} -'", "T_IS_GREATER_OR_EQUAL", "T_SL", "T_SR", "'+'", "'-'", "'.'", "'*'", "'/'", "'%'", "'!'", "T_INSTANCEOF", "'~'", "T_INC", "T_DEC", "T_INT_CAST", "T_DOUBLE_CAST", "T_STRING_CAST", "T_ARRAY_CAST", "T_OBJECT_CAST", "T_BOOL_CAST", "T_UNSET_CAST", "'@'", "T_POW", "'['", "T_NEW", "T_CLONE", "T_EXIT", "T_IF", "T_ELSEIF", "T_ELSE", "T_ENDIF", "T_LNUMBER", "T_DNUMBER", "T_STRING", "T_STRING_VARNAME", "T_VARIABLE", "T_NUM_STRING", "T_INLINE_HTML", "T_ENCAPSED_AND_WHITESPACE", "T_CONSTANT_ENCAPSED_STRING", "T_ECHO", "T_DO", "T_WHILE", "T_ENDWHILE", "T_FOR", "T_ENDFOR", "T_FOREACH", "T_ENDFOREACH", "T_DECLARE", "T_ENDDECLARE", "T_AS", "T_SWITCH", "T_MATCH", "T_ENDSWITCH", "T_CASE", "T_DEFAULT", "T_BREAK", "T_CONTINUE", "T_GOTO", "T_FUNCTION", "T_FN", "T_CONST", "T_RETURN", "T_TRY", "T_CATCH", "T_FINALLY", "T_USE", "T_INSTEADOF", "T_GLOBAL", "T_STATIC", "T_ABSTRACT", "T_FINAL", "T_PRIVATE", "T_PROTECTED", "T_PUBLIC", "T_READONLY", "T_VAR", "T_UNSET", "T_ISSET", "T_EMPTY", "T_HALT_COMPILER", "T_CLASS", "T_TRAIT", "T_INTERFACE", "T_ENUM", "T_EXTENDS", "T_IMPLEMENTS", "T_OBJECT_OPERATOR", "T_NULLSAFE_OBJECT_OPERATOR", "T_LIST", "T_ARRAY", "T_CALLABLE", "T_CLASS_C", "T_TRAIT_C", "T_METHOD_C", "T_FUNC_C", "T_LINE", "T_FILE", "T_START_HEREDOC", "T_END_HEREDOC", "T_DOLLAR_OPEN_CURLY_BRACES", "T_CURLY_OPEN", "T_PAAMAYIM_NEKUDOTAYIM", "T_NAMESPACE", "T_NS_C", "T_DIR", "T_NS_SEPARATOR", "T_ELLIPSIS", "T_NAME_FULLY_QUALIFIED", "T_NAME_QUALIFIED", "T_NAME_RELATIVE", "T_ATTRIBUTE", "';'", "']'", "'{'", "'}'", "'('", "')'", "'`'", "'\"'", "'\$'"); - protected $tokenToSymbol = array(0, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, 163, 164, 53, 50, 8, 51, 52, 54, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 161, 35, 162, 58, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158); - protected $action = array(131, 132, 133, 569, 134, 135, 0, 722, 723, 724, 136, 36, 834, 911, 835, 468, -32766, -32766, -32766, -32767, -32767, -32767, -32767, 100, 101, 102, 103, 104, 1068, 1069, 1070, 1067, 1066, 1065, 1071, 716, 715, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, -32767, 545, 546, -32766, -32766, 725, -32766, -32766, -32766, 998, 999, 806, 922, 446, 447, 448, 369, 370, 2, 266, 137, 395, 729, 730, 731, 732, 413, -32766, 419, -32766, -32766, -32766, -32766, -32766, 990, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 763, 570, 764, 765, 766, 767, 755, 756, 335, 336, 758, 759, 744, 745, 746, 748, 749, 750, 345, 790, 791, 792, 793, 794, 795, 751, 752, 571, 572, 784, 775, 773, 774, 787, 770, 771, 282, 419, 573, 574, 769, 575, 576, 577, 578, 579, 580, 598, -575, 469, 491, 798, 772, 581, 582, -575, 138, -32766, -32766, -32766, 131, 132, 133, 569, 134, 135, 1017, 722, 723, 724, 136, 36, 1060, -32766, -32766, -32766, 1303, 696, -32766, 1304, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 1068, 1069, 1070, 1067, 1066, 1065, 1071, -32766, 716, 715, 371, 370, 1258, -32766, -32766, -32766, -572, 105, 106, 107, 413, 269, 891, -572, 239, 1193, 1192, 1194, 725, -32766, -32766, -32766, 1046, 108, -32766, -32766, -32766, -32766, 986, 985, 984, 987, 266, 137, 395, 729, 730, 731, 732, 12, -32766, 419, -32766, -32766, -32766, -32766, 998, 999, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 763, 570, 764, 765, 766, 767, 755, 756, 335, 336, 758, 759, 744, 745, 746, 748, 749, 750, 345, 790, 791, 792, 793, 794, 795, 751, 752, 571, 572, 784, 775, 773, 774, 787, 770, 771, 881, 320, 573, 574, 769, 575, 576, 577, 578, 579, 580, -32766, 81, 82, 83, -575, 772, 581, 582, -575, 147, 747, 717, 718, 719, 720, 721, 1278, 722, 723, 724, 760, 761, 35, 1277, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 996, 269, 149, -32766, -32766, -32766, 454, 455, 80, 33, -264, -572, 1016, 108, 319, -572, 893, 725, 682, 803, 127, 998, 999, 592, -32766, 1044, -32766, -32766, -32766, 809, 150, 726, 727, 728, 729, 730, 731, 732, -88, 1198, 796, 277, -526, 282, -32766, -32766, -32766, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 763, 786, 764, 765, 766, 767, 755, 756, 757, 785, 758, 759, 744, 745, 746, 748, 749, 750, 789, 790, 791, 792, 793, 794, 795, 751, 752, 753, 754, 784, 775, 773, 774, 787, 770, 771, 143, 804, 762, 768, 769, 776, 777, 779, 778, 780, 781, -314, -526, -526, -193, -192, 772, 783, 782, 48, 49, 50, 500, 51, 52, 238, 807, -526, -86, 53, 54, -111, 55, 996, 252, -32766, -111, 800, -111, -526, 541, -532, -352, 299, -352, 303, -111, -111, -111, -111, -111, -111, -111, -111, 998, 999, 998, 999, 152, -32766, -32766, -32766, 1191, 807, 125, 305, 1293, 56, 57, 102, 103, 104, -111, 58, 1218, 59, 245, 246, 60, 61, 62, 63, 64, 65, 66, 67, -525, 26, 267, 68, 435, 501, -328, 808, -86, 1224, 1225, 502, 1189, 807, 1198, 1230, 292, 1222, 40, 23, 503, 73, 504, 953, 505, 319, 506, 802, 153, 507, 508, 278, 684, 279, 42, 43, 436, 366, 365, 891, 44, 509, 34, 248, -16, -566, 357, 331, 317, -566, 1198, 1193, 1192, 1194, -527, 510, 511, 512, 332, -524, 1274, 47, 716, 715, -525, -525, 333, 513, 514, 807, 1212, 1213, 1214, 1215, 1209, 1210, 291, 359, 283, -525, 284, -314, 1216, 1211, -193, -192, 1193, 1192, 1194, 292, 891, -525, 363, -531, 69, 807, 315, 316, 319, 30, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, -153, -153, -153, 638, 24, -527, -527, 687, 378, 881, -524, -524, 295, 296, 891, -153, 431, -153, 807, -153, -527, -153, 716, 715, 432, -524, 798, 362, -111, 1105, 1107, 364, -527, 433, 891, 139, 434, -524, 954, 126, -524, 319, -111, -111, 688, 813, 380, -529, 11, 834, 154, 835, 867, -111, -111, -111, -111, 46, 292, -32766, 881, 654, 655, 73, 689, 1191, 1045, 319, 708, 148, 398, 156, -32766, -32766, -32766, 31, -32766, -79, -32766, 122, -32766, 716, 715, -32766, 893, 891, 682, -153, -32766, -32766, -32766, 716, 715, 891, -32766, -32766, 123, 881, 128, 73, -32766, 410, 129, 319, -524, -524, 142, 140, -75, -32766, 157, -529, -529, 319, 26, 691, 158, 881, 159, -524, 160, 293, 294, 698, 367, 368, 807, -73, -32766, -72, 1222, -524, 372, 373, 1191, 893, -71, 682, -529, 72, -70, -32766, -32766, -32766, -69, -32766, -68, -32766, 124, -32766, 630, 631, -32766, -67, -66, -47, -51, -32766, -32766, -32766, -18, 146, 270, -32766, -32766, 276, 697, 700, 881, -32766, 410, 890, 893, 145, 682, 281, 881, 907, -32766, 280, 513, 514, 285, 1212, 1213, 1214, 1215, 1209, 1210, 325, 130, 144, 939, 286, 682, 1216, 1211, 108, 269, -32766, 798, 807, -32766, 662, 639, 1191, 657, 71, 675, 1075, 316, 319, -32766, -32766, -32766, 1305, -32766, 300, -32766, 628, -32766, 430, 543, -32766, -32766, 923, 555, 924, -32766, -32766, -32766, 1229, 549, -32766, -32766, -32766, -4, 891, -490, 1191, -32766, 410, 644, 893, 298, 682, -32766, -32766, -32766, -32766, -32766, 893, -32766, 682, -32766, 13, 1231, -32766, 451, 479, 645, 909, -32766, -32766, -32766, -32766, 658, -480, -32766, -32766, 0, 1191, 0, 0, -32766, 410, 0, 297, -32766, -32766, -32766, 304, -32766, -32766, -32766, 0, -32766, 0, 806, -32766, 0, 0, 0, 474, -32766, -32766, -32766, -32766, 0, 7, -32766, -32766, 15, 1191, 561, 596, -32766, 410, 1219, 891, -32766, -32766, -32766, 361, -32766, -32766, -32766, 818, -32766, -267, 881, -32766, 38, 292, 0, 0, -32766, -32766, -32766, 39, 705, 706, -32766, -32766, 872, 963, 940, 947, -32766, 410, 937, 948, 364, 870, 426, 891, 935, -32766, 1049, 290, 1244, 1052, 1053, -111, -111, 1050, 1051, 1057, -560, 1262, 1296, 633, 0, 826, -111, -111, -111, -111, 32, 314, -32766, 360, 683, 686, 690, 692, 1191, 693, 694, 695, 699, 685, 319, -32766, -32766, -32766, 9, -32766, 702, -32766, 868, -32766, 881, 1300, -32766, 893, 1302, 682, -4, -32766, -32766, -32766, 829, 828, 837, -32766, -32766, 916, -242, -242, -242, -32766, 410, 955, 364, 26, 836, 1301, 915, 917, -32766, 914, 1177, 900, 910, -111, -111, 807, 881, 898, 945, 1222, 946, 1299, 1256, 867, -111, -111, -111, -111, 1245, 1263, 1269, 1272, -241, -241, -241, -558, -532, -531, 364, -530, 1, 27, 28, 37, 41, 45, 70, 0, 74, -111, -111, 75, 76, 77, 78, 893, 79, 682, -242, 867, -111, -111, -111, -111, 141, 151, 155, 244, 321, 346, 514, 347, 1212, 1213, 1214, 1215, 1209, 1210, 348, 349, 350, 351, 352, 353, 1216, 1211, 354, 355, 356, 358, 427, 893, -265, 682, -241, -264, 71, 0, 17, 316, 319, 18, 19, 20, 22, 397, 470, 471, 478, 481, 482, 483, 484, 488, 489, 490, 498, 669, 1202, 1145, 1220, 1019, 1018, 1181, -269, -103, 16, 21, 25, 289, 396, 589, 593, 620, 674, 1149, 1197, 1146, 1275, 0, -494, 1162, 0, 1223); - protected $actionCheck = array(2, 3, 4, 5, 6, 7, 0, 9, 10, 11, 12, 13, 106, 1, 108, 31, 9, 10, 11, 44, 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, 118, 119, 120, 121, 122, 37, 38, 30, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 117, 118, 9, 10, 57, 9, 10, 11, 137, 138, 155, 128, 129, 130, 131, 106, 107, 8, 71, 72, 73, 74, 75, 76, 77, 116, 30, 80, 32, 33, 34, 35, 36, 1, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 30, 80, 136, 137, 138, 139, 140, 141, 142, 143, 144, 51, 1, 161, 101, 80, 150, 151, 152, 8, 154, 9, 10, 11, 2, 3, 4, 5, 6, 7, 164, 9, 10, 11, 12, 13, 123, 9, 10, 11, 80, 161, 30, 83, 32, 33, 34, 35, 36, 37, 38, 116, 117, 118, 119, 120, 121, 122, 30, 37, 38, 106, 107, 1, 9, 10, 11, 1, 53, 54, 55, 116, 57, 1, 8, 14, 155, 156, 157, 57, 9, 10, 11, 162, 69, 30, 116, 32, 33, 119, 120, 121, 122, 71, 72, 73, 74, 75, 76, 77, 8, 30, 80, 32, 33, 34, 35, 137, 138, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 84, 70, 136, 137, 138, 139, 140, 141, 142, 143, 144, 9, 9, 10, 11, 160, 150, 151, 152, 164, 154, 2, 3, 4, 5, 6, 7, 1, 9, 10, 11, 12, 13, 30, 8, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 116, 57, 14, 9, 10, 11, 134, 135, 161, 8, 164, 160, 1, 69, 167, 164, 159, 57, 161, 80, 8, 137, 138, 1, 30, 1, 32, 33, 34, 1, 14, 71, 72, 73, 74, 75, 76, 77, 31, 1, 80, 30, 70, 30, 9, 10, 11, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 8, 156, 136, 137, 138, 139, 140, 141, 142, 143, 144, 8, 134, 135, 8, 8, 150, 151, 152, 2, 3, 4, 5, 6, 7, 97, 82, 149, 31, 12, 13, 101, 15, 116, 8, 116, 106, 80, 108, 161, 85, 163, 106, 113, 108, 8, 116, 117, 118, 119, 120, 121, 122, 123, 137, 138, 137, 138, 14, 9, 10, 11, 80, 82, 14, 8, 85, 50, 51, 50, 51, 52, 128, 56, 1, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 70, 71, 72, 73, 74, 162, 159, 97, 78, 79, 80, 116, 82, 1, 146, 158, 86, 87, 88, 89, 163, 91, 31, 93, 167, 95, 156, 14, 98, 99, 35, 161, 37, 103, 104, 105, 106, 107, 1, 109, 110, 147, 148, 31, 160, 115, 116, 8, 164, 1, 155, 156, 157, 70, 124, 125, 126, 8, 70, 1, 70, 37, 38, 134, 135, 8, 136, 137, 82, 139, 140, 141, 142, 143, 144, 145, 8, 35, 149, 37, 164, 151, 152, 164, 164, 155, 156, 157, 158, 1, 161, 8, 163, 163, 82, 165, 166, 167, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 75, 76, 77, 75, 76, 134, 135, 31, 8, 84, 134, 135, 134, 135, 1, 90, 8, 92, 82, 94, 149, 96, 37, 38, 8, 149, 80, 149, 128, 59, 60, 106, 161, 8, 1, 161, 8, 161, 159, 161, 70, 167, 117, 118, 31, 8, 106, 70, 108, 106, 14, 108, 127, 128, 129, 130, 131, 70, 158, 74, 84, 75, 76, 163, 31, 80, 159, 167, 161, 101, 102, 14, 87, 88, 89, 14, 91, 31, 93, 16, 95, 37, 38, 98, 159, 1, 161, 162, 103, 104, 105, 37, 38, 1, 109, 110, 16, 84, 16, 163, 115, 116, 16, 167, 134, 135, 16, 161, 31, 124, 16, 134, 135, 167, 70, 31, 16, 84, 16, 149, 16, 134, 135, 31, 106, 107, 82, 31, 74, 31, 86, 161, 106, 107, 80, 159, 31, 161, 161, 154, 31, 87, 88, 89, 31, 91, 31, 93, 161, 95, 111, 112, 98, 31, 31, 31, 31, 103, 104, 105, 31, 31, 31, 109, 110, 31, 31, 31, 84, 115, 116, 31, 159, 31, 161, 37, 84, 38, 124, 35, 136, 137, 35, 139, 140, 141, 142, 143, 144, 35, 31, 70, 159, 37, 161, 151, 152, 69, 57, 74, 80, 82, 85, 77, 90, 80, 94, 163, 92, 82, 166, 167, 87, 88, 89, 83, 91, 114, 93, 113, 95, 128, 85, 98, 116, 128, 153, 128, 103, 104, 105, 146, 89, 74, 109, 110, 0, 1, 149, 80, 115, 116, 96, 159, 133, 161, 87, 88, 89, 124, 91, 159, 93, 161, 95, 97, 146, 98, 97, 97, 100, 154, 103, 104, 105, 74, 100, 149, 109, 110, -1, 80, -1, -1, 115, 116, -1, 132, 87, 88, 89, 132, 91, 124, 93, -1, 95, -1, 155, 98, -1, -1, -1, 102, 103, 104, 105, 74, -1, 149, 109, 110, 149, 80, 81, 153, 115, 116, 160, 1, 87, 88, 89, 149, 91, 124, 93, 160, 95, 164, 84, 98, 159, 158, -1, -1, 103, 104, 105, 159, 159, 159, 109, 110, 159, 159, 159, 159, 115, 116, 159, 159, 106, 159, 108, 1, 159, 124, 159, 113, 160, 159, 159, 117, 118, 159, 159, 159, 163, 160, 160, 160, -1, 127, 128, 129, 130, 131, 161, 161, 74, 161, 161, 161, 161, 161, 80, 161, 161, 161, 161, 161, 167, 87, 88, 89, 150, 91, 162, 93, 162, 95, 84, 162, 98, 159, 162, 161, 162, 103, 104, 105, 162, 162, 162, 109, 110, 162, 100, 101, 102, 115, 116, 162, 106, 70, 162, 162, 162, 162, 124, 162, 162, 162, 162, 117, 118, 82, 84, 162, 162, 86, 162, 162, 162, 127, 128, 129, 130, 131, 162, 162, 162, 162, 100, 101, 102, 163, 163, 163, 106, 163, 163, 163, 163, 163, 163, 163, 163, -1, 163, 117, 118, 163, 163, 163, 163, 159, 163, 161, 162, 127, 128, 129, 130, 131, 163, 163, 163, 163, 163, 163, 137, 163, 139, 140, 141, 142, 143, 144, 163, 163, 163, 163, 163, 163, 151, 152, 163, 163, 163, 163, 163, 159, 164, 161, 162, 164, 163, -1, 164, 166, 167, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, -1, 165, 165, -1, 166); - protected $actionBase = array(0, -2, 154, 565, 876, 948, 984, 514, 53, 398, 837, 307, 307, 67, 307, 307, 653, 724, 724, 732, 724, 616, 673, 204, 204, 204, 625, 625, 625, 625, 694, 694, 831, 831, 863, 799, 765, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, 375, 519, 369, 701, 1017, 1023, 1019, 1024, 1015, 1014, 1018, 1020, 1025, 911, 912, 782, 918, 919, 920, 921, 1021, 841, 1016, 1022, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 290, 491, 44, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 160, 160, 160, 187, 684, 684, 341, 203, 610, 47, 985, 985, 985, 985, 985, 985, 985, 985, 985, 985, 144, 144, 7, 7, 7, 7, 7, 371, -25, -25, -25, -25, 540, 385, 102, 576, 358, 45, 377, 460, 460, 360, 231, 231, 231, 231, 231, 231, -78, -78, -78, -78, -78, -66, 319, 457, -94, 396, 423, 586, 586, 586, 586, 423, 423, 423, 423, 750, 1029, 423, 423, 423, 511, 516, 516, 518, 147, 147, 147, 516, 583, 777, 422, 583, 422, 194, 92, 748, -40, 87, 412, 748, 617, 627, 198, 143, 773, 658, 773, 1013, 757, 764, 717, 838, 860, 1026, 800, 908, 806, 910, 219, 686, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 1012, 855, 552, 1013, 286, 855, 855, 855, 552, 552, 552, 552, 552, 552, 552, 552, 552, 552, 679, 286, 568, 626, 286, 794, 552, 375, 758, 375, 375, 375, 375, 958, 375, 375, 375, 375, 375, 375, 970, 769, -16, 375, 519, 12, 12, 547, 83, 12, 12, 12, 12, 375, 375, 375, 658, 781, 713, 666, 792, 448, 781, 781, 781, 438, 444, 193, 447, 570, 523, 580, 760, 760, 767, 929, 929, 760, 759, 760, 767, 934, 760, 929, 805, 359, 648, 577, 611, 656, 929, 478, 760, 760, 760, 760, 665, 760, 467, 433, 760, 760, 785, 774, 789, 60, 929, 929, 929, 789, 596, 751, 751, 751, 811, 812, 746, 771, 567, 498, 677, 348, 779, 771, 771, 760, 640, 746, 771, 746, 771, 747, 771, 771, 771, 746, 771, 760, 759, 585, 771, 734, 668, 224, 771, 6, 935, 937, 354, 940, 932, 941, 979, 942, 943, 851, 956, 933, 945, 931, 930, 780, 703, 720, 790, 729, 928, 768, 768, 768, 925, 768, 768, 768, 768, 768, 768, 768, 768, 703, 788, 804, 733, 783, 960, 722, 726, 725, 868, 1027, 1028, 737, 739, 958, 1006, 953, 803, 730, 992, 967, 866, 848, 968, 969, 993, 1007, 1008, 871, 761, 874, 880, 797, 971, 852, 768, 935, 943, 933, 945, 931, 930, 763, 762, 753, 755, 749, 745, 736, 738, 770, 1009, 924, 835, 830, 970, 926, 703, 839, 986, 847, 994, 995, 850, 801, 772, 840, 881, 972, 975, 976, 853, 1010, 810, 989, 795, 996, 802, 882, 997, 998, 999, 1000, 885, 854, 856, 857, 815, 754, 980, 786, 891, 335, 787, 796, 978, 363, 957, 858, 894, 895, 1001, 1002, 1003, 896, 954, 816, 990, 752, 991, 983, 817, 818, 485, 784, 778, 541, 676, 897, 899, 900, 955, 775, 766, 821, 822, 1011, 901, 697, 824, 740, 902, 1005, 742, 744, 756, 859, 793, 743, 798, 977, 776, 827, 907, 829, 832, 833, 1004, 836, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 458, 458, 458, 458, 458, 458, 307, 307, 307, 307, 0, 0, 307, 0, 0, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 423, 423, 291, 291, 0, 291, 423, 423, 423, 423, 423, 423, 423, 423, 423, 423, 291, 291, 291, 291, 291, 291, 291, 805, 147, 147, 147, 147, 423, 423, 423, 423, 423, -88, -88, 147, 147, 423, 423, 423, 423, 423, 423, 423, 423, 423, 423, 423, 423, 0, 0, 0, 286, 422, 0, 759, 759, 759, 759, 0, 0, 0, 0, 422, 422, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 286, 422, 0, 286, 0, 759, 759, 423, 805, 805, 314, 423, 0, 0, 0, 0, 286, 759, 286, 552, 422, 552, 552, 12, 375, 314, 608, 608, 608, 608, 0, 658, 805, 805, 805, 805, 805, 805, 805, 805, 805, 805, 805, 759, 0, 805, 0, 759, 759, 759, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 759, 0, 0, 929, 0, 0, 0, 0, 760, 0, 0, 0, 0, 0, 0, 760, 934, 0, 0, 0, 0, 0, 0, 759, 0, 0, 0, 0, 0, 0, 0, 0, 768, 801, 0, 801, 0, 768, 768, 768); - protected $actionDefault = array(3, 32767, 103, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 101, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 578, 578, 578, 578, 32767, 32767, 246, 103, 32767, 32767, 454, 372, 372, 372, 32767, 32767, 522, 522, 522, 522, 522, 522, 32767, 32767, 32767, 32767, 32767, 32767, 454, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 101, 32767, 32767, 32767, 37, 7, 8, 10, 11, 50, 17, 310, 32767, 32767, 32767, 32767, 103, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 571, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 458, 437, 438, 440, 441, 371, 523, 577, 313, 574, 370, 146, 325, 315, 234, 316, 250, 459, 251, 460, 463, 464, 211, 279, 367, 150, 401, 455, 403, 453, 457, 402, 377, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 375, 376, 456, 434, 433, 432, 399, 32767, 32767, 400, 404, 374, 407, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 103, 32767, 405, 406, 423, 424, 421, 422, 425, 32767, 426, 427, 428, 429, 32767, 32767, 302, 32767, 32767, 351, 349, 414, 415, 302, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 516, 431, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 103, 32767, 101, 518, 396, 398, 486, 409, 410, 408, 378, 32767, 493, 32767, 103, 495, 32767, 32767, 32767, 112, 32767, 32767, 32767, 517, 32767, 524, 524, 32767, 479, 101, 194, 32767, 194, 194, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 585, 479, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 32767, 194, 111, 32767, 32767, 32767, 101, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 189, 32767, 260, 262, 103, 539, 194, 32767, 498, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 491, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 479, 419, 139, 32767, 139, 524, 411, 412, 413, 481, 524, 524, 524, 298, 281, 32767, 32767, 32767, 32767, 496, 496, 101, 101, 101, 101, 491, 32767, 32767, 112, 100, 100, 100, 100, 100, 104, 102, 32767, 32767, 32767, 32767, 100, 32767, 102, 102, 32767, 32767, 217, 208, 215, 102, 32767, 543, 544, 215, 102, 219, 219, 219, 239, 239, 470, 304, 102, 100, 102, 102, 196, 304, 304, 32767, 102, 470, 304, 470, 304, 198, 304, 304, 304, 470, 304, 32767, 32767, 102, 304, 210, 100, 100, 304, 32767, 32767, 32767, 481, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 511, 32767, 528, 541, 417, 418, 420, 526, 442, 443, 444, 445, 446, 447, 448, 450, 573, 32767, 485, 32767, 32767, 32767, 32767, 324, 583, 32767, 583, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 584, 32767, 524, 32767, 32767, 32767, 32767, 416, 9, 76, 43, 44, 52, 58, 502, 503, 504, 505, 499, 500, 506, 501, 32767, 32767, 507, 549, 32767, 32767, 525, 576, 32767, 32767, 32767, 32767, 32767, 32767, 139, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 511, 32767, 137, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 524, 32767, 32767, 32767, 300, 301, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 524, 32767, 32767, 32767, 283, 284, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 278, 32767, 32767, 366, 32767, 32767, 32767, 32767, 345, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 152, 152, 3, 3, 327, 152, 152, 152, 327, 152, 327, 327, 327, 152, 152, 152, 152, 152, 152, 272, 184, 254, 257, 239, 239, 152, 337, 152); - protected $goto = array(193, 193, 670, 421, 643, 1022, 1290, 1290, 824, 415, 307, 308, 328, 563, 313, 420, 329, 422, 622, 801, 678, 341, 586, 1290, 825, 164, 164, 164, 164, 217, 194, 190, 190, 174, 176, 212, 190, 190, 190, 190, 190, 191, 191, 191, 191, 191, 191, 185, 186, 187, 188, 189, 214, 212, 215, 521, 522, 411, 523, 525, 526, 527, 528, 529, 530, 531, 532, 1091, 165, 166, 167, 192, 168, 169, 170, 163, 171, 172, 173, 175, 211, 213, 216, 234, 237, 240, 241, 243, 254, 255, 256, 257, 258, 259, 260, 262, 263, 264, 265, 273, 274, 310, 311, 312, 416, 417, 418, 568, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 177, 233, 178, 195, 196, 197, 235, 185, 186, 187, 188, 189, 214, 1091, 198, 179, 180, 181, 199, 195, 182, 236, 200, 198, 162, 201, 202, 183, 203, 204, 205, 184, 206, 207, 208, 209, 210, 322, 322, 322, 322, 827, 608, 608, 858, 547, 538, 1186, 1221, 1221, 1221, 1221, 1221, 1221, 1221, 1221, 1221, 1221, 1239, 1239, 462, 1264, 1265, 799, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 387, 538, 547, 556, 557, 394, 566, 588, 602, 603, 832, 938, 880, 875, 876, 889, 14, 833, 877, 830, 878, 879, 831, 453, 453, 884, 883, 885, 1187, 250, 250, 560, 453, 1237, 1237, 815, 1043, 1039, 1040, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 820, 820, 1188, 1247, 1248, 247, 247, 247, 247, 249, 251, 342, 343, 339, 1190, 1190, 997, 1190, 997, 1279, 930, 401, 677, 997, 997, 997, 997, 997, 997, 997, 997, 997, 997, 997, 997, 1261, 1261, 414, 1261, 597, 1190, 287, 287, 287, 287, 1190, 1190, 1190, 1190, 959, 344, 1190, 1190, 1190, 1271, 1271, 1271, 1271, 606, 640, 344, 344, 1273, 1273, 1273, 1273, 1063, 1064, 637, 896, 651, 652, 653, 897, 344, 344, 383, 344, 486, 1306, 487, 535, 535, 5, 535, 6, 494, 559, 1257, 1140, 540, 524, 524, 344, 318, 302, 642, 524, 524, 524, 524, 524, 524, 524, 524, 524, 524, 444, 1266, 1267, 618, 619, 932, 932, 932, 932, 820, 428, 444, 926, 933, 330, 533, 533, 533, 533, 1030, 590, 817, 554, 1259, 1259, 1030, 704, 621, 623, 845, 641, 1250, 805, 393, 660, 664, 973, 668, 676, 969, 1183, 553, 842, 823, 1289, 1289, 564, 600, 601, 385, 389, 548, 587, 591, 663, 962, 936, 936, 934, 936, 703, 1289, 537, 971, 966, 438, 901, 1079, 981, 1028, 438, 438, 805, 605, 805, 707, 854, 1292, 978, 463, 539, 551, 1074, 467, 540, 539, 844, 551, 646, 957, 386, 1171, 912, 1032, 838, 1172, 1175, 913, 1176, 943, 567, 456, 457, 458, 0, 850, 0, 1182, 1297, 1298, 253, 253, 0, 399, 400, 0, 0, 0, 649, 0, 650, 423, 403, 404, 405, 840, 661, 0, 423, 0, 406, 0, 0, 848, 337, 1009, 1002, 1006, 1003, 1007, 852, 0, 0, 839, 1185, 495, 0, 0, 0, 0, 438, 438, 438, 438, 438, 438, 438, 438, 438, 438, 438, 0, 0, 438, 595, 609, 612, 613, 614, 615, 634, 635, 636, 680, 853, 841, 1027, 1031, 585, 1056, 0, 681, 667, 667, 941, 673, 1054, 0, 0, 0, 0, 0, 271, 0, 0, 0, 0, 536, 536, 919, 992, 1000, 1004, 1001, 1005, 0, 0, 931, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 976, 976, 0, 1072, 857); - protected $gotoCheck = array(42, 42, 72, 65, 65, 119, 173, 173, 26, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 7, 9, 93, 122, 173, 27, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 23, 23, 23, 23, 15, 104, 104, 45, 75, 75, 20, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 160, 160, 166, 166, 166, 6, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 15, 49, 15, 15, 15, 15, 75, 15, 15, 15, 15, 15, 15, 141, 141, 64, 15, 64, 20, 5, 5, 162, 141, 161, 161, 20, 15, 15, 15, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 22, 22, 20, 20, 20, 5, 5, 5, 5, 5, 5, 93, 93, 169, 72, 72, 72, 72, 72, 171, 89, 89, 89, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 122, 122, 13, 122, 13, 72, 24, 24, 24, 24, 72, 72, 72, 72, 99, 14, 72, 72, 72, 9, 9, 9, 9, 55, 55, 14, 14, 122, 122, 122, 122, 136, 136, 84, 72, 84, 84, 84, 72, 14, 14, 61, 14, 147, 14, 147, 19, 19, 46, 19, 46, 147, 100, 122, 143, 14, 163, 163, 14, 159, 159, 63, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 19, 168, 168, 83, 83, 19, 19, 19, 19, 22, 109, 19, 19, 19, 29, 103, 103, 103, 103, 122, 103, 18, 48, 122, 122, 122, 48, 48, 48, 39, 48, 14, 12, 28, 48, 48, 48, 48, 48, 48, 152, 9, 37, 25, 172, 172, 2, 2, 9, 58, 58, 58, 58, 58, 14, 25, 25, 25, 25, 25, 25, 172, 25, 25, 25, 23, 17, 17, 106, 121, 23, 23, 12, 17, 12, 95, 41, 172, 17, 149, 9, 9, 139, 82, 14, 9, 17, 9, 17, 17, 9, 78, 78, 124, 17, 78, 78, 78, 78, 92, 9, 9, 9, 9, -1, 9, -1, 17, 9, 9, 5, 5, -1, 80, 80, -1, -1, -1, 80, -1, 80, 113, 80, 80, 80, 35, 80, -1, 113, -1, 80, -1, -1, 9, 80, 113, 113, 113, 113, 113, 35, -1, -1, 35, 14, 9, -1, -1, -1, -1, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, -1, -1, 23, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 16, 16, 16, 16, 8, 8, -1, 8, 8, 8, 16, 8, 8, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, 24, 24, 87, 87, 87, 87, 87, 87, -1, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 103, 103, -1, 16, 16); - protected $gotoBase = array(0, 0, -285, 0, 0, 225, 173, 10, 524, 7, 0, 0, 95, -47, 5, -174, 87, -33, 71, 61, -212, 0, -76, 157, 284, 392, 4, 20, 56, 77, 0, 0, 0, 0, 0, 118, 0, 63, 0, 65, 0, -2, -1, 0, 0, 155, -378, 0, -308, 186, 0, 0, 0, 0, 0, 266, 0, 0, 359, 0, 0, 282, 0, 103, 204, -235, 0, 0, 0, 0, 0, 0, -6, 0, 0, -167, 0, 0, 45, 170, -11, 0, -27, -110, -376, 0, 0, 276, 0, -32, 0, 0, 19, -448, 0, 30, 0, 0, 0, 262, 292, 0, 0, 342, -73, 0, 62, 0, 0, 88, 0, 0, 0, 206, 0, 0, 0, 0, 0, 3, 0, 59, 15, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 1, 0, 188, 0, 66, 0, 0, 0, -157, 0, 2, 0, 0, 35, 0, 0, 0, 0, 0, 0, 25, -57, -8, 201, 99, 0, 0, -111, 0, -7, 231, 0, 236, 96, -295, 0, 0); - protected $gotoDefault = array(-32768, 499, 711, 4, 712, 905, 788, 797, 583, 515, 679, 338, 610, 412, 1255, 882, 1078, 565, 816, 1199, 1207, 445, 819, 323, 701, 864, 865, 866, 390, 375, 381, 388, 632, 611, 480, 851, 441, 843, 472, 846, 440, 855, 161, 409, 497, 859, 3, 861, 542, 892, 376, 869, 377, 656, 871, 550, 873, 874, 384, 391, 392, 1083, 558, 607, 886, 242, 552, 887, 374, 888, 895, 379, 382, 665, 452, 492, 485, 402, 1058, 594, 629, 449, 466, 617, 616, 604, 465, 424, 407, 928, 473, 450, 942, 340, 950, 709, 1090, 624, 475, 958, 625, 965, 968, 516, 517, 464, 980, 268, 983, 476, 1015, 647, 648, 995, 626, 627, 1013, 459, 584, 1021, 442, 1029, 1243, 443, 1033, 261, 1036, 275, 408, 425, 1041, 1042, 8, 1048, 671, 672, 10, 272, 496, 1073, 666, 439, 1089, 429, 1159, 1161, 544, 477, 1179, 1178, 659, 493, 1184, 1246, 437, 518, 460, 309, 519, 301, 326, 306, 534, 288, 327, 520, 461, 1252, 1260, 324, 29, 1280, 1291, 334, 562, 599); - protected $ruleToNonTerminal = array(0, 1, 3, 3, 2, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 25, 25, 68, 68, 71, 71, 70, 69, 69, 62, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 26, 26, 27, 27, 27, 27, 86, 86, 88, 88, 81, 81, 81, 82, 82, 85, 85, 83, 83, 89, 90, 90, 56, 56, 64, 64, 67, 67, 67, 66, 91, 91, 92, 57, 57, 57, 57, 93, 93, 94, 94, 95, 95, 96, 97, 97, 98, 98, 99, 99, 54, 54, 50, 50, 101, 52, 52, 102, 51, 51, 53, 53, 63, 63, 63, 63, 79, 79, 105, 105, 107, 107, 108, 108, 108, 108, 106, 106, 106, 110, 110, 110, 110, 87, 87, 113, 113, 113, 111, 111, 114, 114, 112, 112, 115, 115, 116, 116, 116, 116, 109, 109, 80, 80, 80, 20, 20, 20, 118, 117, 117, 119, 119, 119, 119, 59, 120, 120, 121, 60, 123, 123, 124, 124, 125, 125, 84, 126, 126, 126, 126, 126, 126, 131, 131, 132, 132, 133, 133, 133, 133, 133, 134, 135, 135, 130, 130, 127, 127, 129, 129, 137, 137, 136, 136, 136, 136, 136, 136, 136, 128, 138, 138, 140, 139, 139, 61, 100, 141, 141, 55, 55, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 148, 142, 142, 147, 147, 150, 151, 151, 152, 153, 153, 153, 19, 19, 72, 72, 72, 72, 143, 143, 143, 143, 155, 155, 144, 144, 146, 146, 146, 149, 149, 160, 160, 160, 160, 160, 160, 160, 160, 160, 161, 161, 104, 163, 163, 163, 163, 145, 145, 145, 145, 145, 145, 145, 145, 58, 58, 158, 158, 158, 158, 164, 164, 154, 154, 154, 165, 165, 165, 165, 165, 165, 73, 73, 65, 65, 65, 65, 122, 122, 122, 122, 168, 167, 157, 157, 157, 157, 157, 157, 157, 156, 156, 156, 166, 166, 166, 166, 103, 162, 170, 170, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 159, 159, 159, 159, 173, 174, 172, 172, 172, 172, 172, 172, 172, 172, 175, 175, 175, 175); - protected $ruleToLength = array(1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, 0, 1, 1, 1, 1, 1, 3, 5, 4, 3, 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, 2, 1, 1, 1, 0, 2, 1, 3, 8, 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, 1, 3, 1, 8, 9, 8, 7, 6, 8, 0, 2, 0, 2, 1, 2, 2, 0, 2, 0, 2, 0, 2, 2, 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 1, 1, 0, 1, 0, 2, 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, 2, 0, 1, 5, 5, 10, 3, 5, 1, 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, 3, 3, 3, 4, 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, 10, 9, 10, 8, 3, 2, 0, 4, 2, 1, 3, 2, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 0, 3, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 4, 1, 1, 3, 1, 1, 1, 1, 1, 3, 2, 3, 0, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 4, 4, 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, 1); - protected function initReduceCallbacks() - { - $this->reduceCallbacks = [0 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 1 => function ($stackPos) { - $this->semValue = $this->handleNamespaces($this->semStack[$stackPos - (1 - 1)]); - }, 2 => function ($stackPos) { - if (\is_array($this->semStack[$stackPos - (2 - 2)])) { - $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - } else { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 3 => function ($stackPos) { - $this->semValue = array(); - }, 4 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 5 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 6 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 7 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 8 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 9 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 10 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 11 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 12 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 13 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 14 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 15 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 16 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 17 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 18 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 19 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 20 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 21 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 22 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 23 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 24 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 25 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 26 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 27 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 28 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 29 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 30 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 31 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 32 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 33 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 34 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 35 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 36 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 37 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 38 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 39 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 40 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 41 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 42 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 43 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 44 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 45 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 46 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 47 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 48 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 49 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 50 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 51 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 52 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 53 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 54 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 55 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 56 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 57 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 58 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 59 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 60 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 61 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 62 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 63 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 64 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 65 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 66 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 67 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 68 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 69 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 70 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 71 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 72 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 73 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 74 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 75 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 76 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 77 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 78 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 79 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 80 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 81 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 82 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 83 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 84 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 85 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 86 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 87 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 88 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 89 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 90 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 91 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 92 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 93 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 94 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 95 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 96 => function ($stackPos) { - $this->semValue = new Name(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 97 => function ($stackPos) { - $this->semValue = new Expr\Variable(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 98 => function ($stackPos) { - /* nothing */ - }, 99 => function ($stackPos) { - /* nothing */ - }, 100 => function ($stackPos) { - /* nothing */ - }, 101 => function ($stackPos) { - $this->emitError(new Error('A trailing comma is not allowed here', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); - }, 102 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 103 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 104 => function ($stackPos) { - $this->semValue = new Node\Attribute($this->semStack[$stackPos - (1 - 1)], [], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 105 => function ($stackPos) { - $this->semValue = new Node\Attribute($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 106 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 107 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 108 => function ($stackPos) { - $this->semValue = new Node\AttributeGroup($this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 109 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 110 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 111 => function ($stackPos) { - $this->semValue = []; - }, 112 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 113 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 114 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 115 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 116 => function ($stackPos) { - $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 117 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (3 - 2)], null, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); - $this->checkNamespace($this->semValue); - }, 118 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); - $this->checkNamespace($this->semValue); - }, 119 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); - $this->checkNamespace($this->semValue); - }, 120 => function ($stackPos) { - $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (3 - 2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 121 => function ($stackPos) { - $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 122 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 123 => function ($stackPos) { - $this->semValue = new Stmt\Const_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 124 => function ($stackPos) { - $this->semValue = Stmt\Use_::TYPE_FUNCTION; - }, 125 => function ($stackPos) { - $this->semValue = Stmt\Use_::TYPE_CONSTANT; - }, 126 => function ($stackPos) { - $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->semStack[$stackPos - (7 - 2)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 127 => function ($stackPos) { - $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 128 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 129 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 130 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 131 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 132 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 133 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 134 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 135 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 136 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 137 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); - }, 138 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); - }, 139 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); - }, 140 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); - }, 141 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - $this->semValue->type = Stmt\Use_::TYPE_NORMAL; - }, 142 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue->type = $this->semStack[$stackPos - (2 - 1)]; - }, 143 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 144 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 145 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 146 => function ($stackPos) { - $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 147 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 148 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 149 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 150 => function ($stackPos) { - $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 151 => function ($stackPos) { - if (\is_array($this->semStack[$stackPos - (2 - 2)])) { - $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - } else { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 152 => function ($stackPos) { - $this->semValue = array(); - }, 153 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 154 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 155 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 156 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 157 => function ($stackPos) { - throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 158 => function ($stackPos) { - if ($this->semStack[$stackPos - (3 - 2)]) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)]; - $stmts = $this->semValue; - if (!empty($attrs['comments'])) { - $stmts[0]->setAttribute('comments', \array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); - } - } else { - $startAttributes = $this->startAttributeStack[$stackPos - (3 - 1)]; - if (isset($startAttributes['comments'])) { - $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); - } else { - $this->semValue = null; - } - if (null === $this->semValue) { - $this->semValue = array(); - } - } - }, 159 => function ($stackPos) { - $this->semValue = new Stmt\If_($this->semStack[$stackPos - (7 - 3)], ['stmts' => \is_array($this->semStack[$stackPos - (7 - 5)]) ? $this->semStack[$stackPos - (7 - 5)] : array($this->semStack[$stackPos - (7 - 5)]), 'elseifs' => $this->semStack[$stackPos - (7 - 6)], 'else' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 160 => function ($stackPos) { - $this->semValue = new Stmt\If_($this->semStack[$stackPos - (10 - 3)], ['stmts' => $this->semStack[$stackPos - (10 - 6)], 'elseifs' => $this->semStack[$stackPos - (10 - 7)], 'else' => $this->semStack[$stackPos - (10 - 8)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - }, 161 => function ($stackPos) { - $this->semValue = new Stmt\While_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 162 => function ($stackPos) { - $this->semValue = new Stmt\Do_($this->semStack[$stackPos - (7 - 5)], \is_array($this->semStack[$stackPos - (7 - 2)]) ? $this->semStack[$stackPos - (7 - 2)] : array($this->semStack[$stackPos - (7 - 2)]), $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 163 => function ($stackPos) { - $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos - (9 - 3)], 'cond' => $this->semStack[$stackPos - (9 - 5)], 'loop' => $this->semStack[$stackPos - (9 - 7)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 164 => function ($stackPos) { - $this->semValue = new Stmt\Switch_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 165 => function ($stackPos) { - $this->semValue = new Stmt\Break_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 166 => function ($stackPos) { - $this->semValue = new Stmt\Continue_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 167 => function ($stackPos) { - $this->semValue = new Stmt\Return_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 168 => function ($stackPos) { - $this->semValue = new Stmt\Global_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 169 => function ($stackPos) { - $this->semValue = new Stmt\Static_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 170 => function ($stackPos) { - $this->semValue = new Stmt\Echo_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 171 => function ($stackPos) { - $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 172 => function ($stackPos) { - $e = $this->semStack[$stackPos - (2 - 1)]; - if ($e instanceof Expr\Throw_) { - // For backwards-compatibility reasons, convert throw in statement position into - // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). - $this->semValue = new Stmt\Throw_($e->expr, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - } else { - $this->semValue = new Stmt\Expression($e, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - } - }, 173 => function ($stackPos) { - $this->semValue = new Stmt\Unset_($this->semStack[$stackPos - (5 - 3)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 174 => function ($stackPos) { - $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos - (7 - 5)][1], 'stmts' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 175 => function ($stackPos) { - $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (9 - 3)], $this->semStack[$stackPos - (9 - 7)][0], ['keyVar' => $this->semStack[$stackPos - (9 - 5)], 'byRef' => $this->semStack[$stackPos - (9 - 7)][1], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 176 => function ($stackPos) { - $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (6 - 3)], new Expr\Error($this->startAttributeStack[$stackPos - (6 - 4)] + $this->endAttributeStack[$stackPos - (6 - 4)]), ['stmts' => $this->semStack[$stackPos - (6 - 6)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 177 => function ($stackPos) { - $this->semValue = new Stmt\Declare_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 178 => function ($stackPos) { - $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 5)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - $this->checkTryCatch($this->semValue); - }, 179 => function ($stackPos) { - $this->semValue = new Stmt\Goto_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 180 => function ($stackPos) { - $this->semValue = new Stmt\Label($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 181 => function ($stackPos) { - $this->semValue = array(); - /* means: no statement */ - }, 182 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 183 => function ($stackPos) { - $startAttributes = $this->startAttributeStack[$stackPos - (1 - 1)]; - if (isset($startAttributes['comments'])) { - $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); - } else { - $this->semValue = null; - } - if ($this->semValue === null) { - $this->semValue = array(); - } - /* means: no statement */ - }, 184 => function ($stackPos) { - $this->semValue = array(); - }, 185 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 186 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 187 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 188 => function ($stackPos) { - $this->semValue = new Stmt\Catch_($this->semStack[$stackPos - (8 - 3)], $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 7)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 189 => function ($stackPos) { - $this->semValue = null; - }, 190 => function ($stackPos) { - $this->semValue = new Stmt\Finally_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 191 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 192 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 193 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 194 => function ($stackPos) { - $this->semValue = \false; - }, 195 => function ($stackPos) { - $this->semValue = \true; - }, 196 => function ($stackPos) { - $this->semValue = \false; - }, 197 => function ($stackPos) { - $this->semValue = \true; - }, 198 => function ($stackPos) { - $this->semValue = \false; - }, 199 => function ($stackPos) { - $this->semValue = \true; - }, 200 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 201 => function ($stackPos) { - $this->semValue = []; - }, 202 => function ($stackPos) { - $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (8 - 3)], ['byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 5)], 'returnType' => $this->semStack[$stackPos - (8 - 7)], 'stmts' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 203 => function ($stackPos) { - $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (9 - 4)], ['byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 6)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 204 => function ($stackPos) { - $this->semValue = new Stmt\Class_($this->semStack[$stackPos - (8 - 3)], ['type' => $this->semStack[$stackPos - (8 - 2)], 'extends' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - $this->checkClass($this->semValue, $stackPos - (8 - 3)); - }, 205 => function ($stackPos) { - $this->semValue = new Stmt\Interface_($this->semStack[$stackPos - (7 - 3)], ['extends' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)], 'attrGroups' => $this->semStack[$stackPos - (7 - 1)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - $this->checkInterface($this->semValue, $stackPos - (7 - 3)); - }, 206 => function ($stackPos) { - $this->semValue = new Stmt\Trait_($this->semStack[$stackPos - (6 - 3)], ['stmts' => $this->semStack[$stackPos - (6 - 5)], 'attrGroups' => $this->semStack[$stackPos - (6 - 1)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 207 => function ($stackPos) { - $this->semValue = new Stmt\Enum_($this->semStack[$stackPos - (8 - 3)], ['scalarType' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - $this->checkEnum($this->semValue, $stackPos - (8 - 3)); - }, 208 => function ($stackPos) { - $this->semValue = null; - }, 209 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 210 => function ($stackPos) { - $this->semValue = null; - }, 211 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 212 => function ($stackPos) { - $this->semValue = 0; - }, 213 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; - }, 214 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; - }, 215 => function ($stackPos) { - $this->semValue = null; - }, 216 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 217 => function ($stackPos) { - $this->semValue = array(); - }, 218 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 219 => function ($stackPos) { - $this->semValue = array(); - }, 220 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 221 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 222 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 223 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 224 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 225 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 226 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 227 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 228 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 229 => function ($stackPos) { - $this->semValue = null; - }, 230 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 231 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 232 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 233 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 234 => function ($stackPos) { - $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 235 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 236 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 3)]; - }, 237 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 238 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (5 - 3)]; - }, 239 => function ($stackPos) { - $this->semValue = array(); - }, 240 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 241 => function ($stackPos) { - $this->semValue = new Stmt\Case_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 242 => function ($stackPos) { - $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 243 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 244 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 245 => function ($stackPos) { - $this->semValue = new Expr\Match_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 246 => function ($stackPos) { - $this->semValue = []; - }, 247 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 248 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 249 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 250 => function ($stackPos) { - $this->semValue = new Node\MatchArm($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 251 => function ($stackPos) { - $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 252 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 253 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 254 => function ($stackPos) { - $this->semValue = array(); - }, 255 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 256 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (5 - 3)], \is_array($this->semStack[$stackPos - (5 - 5)]) ? $this->semStack[$stackPos - (5 - 5)] : array($this->semStack[$stackPos - (5 - 5)]), $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 257 => function ($stackPos) { - $this->semValue = array(); - }, 258 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 259 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 260 => function ($stackPos) { - $this->semValue = null; - }, 261 => function ($stackPos) { - $this->semValue = new Stmt\Else_(\is_array($this->semStack[$stackPos - (2 - 2)]) ? $this->semStack[$stackPos - (2 - 2)] : array($this->semStack[$stackPos - (2 - 2)]), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 262 => function ($stackPos) { - $this->semValue = null; - }, 263 => function ($stackPos) { - $this->semValue = new Stmt\Else_($this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 264 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); - }, 265 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (2 - 2)], \true); - }, 266 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); - }, 267 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); - }, 268 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 269 => function ($stackPos) { - $this->semValue = array(); - }, 270 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 271 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 272 => function ($stackPos) { - $this->semValue = 0; - }, 273 => function ($stackPos) { - $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); - $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; - }, 274 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; - }, 275 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; - }, 276 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; - }, 277 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_READONLY; - }, 278 => function ($stackPos) { - $this->semValue = new Node\Param($this->semStack[$stackPos - (6 - 6)], null, $this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 1)]); - $this->checkParam($this->semValue); - }, 279 => function ($stackPos) { - $this->semValue = new Node\Param($this->semStack[$stackPos - (8 - 6)], $this->semStack[$stackPos - (8 - 8)], $this->semStack[$stackPos - (8 - 3)], $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 5)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (8 - 2)], $this->semStack[$stackPos - (8 - 1)]); - $this->checkParam($this->semValue); - }, 280 => function ($stackPos) { - $this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes), null, $this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 1)]); - }, 281 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 282 => function ($stackPos) { - $this->semValue = new Node\NullableType($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 283 => function ($stackPos) { - $this->semValue = new Node\UnionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 284 => function ($stackPos) { - $this->semValue = new Node\IntersectionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 285 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 286 => function ($stackPos) { - $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 287 => function ($stackPos) { - $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos - (1 - 1)]); - }, 288 => function ($stackPos) { - $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 289 => function ($stackPos) { - $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 290 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 291 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 292 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 293 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 294 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 295 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 296 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 297 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 298 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 299 => function ($stackPos) { - $this->semValue = new Node\NullableType($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 300 => function ($stackPos) { - $this->semValue = new Node\UnionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 301 => function ($stackPos) { - $this->semValue = new Node\IntersectionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 302 => function ($stackPos) { - $this->semValue = null; - }, 303 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 304 => function ($stackPos) { - $this->semValue = null; - }, 305 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 306 => function ($stackPos) { - $this->semValue = null; - }, 307 => function ($stackPos) { - $this->semValue = array(); - }, 308 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 309 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 2)]); - }, 310 => function ($stackPos) { - $this->semValue = new Node\VariadicPlaceholder($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 311 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 312 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 313 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (1 - 1)], \false, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 314 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \true, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 315 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \false, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 316 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (3 - 3)], \false, \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (3 - 1)]); - }, 317 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 318 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 319 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 320 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 321 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 322 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 323 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 324 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 325 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 326 => function ($stackPos) { - if ($this->semStack[$stackPos - (2 - 2)] !== null) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 327 => function ($stackPos) { - $this->semValue = array(); - }, 328 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 329 => function ($stackPos) { - $this->semValue = new Stmt\Property($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 1)]); - $this->checkProperty($this->semValue, $stackPos - (5 - 2)); - }, 330 => function ($stackPos) { - $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos - (5 - 4)], $this->semStack[$stackPos - (5 - 2)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (5 - 1)]); - $this->checkClassConst($this->semValue, $stackPos - (5 - 2)); - }, 331 => function ($stackPos) { - $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos - (10 - 5)], ['type' => $this->semStack[$stackPos - (10 - 2)], 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 7)], 'returnType' => $this->semStack[$stackPos - (10 - 9)], 'stmts' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - $this->checkClassMethod($this->semValue, $stackPos - (10 - 2)); - }, 332 => function ($stackPos) { - $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 333 => function ($stackPos) { - $this->semValue = new Stmt\EnumCase($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->semStack[$stackPos - (5 - 1)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 334 => function ($stackPos) { - $this->semValue = null; - /* will be skipped */ - }, 335 => function ($stackPos) { - $this->semValue = array(); - }, 336 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 337 => function ($stackPos) { - $this->semValue = array(); - }, 338 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 339 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 340 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (5 - 1)][0], $this->semStack[$stackPos - (5 - 1)][1], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 341 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], null, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 342 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 343 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 344 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 345 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 346 => function ($stackPos) { - $this->semValue = array(null, $this->semStack[$stackPos - (1 - 1)]); - }, 347 => function ($stackPos) { - $this->semValue = null; - }, 348 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 349 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 350 => function ($stackPos) { - $this->semValue = 0; - }, 351 => function ($stackPos) { - $this->semValue = 0; - }, 352 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 353 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 354 => function ($stackPos) { - $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); - $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; - }, 355 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; - }, 356 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; - }, 357 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; - }, 358 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_STATIC; - }, 359 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; - }, 360 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; - }, 361 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_READONLY; - }, 362 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 363 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 364 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 365 => function ($stackPos) { - $this->semValue = new Node\VarLikeIdentifier(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 366 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 367 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 368 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 369 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 370 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 371 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 372 => function ($stackPos) { - $this->semValue = array(); - }, 373 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 374 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 375 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 376 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 377 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 378 => function ($stackPos) { - $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 379 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 380 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 381 => function ($stackPos) { - $this->semValue = new Expr\Clone_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 382 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 383 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 384 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 385 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 386 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 387 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 388 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 389 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 390 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 391 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 392 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 393 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 394 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 395 => function ($stackPos) { - $this->semValue = new Expr\PostInc($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 396 => function ($stackPos) { - $this->semValue = new Expr\PreInc($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 397 => function ($stackPos) { - $this->semValue = new Expr\PostDec($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 398 => function ($stackPos) { - $this->semValue = new Expr\PreDec($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 399 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 400 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 401 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 402 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 403 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 404 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 405 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 406 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 407 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 408 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 409 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 410 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 411 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 412 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 413 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 414 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 415 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 416 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 417 => function ($stackPos) { - $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 418 => function ($stackPos) { - $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 419 => function ($stackPos) { - $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 420 => function ($stackPos) { - $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 421 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 422 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 423 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 424 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 425 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 426 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 427 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 428 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 429 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 430 => function ($stackPos) { - $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 431 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 432 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 433 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 434 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 435 => function ($stackPos) { - $this->semValue = new Expr\Isset_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 436 => function ($stackPos) { - $this->semValue = new Expr\Empty_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 437 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 438 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 439 => function ($stackPos) { - $this->semValue = new Expr\Eval_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 440 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 441 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 442 => function ($stackPos) { - $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 443 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; - $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos - (2 - 1)]); - $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos - (2 - 2)], $attrs); - }, 444 => function ($stackPos) { - $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 445 => function ($stackPos) { - $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 446 => function ($stackPos) { - $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 447 => function ($stackPos) { - $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 448 => function ($stackPos) { - $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 449 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; - $attrs['kind'] = \strtolower($this->semStack[$stackPos - (2 - 1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; - $this->semValue = new Expr\Exit_($this->semStack[$stackPos - (2 - 2)], $attrs); - }, 450 => function ($stackPos) { - $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 451 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 452 => function ($stackPos) { - $this->semValue = new Expr\ShellExec($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 453 => function ($stackPos) { - $this->semValue = new Expr\Print_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 454 => function ($stackPos) { - $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 455 => function ($stackPos) { - $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (2 - 2)], null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 456 => function ($stackPos) { - $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 457 => function ($stackPos) { - $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 458 => function ($stackPos) { - $this->semValue = new Expr\Throw_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 459 => function ($stackPos) { - $this->semValue = new Expr\ArrowFunction(['static' => \false, 'byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 4)], 'returnType' => $this->semStack[$stackPos - (8 - 6)], 'expr' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 460 => function ($stackPos) { - $this->semValue = new Expr\ArrowFunction(['static' => \true, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'returnType' => $this->semStack[$stackPos - (9 - 7)], 'expr' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 461 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 4)], 'uses' => $this->semStack[$stackPos - (8 - 6)], 'returnType' => $this->semStack[$stackPos - (8 - 7)], 'stmts' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 462 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'uses' => $this->semStack[$stackPos - (9 - 7)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 463 => function ($stackPos) { - $this->semValue = new Expr\ArrowFunction(['static' => \false, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'returnType' => $this->semStack[$stackPos - (9 - 7)], 'expr' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 464 => function ($stackPos) { - $this->semValue = new Expr\ArrowFunction(['static' => \true, 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 6)], 'returnType' => $this->semStack[$stackPos - (10 - 8)], 'expr' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - }, 465 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'uses' => $this->semStack[$stackPos - (9 - 7)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 466 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 6)], 'uses' => $this->semStack[$stackPos - (10 - 8)], 'returnType' => $this->semStack[$stackPos - (10 - 9)], 'stmts' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - }, 467 => function ($stackPos) { - $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes), $this->semStack[$stackPos - (8 - 3)]); - $this->checkClass($this->semValue[0], -1); - }, 468 => function ($stackPos) { - $this->semValue = new Expr\New_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 469 => function ($stackPos) { - list($class, $ctorArgs) = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 470 => function ($stackPos) { - $this->semValue = array(); - }, 471 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 3)]; - }, 472 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 473 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 474 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 475 => function ($stackPos) { - $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos - (2 - 2)], $this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 476 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 477 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 478 => function ($stackPos) { - $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 479 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 480 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 481 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 482 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 483 => function ($stackPos) { - $this->semValue = new Name\FullyQualified(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 484 => function ($stackPos) { - $this->semValue = new Name\Relative(\substr($this->semStack[$stackPos - (1 - 1)], 10), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 485 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 486 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 487 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 488 => function ($stackPos) { - $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->errorState = 2; - }, 489 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 490 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 491 => function ($stackPos) { - $this->semValue = null; - }, 492 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 493 => function ($stackPos) { - $this->semValue = array(); - }, 494 => function ($stackPos) { - $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos - (1 - 1)], '`'), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); - }, 495 => function ($stackPos) { - foreach ($this->semStack[$stackPos - (1 - 1)] as $s) { - if ($s instanceof Node\Scalar\EncapsedStringPart) { - $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', \true); - } - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 496 => function ($stackPos) { - $this->semValue = array(); - }, 497 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 498 => function ($stackPos) { - $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 499 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 500 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 501 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 502 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 503 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 504 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 505 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 506 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 507 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 508 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], new Expr\Error($this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)]), $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->errorState = 2; - }, 509 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; - $attrs['kind'] = Expr\Array_::KIND_SHORT; - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $attrs); - }, 510 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes; - $attrs['kind'] = Expr\Array_::KIND_LONG; - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $attrs); - }, 511 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 512 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes; - $attrs['kind'] = $this->semStack[$stackPos - (1 - 1)][0] === "'" || $this->semStack[$stackPos - (1 - 1)][1] === "'" && ($this->semStack[$stackPos - (1 - 1)][0] === 'b' || $this->semStack[$stackPos - (1 - 1)][0] === 'B') ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED; - $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos - (1 - 1)]), $attrs); - }, 513 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; - $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; - foreach ($this->semStack[$stackPos - (3 - 2)] as $s) { - if ($s instanceof Node\Scalar\EncapsedStringPart) { - $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', \true); - } - } - $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos - (3 - 2)], $attrs); - }, 514 => function ($stackPos) { - $this->semValue = $this->parseLNumber($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 515 => function ($stackPos) { - $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos - (1 - 1)]), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 516 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 517 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 518 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 519 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); - }, 520 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (2 - 1)], '', $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (2 - 2)] + $this->endAttributeStack[$stackPos - (2 - 2)], \true); - }, 521 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); - }, 522 => function ($stackPos) { - $this->semValue = null; - }, 523 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 524 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 525 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 526 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 527 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 528 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 529 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 530 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 531 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 532 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 533 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 534 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 535 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 536 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 537 => function ($stackPos) { - $this->semValue = new Expr\MethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 538 => function ($stackPos) { - $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 539 => function ($stackPos) { - $this->semValue = null; - }, 540 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 541 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 542 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 543 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 544 => function ($stackPos) { - $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 545 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 546 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 547 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 548 => function ($stackPos) { - $this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - $this->errorState = 2; - }, 549 => function ($stackPos) { - $var = $this->semStack[$stackPos - (1 - 1)]->name; - $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes) : $var; - }, 550 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 551 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 552 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 553 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 554 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 555 => function ($stackPos) { - $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 556 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 557 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 558 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 559 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 560 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 561 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 562 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 563 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 564 => function ($stackPos) { - $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->errorState = 2; - }, 565 => function ($stackPos) { - $this->semValue = new Expr\List_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 566 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - $end = \count($this->semValue) - 1; - if ($this->semValue[$end] === null) { - \array_pop($this->semValue); - } - }, 567 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 568 => function ($stackPos) { - /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ - }, 569 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 570 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 571 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 572 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 573 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 574 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 575 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 1)], \true, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 576 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 577 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 578 => function ($stackPos) { - $this->semValue = null; - }, 579 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 580 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 581 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 582 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - }, 583 => function ($stackPos) { - $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 584 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 585 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 586 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 587 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 588 => function ($stackPos) { - $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 589 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 590 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 591 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 4)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 592 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 593 => function ($stackPos) { - $this->semValue = new Scalar\String_($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 594 => function ($stackPos) { - $this->semValue = $this->parseNumString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 595 => function ($stackPos) { - $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 596 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }]; - } -} -parsers = $parsers; - } - public function parse(string $code, ErrorHandler $errorHandler = null) - { - if (null === $errorHandler) { - $errorHandler = new ErrorHandler\Throwing(); - } - list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code); - if ($firstError === null) { - return $firstStmts; - } - for ($i = 1, $c = \count($this->parsers); $i < $c; ++$i) { - list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code); - if ($error === null) { - return $stmts; - } - } - throw $firstError; - } - private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) - { - $stmts = null; - $error = null; - try { - $stmts = $parser->parse($code, $errorHandler); - } catch (Error $error) { - } - return [$stmts, $error]; - } -} -'", "T_IS_GREATER_OR_EQUAL", "T_SL", "T_SR", "'+'", "'-'", "'.'", "'*'", "'/'", "'%'", "'!'", "T_INSTANCEOF", "'~'", "T_INC", "T_DEC", "T_INT_CAST", "T_DOUBLE_CAST", "T_STRING_CAST", "T_ARRAY_CAST", "T_OBJECT_CAST", "T_BOOL_CAST", "T_UNSET_CAST", "'@'", "T_POW", "'['", "T_NEW", "T_CLONE", "T_EXIT", "T_IF", "T_ELSEIF", "T_ELSE", "T_ENDIF", "T_LNUMBER", "T_DNUMBER", "T_STRING", "T_STRING_VARNAME", "T_VARIABLE", "T_NUM_STRING", "T_INLINE_HTML", "T_ENCAPSED_AND_WHITESPACE", "T_CONSTANT_ENCAPSED_STRING", "T_ECHO", "T_DO", "T_WHILE", "T_ENDWHILE", "T_FOR", "T_ENDFOR", "T_FOREACH", "T_ENDFOREACH", "T_DECLARE", "T_ENDDECLARE", "T_AS", "T_SWITCH", "T_MATCH", "T_ENDSWITCH", "T_CASE", "T_DEFAULT", "T_BREAK", "T_CONTINUE", "T_GOTO", "T_FUNCTION", "T_FN", "T_CONST", "T_RETURN", "T_TRY", "T_CATCH", "T_FINALLY", "T_USE", "T_INSTEADOF", "T_GLOBAL", "T_STATIC", "T_ABSTRACT", "T_FINAL", "T_PRIVATE", "T_PROTECTED", "T_PUBLIC", "T_VAR", "T_UNSET", "T_ISSET", "T_EMPTY", "T_HALT_COMPILER", "T_CLASS", "T_TRAIT", "T_INTERFACE", "T_EXTENDS", "T_IMPLEMENTS", "T_OBJECT_OPERATOR", "T_LIST", "T_ARRAY", "T_CALLABLE", "T_CLASS_C", "T_TRAIT_C", "T_METHOD_C", "T_FUNC_C", "T_LINE", "T_FILE", "T_START_HEREDOC", "T_END_HEREDOC", "T_DOLLAR_OPEN_CURLY_BRACES", "T_CURLY_OPEN", "T_PAAMAYIM_NEKUDOTAYIM", "T_NAMESPACE", "T_NS_C", "T_DIR", "T_NS_SEPARATOR", "T_ELLIPSIS", "T_NAME_FULLY_QUALIFIED", "T_NAME_QUALIFIED", "T_NAME_RELATIVE", "';'", "'{'", "'}'", "'('", "')'", "'\$'", "'`'", "']'", "'\"'", "T_READONLY", "T_ENUM", "T_NULLSAFE_OBJECT_OPERATOR", "T_ATTRIBUTE"); - protected $tokenToSymbol = array(0, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 56, 163, 168, 160, 55, 168, 168, 158, 159, 53, 50, 8, 51, 52, 54, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 31, 155, 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 70, 168, 162, 36, 168, 161, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 156, 35, 157, 58, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 164, 122, 123, 124, 125, 126, 127, 128, 129, 165, 130, 131, 132, 166, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 167); - protected $action = array(699, 669, 670, 671, 672, 673, 286, 674, 675, 676, 712, 713, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 0, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, 245, 246, 242, 243, 244, -32766, -32766, 677, -32766, 750, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 1224, 245, 246, 1225, 678, 679, 680, 681, 682, 683, 684, -32766, 48, 746, -32766, -32766, -32766, -32766, -32766, -32766, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 715, 738, 716, 717, 718, 719, 707, 708, 709, 737, 710, 711, 696, 697, 698, 700, 701, 702, 740, 741, 742, 743, 744, 745, 703, 704, 705, 706, 736, 727, 725, 726, 722, 723, 751, 714, 720, 721, 728, 729, 731, 730, 732, 733, 55, 56, 425, 57, 58, 724, 735, 734, 1073, 59, 60, -224, 61, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 121, -32767, -32767, -32767, -32767, 29, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 1043, 766, 1071, 767, 580, 62, 63, -32766, -32766, -32766, -32766, 64, 516, 65, 294, 295, 66, 67, 68, 69, 70, 71, 72, 73, 822, 25, 302, 74, 418, 981, 983, 1043, 1181, 1095, 1096, 1073, 748, 754, 1075, 1074, 1076, 469, -32766, -32766, -32766, 337, 823, 54, -32767, -32767, -32767, -32767, 98, 99, 100, 101, 102, 220, 221, 222, 78, 361, 1107, -32766, 341, -32766, -32766, -32766, -32766, -32766, 1107, 492, 949, 950, 951, 948, 947, 946, 207, 477, 478, 949, 950, 951, 948, 947, 946, 1043, 479, 480, 52, 1101, 1102, 1103, 1104, 1098, 1099, 319, 872, 668, 667, 27, -511, 1105, 1100, -32766, 130, 1075, 1074, 1076, 345, 668, 667, 41, 126, 341, 334, 369, 336, 426, -128, -128, -128, 896, 897, 468, 220, 221, 222, 811, 1195, 619, 40, 21, 427, -128, 470, -128, 471, -128, 472, -128, 802, 428, -4, 823, 54, 207, 33, 34, 429, 360, 317, 28, 35, 473, -32766, -32766, -32766, 211, 356, 357, 474, 475, -32766, -32766, -32766, 754, 476, 49, 313, 794, 843, 430, 431, 289, 125, -32766, 813, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, -32767, -32766, -32766, -32766, 769, 103, 104, 105, 327, 307, 825, 633, -128, 1075, 1074, 1076, 221, 222, 927, 748, 1146, 106, -32766, 129, -32766, -32766, -32766, -32766, 426, 823, 54, 902, 873, 302, 468, 75, 207, 359, 811, 668, 667, 40, 21, 427, 754, 470, 754, 471, 423, 472, 1043, 127, 428, 435, 1043, 341, 1043, 33, 34, 429, 360, 1181, 415, 35, 473, 122, 10, 315, 128, 356, 357, 474, 475, -32766, -32766, -32766, 768, 476, 668, 667, 758, 843, 430, 431, 754, 1043, 1147, -32766, -32766, -32766, 754, 419, 342, 1215, -32766, 131, -32766, -32766, -32766, 341, 363, 346, 426, 823, 54, 100, 101, 102, 468, 825, 633, -4, 811, 442, 903, 40, 21, 427, 754, 470, 435, 471, 341, 472, 341, 766, 428, 767, -209, -209, -209, 33, 34, 429, 360, 479, 1196, 35, 473, 345, -32766, -32766, -32766, 356, 357, 474, 475, 220, 221, 222, 421, 476, 32, 297, 794, 843, 430, 431, 754, 754, 435, -32766, 341, -32766, -32766, 9, 300, 51, 207, 249, 324, 753, 120, 220, 221, 222, 426, 30, 247, 941, 422, 424, 468, 825, 633, -209, 811, 1043, 1061, 40, 21, 427, 129, 470, 207, 471, 341, 472, 804, 20, 428, 124, -208, -208, -208, 33, 34, 429, 360, 479, 212, 35, 473, 923, -259, 823, 54, 356, 357, 474, 475, -32766, -32766, -32766, 1043, 476, 213, 806, 794, 843, 430, 431, -32766, -32766, 435, 435, 341, 341, 443, 79, 80, 81, -32766, 668, 667, 636, 344, 808, 668, 667, 239, 240, 241, 123, 214, 538, 250, 825, 633, -208, 36, 251, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 252, 307, 426, 220, 221, 222, 823, 54, 468, -32766, 222, 765, 811, 106, 134, 40, 21, 427, 571, 470, 207, 471, 445, 472, 207, -32766, 428, 896, 897, 207, 307, 33, 34, 429, 245, 246, 637, 35, 473, 452, 22, 809, 922, 356, 357, 457, 588, 135, 374, 595, 596, 476, -228, 759, 639, 938, 653, 926, 661, -86, 823, 54, 314, 644, 647, 821, 133, 836, 43, 106, 603, 44, 45, 46, 47, 748, 50, 53, 132, 426, 302, -32766, 520, 825, 633, 468, -84, 607, 577, 811, 641, 362, 40, 21, 427, -278, 470, 754, 471, 954, 472, 441, 627, 428, 823, 54, 574, 844, 33, 34, 429, 11, 615, 845, 35, 473, 444, 461, 285, -511, 356, 357, 592, -419, 593, 1106, 1153, -410, 476, 368, 838, 38, 658, 426, 645, 795, 1052, 0, 325, 468, 0, -32766, 0, 811, 0, 0, 40, 21, 427, 0, 470, 0, 471, 0, 472, 0, 322, 428, 823, 54, 825, 633, 33, 34, 429, 0, 326, 0, 35, 473, 323, 0, 316, 318, 356, 357, -512, 426, 0, 753, 531, 0, 476, 468, 6, 0, 0, 811, 650, 7, 40, 21, 427, 12, 470, 14, 471, 373, 472, -420, 562, 428, 823, 54, 78, -225, 33, 34, 429, 39, 656, 657, 35, 473, 859, 633, 764, 812, 356, 357, 820, 799, 814, 875, 866, 867, 476, 797, 860, 857, 855, 426, 933, 934, 931, 819, 803, 468, 805, 807, 810, 811, 930, 762, 40, 21, 427, 763, 470, 932, 471, 335, 472, 358, 634, 428, 638, 640, 825, 633, 33, 34, 429, 642, 643, 646, 35, 473, 648, 649, 651, 652, 356, 357, 635, 426, 1221, 1223, 761, 842, 476, 468, 248, 760, 841, 811, 1222, 840, 40, 21, 427, 1057, 470, 830, 471, 1045, 472, 839, 1046, 428, 828, 215, 216, 939, 33, 34, 429, 217, 864, 218, 35, 473, 825, 633, 24, 865, 356, 357, 456, 1220, 1189, 209, 1187, 1172, 476, 1185, 215, 216, 1086, 1095, 1096, 914, 217, 1193, 218, 1183, -224, 1097, 26, 31, 37, 42, 76, 77, 210, 288, 209, 292, 293, 308, 309, 310, 311, 339, 1095, 1096, 825, 633, 355, 291, 416, 1152, 1097, 16, 17, 18, 393, 453, 460, 462, 466, 552, 624, 1048, 1051, 904, 1111, 1047, 1023, 563, 1022, 1088, 0, 0, -429, 558, 1041, 1101, 1102, 1103, 1104, 1098, 1099, 398, 1054, 1053, 1056, 1055, 1070, 1105, 1100, 1186, 1171, 1167, 1184, 1085, 1218, 1112, 1166, 219, 558, 599, 1101, 1102, 1103, 1104, 1098, 1099, 398, 0, 0, 0, 0, 0, 1105, 1100, 0, 0, 0, 0, 0, 0, 0, 0, 219); - protected $actionCheck = array(2, 3, 4, 5, 6, 7, 14, 9, 10, 11, 12, 13, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 0, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 9, 10, 11, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 69, 70, 53, 54, 55, 9, 10, 57, 30, 80, 32, 33, 34, 35, 36, 37, 38, 80, 69, 70, 83, 71, 72, 73, 74, 75, 76, 77, 9, 70, 80, 33, 34, 35, 36, 37, 38, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 153, 133, 134, 135, 136, 137, 138, 139, 140, 141, 3, 4, 5, 6, 7, 147, 148, 149, 80, 12, 13, 159, 15, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 156, 44, 45, 46, 47, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 13, 106, 116, 108, 85, 50, 51, 33, 34, 35, 36, 56, 85, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 1, 70, 71, 72, 73, 59, 60, 13, 82, 78, 79, 80, 80, 82, 152, 153, 154, 86, 9, 10, 11, 8, 1, 2, 44, 45, 46, 47, 48, 49, 50, 51, 52, 9, 10, 11, 156, 106, 143, 30, 160, 32, 33, 34, 35, 36, 143, 116, 116, 117, 118, 119, 120, 121, 30, 124, 125, 116, 117, 118, 119, 120, 121, 13, 133, 134, 70, 136, 137, 138, 139, 140, 141, 142, 31, 37, 38, 8, 132, 148, 149, 116, 156, 152, 153, 154, 160, 37, 38, 158, 8, 160, 161, 8, 163, 74, 75, 76, 77, 134, 135, 80, 9, 10, 11, 84, 1, 80, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 155, 98, 0, 1, 2, 30, 103, 104, 105, 106, 132, 8, 109, 110, 9, 10, 11, 8, 115, 116, 117, 118, 9, 10, 11, 82, 123, 70, 8, 126, 127, 128, 129, 8, 156, 30, 155, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 9, 10, 11, 157, 53, 54, 55, 8, 57, 155, 156, 157, 152, 153, 154, 10, 11, 157, 80, 162, 69, 30, 151, 32, 33, 34, 35, 74, 1, 2, 159, 155, 71, 80, 151, 30, 8, 84, 37, 38, 87, 88, 89, 82, 91, 82, 93, 8, 95, 13, 156, 98, 158, 13, 160, 13, 103, 104, 105, 106, 82, 108, 109, 110, 156, 8, 113, 31, 115, 116, 117, 118, 9, 10, 11, 157, 123, 37, 38, 126, 127, 128, 129, 82, 13, 159, 33, 34, 35, 82, 127, 8, 85, 30, 156, 32, 33, 34, 160, 8, 147, 74, 1, 2, 50, 51, 52, 80, 155, 156, 157, 84, 31, 159, 87, 88, 89, 82, 91, 158, 93, 160, 95, 160, 106, 98, 108, 100, 101, 102, 103, 104, 105, 106, 133, 159, 109, 110, 160, 9, 10, 11, 115, 116, 117, 118, 9, 10, 11, 8, 123, 144, 145, 126, 127, 128, 129, 82, 82, 158, 30, 160, 32, 33, 108, 8, 70, 30, 31, 113, 152, 16, 9, 10, 11, 74, 14, 14, 122, 8, 8, 80, 155, 156, 157, 84, 13, 159, 87, 88, 89, 151, 91, 30, 93, 160, 95, 155, 159, 98, 14, 100, 101, 102, 103, 104, 105, 106, 133, 16, 109, 110, 155, 157, 1, 2, 115, 116, 117, 118, 9, 10, 11, 13, 123, 16, 155, 126, 127, 128, 129, 33, 34, 158, 158, 160, 160, 156, 9, 10, 11, 30, 37, 38, 31, 70, 155, 37, 38, 50, 51, 52, 156, 16, 81, 16, 155, 156, 157, 30, 16, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 16, 57, 74, 9, 10, 11, 1, 2, 80, 116, 11, 155, 84, 69, 156, 87, 88, 89, 160, 91, 30, 93, 132, 95, 30, 33, 98, 134, 135, 30, 57, 103, 104, 105, 69, 70, 31, 109, 110, 75, 76, 155, 155, 115, 116, 75, 76, 101, 102, 111, 112, 123, 159, 155, 156, 155, 156, 155, 156, 31, 1, 2, 31, 31, 31, 31, 31, 38, 70, 69, 77, 70, 70, 70, 70, 80, 70, 70, 70, 74, 71, 85, 85, 155, 156, 80, 97, 96, 100, 84, 31, 106, 87, 88, 89, 82, 91, 82, 93, 82, 95, 89, 92, 98, 1, 2, 90, 127, 103, 104, 105, 97, 94, 127, 109, 110, 97, 97, 97, 132, 115, 116, 100, 146, 113, 143, 143, 146, 123, 106, 151, 155, 157, 74, 31, 157, 162, -1, 114, 80, -1, 116, -1, 84, -1, -1, 87, 88, 89, -1, 91, -1, 93, -1, 95, -1, 130, 98, 1, 2, 155, 156, 103, 104, 105, -1, 130, -1, 109, 110, 131, -1, 132, 132, 115, 116, 132, 74, -1, 152, 150, -1, 123, 80, 146, -1, -1, 84, 31, 146, 87, 88, 89, 146, 91, 146, 93, 146, 95, 146, 150, 98, 1, 2, 156, 159, 103, 104, 105, 155, 155, 155, 109, 110, 155, 156, 155, 155, 115, 116, 155, 155, 155, 155, 155, 155, 123, 155, 155, 155, 155, 74, 155, 155, 155, 155, 155, 80, 155, 155, 155, 84, 155, 155, 87, 88, 89, 155, 91, 155, 93, 156, 95, 156, 156, 98, 156, 156, 155, 156, 103, 104, 105, 156, 156, 156, 109, 110, 156, 156, 156, 156, 115, 116, 156, 74, 157, 157, 157, 157, 123, 80, 31, 157, 157, 84, 157, 157, 87, 88, 89, 157, 91, 157, 93, 157, 95, 157, 157, 98, 157, 50, 51, 157, 103, 104, 105, 56, 157, 58, 109, 110, 155, 156, 158, 157, 115, 116, 157, 157, 157, 70, 157, 157, 123, 157, 50, 51, 157, 78, 79, 157, 56, 157, 58, 157, 159, 86, 158, 158, 158, 158, 158, 158, 158, 158, 70, 158, 158, 158, 158, 158, 158, 158, 78, 79, 155, 156, 158, 160, 158, 163, 86, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, -1, -1, 161, 134, 161, 136, 137, 138, 139, 140, 141, 142, 162, 162, 162, 162, 162, 148, 149, 162, 162, 162, 162, 162, 162, 162, 162, 158, 134, 162, 136, 137, 138, 139, 140, 141, 142, -1, -1, -1, -1, -1, 148, 149, -1, -1, -1, -1, -1, -1, -1, -1, 158); - protected $actionBase = array(0, 227, 326, 400, 474, 233, 132, 132, 752, -2, -2, 138, -2, -2, -2, 663, 761, 815, 761, 586, 717, 859, 859, 859, 244, 256, 256, 256, 413, 583, 583, 880, 546, 169, 415, 444, 409, 200, 200, 200, 200, 137, 137, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 249, 205, 738, 559, 535, 739, 741, 742, 876, 679, 877, 820, 821, 693, 823, 824, 826, 829, 832, 819, 834, 907, 836, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 67, 536, 299, 510, 230, 44, 652, 652, 652, 652, 652, 652, 652, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 378, 584, 584, 584, 657, 909, 648, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, 503, -21, -21, 436, 650, 364, 571, 215, 426, 156, 26, 26, 329, 329, 329, 329, 329, 46, 46, 5, 5, 5, 5, 152, 186, 186, 186, 186, 120, 120, 120, 120, 374, 374, 429, 448, 448, 334, 267, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 336, 427, 427, 572, 572, 408, 551, 551, 551, 551, 671, 171, 171, 391, 311, 311, 311, 109, 641, 856, 68, 68, 68, 68, 68, 68, 324, 324, 324, -3, -3, -3, 655, 77, 380, 77, 380, 683, 685, 86, 685, 654, -15, 516, 776, 281, 646, 809, 680, 816, 560, 711, 202, 578, 857, 643, -23, 578, 578, 578, 578, 857, 622, 628, 596, -23, 578, -23, 639, 454, 849, 351, 249, 558, 469, 631, 743, 514, 688, 746, 464, 544, 548, 556, 7, 412, 708, 750, 878, 879, 349, 702, 631, 631, 631, 327, 101, 7, -8, 623, 623, 623, 623, 219, 623, 623, 623, 623, 291, 430, 545, 401, 745, 653, 653, 675, 839, 814, 814, 653, 673, 653, 675, 841, 841, 841, 841, 653, 653, 653, 653, 814, 814, 667, 814, 275, 684, 694, 694, 841, 713, 714, 653, 653, 697, 814, 814, 814, 697, 687, 841, 669, 637, 333, 814, 841, 689, 673, 689, 653, 669, 689, 673, 673, 689, 22, 686, 656, 840, 842, 860, 756, 638, 644, 847, 848, 843, 845, 838, 692, 719, 720, 528, 659, 660, 661, 662, 696, 664, 698, 643, 658, 658, 658, 645, 701, 645, 658, 658, 658, 658, 658, 658, 658, 658, 632, 635, 709, 699, 670, 723, 566, 582, 758, 640, 636, 872, 865, 881, 883, 849, 870, 645, 890, 634, 288, 610, 850, 633, 753, 645, 851, 645, 759, 645, 873, 777, 666, 778, 779, 658, 874, 891, 892, 893, 894, 897, 898, 899, 900, 665, 901, 724, 674, 866, 344, 844, 639, 705, 677, 755, 725, 780, 372, 902, 784, 645, 645, 765, 706, 645, 766, 726, 712, 862, 727, 867, 903, 640, 678, 868, 645, 681, 785, 904, 372, 690, 651, 704, 649, 728, 858, 875, 853, 767, 612, 617, 787, 788, 792, 691, 730, 863, 864, 835, 731, 770, 642, 771, 676, 794, 772, 852, 732, 796, 798, 871, 647, 707, 682, 672, 668, 773, 799, 869, 733, 735, 736, 801, 737, 804, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, 137, 137, 137, -2, -2, -2, -2, 0, 0, -2, 0, 0, 0, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 0, 0, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 602, -21, -21, -21, -21, 602, -21, -21, -21, -21, -21, -21, -21, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, -21, 602, 602, 602, -21, 68, -21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 602, 0, 0, 602, -21, 602, -21, 602, -21, -21, 602, 602, 602, 602, 602, 602, 602, -21, -21, -21, -21, -21, -21, 0, 324, 324, 324, 324, -21, -21, -21, -21, 68, 68, 147, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 324, 324, -3, -3, 68, 68, 68, 68, 68, 147, 68, 68, -23, 673, 673, 673, 380, 380, 380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 380, -23, 0, -23, 0, 68, -23, 673, -23, 380, 673, 673, -23, 814, 604, 604, 604, 604, 372, 7, 0, 0, 673, 673, 0, 0, 0, 0, 0, 673, 0, 0, 0, 0, 0, 0, 814, 0, 653, 0, 0, 0, 0, 658, 288, 0, 677, 456, 0, 0, 0, 0, 0, 0, 677, 456, 530, 530, 0, 665, 658, 658, 658, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 372); - protected $actionDefault = array(3, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 540, 540, 495, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 297, 297, 297, 32767, 32767, 32767, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 32767, 32767, 32767, 32767, 32767, 32767, 381, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 387, 545, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 362, 363, 365, 366, 296, 548, 529, 245, 388, 544, 295, 247, 325, 499, 32767, 32767, 32767, 327, 122, 256, 201, 498, 125, 294, 232, 380, 382, 326, 301, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 300, 454, 359, 358, 357, 456, 32767, 455, 492, 492, 495, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 323, 483, 482, 324, 452, 328, 453, 331, 457, 460, 329, 330, 347, 348, 345, 346, 349, 458, 459, 476, 477, 474, 475, 299, 350, 351, 352, 353, 478, 479, 480, 481, 32767, 32767, 280, 539, 539, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 338, 339, 467, 468, 32767, 236, 236, 236, 236, 281, 236, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 333, 334, 332, 462, 463, 461, 428, 32767, 32767, 32767, 430, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 500, 32767, 32767, 32767, 32767, 32767, 513, 417, 171, 32767, 409, 32767, 171, 171, 171, 171, 32767, 220, 222, 167, 32767, 171, 32767, 486, 32767, 32767, 32767, 32767, 32767, 518, 343, 32767, 32767, 116, 32767, 32767, 32767, 555, 32767, 513, 32767, 116, 32767, 32767, 32767, 32767, 356, 335, 336, 337, 32767, 32767, 517, 511, 470, 471, 472, 473, 32767, 464, 465, 466, 469, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 425, 431, 431, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 516, 515, 32767, 410, 494, 186, 184, 184, 32767, 206, 206, 32767, 32767, 188, 487, 506, 32767, 188, 173, 32767, 398, 175, 494, 32767, 32767, 238, 32767, 238, 32767, 398, 238, 32767, 32767, 238, 32767, 411, 435, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 377, 378, 489, 502, 32767, 503, 32767, 409, 341, 342, 344, 320, 32767, 322, 367, 368, 369, 370, 371, 372, 373, 375, 32767, 415, 32767, 418, 32767, 32767, 32767, 255, 32767, 553, 32767, 32767, 304, 553, 32767, 32767, 32767, 547, 32767, 32767, 298, 32767, 32767, 32767, 32767, 251, 32767, 169, 32767, 537, 32767, 554, 32767, 511, 32767, 340, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 512, 32767, 32767, 32767, 32767, 227, 32767, 448, 32767, 116, 32767, 32767, 32767, 187, 32767, 32767, 302, 246, 32767, 32767, 546, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 114, 32767, 170, 32767, 32767, 32767, 189, 32767, 32767, 511, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 293, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 511, 32767, 32767, 231, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 411, 32767, 274, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 127, 127, 3, 127, 127, 258, 3, 258, 127, 258, 258, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 214, 217, 206, 206, 164, 127, 127, 266); - protected $goto = array(166, 140, 140, 140, 166, 187, 168, 144, 147, 141, 142, 143, 149, 163, 163, 163, 163, 144, 144, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 138, 159, 160, 161, 162, 184, 139, 185, 493, 494, 377, 495, 499, 500, 501, 502, 503, 504, 505, 506, 967, 164, 145, 146, 148, 171, 176, 186, 203, 253, 256, 258, 260, 263, 264, 265, 266, 267, 268, 269, 277, 278, 279, 280, 303, 304, 328, 329, 330, 394, 395, 396, 542, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 150, 151, 152, 167, 153, 169, 154, 204, 170, 155, 156, 157, 205, 158, 136, 620, 560, 756, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 1108, 628, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 757, 888, 888, 508, 1200, 1200, 400, 606, 508, 536, 536, 568, 532, 534, 534, 496, 498, 524, 540, 569, 572, 583, 590, 852, 852, 852, 852, 847, 853, 174, 585, 519, 600, 601, 177, 178, 179, 401, 402, 403, 404, 173, 202, 206, 208, 257, 259, 261, 262, 270, 271, 272, 273, 274, 275, 281, 282, 283, 284, 305, 306, 331, 332, 333, 406, 407, 408, 409, 175, 180, 254, 255, 181, 182, 183, 497, 497, 785, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 509, 578, 582, 626, 749, 509, 544, 545, 546, 547, 548, 549, 550, 551, 553, 586, 338, 559, 321, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, 530, 349, 655, 555, 587, 352, 414, 591, 575, 604, 885, 611, 612, 881, 616, 617, 623, 625, 630, 632, 298, 296, 296, 296, 298, 290, 299, 944, 610, 816, 1170, 613, 436, 436, 375, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 436, 1072, 1084, 1083, 945, 1065, 1072, 895, 895, 895, 895, 1178, 895, 895, 1212, 1212, 1178, 388, 858, 561, 755, 1072, 1072, 1072, 1072, 1072, 1072, 3, 4, 384, 384, 384, 1212, 874, 856, 854, 856, 654, 465, 511, 883, 878, 1089, 541, 384, 537, 384, 567, 384, 1026, 19, 15, 371, 384, 1226, 510, 1204, 1192, 1192, 1192, 510, 906, 372, 522, 533, 554, 912, 514, 1068, 1069, 13, 1065, 378, 912, 1158, 594, 23, 965, 386, 386, 386, 602, 1066, 1169, 1066, 937, 447, 449, 631, 752, 1177, 1067, 1109, 614, 935, 1177, 605, 1197, 391, 1211, 1211, 543, 892, 386, 1194, 1194, 1194, 399, 518, 1016, 901, 389, 771, 529, 752, 340, 752, 1211, 518, 518, 385, 781, 1214, 770, 772, 1063, 910, 774, 1058, 1176, 659, 953, 514, 782, 862, 915, 450, 573, 1155, 0, 463, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 513, 528, 0, 0, 0, 0, 513, 0, 528, 0, 350, 351, 0, 609, 512, 515, 438, 439, 1064, 618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 779, 1219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 301); - protected $gotoCheck = array(43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 57, 68, 15, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 126, 9, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 16, 76, 76, 68, 76, 76, 51, 51, 68, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 68, 68, 68, 68, 68, 68, 27, 66, 101, 66, 66, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 117, 117, 29, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 61, 61, 61, 6, 117, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 125, 57, 125, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 32, 71, 32, 32, 69, 69, 69, 32, 40, 40, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 5, 5, 5, 5, 5, 5, 97, 62, 50, 81, 62, 57, 57, 62, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 124, 124, 97, 81, 57, 57, 57, 57, 57, 118, 57, 57, 142, 142, 118, 12, 33, 12, 14, 57, 57, 57, 57, 57, 57, 30, 30, 13, 13, 13, 142, 14, 14, 14, 14, 14, 57, 14, 14, 14, 34, 2, 13, 109, 13, 2, 13, 34, 34, 34, 34, 13, 13, 122, 140, 9, 9, 9, 122, 83, 58, 58, 58, 34, 13, 13, 81, 81, 58, 81, 46, 13, 131, 127, 34, 101, 123, 123, 123, 34, 81, 81, 81, 8, 8, 8, 8, 11, 119, 81, 8, 8, 8, 119, 49, 138, 48, 141, 141, 47, 78, 123, 119, 119, 119, 123, 47, 102, 80, 17, 23, 9, 11, 18, 11, 141, 47, 47, 11, 23, 141, 23, 24, 115, 84, 25, 113, 119, 73, 99, 13, 26, 70, 85, 64, 65, 130, -1, 108, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, -1, -1, -1, -1, 9, -1, 9, -1, 71, 71, -1, 13, 9, 9, 9, 9, 13, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, 5); - protected $gotoBase = array(0, 0, -184, 0, 0, 356, 290, 0, 488, 149, 0, 182, 85, 118, 426, 112, 203, 179, 208, 0, 0, 0, 0, 162, 190, 198, 120, 27, 0, 272, -224, 0, -274, 406, 32, 0, 0, 0, 0, 0, 330, 0, 0, -24, 0, 0, 440, 485, 213, 218, 371, -74, 0, 0, 0, 0, 0, 107, 110, 0, 0, -11, -72, 0, 104, 95, -405, 0, -94, 41, 119, -82, 0, 164, 0, 0, -79, 0, 197, 0, 204, 43, 0, 441, 171, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 115, 0, 195, 210, 0, 0, 0, 0, 0, 86, 427, 259, 0, 0, 116, 0, 174, 0, -5, 117, 196, 0, 0, 161, 170, 93, -21, -48, 273, 0, 0, 91, 271, 0, 0, 0, 0, 0, 0, 216, 0, 437, 187, 102, 0, 0); - protected $gotoDefault = array(-32768, 467, 663, 2, 664, 834, 739, 747, 597, 481, 629, 581, 380, 1188, 791, 792, 793, 381, 367, 482, 379, 410, 405, 780, 773, 775, 783, 172, 411, 786, 1, 788, 517, 824, 1017, 364, 796, 365, 589, 798, 526, 800, 801, 137, 382, 383, 527, 483, 390, 576, 815, 276, 387, 817, 366, 818, 827, 370, 464, 454, 459, 556, 608, 432, 446, 570, 564, 535, 1081, 565, 861, 348, 869, 660, 877, 880, 484, 557, 891, 451, 899, 1094, 397, 905, 911, 916, 287, 919, 417, 412, 584, 924, 925, 5, 929, 621, 622, 8, 312, 952, 598, 966, 420, 1036, 1038, 485, 486, 521, 458, 507, 525, 487, 1059, 440, 413, 1062, 488, 489, 433, 434, 1078, 354, 1163, 353, 448, 320, 1150, 579, 1113, 455, 1203, 1159, 347, 490, 491, 376, 1182, 392, 1198, 437, 1205, 1213, 343, 539, 566); - protected $ruleToNonTerminal = array(0, 1, 3, 3, 2, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, 11, 12, 12, 13, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 18, 18, 19, 19, 21, 21, 17, 17, 22, 22, 23, 23, 24, 24, 25, 25, 20, 20, 26, 28, 28, 29, 30, 30, 32, 31, 31, 31, 31, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 14, 14, 54, 54, 56, 55, 55, 48, 48, 58, 58, 59, 59, 60, 60, 15, 16, 16, 16, 63, 63, 63, 64, 64, 67, 67, 65, 65, 69, 69, 41, 41, 50, 50, 53, 53, 53, 52, 52, 70, 42, 42, 42, 42, 71, 71, 72, 72, 73, 73, 39, 39, 35, 35, 74, 37, 37, 75, 36, 36, 38, 38, 49, 49, 49, 61, 61, 77, 77, 78, 78, 80, 80, 80, 79, 79, 62, 62, 81, 81, 81, 82, 82, 83, 83, 83, 44, 44, 84, 84, 84, 45, 45, 85, 85, 86, 86, 66, 87, 87, 87, 87, 92, 92, 93, 93, 94, 94, 94, 94, 94, 95, 96, 96, 91, 91, 88, 88, 90, 90, 98, 98, 97, 97, 97, 97, 97, 97, 89, 89, 100, 99, 99, 46, 46, 40, 40, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 34, 34, 47, 47, 105, 105, 106, 106, 106, 106, 112, 101, 101, 108, 108, 114, 114, 115, 116, 116, 116, 116, 116, 116, 68, 68, 57, 57, 57, 57, 102, 102, 120, 120, 117, 117, 121, 121, 121, 121, 103, 103, 103, 107, 107, 107, 113, 113, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 27, 27, 27, 27, 27, 27, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 111, 111, 104, 104, 104, 104, 127, 127, 130, 130, 129, 129, 131, 131, 51, 51, 51, 51, 133, 133, 132, 132, 132, 132, 132, 134, 134, 119, 119, 122, 122, 118, 118, 136, 135, 135, 135, 135, 123, 123, 123, 123, 110, 110, 124, 124, 124, 124, 76, 137, 137, 138, 138, 138, 109, 109, 139, 139, 140, 140, 140, 140, 140, 125, 125, 125, 125, 142, 143, 141, 141, 141, 141, 141, 141, 141, 144, 144, 144); - protected $ruleToLength = array(1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 4, 3, 4, 2, 3, 1, 1, 7, 6, 3, 1, 3, 1, 3, 1, 1, 3, 1, 3, 1, 2, 3, 1, 3, 3, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 5, 8, 3, 5, 9, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 1, 2, 2, 5, 7, 9, 5, 6, 3, 3, 2, 2, 1, 1, 1, 0, 2, 8, 0, 4, 1, 3, 0, 1, 0, 1, 0, 1, 10, 7, 6, 5, 1, 2, 2, 0, 2, 0, 2, 0, 2, 1, 3, 1, 4, 1, 4, 1, 1, 4, 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, 3, 1, 1, 1, 4, 0, 2, 3, 0, 2, 4, 0, 2, 0, 3, 1, 2, 1, 1, 0, 1, 3, 4, 6, 1, 1, 1, 0, 1, 0, 2, 2, 3, 3, 1, 3, 1, 2, 2, 3, 1, 1, 2, 4, 3, 1, 1, 3, 2, 0, 1, 3, 3, 9, 3, 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, 1, 1, 3, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 3, 1, 0, 1, 1, 3, 3, 4, 4, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 5, 4, 3, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 3, 2, 1, 2, 10, 11, 3, 3, 2, 4, 4, 3, 4, 4, 4, 4, 7, 3, 2, 0, 4, 1, 3, 2, 2, 4, 6, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 4, 4, 0, 2, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 3, 1, 4, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 4, 3, 1, 3, 1, 1, 3, 3, 0, 2, 0, 1, 3, 1, 3, 1, 1, 1, 1, 1, 6, 4, 3, 4, 2, 4, 4, 1, 3, 1, 2, 1, 1, 4, 1, 1, 3, 6, 4, 4, 4, 4, 1, 4, 0, 1, 1, 3, 1, 1, 4, 3, 1, 1, 1, 0, 0, 2, 3, 1, 3, 1, 4, 2, 2, 2, 2, 1, 2, 1, 1, 1, 4, 3, 3, 3, 6, 3, 1, 1, 1); - protected function initReduceCallbacks() - { - $this->reduceCallbacks = [0 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 1 => function ($stackPos) { - $this->semValue = $this->handleNamespaces($this->semStack[$stackPos - (1 - 1)]); - }, 2 => function ($stackPos) { - if (\is_array($this->semStack[$stackPos - (2 - 2)])) { - $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - } else { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 3 => function ($stackPos) { - $this->semValue = array(); - }, 4 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 5 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 6 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 7 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 8 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 9 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 10 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 11 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 12 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 13 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 14 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 15 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 16 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 17 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 18 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 19 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 20 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 21 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 22 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 23 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 24 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 25 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 26 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 27 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 28 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 29 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 30 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 31 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 32 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 33 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 34 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 35 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 36 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 37 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 38 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 39 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 40 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 41 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 42 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 43 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 44 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 45 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 46 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 47 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 48 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 49 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 50 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 51 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 52 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 53 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 54 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 55 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 56 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 57 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 58 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 59 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 60 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 61 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 62 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 63 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 64 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 65 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 66 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 67 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 68 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 69 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 70 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 71 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 72 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 73 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 74 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 75 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 76 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 77 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 78 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 79 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 80 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 81 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 82 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 83 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 84 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 85 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 86 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 87 => function ($stackPos) { - $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 88 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 89 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 90 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 91 => function ($stackPos) { - $this->semValue = new Name(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 92 => function ($stackPos) { - $this->semValue = new Expr\Variable(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 93 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 94 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 95 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 96 => function ($stackPos) { - $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 97 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (3 - 2)], null, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); - $this->checkNamespace($this->semValue); - }, 98 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); - $this->checkNamespace($this->semValue); - }, 99 => function ($stackPos) { - $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); - $this->checkNamespace($this->semValue); - }, 100 => function ($stackPos) { - $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (3 - 2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 101 => function ($stackPos) { - $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 102 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 103 => function ($stackPos) { - $this->semValue = new Stmt\Const_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 104 => function ($stackPos) { - $this->semValue = Stmt\Use_::TYPE_FUNCTION; - }, 105 => function ($stackPos) { - $this->semValue = Stmt\Use_::TYPE_CONSTANT; - }, 106 => function ($stackPos) { - $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->semStack[$stackPos - (7 - 2)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 107 => function ($stackPos) { - $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 108 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 109 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 110 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 111 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 112 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 113 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 114 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); - }, 115 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); - }, 116 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); - }, 117 => function ($stackPos) { - $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); - }, 118 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - $this->semValue->type = Stmt\Use_::TYPE_NORMAL; - }, 119 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue->type = $this->semStack[$stackPos - (2 - 1)]; - }, 120 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 121 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 122 => function ($stackPos) { - $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 123 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 124 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 125 => function ($stackPos) { - $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 126 => function ($stackPos) { - if (\is_array($this->semStack[$stackPos - (2 - 2)])) { - $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - } else { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 127 => function ($stackPos) { - $this->semValue = array(); - }, 128 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 129 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 130 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 131 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 132 => function ($stackPos) { - throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 133 => function ($stackPos) { - if ($this->semStack[$stackPos - (3 - 2)]) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)]; - $stmts = $this->semValue; - if (!empty($attrs['comments'])) { - $stmts[0]->setAttribute('comments', \array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); - } - } else { - $startAttributes = $this->startAttributeStack[$stackPos - (3 - 1)]; - if (isset($startAttributes['comments'])) { - $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); - } else { - $this->semValue = null; - } - if (null === $this->semValue) { - $this->semValue = array(); - } - } - }, 134 => function ($stackPos) { - $this->semValue = new Stmt\If_($this->semStack[$stackPos - (5 - 2)], ['stmts' => \is_array($this->semStack[$stackPos - (5 - 3)]) ? $this->semStack[$stackPos - (5 - 3)] : array($this->semStack[$stackPos - (5 - 3)]), 'elseifs' => $this->semStack[$stackPos - (5 - 4)], 'else' => $this->semStack[$stackPos - (5 - 5)]], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 135 => function ($stackPos) { - $this->semValue = new Stmt\If_($this->semStack[$stackPos - (8 - 2)], ['stmts' => $this->semStack[$stackPos - (8 - 4)], 'elseifs' => $this->semStack[$stackPos - (8 - 5)], 'else' => $this->semStack[$stackPos - (8 - 6)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 136 => function ($stackPos) { - $this->semValue = new Stmt\While_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 137 => function ($stackPos) { - $this->semValue = new Stmt\Do_($this->semStack[$stackPos - (5 - 4)], \is_array($this->semStack[$stackPos - (5 - 2)]) ? $this->semStack[$stackPos - (5 - 2)] : array($this->semStack[$stackPos - (5 - 2)]), $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 138 => function ($stackPos) { - $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos - (9 - 3)], 'cond' => $this->semStack[$stackPos - (9 - 5)], 'loop' => $this->semStack[$stackPos - (9 - 7)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 139 => function ($stackPos) { - $this->semValue = new Stmt\Switch_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 140 => function ($stackPos) { - $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 141 => function ($stackPos) { - $this->semValue = new Stmt\Break_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 142 => function ($stackPos) { - $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 143 => function ($stackPos) { - $this->semValue = new Stmt\Continue_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 144 => function ($stackPos) { - $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 145 => function ($stackPos) { - $this->semValue = new Stmt\Return_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 146 => function ($stackPos) { - $this->semValue = new Stmt\Global_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 147 => function ($stackPos) { - $this->semValue = new Stmt\Static_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 148 => function ($stackPos) { - $this->semValue = new Stmt\Echo_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 149 => function ($stackPos) { - $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 150 => function ($stackPos) { - $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 151 => function ($stackPos) { - $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 152 => function ($stackPos) { - $this->semValue = new Stmt\Unset_($this->semStack[$stackPos - (5 - 3)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 153 => function ($stackPos) { - $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos - (7 - 5)][1], 'stmts' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - }, 154 => function ($stackPos) { - $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (9 - 3)], $this->semStack[$stackPos - (9 - 7)][0], ['keyVar' => $this->semStack[$stackPos - (9 - 5)], 'byRef' => $this->semStack[$stackPos - (9 - 7)][1], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - }, 155 => function ($stackPos) { - $this->semValue = new Stmt\Declare_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 156 => function ($stackPos) { - $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 5)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - $this->checkTryCatch($this->semValue); - }, 157 => function ($stackPos) { - $this->semValue = new Stmt\Throw_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 158 => function ($stackPos) { - $this->semValue = new Stmt\Goto_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 159 => function ($stackPos) { - $this->semValue = new Stmt\Label($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 160 => function ($stackPos) { - $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 161 => function ($stackPos) { - $this->semValue = array(); - /* means: no statement */ - }, 162 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 163 => function ($stackPos) { - $startAttributes = $this->startAttributeStack[$stackPos - (1 - 1)]; - if (isset($startAttributes['comments'])) { - $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); - } else { - $this->semValue = null; - } - if ($this->semValue === null) { - $this->semValue = array(); - } - /* means: no statement */ - }, 164 => function ($stackPos) { - $this->semValue = array(); - }, 165 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 166 => function ($stackPos) { - $this->semValue = new Stmt\Catch_(array($this->semStack[$stackPos - (8 - 3)]), $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 7)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); - }, 167 => function ($stackPos) { - $this->semValue = null; - }, 168 => function ($stackPos) { - $this->semValue = new Stmt\Finally_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 169 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 170 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 171 => function ($stackPos) { - $this->semValue = \false; - }, 172 => function ($stackPos) { - $this->semValue = \true; - }, 173 => function ($stackPos) { - $this->semValue = \false; - }, 174 => function ($stackPos) { - $this->semValue = \true; - }, 175 => function ($stackPos) { - $this->semValue = \false; - }, 176 => function ($stackPos) { - $this->semValue = \true; - }, 177 => function ($stackPos) { - $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (10 - 3)], ['byRef' => $this->semStack[$stackPos - (10 - 2)], 'params' => $this->semStack[$stackPos - (10 - 5)], 'returnType' => $this->semStack[$stackPos - (10 - 7)], 'stmts' => $this->semStack[$stackPos - (10 - 9)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - }, 178 => function ($stackPos) { - $this->semValue = new Stmt\Class_($this->semStack[$stackPos - (7 - 2)], ['type' => $this->semStack[$stackPos - (7 - 1)], 'extends' => $this->semStack[$stackPos - (7 - 3)], 'implements' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); - $this->checkClass($this->semValue, $stackPos - (7 - 2)); - }, 179 => function ($stackPos) { - $this->semValue = new Stmt\Interface_($this->semStack[$stackPos - (6 - 2)], ['extends' => $this->semStack[$stackPos - (6 - 3)], 'stmts' => $this->semStack[$stackPos - (6 - 5)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - $this->checkInterface($this->semValue, $stackPos - (6 - 2)); - }, 180 => function ($stackPos) { - $this->semValue = new Stmt\Trait_($this->semStack[$stackPos - (5 - 2)], ['stmts' => $this->semStack[$stackPos - (5 - 4)]], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 181 => function ($stackPos) { - $this->semValue = 0; - }, 182 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; - }, 183 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; - }, 184 => function ($stackPos) { - $this->semValue = null; - }, 185 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 186 => function ($stackPos) { - $this->semValue = array(); - }, 187 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 188 => function ($stackPos) { - $this->semValue = array(); - }, 189 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 190 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 191 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 192 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 193 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 194 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 195 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 196 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 197 => function ($stackPos) { - $this->semValue = null; - }, 198 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 199 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 200 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 201 => function ($stackPos) { - $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 202 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 203 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 3)]; - }, 204 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 205 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (5 - 3)]; - }, 206 => function ($stackPos) { - $this->semValue = array(); - }, 207 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 208 => function ($stackPos) { - $this->semValue = new Stmt\Case_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 209 => function ($stackPos) { - $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 210 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 211 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 212 => function ($stackPos) { - $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); - }, 213 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 2)]; - }, 214 => function ($stackPos) { - $this->semValue = array(); - }, 215 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 216 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (3 - 2)], \is_array($this->semStack[$stackPos - (3 - 3)]) ? $this->semStack[$stackPos - (3 - 3)] : array($this->semStack[$stackPos - (3 - 3)]), $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 217 => function ($stackPos) { - $this->semValue = array(); - }, 218 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 219 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 220 => function ($stackPos) { - $this->semValue = null; - }, 221 => function ($stackPos) { - $this->semValue = new Stmt\Else_(\is_array($this->semStack[$stackPos - (2 - 2)]) ? $this->semStack[$stackPos - (2 - 2)] : array($this->semStack[$stackPos - (2 - 2)]), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 222 => function ($stackPos) { - $this->semValue = null; - }, 223 => function ($stackPos) { - $this->semValue = new Stmt\Else_($this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 224 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); - }, 225 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (2 - 2)], \true); - }, 226 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); - }, 227 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 228 => function ($stackPos) { - $this->semValue = array(); - }, 229 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 230 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 231 => function ($stackPos) { - $this->semValue = new Node\Param($this->semStack[$stackPos - (4 - 4)], null, $this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - $this->checkParam($this->semValue); - }, 232 => function ($stackPos) { - $this->semValue = new Node\Param($this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 6)], $this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 3)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - $this->checkParam($this->semValue); - }, 233 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 234 => function ($stackPos) { - $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 235 => function ($stackPos) { - $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 236 => function ($stackPos) { - $this->semValue = null; - }, 237 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 238 => function ($stackPos) { - $this->semValue = null; - }, 239 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 2)]; - }, 240 => function ($stackPos) { - $this->semValue = array(); - }, 241 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 242 => function ($stackPos) { - $this->semValue = array(new Node\Arg($this->semStack[$stackPos - (3 - 2)], \false, \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes)); - }, 243 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 244 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 245 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (1 - 1)], \false, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 246 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \true, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 247 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \false, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 248 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 249 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 250 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 251 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 252 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 253 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 254 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 255 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 256 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 257 => function ($stackPos) { - if ($this->semStack[$stackPos - (2 - 2)] !== null) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - } - }, 258 => function ($stackPos) { - $this->semValue = array(); - }, 259 => function ($stackPos) { - $startAttributes = $this->lookaheadStartAttributes; - if (isset($startAttributes['comments'])) { - $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); - } else { - $nop = null; - } - if ($nop !== null) { - $this->semStack[$stackPos - (1 - 1)][] = $nop; - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 260 => function ($stackPos) { - $this->semValue = new Stmt\Property($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - $this->checkProperty($this->semValue, $stackPos - (3 - 1)); - }, 261 => function ($stackPos) { - $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos - (3 - 2)], 0, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 262 => function ($stackPos) { - $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos - (9 - 4)], ['type' => $this->semStack[$stackPos - (9 - 1)], 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 6)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); - $this->checkClassMethod($this->semValue, $stackPos - (9 - 1)); - }, 263 => function ($stackPos) { - $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 264 => function ($stackPos) { - $this->semValue = array(); - }, 265 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 266 => function ($stackPos) { - $this->semValue = array(); - }, 267 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 268 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 269 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (5 - 1)][0], $this->semStack[$stackPos - (5 - 1)][1], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 270 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], null, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 271 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 272 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 273 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); - }, 274 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 275 => function ($stackPos) { - $this->semValue = array(null, $this->semStack[$stackPos - (1 - 1)]); - }, 276 => function ($stackPos) { - $this->semValue = null; - }, 277 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 278 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 279 => function ($stackPos) { - $this->semValue = 0; - }, 280 => function ($stackPos) { - $this->semValue = 0; - }, 281 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 282 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 283 => function ($stackPos) { - $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); - $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; - }, 284 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; - }, 285 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; - }, 286 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; - }, 287 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_STATIC; - }, 288 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; - }, 289 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; - }, 290 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 291 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 292 => function ($stackPos) { - $this->semValue = new Node\VarLikeIdentifier(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 293 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 294 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 295 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 296 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 297 => function ($stackPos) { - $this->semValue = array(); - }, 298 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 299 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 300 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 301 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 302 => function ($stackPos) { - $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 303 => function ($stackPos) { - $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 304 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 305 => function ($stackPos) { - $this->semValue = new Expr\Clone_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 306 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 307 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 308 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 309 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 310 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 311 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 312 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 313 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 314 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 315 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 316 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 317 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 318 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 319 => function ($stackPos) { - $this->semValue = new Expr\PostInc($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 320 => function ($stackPos) { - $this->semValue = new Expr\PreInc($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 321 => function ($stackPos) { - $this->semValue = new Expr\PostDec($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 322 => function ($stackPos) { - $this->semValue = new Expr\PreDec($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 323 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 324 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 325 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 326 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 327 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 328 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 329 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 330 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 331 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 332 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 333 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 334 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 335 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 336 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 337 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 338 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 339 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 340 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 341 => function ($stackPos) { - $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 342 => function ($stackPos) { - $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 343 => function ($stackPos) { - $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 344 => function ($stackPos) { - $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 345 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 346 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 347 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 348 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 349 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 350 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 351 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 352 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 353 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 354 => function ($stackPos) { - $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 355 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 356 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 357 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 358 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 359 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 360 => function ($stackPos) { - $this->semValue = new Expr\Isset_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 361 => function ($stackPos) { - $this->semValue = new Expr\Empty_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 362 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 363 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 364 => function ($stackPos) { - $this->semValue = new Expr\Eval_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 365 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 366 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 367 => function ($stackPos) { - $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 368 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; - $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos - (2 - 1)]); - $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos - (2 - 2)], $attrs); - }, 369 => function ($stackPos) { - $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 370 => function ($stackPos) { - $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 371 => function ($stackPos) { - $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 372 => function ($stackPos) { - $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 373 => function ($stackPos) { - $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 374 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; - $attrs['kind'] = \strtolower($this->semStack[$stackPos - (2 - 1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; - $this->semValue = new Expr\Exit_($this->semStack[$stackPos - (2 - 2)], $attrs); - }, 375 => function ($stackPos) { - $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 376 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 377 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 378 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 379 => function ($stackPos) { - $this->semValue = new Expr\ShellExec($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 380 => function ($stackPos) { - $this->semValue = new Expr\Print_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 381 => function ($stackPos) { - $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 382 => function ($stackPos) { - $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 383 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (10 - 2)], 'params' => $this->semStack[$stackPos - (10 - 4)], 'uses' => $this->semStack[$stackPos - (10 - 6)], 'returnType' => $this->semStack[$stackPos - (10 - 7)], 'stmts' => $this->semStack[$stackPos - (10 - 9)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); - }, 384 => function ($stackPos) { - $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (11 - 3)], 'params' => $this->semStack[$stackPos - (11 - 5)], 'uses' => $this->semStack[$stackPos - (11 - 7)], 'returnType' => $this->semStack[$stackPos - (11 - 8)], 'stmts' => $this->semStack[$stackPos - (11 - 10)]], $this->startAttributeStack[$stackPos - (11 - 1)] + $this->endAttributes); - }, 385 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 386 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 387 => function ($stackPos) { - $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (2 - 2)], null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 388 => function ($stackPos) { - $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 389 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes; - $attrs['kind'] = Expr\Array_::KIND_LONG; - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $attrs); - }, 390 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; - $attrs['kind'] = Expr\Array_::KIND_SHORT; - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $attrs); - }, 391 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 392 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes; - $attrs['kind'] = $this->semStack[$stackPos - (4 - 1)][0] === "'" || $this->semStack[$stackPos - (4 - 1)][1] === "'" && ($this->semStack[$stackPos - (4 - 1)][0] === 'b' || $this->semStack[$stackPos - (4 - 1)][0] === 'B') ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED; - $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos - (4 - 1)]), $attrs), $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 393 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 394 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 395 => function ($stackPos) { - $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos - (7 - 3)], 'implements' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes), $this->semStack[$stackPos - (7 - 2)]); - $this->checkClass($this->semValue[0], -1); - }, 396 => function ($stackPos) { - $this->semValue = new Expr\New_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 397 => function ($stackPos) { - list($class, $ctorArgs) = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 398 => function ($stackPos) { - $this->semValue = array(); - }, 399 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (4 - 3)]; - }, 400 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 401 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 402 => function ($stackPos) { - $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos - (2 - 2)], $this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 403 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 404 => function ($stackPos) { - $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 405 => function ($stackPos) { - $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 406 => function ($stackPos) { - $this->semValue = $this->fixupPhp5StaticPropCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 407 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 408 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 409 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 410 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 411 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 412 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 413 => function ($stackPos) { - $this->semValue = new Name\FullyQualified(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 414 => function ($stackPos) { - $this->semValue = new Name\Relative(\substr($this->semStack[$stackPos - (1 - 1)], 10), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 415 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 416 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 417 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 418 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 419 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 420 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 421 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 422 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 423 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 424 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 425 => function ($stackPos) { - $this->semValue = null; - }, 426 => function ($stackPos) { - $this->semValue = null; - }, 427 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 428 => function ($stackPos) { - $this->semValue = array(); - }, 429 => function ($stackPos) { - $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos - (1 - 1)], '`', \false), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); - }, 430 => function ($stackPos) { - foreach ($this->semStack[$stackPos - (1 - 1)] as $s) { - if ($s instanceof Node\Scalar\EncapsedStringPart) { - $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', \false); - } - } - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 431 => function ($stackPos) { - $this->semValue = array(); - }, 432 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 433 => function ($stackPos) { - $this->semValue = $this->parseLNumber($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes, \true); - }, 434 => function ($stackPos) { - $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos - (1 - 1)]), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 435 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes; - $attrs['kind'] = $this->semStack[$stackPos - (1 - 1)][0] === "'" || $this->semStack[$stackPos - (1 - 1)][1] === "'" && ($this->semStack[$stackPos - (1 - 1)][0] === 'b' || $this->semStack[$stackPos - (1 - 1)][0] === 'B') ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED; - $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos - (1 - 1)], \false), $attrs); - }, 436 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 437 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 438 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 439 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 440 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 441 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 442 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 443 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 444 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \false); - }, 445 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (2 - 1)], '', $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (2 - 2)] + $this->endAttributeStack[$stackPos - (2 - 2)], \false); - }, 446 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 447 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 448 => function ($stackPos) { - $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 449 => function ($stackPos) { - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 450 => function ($stackPos) { - $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 451 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 452 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 453 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 454 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 455 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 456 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 457 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 458 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 459 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 460 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 461 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 462 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 463 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 464 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 465 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 466 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 467 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 468 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 469 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 470 => function ($stackPos) { - $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 471 => function ($stackPos) { - $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 472 => function ($stackPos) { - $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 473 => function ($stackPos) { - $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 474 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 475 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 476 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 477 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 478 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 479 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 480 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 481 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 482 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); - }, 483 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 484 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 485 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 486 => function ($stackPos) { - $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 487 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 488 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 489 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 490 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; - $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; - foreach ($this->semStack[$stackPos - (3 - 2)] as $s) { - if ($s instanceof Node\Scalar\EncapsedStringPart) { - $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', \true); - } - } - $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos - (3 - 2)], $attrs); - }, 491 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); - }, 492 => function ($stackPos) { - $this->semValue = array(); - }, 493 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 494 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 495 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, 496 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 497 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 498 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 499 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 500 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 501 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 502 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 503 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 504 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 505 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 506 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 507 => function ($stackPos) { - $this->semValue = new Expr\MethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 508 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 509 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 510 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 511 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 512 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 513 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 514 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 515 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 516 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 517 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 518 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 519 => function ($stackPos) { - $var = \substr($this->semStack[$stackPos - (1 - 1)], 1); - $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes) : $var; - }, 520 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 521 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 522 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 523 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 524 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 525 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 526 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 527 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 528 => function ($stackPos) { - $this->semValue = null; - }, 529 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 530 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 531 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 532 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 533 => function ($stackPos) { - $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - $this->errorState = 2; - }, 534 => function ($stackPos) { - $this->semValue = new Expr\List_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 535 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 536 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 537 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 538 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 539 => function ($stackPos) { - $this->semValue = null; - }, 540 => function ($stackPos) { - $this->semValue = array(); - }, 541 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 542 => function ($stackPos) { - $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; - $this->semValue = $this->semStack[$stackPos - (3 - 1)]; - }, 543 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 544 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 545 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 546 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 1)], \true, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 547 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 548 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); - }, 549 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 550 => function ($stackPos) { - $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; - $this->semValue = $this->semStack[$stackPos - (2 - 1)]; - }, 551 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); - }, 552 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); - }, 553 => function ($stackPos) { - $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 554 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 555 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }, 556 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); - }, 557 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 558 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 559 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); - }, 560 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 4)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); - }, 561 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (3 - 2)]; - }, 562 => function ($stackPos) { - $this->semValue = new Scalar\String_($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 563 => function ($stackPos) { - $this->semValue = $this->parseNumString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); - }, 564 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos - (1 - 1)]; - }]; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use function array_pop; -use function hrtime; -final class Timer -{ - /** - * @psalm-var list - */ - private $startTimes = []; - public function start() : void - { - $this->startTimes[] = (float) hrtime(\true); - } - /** - * @throws NoActiveTimerException - */ - public function stop() : Duration - { - if (empty($this->startTimes)) { - throw new NoActiveTimerException('Timer::start() has to be called before Timer::stop()'); - } - return Duration::fromNanoseconds((float) hrtime(\true) - array_pop($this->startTimes)); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use function is_float; -use function memory_get_peak_usage; -use function microtime; -use function sprintf; -final class ResourceUsageFormatter -{ - /** - * @psalm-var array - */ - private const SIZES = ['GB' => 1073741824, 'MB' => 1048576, 'KB' => 1024]; - public function resourceUsage(Duration $duration) : string - { - return sprintf('Time: %s, Memory: %s', $duration->asString(), $this->bytesToString(memory_get_peak_usage(\true))); - } - /** - * @throws TimeSinceStartOfRequestNotAvailableException - */ - public function resourceUsageSinceStartOfRequest() : string - { - if (!isset($_SERVER['REQUEST_TIME_FLOAT'])) { - throw new TimeSinceStartOfRequestNotAvailableException('Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available'); - } - if (!is_float($_SERVER['REQUEST_TIME_FLOAT'])) { - throw new TimeSinceStartOfRequestNotAvailableException('Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float'); - } - return $this->resourceUsage(Duration::fromMicroseconds(1000000 * (microtime(\true) - $_SERVER['REQUEST_TIME_FLOAT']))); - } - private function bytesToString(int $bytes) : string - { - foreach (self::SIZES as $unit => $value) { - if ($bytes >= $value) { - return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); - } - } - // @codeCoverageIgnoreStart - return $bytes . ' byte' . ($bytes !== 1 ? 's' : ''); - // @codeCoverageIgnoreEnd - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use function floor; -use function sprintf; -/** - * @psalm-immutable - */ -final class Duration -{ - /** - * @var float - */ - private $nanoseconds; - /** - * @var int - */ - private $hours; - /** - * @var int - */ - private $minutes; - /** - * @var int - */ - private $seconds; - /** - * @var int - */ - private $milliseconds; - public static function fromMicroseconds(float $microseconds) : self - { - return new self($microseconds * 1000); - } - public static function fromNanoseconds(float $nanoseconds) : self - { - return new self($nanoseconds); - } - private function __construct(float $nanoseconds) - { - $this->nanoseconds = $nanoseconds; - $timeInMilliseconds = $nanoseconds / 1000000; - $hours = floor($timeInMilliseconds / 60 / 60 / 1000); - $hoursInMilliseconds = $hours * 60 * 60 * 1000; - $minutes = floor($timeInMilliseconds / 60 / 1000) % 60; - $minutesInMilliseconds = $minutes * 60 * 1000; - $seconds = floor(($timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds) / 1000); - $secondsInMilliseconds = $seconds * 1000; - $milliseconds = $timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds - $secondsInMilliseconds; - $this->hours = (int) $hours; - $this->minutes = $minutes; - $this->seconds = (int) $seconds; - $this->milliseconds = (int) $milliseconds; - } - public function asNanoseconds() : float - { - return $this->nanoseconds; - } - public function asMicroseconds() : float - { - return $this->nanoseconds / 1000; - } - public function asMilliseconds() : float - { - return $this->nanoseconds / 1000000; - } - public function asSeconds() : float - { - return $this->nanoseconds / 1000000000; - } - public function asString() : string - { - $result = ''; - if ($this->hours > 0) { - $result = sprintf('%02d', $this->hours) . ':'; - } - $result .= sprintf('%02d', $this->minutes) . ':'; - $result .= sprintf('%02d', $this->seconds); - if ($this->milliseconds > 0) { - $result .= '.' . sprintf('%03d', $this->milliseconds); - } - return $result; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use LogicException; -final class NoActiveTimerException extends LogicException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use RuntimeException; -final class TimeSinceStartOfRequestNotAvailableException extends RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Timer; - -use Throwable; -interface Exception extends Throwable -{ -} -phpunit/php-timer - -Copyright (c) 2010-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -use function array_keys; -use function array_merge; -use function array_reverse; -use function func_get_args; -use function get_declared_classes; -use function get_declared_interfaces; -use function get_declared_traits; -use function get_defined_constants; -use function get_defined_functions; -use function get_included_files; -use function in_array; -use function ini_get_all; -use function is_array; -use function is_object; -use function is_resource; -use function is_scalar; -use function serialize; -use function unserialize; -use ReflectionClass; -use PHPUnit\SebastianBergmann\ObjectReflector\ObjectReflector; -use PHPUnit\SebastianBergmann\RecursionContext\Context; -use Throwable; -/** - * A snapshot of global state. - */ -class Snapshot -{ - /** - * @var ExcludeList - */ - private $excludeList; - /** - * @var array - */ - private $globalVariables = []; - /** - * @var array - */ - private $superGlobalArrays = []; - /** - * @var array - */ - private $superGlobalVariables = []; - /** - * @var array - */ - private $staticAttributes = []; - /** - * @var array - */ - private $iniSettings = []; - /** - * @var array - */ - private $includedFiles = []; - /** - * @var array - */ - private $constants = []; - /** - * @var array - */ - private $functions = []; - /** - * @var array - */ - private $interfaces = []; - /** - * @var array - */ - private $classes = []; - /** - * @var array - */ - private $traits = []; - /** - * Creates a snapshot of the current global state. - */ - public function __construct(ExcludeList $excludeList = null, bool $includeGlobalVariables = \true, bool $includeStaticAttributes = \true, bool $includeConstants = \true, bool $includeFunctions = \true, bool $includeClasses = \true, bool $includeInterfaces = \true, bool $includeTraits = \true, bool $includeIniSettings = \true, bool $includeIncludedFiles = \true) - { - $this->excludeList = $excludeList ?: new ExcludeList(); - if ($includeConstants) { - $this->snapshotConstants(); - } - if ($includeFunctions) { - $this->snapshotFunctions(); - } - if ($includeClasses || $includeStaticAttributes) { - $this->snapshotClasses(); - } - if ($includeInterfaces) { - $this->snapshotInterfaces(); - } - if ($includeGlobalVariables) { - $this->setupSuperGlobalArrays(); - $this->snapshotGlobals(); - } - if ($includeStaticAttributes) { - $this->snapshotStaticAttributes(); - } - if ($includeIniSettings) { - $this->iniSettings = ini_get_all(null, \false); - } - if ($includeIncludedFiles) { - $this->includedFiles = get_included_files(); - } - $this->traits = get_declared_traits(); - } - public function excludeList() : ExcludeList - { - return $this->excludeList; - } - public function globalVariables() : array - { - return $this->globalVariables; - } - public function superGlobalVariables() : array - { - return $this->superGlobalVariables; - } - public function superGlobalArrays() : array - { - return $this->superGlobalArrays; - } - public function staticAttributes() : array - { - return $this->staticAttributes; - } - public function iniSettings() : array - { - return $this->iniSettings; - } - public function includedFiles() : array - { - return $this->includedFiles; - } - public function constants() : array - { - return $this->constants; - } - public function functions() : array - { - return $this->functions; - } - public function interfaces() : array - { - return $this->interfaces; - } - public function classes() : array - { - return $this->classes; - } - public function traits() : array - { - return $this->traits; - } - /** - * Creates a snapshot user-defined constants. - */ - private function snapshotConstants() : void - { - $constants = get_defined_constants(\true); - if (isset($constants['user'])) { - $this->constants = $constants['user']; - } - } - /** - * Creates a snapshot user-defined functions. - */ - private function snapshotFunctions() : void - { - $functions = get_defined_functions(); - $this->functions = $functions['user']; - } - /** - * Creates a snapshot user-defined classes. - */ - private function snapshotClasses() : void - { - foreach (array_reverse(get_declared_classes()) as $className) { - $class = new ReflectionClass($className); - if (!$class->isUserDefined()) { - break; - } - $this->classes[] = $className; - } - $this->classes = array_reverse($this->classes); - } - /** - * Creates a snapshot user-defined interfaces. - */ - private function snapshotInterfaces() : void - { - foreach (array_reverse(get_declared_interfaces()) as $interfaceName) { - $class = new ReflectionClass($interfaceName); - if (!$class->isUserDefined()) { - break; - } - $this->interfaces[] = $interfaceName; - } - $this->interfaces = array_reverse($this->interfaces); - } - /** - * Creates a snapshot of all global and super-global variables. - */ - private function snapshotGlobals() : void - { - $superGlobalArrays = $this->superGlobalArrays(); - foreach ($superGlobalArrays as $superGlobalArray) { - $this->snapshotSuperGlobalArray($superGlobalArray); - } - foreach (array_keys($GLOBALS) as $key) { - if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, \true) && $this->canBeSerialized($GLOBALS[$key]) && !$this->excludeList->isGlobalVariableExcluded($key)) { - /* @noinspection UnserializeExploitsInspection */ - $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key])); - } - } - } - /** - * Creates a snapshot a super-global variable array. - */ - private function snapshotSuperGlobalArray(string $superGlobalArray) : void - { - $this->superGlobalVariables[$superGlobalArray] = []; - if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { - foreach ($GLOBALS[$superGlobalArray] as $key => $value) { - /* @noinspection UnserializeExploitsInspection */ - $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value)); - } - } - } - /** - * Creates a snapshot of all static attributes in user-defined classes. - */ - private function snapshotStaticAttributes() : void - { - foreach ($this->classes as $className) { - $class = new ReflectionClass($className); - $snapshot = []; - foreach ($class->getProperties() as $attribute) { - if ($attribute->isStatic()) { - $name = $attribute->getName(); - if ($this->excludeList->isStaticAttributeExcluded($className, $name)) { - continue; - } - $attribute->setAccessible(\true); - $value = $attribute->getValue(); - if ($this->canBeSerialized($value)) { - /* @noinspection UnserializeExploitsInspection */ - $snapshot[$name] = unserialize(serialize($value)); - } - } - } - if (!empty($snapshot)) { - $this->staticAttributes[$className] = $snapshot; - } - } - } - /** - * Returns a list of all super-global variable arrays. - */ - private function setupSuperGlobalArrays() : void - { - $this->superGlobalArrays = ['_ENV', '_POST', '_GET', '_COOKIE', '_SERVER', '_FILES', '_REQUEST']; - } - private function canBeSerialized($variable) : bool - { - if (is_scalar($variable) || $variable === null) { - return \true; - } - if (is_resource($variable)) { - return \false; - } - foreach ($this->enumerateObjectsAndResources($variable) as $value) { - if (is_resource($value)) { - return \false; - } - if (is_object($value)) { - $class = new ReflectionClass($value); - if ($class->isAnonymous()) { - return \false; - } - try { - @serialize($value); - } catch (Throwable $t) { - return \false; - } - } - } - return \true; - } - private function enumerateObjectsAndResources($variable) : array - { - if (isset(func_get_args()[1])) { - $processed = func_get_args()[1]; - } else { - $processed = new Context(); - } - $result = []; - if ($processed->contains($variable)) { - return $result; - } - $array = $variable; - $processed->add($variable); - if (is_array($variable)) { - foreach ($array as $element) { - if (!is_array($element) && !is_object($element) && !is_resource($element)) { - continue; - } - if (!is_resource($element)) { - /** @noinspection SlowArrayOperationsInLoopInspection */ - $result = array_merge($result, $this->enumerateObjectsAndResources($element, $processed)); - } else { - $result[] = $element; - } - } - } else { - $result[] = $variable; - foreach ((new ObjectReflector())->getAttributes($variable) as $value) { - if (!is_array($value) && !is_object($value) && !is_resource($value)) { - continue; - } - if (!is_resource($value)) { - /** @noinspection SlowArrayOperationsInLoopInspection */ - $result = array_merge($result, $this->enumerateObjectsAndResources($value, $processed)); - } else { - $result[] = $value; - } - } - } - return $result; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -use const PHP_EOL; -use function is_array; -use function is_scalar; -use function serialize; -use function sprintf; -use function var_export; -/** - * Exports parts of a Snapshot as PHP code. - */ -final class CodeExporter -{ - public function constants(Snapshot $snapshot) : string - { - $result = ''; - foreach ($snapshot->constants() as $name => $value) { - $result .= sprintf('if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, $this->exportVariable($value)); - } - return $result; - } - public function globalVariables(Snapshot $snapshot) : string - { - $result = <<<'EOT' -call_user_func( - function () - { - foreach (array_keys($GLOBALS) as $key) { - unset($GLOBALS[$key]); - } - } -); - - -EOT; - foreach ($snapshot->globalVariables() as $name => $value) { - $result .= sprintf('$GLOBALS[%s] = %s;' . \PHP_EOL, $this->exportVariable($name), $this->exportVariable($value)); - } - return $result; - } - public function iniSettings(Snapshot $snapshot) : string - { - $result = ''; - foreach ($snapshot->iniSettings() as $key => $value) { - $result .= sprintf('@ini_set(%s, %s);' . "\n", $this->exportVariable($key), $this->exportVariable($value)); - } - return $result; - } - private function exportVariable($variable) : string - { - if (is_scalar($variable) || null === $variable || is_array($variable) && $this->arrayOnlyContainsScalars($variable)) { - return var_export($variable, \true); - } - return 'unserialize(' . var_export(serialize($variable), \true) . ')'; - } - private function arrayOnlyContainsScalars(array $array) : bool - { - $result = \true; - foreach ($array as $element) { - if (is_array($element)) { - $result = $this->arrayOnlyContainsScalars($element); - } elseif (!is_scalar($element) && null !== $element) { - $result = \false; - } - if ($result === \false) { - break; - } - } - return $result; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -use function array_diff; -use function array_key_exists; -use function array_keys; -use function array_merge; -use function function_exists; -use function get_defined_functions; -use function in_array; -use function is_array; -use ReflectionClass; -use ReflectionProperty; -/** - * Restorer of snapshots of global state. - */ -class Restorer -{ - /** - * Deletes function definitions that are not defined in a snapshot. - * - * @throws RuntimeException when the uopz_delete() function is not available - * - * @see https://github.com/krakjoe/uopz - */ - public function restoreFunctions(Snapshot $snapshot) : void - { - if (!function_exists('PHPUnit\\uopz_delete')) { - throw new RuntimeException('The uopz_delete() function is required for this operation'); - } - $functions = get_defined_functions(); - foreach (array_diff($functions['user'], $snapshot->functions()) as $function) { - uopz_delete($function); - } - } - /** - * Restores all global and super-global variables from a snapshot. - */ - public function restoreGlobalVariables(Snapshot $snapshot) : void - { - $superGlobalArrays = $snapshot->superGlobalArrays(); - foreach ($superGlobalArrays as $superGlobalArray) { - $this->restoreSuperGlobalArray($snapshot, $superGlobalArray); - } - $globalVariables = $snapshot->globalVariables(); - foreach (array_keys($GLOBALS) as $key) { - if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, \true) && !$snapshot->excludeList()->isGlobalVariableExcluded($key)) { - if (array_key_exists($key, $globalVariables)) { - $GLOBALS[$key] = $globalVariables[$key]; - } else { - unset($GLOBALS[$key]); - } - } - } - } - /** - * Restores all static attributes in user-defined classes from this snapshot. - */ - public function restoreStaticAttributes(Snapshot $snapshot) : void - { - $current = new Snapshot($snapshot->excludeList(), \false, \false, \false, \false, \true, \false, \false, \false, \false); - $newClasses = array_diff($current->classes(), $snapshot->classes()); - unset($current); - foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { - foreach ($staticAttributes as $name => $value) { - $reflector = new ReflectionProperty($className, $name); - $reflector->setAccessible(\true); - $reflector->setValue($value); - } - } - foreach ($newClasses as $className) { - $class = new ReflectionClass($className); - $defaults = $class->getDefaultProperties(); - foreach ($class->getProperties() as $attribute) { - if (!$attribute->isStatic()) { - continue; - } - $name = $attribute->getName(); - if ($snapshot->excludeList()->isStaticAttributeExcluded($className, $name)) { - continue; - } - if (!isset($defaults[$name])) { - continue; - } - $attribute->setAccessible(\true); - $attribute->setValue($defaults[$name]); - } - } - } - /** - * Restores a super-global variable array from this snapshot. - */ - private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray) : void - { - $superGlobalVariables = $snapshot->superGlobalVariables(); - if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray]) && isset($superGlobalVariables[$superGlobalArray])) { - $keys = array_keys(array_merge($GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray])); - foreach ($keys as $key) { - if (isset($superGlobalVariables[$superGlobalArray][$key])) { - $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; - } else { - unset($GLOBALS[$superGlobalArray][$key]); - } - } - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -final class RuntimeException extends \RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -use Throwable; -interface Exception extends Throwable -{ -} -sebastian/global-state - -Copyright (c) 2001-2021, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\GlobalState; - -use function in_array; -use function strpos; -use ReflectionClass; -final class ExcludeList -{ - /** - * @var array - */ - private $globalVariables = []; - /** - * @var string[] - */ - private $classes = []; - /** - * @var string[] - */ - private $classNamePrefixes = []; - /** - * @var string[] - */ - private $parentClasses = []; - /** - * @var string[] - */ - private $interfaces = []; - /** - * @var array - */ - private $staticAttributes = []; - public function addGlobalVariable(string $variableName) : void - { - $this->globalVariables[$variableName] = \true; - } - public function addClass(string $className) : void - { - $this->classes[] = $className; - } - public function addSubclassesOf(string $className) : void - { - $this->parentClasses[] = $className; - } - public function addImplementorsOf(string $interfaceName) : void - { - $this->interfaces[] = $interfaceName; - } - public function addClassNamePrefix(string $classNamePrefix) : void - { - $this->classNamePrefixes[] = $classNamePrefix; - } - public function addStaticAttribute(string $className, string $attributeName) : void - { - if (!isset($this->staticAttributes[$className])) { - $this->staticAttributes[$className] = []; - } - $this->staticAttributes[$className][$attributeName] = \true; - } - public function isGlobalVariableExcluded(string $variableName) : bool - { - return isset($this->globalVariables[$variableName]); - } - public function isStaticAttributeExcluded(string $className, string $attributeName) : bool - { - if (in_array($className, $this->classes, \true)) { - return \true; - } - foreach ($this->classNamePrefixes as $prefix) { - if (strpos($className, $prefix) === 0) { - return \true; - } - } - $class = new ReflectionClass($className); - foreach ($this->parentClasses as $type) { - if ($class->isSubclassOf($type)) { - return \true; - } - } - foreach ($this->interfaces as $type) { - if ($class->implementsInterface($type)) { - return \true; - } - } - if (isset($this->staticAttributes[$className][$attributeName])) { - return \true; - } - return \false; - } -} -property = $property; - } - /** - * Matches a property by its name. - * - * {@inheritdoc} - */ - public function matches($object, $property) - { - return $property == $this->property; - } -} -class = $class; - $this->property = $property; - } - /** - * Matches a specific property of a specific class. - * - * {@inheritdoc} - */ - public function matches($object, $property) - { - return $object instanceof $this->class && $property == $this->property; - } -} -propertyType = $propertyType; - } - /** - * {@inheritdoc} - */ - public function matches($object, $property) - { - try { - $reflectionProperty = ReflectionHelper::getProperty($object, $property); - } catch (ReflectionException $exception) { - return \false; - } - $reflectionProperty->setAccessible(\true); - // Uninitialized properties (for PHP >7.4) - if (\method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) { - // null instanceof $this->propertyType - return \false; - } - return $reflectionProperty->getValue($object) instanceof $this->propertyType; - } -} -copy($value); - } -} -callback = $callable; - } - /** - * Replaces the object property by the result of the callback called with the object property. - * - * {@inheritdoc} - */ - public function apply($object, $property, $objectCopier) - { - $reflectionProperty = ReflectionHelper::getProperty($object, $property); - $reflectionProperty->setAccessible(\true); - $value = \call_user_func($this->callback, $reflectionProperty->getValue($object)); - $reflectionProperty->setValue($object, $value); - } -} -setAccessible(\true); - $reflectionProperty->setValue($object, null); - } -} -setAccessible(\true); - $oldCollection = $reflectionProperty->getValue($object); - $newCollection = $oldCollection->map(function ($item) use($objectCopier) { - return $objectCopier($item); - }); - $reflectionProperty->setValue($object, $newCollection); - } -} -setAccessible(\true); - $reflectionProperty->setValue($object, new ArrayCollection()); - } -} -__load(); - } -} -type = $type; - } - /** - * @param mixed $element - * - * @return boolean - */ - public function matches($element) - { - return \is_object($element) ? \is_a($element, $this->type) : \gettype($element) === $this->type; - } -} -callback = $callable; - } - /** - * {@inheritdoc} - */ - public function apply($element) - { - return \call_user_func($this->callback, $element); - } -} -copier = $copier; - } - /** - * {@inheritdoc} - */ - public function apply($arrayObject) - { - $clone = clone $arrayObject; - foreach ($arrayObject->getArrayCopy() as $k => $v) { - $clone->offsetSet($k, $this->copier->copy($v)); - } - return $clone; - } -} -copier = $copier; - } - /** - * {@inheritdoc} - */ - public function apply($element) - { - $newElement = clone $element; - $copy = $this->createCopyClosure(); - return $copy($newElement); - } - private function createCopyClosure() - { - $copier = $this->copier; - $copy = function (SplDoublyLinkedList $list) use($copier) { - // Replace each element in the list with a deep copy of itself - for ($i = 1; $i <= $list->count(); $i++) { - $copy = $copier->recursiveCopy($list->shift()); - $list->push($copy); - } - return $list; - }; - return Closure::bind($copy, null, DeepCopy::class); - } -} - $propertyValue) { - $copy->{$propertyName} = $propertyValue; - } - return $copy; - } -} -getProperties() does not return private properties from ancestor classes. - * - * @author muratyaman@gmail.com - * @see http://php.net/manual/en/reflectionclass.getproperties.php - * - * @param ReflectionClass $ref - * - * @return ReflectionProperty[] - */ - public static function getProperties(ReflectionClass $ref) - { - $props = $ref->getProperties(); - $propsArr = array(); - foreach ($props as $prop) { - $propertyName = $prop->getName(); - $propsArr[$propertyName] = $prop; - } - if ($parentClass = $ref->getParentClass()) { - $parentPropsArr = self::getProperties($parentClass); - foreach ($propsArr as $key => $property) { - $parentPropsArr[$key] = $property; - } - return $parentPropsArr; - } - return $propsArr; - } - /** - * Retrieves property by name from object and all its ancestors. - * - * @param object|string $object - * @param string $name - * - * @throws PropertyException - * @throws ReflectionException - * - * @return ReflectionProperty - */ - public static function getProperty($object, $name) - { - $reflection = \is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); - if ($reflection->hasProperty($name)) { - return $reflection->getProperty($name); - } - if ($parentClass = $reflection->getParentClass()) { - return self::getProperty($parentClass->getName(), $name); - } - throw new PropertyException(\sprintf('The class "%s" doesn\'t have a property with the given name: "%s".', \is_object($object) ? \get_class($object) : $object, $name)); - } -} - Filter, 'matcher' => Matcher] pairs. - */ - private $filters = []; - /** - * Type Filters to apply. - * - * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs. - */ - private $typeFilters = []; - /** - * @var bool - */ - private $skipUncloneable = \false; - /** - * @var bool - */ - private $useCloneMethod; - /** - * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used - * instead of the regular deep cloning. - */ - public function __construct($useCloneMethod = \false) - { - $this->useCloneMethod = $useCloneMethod; - $this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class)); - $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); - $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); - } - /** - * If enabled, will not throw an exception when coming across an uncloneable property. - * - * @param $skipUncloneable - * - * @return $this - */ - public function skipUncloneable($skipUncloneable = \true) - { - $this->skipUncloneable = $skipUncloneable; - return $this; - } - /** - * Deep copies the given object. - * - * @param mixed $object - * - * @return mixed - */ - public function copy($object) - { - $this->hashMap = []; - return $this->recursiveCopy($object); - } - public function addFilter(Filter $filter, Matcher $matcher) - { - $this->filters[] = ['matcher' => $matcher, 'filter' => $filter]; - } - public function prependFilter(Filter $filter, Matcher $matcher) - { - \array_unshift($this->filters, ['matcher' => $matcher, 'filter' => $filter]); - } - public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) - { - $this->typeFilters[] = ['matcher' => $matcher, 'filter' => $filter]; - } - private function recursiveCopy($var) - { - // Matches Type Filter - if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { - return $filter->apply($var); - } - // Resource - if (\is_resource($var)) { - return $var; - } - // Array - if (\is_array($var)) { - return $this->copyArray($var); - } - // Scalar - if (!\is_object($var)) { - return $var; - } - // Object - return $this->copyObject($var); - } - /** - * Copy an array - * @param array $array - * @return array - */ - private function copyArray(array $array) - { - foreach ($array as $key => $value) { - $array[$key] = $this->recursiveCopy($value); - } - return $array; - } - /** - * Copies an object. - * - * @param object $object - * - * @throws CloneException - * - * @return object - */ - private function copyObject($object) - { - $objectHash = \spl_object_hash($object); - if (isset($this->hashMap[$objectHash])) { - return $this->hashMap[$objectHash]; - } - $reflectedObject = new ReflectionObject($object); - $isCloneable = $reflectedObject->isCloneable(); - if (\false === $isCloneable) { - if ($this->skipUncloneable) { - $this->hashMap[$objectHash] = $object; - return $object; - } - throw new CloneException(\sprintf('The class "%s" is not cloneable.', $reflectedObject->getName())); - } - $newObject = clone $object; - $this->hashMap[$objectHash] = $newObject; - if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { - return $newObject; - } - if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { - return $newObject; - } - foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { - $this->copyObjectProperty($newObject, $property); - } - return $newObject; - } - private function copyObjectProperty($object, ReflectionProperty $property) - { - // Ignore static properties - if ($property->isStatic()) { - return; - } - // Apply the filters - foreach ($this->filters as $item) { - /** @var Matcher $matcher */ - $matcher = $item['matcher']; - /** @var Filter $filter */ - $filter = $item['filter']; - if ($matcher->matches($object, $property->getName())) { - $filter->apply($object, $property->getName(), function ($object) { - return $this->recursiveCopy($object); - }); - // If a filter matches, we stop processing this property - return; - } - } - $property->setAccessible(\true); - // Ignore uninitialized properties (for PHP >7.4) - if (\method_exists($property, 'isInitialized') && !$property->isInitialized($object)) { - return; - } - $propertyValue = $property->getValue($object); - // Copy the property - $property->setValue($object, $this->recursiveCopy($propertyValue)); - } - /** - * Returns first filter that matches variable, `null` if no such filter found. - * - * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and - * 'matcher' with value of type {@see TypeMatcher} - * @param mixed $var - * - * @return TypeFilter|null - */ - private function getFirstMatchedTypeFilter(array $filterRecords, $var) - { - $matched = $this->first($filterRecords, function (array $record) use($var) { - /* @var TypeMatcher $matcher */ - $matcher = $record['matcher']; - return $matcher->matches($var); - }); - return isset($matched) ? $matched['filter'] : null; - } - /** - * Returns first element that matches predicate, `null` if no such element found. - * - * @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs. - * @param callable $predicate Predicate arguments are: element. - * - * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher' - * with value of type {@see TypeMatcher} or `null`. - */ - private function first(array $elements, callable $predicate) - { - foreach ($elements as $element) { - if (\call_user_func($predicate, $element)) { - return $element; - } - } - return null; - } -} -The MIT License (MIT) - -Copyright (c) 2013 My C-Sense - -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. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class UnknownType extends Type -{ - public function isAssignable(Type $other) : bool - { - return \true; - } - public function name() : string - { - return 'unknown type'; - } - public function asString() : string - { - return ''; - } - /** - * @deprecated - * - * @codeCoverageIgnore - */ - public function getReturnTypeDeclaration() : string - { - return ''; - } - public function allowsNull() : bool - { - return \true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class GenericObjectType extends Type -{ - /** - * @var bool - */ - private $allowsNull; - public function __construct(bool $nullable) - { - $this->allowsNull = $nullable; - } - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if (!$other instanceof ObjectType) { - return \false; - } - return \true; - } - public function name() : string - { - return 'object'; - } - public function allowsNull() : bool - { - return $this->allowsNull; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function count; -use function implode; -use function sort; -final class UnionType extends Type -{ - /** - * @psalm-var list - */ - private $types; - /** - * @throws RuntimeException - */ - public function __construct(Type ...$types) - { - $this->ensureMinimumOfTwoTypes(...$types); - $this->ensureOnlyValidTypes(...$types); - $this->types = $types; - } - public function isAssignable(Type $other) : bool - { - foreach ($this->types as $type) { - if ($type->isAssignable($other)) { - return \true; - } - } - return \false; - } - public function asString() : string - { - return $this->name(); - } - /** - * @deprecated - * - * @codeCoverageIgnore - */ - public function getReturnTypeDeclaration() : string - { - return ': ' . $this->name(); - } - public function name() : string - { - $types = []; - foreach ($this->types as $type) { - $types[] = $type->name(); - } - sort($types); - return implode('|', $types); - } - public function allowsNull() : bool - { - foreach ($this->types as $type) { - if ($type instanceof NullType) { - return \true; - } - } - return \false; - } - /** - * @throws RuntimeException - */ - private function ensureMinimumOfTwoTypes(Type ...$types) : void - { - if (count($types) < 2) { - throw new RuntimeException('A union type must be composed of at least two types'); - } - } - /** - * @throws RuntimeException - */ - private function ensureOnlyValidTypes(Type ...$types) : void - { - foreach ($types as $type) { - if ($type instanceof UnknownType) { - throw new RuntimeException('A union type must not be composed of an unknown type'); - } - if ($type instanceof VoidType) { - throw new RuntimeException('A union type must not be composed of a void type'); - } - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class MixedType extends Type -{ - public function isAssignable(Type $other) : bool - { - return !$other instanceof VoidType; - } - public function asString() : string - { - return 'mixed'; - } - public function name() : string - { - return 'mixed'; - } - public function allowsNull() : bool - { - return \true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function assert; -use function class_exists; -use function count; -use function explode; -use function function_exists; -use function is_array; -use function is_object; -use function is_string; -use function strpos; -use Closure; -use ReflectionClass; -use ReflectionException; -use ReflectionObject; -final class CallableType extends Type -{ - /** - * @var bool - */ - private $allowsNull; - public function __construct(bool $nullable) - { - $this->allowsNull = $nullable; - } - /** - * @throws RuntimeException - */ - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if ($other instanceof self) { - return \true; - } - if ($other instanceof ObjectType) { - if ($this->isClosure($other)) { - return \true; - } - if ($this->hasInvokeMethod($other)) { - return \true; - } - } - if ($other instanceof SimpleType) { - if ($this->isFunction($other)) { - return \true; - } - if ($this->isClassCallback($other)) { - return \true; - } - if ($this->isObjectCallback($other)) { - return \true; - } - } - return \false; - } - public function name() : string - { - return 'callable'; - } - public function allowsNull() : bool - { - return $this->allowsNull; - } - private function isClosure(ObjectType $type) : bool - { - return !$type->className()->isNamespaced() && $type->className()->simpleName() === Closure::class; - } - /** - * @throws RuntimeException - */ - private function hasInvokeMethod(ObjectType $type) : bool - { - $className = $type->className()->qualifiedName(); - assert(class_exists($className)); - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - // @codeCoverageIgnoreEnd - } - if ($class->hasMethod('__invoke')) { - return \true; - } - return \false; - } - private function isFunction(SimpleType $type) : bool - { - if (!is_string($type->value())) { - return \false; - } - return function_exists($type->value()); - } - private function isObjectCallback(SimpleType $type) : bool - { - if (!is_array($type->value())) { - return \false; - } - if (count($type->value()) !== 2) { - return \false; - } - if (!is_object($type->value()[0]) || !is_string($type->value()[1])) { - return \false; - } - [$object, $methodName] = $type->value(); - return (new ReflectionObject($object))->hasMethod($methodName); - } - private function isClassCallback(SimpleType $type) : bool - { - if (!is_string($type->value()) && !is_array($type->value())) { - return \false; - } - if (is_string($type->value())) { - if (strpos($type->value(), '::') === \false) { - return \false; - } - [$className, $methodName] = explode('::', $type->value()); - } - if (is_array($type->value())) { - if (count($type->value()) !== 2) { - return \false; - } - if (!is_string($type->value()[0]) || !is_string($type->value()[1])) { - return \false; - } - [$className, $methodName] = $type->value(); - } - assert(isset($className) && is_string($className) && class_exists($className)); - assert(isset($methodName) && is_string($methodName)); - try { - $class = new ReflectionClass($className); - if ($class->hasMethod($methodName)) { - $method = $class->getMethod($methodName); - return $method->isPublic() && $method->isStatic(); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - // @codeCoverageIgnoreEnd - } - return \false; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class VoidType extends Type -{ - public function isAssignable(Type $other) : bool - { - return $other instanceof self; - } - public function name() : string - { - return 'void'; - } - public function allowsNull() : bool - { - return \false; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class NullType extends Type -{ - public function isAssignable(Type $other) : bool - { - return !$other instanceof VoidType; - } - public function name() : string - { - return 'null'; - } - public function asString() : string - { - return 'null'; - } - /** - * @deprecated - * - * @codeCoverageIgnore - */ - public function getReturnTypeDeclaration() : string - { - return ''; - } - public function allowsNull() : bool - { - return \true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function is_subclass_of; -use function strcasecmp; -final class ObjectType extends Type -{ - /** - * @var TypeName - */ - private $className; - /** - * @var bool - */ - private $allowsNull; - public function __construct(TypeName $className, bool $allowsNull) - { - $this->className = $className; - $this->allowsNull = $allowsNull; - } - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if ($other instanceof self) { - if (0 === strcasecmp($this->className->qualifiedName(), $other->className->qualifiedName())) { - return \true; - } - if (is_subclass_of($other->className->qualifiedName(), $this->className->qualifiedName(), \true)) { - return \true; - } - } - return \false; - } - public function name() : string - { - return $this->className->qualifiedName(); - } - public function allowsNull() : bool - { - return $this->allowsNull; - } - public function className() : TypeName - { - return $this->className; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function strtolower; -final class SimpleType extends Type -{ - /** - * @var string - */ - private $name; - /** - * @var bool - */ - private $allowsNull; - /** - * @var mixed - */ - private $value; - public function __construct(string $name, bool $nullable, $value = null) - { - $this->name = $this->normalize($name); - $this->allowsNull = $nullable; - $this->value = $value; - } - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if ($this->name === 'bool' && $other->name() === 'false') { - return \true; - } - if ($other instanceof self) { - return $this->name === $other->name; - } - return \false; - } - public function name() : string - { - return $this->name; - } - public function allowsNull() : bool - { - return $this->allowsNull; - } - public function value() - { - return $this->value; - } - private function normalize(string $name) : string - { - $name = strtolower($name); - switch ($name) { - case 'boolean': - return 'bool'; - case 'real': - case 'double': - return 'float'; - case 'integer': - return 'int'; - case '[]': - return 'array'; - default: - return $name; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function array_pop; -use function explode; -use function implode; -use function substr; -use ReflectionClass; -final class TypeName -{ - /** - * @var ?string - */ - private $namespaceName; - /** - * @var string - */ - private $simpleName; - public static function fromQualifiedName(string $fullClassName) : self - { - if ($fullClassName[0] === '\\') { - $fullClassName = substr($fullClassName, 1); - } - $classNameParts = explode('\\', $fullClassName); - $simpleName = array_pop($classNameParts); - $namespaceName = implode('\\', $classNameParts); - return new self($namespaceName, $simpleName); - } - public static function fromReflection(ReflectionClass $type) : self - { - return new self($type->getNamespaceName(), $type->getShortName()); - } - public function __construct(?string $namespaceName, string $simpleName) - { - if ($namespaceName === '') { - $namespaceName = null; - } - $this->namespaceName = $namespaceName; - $this->simpleName = $simpleName; - } - public function namespaceName() : ?string - { - return $this->namespaceName; - } - public function simpleName() : string - { - return $this->simpleName; - } - public function qualifiedName() : string - { - return $this->namespaceName === null ? $this->simpleName : $this->namespaceName . '\\' . $this->simpleName; - } - /** - * @deprecated Use namespaceName() instead - * - * @codeCoverageIgnore - */ - public function getNamespaceName() : ?string - { - return $this->namespaceName(); - } - /** - * @deprecated Use simpleName() instead - * - * @codeCoverageIgnore - */ - public function getSimpleName() : string - { - return $this->simpleName(); - } - /** - * @deprecated Use qualifiedName() instead - * - * @codeCoverageIgnore - */ - public function getQualifiedName() : string - { - return $this->qualifiedName(); - } - public function isNamespaced() : bool - { - return $this->namespaceName !== null; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class StaticType extends Type -{ - /** - * @var TypeName - */ - private $className; - /** - * @var bool - */ - private $allowsNull; - public function __construct(TypeName $className, bool $allowsNull) - { - $this->className = $className; - $this->allowsNull = $allowsNull; - } - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if (!$other instanceof ObjectType) { - return \false; - } - if (0 === \strcasecmp($this->className->qualifiedName(), $other->className()->qualifiedName())) { - return \true; - } - if (\is_subclass_of($other->className()->qualifiedName(), $this->className->qualifiedName(), \true)) { - return \true; - } - return \false; - } - public function name() : string - { - return 'static'; - } - public function allowsNull() : bool - { - return $this->allowsNull; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class FalseType extends Type -{ - public function isAssignable(Type $other) : bool - { - if ($other instanceof self) { - return \true; - } - return $other instanceof SimpleType && $other->name() === 'bool' && $other->value() === \false; - } - public function name() : string - { - return 'false'; - } - public function allowsNull() : bool - { - return \false; - } - /** - * @deprecated - * - * @codeCoverageIgnore - * - * @throws LogicException - */ - public function getReturnTypeDeclaration() : string - { - throw new LogicException(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function assert; -use function sprintf; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionType; -use ReflectionUnionType; -final class ReflectionMapper -{ - public function fromMethodReturnType(ReflectionMethod $method) : Type - { - if (!$this->reflectionMethodHasReturnType($method)) { - return new UnknownType(); - } - $returnType = $this->reflectionMethodGetReturnType($method); - assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType); - if ($returnType instanceof ReflectionNamedType) { - if ($returnType->getName() === 'self') { - return ObjectType::fromName($method->getDeclaringClass()->getName(), $returnType->allowsNull()); - } - if ($returnType->getName() === 'static') { - return new StaticType(TypeName::fromReflection($method->getDeclaringClass()), $returnType->allowsNull()); - } - if ($returnType->getName() === 'mixed') { - return new MixedType(); - } - if ($returnType->getName() === 'parent') { - $parentClass = $method->getDeclaringClass()->getParentClass(); - // @codeCoverageIgnoreStart - if ($parentClass === \false) { - throw new RuntimeException(sprintf('%s::%s() has a "parent" return type declaration but %s does not have a parent class', $method->getDeclaringClass()->getName(), $method->getName(), $method->getDeclaringClass()->getName())); - } - // @codeCoverageIgnoreEnd - return ObjectType::fromName($parentClass->getName(), $returnType->allowsNull()); - } - return Type::fromName($returnType->getName(), $returnType->allowsNull()); - } - assert($returnType instanceof ReflectionUnionType); - $types = []; - foreach ($returnType->getTypes() as $type) { - assert($type instanceof ReflectionNamedType); - if ($type->getName() === 'self') { - $types[] = ObjectType::fromName($method->getDeclaringClass()->getName(), \false); - } else { - $types[] = Type::fromName($type->getName(), \false); - } - } - return new UnionType(...$types); - } - private function reflectionMethodHasReturnType(ReflectionMethod $method) : bool - { - if ($method->hasReturnType()) { - return \true; - } - if (!\method_exists($method, 'hasTentativeReturnType')) { - return \false; - } - return $method->hasTentativeReturnType(); - } - private function reflectionMethodGetReturnType(ReflectionMethod $method) : ?ReflectionType - { - if ($method->hasReturnType()) { - return $method->getReturnType(); - } - if (!\method_exists($method, 'getTentativeReturnType')) { - return null; - } - return $method->getTentativeReturnType(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function assert; -use function class_exists; -use function is_iterable; -use ReflectionClass; -use ReflectionException; -final class IterableType extends Type -{ - /** - * @var bool - */ - private $allowsNull; - public function __construct(bool $nullable) - { - $this->allowsNull = $nullable; - } - /** - * @throws RuntimeException - */ - public function isAssignable(Type $other) : bool - { - if ($this->allowsNull && $other instanceof NullType) { - return \true; - } - if ($other instanceof self) { - return \true; - } - if ($other instanceof SimpleType) { - return is_iterable($other->value()); - } - if ($other instanceof ObjectType) { - $className = $other->className()->qualifiedName(); - assert(class_exists($className)); - try { - return (new ReflectionClass($className))->isIterable(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - // @codeCoverageIgnoreEnd - } - } - return \false; - } - public function name() : string - { - return 'iterable'; - } - public function allowsNull() : bool - { - return $this->allowsNull; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use function get_class; -use function gettype; -use function strtolower; -abstract class Type -{ - public static function fromValue($value, bool $allowsNull) : self - { - if ($value === \false) { - return new FalseType(); - } - $typeName = gettype($value); - if ($typeName === 'object') { - return new ObjectType(TypeName::fromQualifiedName(get_class($value)), $allowsNull); - } - $type = self::fromName($typeName, $allowsNull); - if ($type instanceof SimpleType) { - $type = new SimpleType($typeName, $allowsNull, $value); - } - return $type; - } - public static function fromName(string $typeName, bool $allowsNull) : self - { - switch (strtolower($typeName)) { - case 'callable': - return new CallableType($allowsNull); - case 'false': - return new FalseType(); - case 'iterable': - return new IterableType($allowsNull); - case 'null': - return new NullType(); - case 'object': - return new GenericObjectType($allowsNull); - case 'unknown type': - return new UnknownType(); - case 'void': - return new VoidType(); - case 'array': - case 'bool': - case 'boolean': - case 'double': - case 'float': - case 'int': - case 'integer': - case 'real': - case 'resource': - case 'resource (closed)': - case 'string': - return new SimpleType($typeName, $allowsNull); - default: - return new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull); - } - } - public function asString() : string - { - return ($this->allowsNull() ? '?' : '') . $this->name(); - } - /** - * @deprecated - * - * @codeCoverageIgnore - */ - public function getReturnTypeDeclaration() : string - { - return ': ' . $this->asString(); - } - public abstract function isAssignable(Type $other) : bool; - public abstract function name() : string; - public abstract function allowsNull() : bool; -} -sebastian/type - -Copyright (c) 2019-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class RuntimeException extends \RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -final class LogicException extends \LogicException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Type; - -use Throwable; -interface Exception extends Throwable -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann; - -final class Version -{ - /** - * @var string - */ - private $path; - /** - * @var string - */ - private $release; - /** - * @var string - */ - private $version; - public function __construct(string $release, string $path) - { - $this->release = $release; - $this->path = $path; - } - public function getVersion() : string - { - if ($this->version === null) { - if (\substr_count($this->release, '.') + 1 === 3) { - $this->version = $this->release; - } else { - $this->version = $this->release . '-dev'; - } - $git = $this->getGitInformation($this->path); - if ($git) { - if (\substr_count($this->release, '.') + 1 === 3) { - $this->version = $git; - } else { - $git = \explode('-', $git); - $this->version = $this->release . '-' . \end($git); - } - } - } - return $this->version; - } - /** - * @return bool|string - */ - private function getGitInformation(string $path) - { - if (!\is_dir($path . \DIRECTORY_SEPARATOR . '.git')) { - return \false; - } - $process = \proc_open('git describe --tags', [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $path); - if (!\is_resource($process)) { - return \false; - } - $result = \trim(\stream_get_contents($pipes[1])); - \fclose($pipes[1]); - \fclose($pipes[2]); - $returnCode = \proc_close($process); - if ($returnCode !== 0) { - return \false; - } - return $result; - } -} -Version - -Copyright (c) 2013-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - - - - This Schema file defines the rules by which the XML configuration file of PHPUnit 8.5 may be structured. - - - - - - Root Element - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The main type specifying the document structure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This Schema file defines the rules by which the XML configuration file of PHPUnit 9.2 may be structured. - - - - - - Root Element - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The main type specifying the document structure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 82 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 83 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 84 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 85 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 86 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 87 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 88 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 89 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 90 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 91 => function ($stackPos) { + $this->semValue = new Name(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 92 => function ($stackPos) { + $this->semValue = new Expr\Variable(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 93 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 94 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 95 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 96 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 97 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (3 - 2)], null, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, 98 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, 99 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, 100 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (3 - 2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 101 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 102 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 103 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 104 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, 105 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, 106 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->semStack[$stackPos - (7 - 2)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 107 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 108 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 109 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 110 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 111 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 112 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 113 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 114 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); + }, 115 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); + }, 116 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); + }, 117 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); + }, 118 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, 119 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue->type = $this->semStack[$stackPos - (2 - 1)]; + }, 120 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 121 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 122 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 123 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 124 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 125 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 126 => function ($stackPos) { + if (\is_array($this->semStack[$stackPos - (2 - 2)])) { + $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); + } else { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + } + }, 127 => function ($stackPos) { + $this->semValue = array(); + }, 128 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; + } + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 129 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 130 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 131 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 132 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 133 => function ($stackPos) { + if ($this->semStack[$stackPos - (3 - 2)]) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)]; + $stmts = $this->semValue; + if (!empty($attrs['comments'])) { + $stmts[0]->setAttribute('comments', \array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); + } + } else { + $startAttributes = $this->startAttributeStack[$stackPos - (3 - 1)]; + if (isset($startAttributes['comments'])) { + $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); + } else { + $this->semValue = null; + } + if (null === $this->semValue) { + $this->semValue = array(); + } + } + }, 134 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos - (5 - 2)], ['stmts' => \is_array($this->semStack[$stackPos - (5 - 3)]) ? $this->semStack[$stackPos - (5 - 3)] : array($this->semStack[$stackPos - (5 - 3)]), 'elseifs' => $this->semStack[$stackPos - (5 - 4)], 'else' => $this->semStack[$stackPos - (5 - 5)]], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 135 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos - (8 - 2)], ['stmts' => $this->semStack[$stackPos - (8 - 4)], 'elseifs' => $this->semStack[$stackPos - (8 - 5)], 'else' => $this->semStack[$stackPos - (8 - 6)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 136 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 137 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos - (5 - 4)], \is_array($this->semStack[$stackPos - (5 - 2)]) ? $this->semStack[$stackPos - (5 - 2)] : array($this->semStack[$stackPos - (5 - 2)]), $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 138 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos - (9 - 3)], 'cond' => $this->semStack[$stackPos - (9 - 5)], 'loop' => $this->semStack[$stackPos - (9 - 7)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 139 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 140 => function ($stackPos) { + $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 141 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 142 => function ($stackPos) { + $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 143 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 144 => function ($stackPos) { + $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 145 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 146 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 147 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 148 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 149 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 150 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 151 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 152 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos - (5 - 3)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 153 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos - (7 - 5)][1], 'stmts' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 154 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (9 - 3)], $this->semStack[$stackPos - (9 - 7)][0], ['keyVar' => $this->semStack[$stackPos - (9 - 5)], 'byRef' => $this->semStack[$stackPos - (9 - 7)][1], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 155 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 156 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 5)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + $this->checkTryCatch($this->semValue); + }, 157 => function ($stackPos) { + $this->semValue = new Stmt\Throw_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 158 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 159 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 160 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 161 => function ($stackPos) { + $this->semValue = array(); + /* means: no statement */ + }, 162 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 163 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos - (1 - 1)]; + if (isset($startAttributes['comments'])) { + $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); + } else { + $this->semValue = null; + } + if ($this->semValue === null) { + $this->semValue = array(); + } + /* means: no statement */ + }, 164 => function ($stackPos) { + $this->semValue = array(); + }, 165 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 166 => function ($stackPos) { + $this->semValue = new Stmt\Catch_(array($this->semStack[$stackPos - (8 - 3)]), $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 7)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 167 => function ($stackPos) { + $this->semValue = null; + }, 168 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 169 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 170 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 171 => function ($stackPos) { + $this->semValue = \false; + }, 172 => function ($stackPos) { + $this->semValue = \true; + }, 173 => function ($stackPos) { + $this->semValue = \false; + }, 174 => function ($stackPos) { + $this->semValue = \true; + }, 175 => function ($stackPos) { + $this->semValue = \false; + }, 176 => function ($stackPos) { + $this->semValue = \true; + }, 177 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (10 - 3)], ['byRef' => $this->semStack[$stackPos - (10 - 2)], 'params' => $this->semStack[$stackPos - (10 - 5)], 'returnType' => $this->semStack[$stackPos - (10 - 7)], 'stmts' => $this->semStack[$stackPos - (10 - 9)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + }, 178 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos - (7 - 2)], ['type' => $this->semStack[$stackPos - (7 - 1)], 'extends' => $this->semStack[$stackPos - (7 - 3)], 'implements' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos - (7 - 2)); + }, 179 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos - (6 - 2)], ['extends' => $this->semStack[$stackPos - (6 - 3)], 'stmts' => $this->semStack[$stackPos - (6 - 5)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos - (6 - 2)); + }, 180 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos - (5 - 2)], ['stmts' => $this->semStack[$stackPos - (5 - 4)]], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 181 => function ($stackPos) { + $this->semValue = 0; + }, 182 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, 183 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, 184 => function ($stackPos) { + $this->semValue = null; + }, 185 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 186 => function ($stackPos) { + $this->semValue = array(); + }, 187 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 188 => function ($stackPos) { + $this->semValue = array(); + }, 189 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 190 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 191 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 192 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 193 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 194 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 195 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 196 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 197 => function ($stackPos) { + $this->semValue = null; + }, 198 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 199 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 200 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 201 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 202 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 203 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 3)]; + }, 204 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 205 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (5 - 3)]; + }, 206 => function ($stackPos) { + $this->semValue = array(); + }, 207 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 208 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 209 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 210 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 211 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 212 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 213 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 214 => function ($stackPos) { + $this->semValue = array(); + }, 215 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 216 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (3 - 2)], \is_array($this->semStack[$stackPos - (3 - 3)]) ? $this->semStack[$stackPos - (3 - 3)] : array($this->semStack[$stackPos - (3 - 3)]), $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 217 => function ($stackPos) { + $this->semValue = array(); + }, 218 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 219 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 220 => function ($stackPos) { + $this->semValue = null; + }, 221 => function ($stackPos) { + $this->semValue = new Stmt\Else_(\is_array($this->semStack[$stackPos - (2 - 2)]) ? $this->semStack[$stackPos - (2 - 2)] : array($this->semStack[$stackPos - (2 - 2)]), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 222 => function ($stackPos) { + $this->semValue = null; + }, 223 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 224 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); + }, 225 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (2 - 2)], \true); + }, 226 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); + }, 227 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 228 => function ($stackPos) { + $this->semValue = array(); + }, 229 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 230 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 231 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos - (4 - 4)], null, $this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + $this->checkParam($this->semValue); + }, 232 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 6)], $this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 3)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + $this->checkParam($this->semValue); + }, 233 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 234 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 235 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 236 => function ($stackPos) { + $this->semValue = null; + }, 237 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 238 => function ($stackPos) { + $this->semValue = null; + }, 239 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 240 => function ($stackPos) { + $this->semValue = array(); + }, 241 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 242 => function ($stackPos) { + $this->semValue = array(new Node\Arg($this->semStack[$stackPos - (3 - 2)], \false, \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes)); + }, 243 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 244 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 245 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (1 - 1)], \false, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 246 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \true, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 247 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \false, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 248 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 249 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 250 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 251 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 252 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 253 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 254 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 255 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 256 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 257 => function ($stackPos) { + if ($this->semStack[$stackPos - (2 - 2)] !== null) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + } + }, 258 => function ($stackPos) { + $this->semValue = array(); + }, 259 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; + } + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 260 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->checkProperty($this->semValue, $stackPos - (3 - 1)); + }, 261 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos - (3 - 2)], 0, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 262 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos - (9 - 4)], ['type' => $this->semStack[$stackPos - (9 - 1)], 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 6)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos - (9 - 1)); + }, 263 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 264 => function ($stackPos) { + $this->semValue = array(); + }, 265 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 266 => function ($stackPos) { + $this->semValue = array(); + }, 267 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 268 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 269 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (5 - 1)][0], $this->semStack[$stackPos - (5 - 1)][1], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 270 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], null, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 271 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 272 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 273 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 274 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 275 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos - (1 - 1)]); + }, 276 => function ($stackPos) { + $this->semValue = null; + }, 277 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 278 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 279 => function ($stackPos) { + $this->semValue = 0; + }, 280 => function ($stackPos) { + $this->semValue = 0; + }, 281 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 282 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 283 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); + $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; + }, 284 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, 285 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, 286 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, 287 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, 288 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, 289 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, 290 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 291 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 292 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 293 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 294 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 295 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 296 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 297 => function ($stackPos) { + $this->semValue = array(); + }, 298 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 299 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 300 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 301 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 302 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 303 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 304 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 305 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 306 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 307 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 308 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 309 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 310 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 311 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 312 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 313 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 314 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 315 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 316 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 317 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 318 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 319 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 320 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 321 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 322 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 323 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 324 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 325 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 326 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 327 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 328 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 329 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 330 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 331 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 332 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 333 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 334 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 335 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 336 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 337 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 338 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 339 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 340 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 341 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 342 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 343 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 344 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 345 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 346 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 347 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 348 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 349 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 350 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 351 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 352 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 353 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 354 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 355 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 356 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 357 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 358 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 359 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 360 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 361 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 362 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 363 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 364 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 365 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 366 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 367 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 368 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos - (2 - 1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos - (2 - 2)], $attrs); + }, 369 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 370 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 371 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 372 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 373 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 374 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; + $attrs['kind'] = \strtolower($this->semStack[$stackPos - (2 - 1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos - (2 - 2)], $attrs); + }, 375 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 376 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 377 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 378 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 379 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 380 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 381 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 382 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 383 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (10 - 2)], 'params' => $this->semStack[$stackPos - (10 - 4)], 'uses' => $this->semStack[$stackPos - (10 - 6)], 'returnType' => $this->semStack[$stackPos - (10 - 7)], 'stmts' => $this->semStack[$stackPos - (10 - 9)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + }, 384 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (11 - 3)], 'params' => $this->semStack[$stackPos - (11 - 5)], 'uses' => $this->semStack[$stackPos - (11 - 7)], 'returnType' => $this->semStack[$stackPos - (11 - 8)], 'stmts' => $this->semStack[$stackPos - (11 - 10)]], $this->startAttributeStack[$stackPos - (11 - 1)] + $this->endAttributes); + }, 385 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 386 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 387 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (2 - 2)], null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 388 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 389 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes; + $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $attrs); + }, 390 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; + $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $attrs); + }, 391 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 392 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch(Scalar\String_::fromString($this->semStack[$stackPos - (4 - 1)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes), $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 393 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 394 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 395 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos - (7 - 3)], 'implements' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes), $this->semStack[$stackPos - (7 - 2)]); + $this->checkClass($this->semValue[0], -1); + }, 396 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 397 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 398 => function ($stackPos) { + $this->semValue = array(); + }, 399 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 3)]; + }, 400 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 401 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 402 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos - (2 - 2)], $this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 403 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 404 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 405 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 406 => function ($stackPos) { + $this->semValue = $this->fixupPhp5StaticPropCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 407 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 408 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 409 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 410 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 411 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 412 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 413 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 414 => function ($stackPos) { + $this->semValue = new Name\Relative(\substr($this->semStack[$stackPos - (1 - 1)], 10), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 415 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 416 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 417 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 418 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 419 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 420 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 421 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 422 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 423 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 424 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 425 => function ($stackPos) { + $this->semValue = null; + }, 426 => function ($stackPos) { + $this->semValue = null; + }, 427 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 428 => function ($stackPos) { + $this->semValue = array(); + }, 429 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos - (1 - 1)], '`', \false), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); + }, 430 => function ($stackPos) { + foreach ($this->semStack[$stackPos - (1 - 1)] as $s) { + if ($s instanceof Node\Scalar\EncapsedStringPart) { + $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', \false); + } + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 431 => function ($stackPos) { + $this->semValue = array(); + }, 432 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 433 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes, \true); + }, 434 => function ($stackPos) { + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 435 => function ($stackPos) { + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes, \false); + }, 436 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 437 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 438 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 439 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 440 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 441 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 442 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 443 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 444 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \false); + }, 445 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (2 - 1)], '', $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (2 - 2)] + $this->endAttributeStack[$stackPos - (2 - 2)], \false); + }, 446 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 447 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 448 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 449 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 450 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 451 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 452 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 453 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 454 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 455 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 456 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 457 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 458 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 459 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 460 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 461 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 462 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 463 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 464 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 465 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 466 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 467 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 468 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 469 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 470 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 471 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 472 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 473 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 474 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 475 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 476 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 477 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 478 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 479 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 480 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 481 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 482 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 483 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 484 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 485 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 486 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 487 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 488 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 489 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 490 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; + $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos - (3 - 2)] as $s) { + if ($s instanceof Node\Scalar\EncapsedStringPart) { + $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', \true); + } + } + $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos - (3 - 2)], $attrs); + }, 491 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); + }, 492 => function ($stackPos) { + $this->semValue = array(); + }, 493 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 494 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 495 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 496 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 497 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 498 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 499 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 500 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 501 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 502 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 503 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 504 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 505 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 506 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 507 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 508 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 509 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 510 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 511 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 512 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 513 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 514 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 515 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 516 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 517 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 518 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 519 => function ($stackPos) { + $var = \substr($this->semStack[$stackPos - (1 - 1)], 1); + $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes) : $var; + }, 520 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 521 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (6 - 1)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 522 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 523 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 524 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 525 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 526 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 527 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 528 => function ($stackPos) { + $this->semValue = null; + }, 529 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 530 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 531 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 532 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 533 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->errorState = 2; + }, 534 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 535 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 536 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 537 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 538 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 539 => function ($stackPos) { + $this->semValue = null; + }, 540 => function ($stackPos) { + $this->semValue = array(); + }, 541 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 542 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 543 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 544 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 545 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 546 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 1)], \true, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 547 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 548 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 549 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 550 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 551 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 552 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); + }, 553 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 554 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 555 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 556 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 557 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 558 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 559 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 560 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 4)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 561 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 562 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 563 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 564 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }]; + } +} +'", "T_IS_GREATER_OR_EQUAL", "T_SL", "T_SR", "'+'", "'-'", "'.'", "'*'", "'/'", "'%'", "'!'", "T_INSTANCEOF", "'~'", "T_INC", "T_DEC", "T_INT_CAST", "T_DOUBLE_CAST", "T_STRING_CAST", "T_ARRAY_CAST", "T_OBJECT_CAST", "T_BOOL_CAST", "T_UNSET_CAST", "'@'", "T_POW", "'['", "T_NEW", "T_CLONE", "T_EXIT", "T_IF", "T_ELSEIF", "T_ELSE", "T_ENDIF", "T_LNUMBER", "T_DNUMBER", "T_STRING", "T_STRING_VARNAME", "T_VARIABLE", "T_NUM_STRING", "T_INLINE_HTML", "T_ENCAPSED_AND_WHITESPACE", "T_CONSTANT_ENCAPSED_STRING", "T_ECHO", "T_DO", "T_WHILE", "T_ENDWHILE", "T_FOR", "T_ENDFOR", "T_FOREACH", "T_ENDFOREACH", "T_DECLARE", "T_ENDDECLARE", "T_AS", "T_SWITCH", "T_MATCH", "T_ENDSWITCH", "T_CASE", "T_DEFAULT", "T_BREAK", "T_CONTINUE", "T_GOTO", "T_FUNCTION", "T_FN", "T_CONST", "T_RETURN", "T_TRY", "T_CATCH", "T_FINALLY", "T_USE", "T_INSTEADOF", "T_GLOBAL", "T_STATIC", "T_ABSTRACT", "T_FINAL", "T_PRIVATE", "T_PROTECTED", "T_PUBLIC", "T_READONLY", "T_VAR", "T_UNSET", "T_ISSET", "T_EMPTY", "T_HALT_COMPILER", "T_CLASS", "T_TRAIT", "T_INTERFACE", "T_ENUM", "T_EXTENDS", "T_IMPLEMENTS", "T_OBJECT_OPERATOR", "T_NULLSAFE_OBJECT_OPERATOR", "T_LIST", "T_ARRAY", "T_CALLABLE", "T_CLASS_C", "T_TRAIT_C", "T_METHOD_C", "T_FUNC_C", "T_LINE", "T_FILE", "T_START_HEREDOC", "T_END_HEREDOC", "T_DOLLAR_OPEN_CURLY_BRACES", "T_CURLY_OPEN", "T_PAAMAYIM_NEKUDOTAYIM", "T_NAMESPACE", "T_NS_C", "T_DIR", "T_NS_SEPARATOR", "T_ELLIPSIS", "T_NAME_FULLY_QUALIFIED", "T_NAME_QUALIFIED", "T_NAME_RELATIVE", "T_ATTRIBUTE", "';'", "']'", "'{'", "'}'", "'('", "')'", "'`'", "'\"'", "'\$'"); + protected $tokenToSymbol = array(0, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, 163, 164, 53, 50, 8, 51, 52, 54, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 161, 35, 162, 58, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158); + protected $action = array(132, 133, 134, 570, 135, 136, 0, 729, 730, 731, 137, 37, 929, 450, 451, 452, -32766, -32766, -32766, -32767, -32767, -32767, -32767, 101, 102, 103, 104, 105, 1085, 1086, 1087, 1084, 1083, 1082, 1088, 723, 722, -32766, 1275, -32766, -32766, -32766, -32766, -32766, -32766, -32766, -32767, -32767, -32767, -32767, -32767, 373, 374, 918, 2, 732, -32766, -32766, -32766, 1001, 472, 417, 150, -32766, -32766, -32766, 375, 374, 12, 267, 138, 399, 736, 737, 738, 739, 417, -32766, 423, -32766, -32766, -32766, -32766, -32766, -32766, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 770, 571, 771, 772, 773, 774, 762, 763, 339, 340, 765, 766, 751, 752, 753, 755, 756, 757, 349, 797, 798, 799, 800, 801, 802, 758, 759, 572, 573, 791, 782, 780, 781, 794, 777, 778, 323, 423, 574, 575, 776, 576, 577, 578, 579, 580, 581, -324, -585, 810, 34, 805, 779, 582, 583, -585, 139, -32766, -32766, -32766, 132, 133, 134, 570, 135, 136, 1034, 729, 730, 731, 137, 37, -32766, -32766, -32766, 544, 814, 126, -32766, 1310, -32766, -32766, -32766, -32766, -32766, -32766, -32766, 1085, 1086, 1087, 1084, 1083, 1082, 1088, 473, 723, 722, -32766, -32766, -32766, 458, 459, 81, -32766, -32766, -32766, -193, -192, 322, 898, 240, 599, 1210, 1209, 1211, 732, 816, 703, -32766, 1063, -32766, -32766, -32766, -32766, -32766, 811, -32766, -32766, -32766, 267, 138, 399, 736, 737, 738, 739, 1247, 1295, 423, 694, 1320, 35, 249, 1321, 1294, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 770, 571, 771, 772, 773, 774, 762, 763, 339, 340, 765, 766, 751, 752, 753, 755, 756, 757, 349, 797, 798, 799, 800, 801, 802, 758, 759, 572, 573, 791, 782, 780, 781, 794, 777, 778, 888, 593, 574, 575, 776, 576, 577, 578, 579, 580, 581, -324, 82, 83, 84, -585, 779, 582, 583, -585, 148, 754, 724, 725, 726, 727, 728, -582, 729, 730, 731, 767, 768, 36, -582, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, -362, 271, -362, -32766, -32766, -32766, 106, 107, 108, -268, 271, -193, -192, 109, 933, 934, 900, 732, 689, 935, 14, 288, 109, 815, -32766, 1061, -32766, -32766, 964, -86, 288, 733, 734, 735, 736, 737, 738, 739, 239, 384, 803, 11, 1077, -539, -32766, -32766, -32766, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 770, 793, 771, 772, 773, 774, 762, 763, 764, 792, 765, 766, 751, 752, 753, 755, 756, 757, 796, 797, 798, 799, 800, 801, 802, 758, 759, 760, 761, 791, 782, 780, 781, 794, 777, 778, 128, -86, 769, 775, 776, 783, 784, 786, 785, 787, 788, -576, 144, -539, -539, -576, 779, 790, 789, 49, 50, 51, 503, 52, 53, 997, 996, 995, 998, 54, 55, -111, 56, -582, 1033, 1010, -111, -582, -111, 1291, -539, -32766, -32766, 302, 1010, 1010, -111, -111, -111, -111, -111, -111, -111, -111, 1208, 841, 898, 842, 253, 807, 287, 306, 965, 284, 898, 723, 722, 57, 58, 287, 287, 1007, -536, 59, 308, 60, 246, 247, 61, 62, 63, 64, 65, 66, 67, 68, 695, 27, 269, 69, 439, 504, -338, 1010, 696, 1241, 1242, 505, 898, 814, 640, 25, 898, 1239, 41, 24, 506, 320, 507, 1235, 508, 1009, 509, 149, 402, 510, 511, 841, 805, 842, 43, 44, 440, 370, 369, 898, 45, 512, 698, 1210, 1209, 1211, 361, 335, 1215, 809, -536, -536, 336, 888, 691, 513, 514, 515, 1215, 1007, 1062, 888, 715, 1007, 337, -536, 363, 516, 517, 705, 1229, 1230, 1231, 1232, 1226, 1227, 294, -536, -16, -542, 813, 1010, 1233, 1228, 367, 1010, 1210, 1209, 1211, 295, -153, -153, -153, 382, 70, 888, 318, 319, 322, 888, 659, 660, -535, 1206, 814, -153, 279, -153, 435, -153, 279, -153, 436, 141, 103, 104, 105, 632, 633, 322, 437, 368, 888, -32766, -32766, 371, 372, 438, 900, 814, 689, 820, -111, -111, 376, 377, 950, -111, 689, 814, -88, 151, 874, -111, -111, -111, -111, 31, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 723, 722, 1206, 153, 154, -535, -535, 155, 898, 900, 157, 689, 1206, 900, -111, 689, -153, 32, 123, 898, -535, 124, 140, -32766, -537, 129, 130, 143, 322, 1122, 1124, 158, -535, -32766, -541, -534, 900, -32766, 689, 159, -534, 723, 722, 1208, 295, 160, 161, -79, -75, 74, -32766, -32766, -32766, 322, -32766, -73, -32766, -298, -32766, 74, -294, -32766, -72, 322, -71, -70, -32766, -32766, -32766, -69, -68, -67, -32766, -32766, 27, -66, -47, 1215, -32766, 414, -18, 147, 275, 270, 281, 704, 814, -32766, -537, -537, 1239, 888, 707, 897, 146, 276, 48, -4, 898, -534, -534, 282, 888, -537, -534, -534, 283, -246, -246, -246, 329, 285, 271, 368, -534, -537, 286, 73, 289, -534, 1206, 47, 723, 722, -111, -111, -534, 290, 109, -111, 914, -534, 550, 669, 874, -111, -111, -111, -111, 145, 516, 517, -32766, 1229, 1230, 1231, 1232, 1226, 1227, 814, 805, 1322, 662, 300, 1092, 1233, 1228, 682, 814, -32766, 298, 299, 546, 641, 647, 1208, 900, 72, 689, -246, 319, 322, -32766, -32766, -32766, 366, -32766, 900, -32766, 689, -32766, 888, 646, -32766, 13, 296, 297, 127, -32766, -32766, -32766, 455, 1206, -51, -32766, -32766, 483, 630, 663, 556, -32766, 414, 303, 368, -111, 430, 434, 39, 930, -32766, 293, 0, 125, -32766, -111, -111, 301, 0, 0, -111, 1010, 307, 0, 0, 833, -111, -111, -111, -111, 0, -32766, 131, 0, 0, 295, 0, -32766, 1246, 0, 74, 0, 1248, 1208, 322, 0, -500, 0, 9, 0, -32766, -32766, -32766, -490, -32766, 7, -32766, 900, -32766, 689, -4, -32766, 16, 365, 597, 813, -32766, -32766, -32766, 916, 295, 709, -32766, -32766, 1240, -32766, 40, 712, -32766, 414, 713, 1208, 879, 898, 974, 951, 958, -32766, -32766, -32766, -32766, 948, -32766, 959, -32766, 877, -32766, 946, 1066, -32766, 1069, 1070, 1067, 1068, -32766, -32766, -32766, -32766, 1074, 33, -32766, -32766, 1236, 1208, 825, 1261, -32766, 414, 1279, 1313, -32766, -32766, -32766, 317, -32766, -32766, -32766, 635, -32766, 364, 690, -32766, 693, 697, 699, 478, -32766, -32766, -32766, -32766, 700, 701, -32766, -32766, 702, 1208, 562, 706, -32766, 414, 692, -570, -32766, -32766, -32766, 875, -32766, -32766, -32766, 1317, -32766, 1319, 836, -32766, 835, 844, 888, 923, -32766, -32766, -32766, 966, 843, 1318, -32766, -32766, 922, 924, 921, 1194, -32766, 414, -245, -245, -245, 907, 917, 905, 368, -32766, 956, 957, 1316, 1273, 1262, 0, 1280, 1286, 1289, -111, -111, -568, 27, -542, -111, -541, -540, 1, 28, 874, -111, -111, -111, -111, 814, 29, -32766, 38, 1239, 42, 46, 71, 1208, 75, 76, 77, 78, 79, 0, -32766, -32766, -32766, 80, -32766, 142, -32766, 152, -32766, 156, 245, -32766, 900, 324, 689, -245, -32766, -32766, -32766, 1206, 350, 351, -32766, -32766, 352, 353, 354, 355, -32766, 414, 356, 357, 358, 359, 360, 362, 431, -32766, -271, -269, 517, -268, 1229, 1230, 1231, 1232, 1226, 1227, 18, 19, 20, 21, 23, 401, 1233, 1228, 474, 475, 482, 485, 486, 487, 488, 492, 493, 494, 72, -504, 501, 319, 322, 676, 1219, 1162, 1237, 1036, 1035, 0, 1016, 1198, 1012, -273, -103, 17, 22, 26, 292, 400, 590, 594, 621, 681, 1166, 1214, 1163, 1292, 0, 1179, 0, 0, 322); + protected $actionCheck = array(2, 3, 4, 5, 6, 7, 0, 9, 10, 11, 12, 13, 128, 129, 130, 131, 9, 10, 11, 44, 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, 118, 119, 120, 121, 122, 37, 38, 30, 1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 106, 107, 1, 8, 57, 9, 10, 11, 1, 31, 116, 14, 9, 10, 11, 106, 107, 8, 71, 72, 73, 74, 75, 76, 77, 116, 30, 80, 32, 33, 34, 35, 36, 30, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 70, 80, 136, 137, 138, 139, 140, 141, 142, 143, 144, 8, 1, 80, 8, 80, 150, 151, 152, 8, 154, 9, 10, 11, 2, 3, 4, 5, 6, 7, 164, 9, 10, 11, 12, 13, 9, 10, 11, 85, 82, 14, 30, 85, 32, 33, 34, 35, 36, 37, 38, 116, 117, 118, 119, 120, 121, 122, 161, 37, 38, 9, 10, 11, 134, 135, 161, 9, 10, 11, 8, 8, 167, 1, 14, 51, 155, 156, 157, 57, 1, 161, 30, 162, 32, 33, 34, 35, 30, 156, 32, 33, 34, 71, 72, 73, 74, 75, 76, 77, 146, 1, 80, 31, 80, 147, 148, 83, 8, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 84, 1, 136, 137, 138, 139, 140, 141, 142, 143, 144, 164, 9, 10, 11, 160, 150, 151, 152, 164, 154, 2, 3, 4, 5, 6, 7, 1, 9, 10, 11, 12, 13, 30, 8, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 106, 57, 108, 9, 10, 11, 53, 54, 55, 164, 57, 164, 164, 69, 117, 118, 159, 57, 161, 122, 101, 30, 69, 159, 30, 1, 32, 33, 31, 31, 30, 71, 72, 73, 74, 75, 76, 77, 97, 106, 80, 108, 123, 70, 9, 10, 11, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 8, 97, 136, 137, 138, 139, 140, 141, 142, 143, 144, 160, 8, 134, 135, 164, 150, 151, 152, 2, 3, 4, 5, 6, 7, 119, 120, 121, 122, 12, 13, 101, 15, 160, 1, 138, 106, 164, 108, 1, 161, 9, 10, 113, 138, 138, 116, 117, 118, 119, 120, 121, 122, 123, 80, 106, 1, 108, 8, 80, 163, 8, 159, 30, 1, 37, 38, 50, 51, 163, 163, 116, 70, 56, 8, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 31, 70, 71, 72, 73, 74, 162, 138, 31, 78, 79, 80, 1, 82, 75, 76, 1, 86, 87, 88, 89, 8, 91, 1, 93, 137, 95, 101, 102, 98, 99, 106, 80, 108, 103, 104, 105, 106, 107, 1, 109, 110, 31, 155, 156, 157, 115, 116, 1, 156, 134, 135, 8, 84, 161, 124, 125, 126, 1, 116, 159, 84, 161, 116, 8, 149, 8, 136, 137, 31, 139, 140, 141, 142, 143, 144, 145, 161, 31, 163, 155, 138, 151, 152, 8, 138, 155, 156, 157, 158, 75, 76, 77, 8, 163, 84, 165, 166, 167, 84, 75, 76, 70, 116, 82, 90, 163, 92, 8, 94, 163, 96, 8, 161, 50, 51, 52, 111, 112, 167, 8, 106, 84, 9, 137, 106, 107, 8, 159, 82, 161, 8, 117, 118, 106, 107, 159, 122, 161, 82, 31, 14, 127, 128, 129, 130, 131, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 37, 38, 116, 14, 14, 134, 135, 14, 1, 159, 14, 161, 116, 159, 128, 161, 162, 14, 16, 1, 149, 16, 161, 137, 70, 16, 16, 16, 167, 59, 60, 16, 161, 137, 163, 70, 159, 74, 161, 16, 70, 37, 38, 80, 158, 16, 16, 31, 31, 163, 87, 88, 89, 167, 91, 31, 93, 35, 95, 163, 35, 98, 31, 167, 31, 31, 103, 104, 105, 31, 31, 31, 109, 110, 70, 31, 31, 1, 115, 116, 31, 31, 35, 31, 31, 31, 82, 124, 134, 135, 86, 84, 31, 31, 31, 35, 70, 0, 1, 134, 135, 35, 84, 149, 134, 135, 35, 100, 101, 102, 35, 37, 57, 106, 149, 161, 37, 154, 37, 149, 116, 70, 37, 38, 117, 118, 161, 37, 69, 122, 38, 161, 89, 77, 127, 128, 129, 130, 131, 70, 136, 137, 85, 139, 140, 141, 142, 143, 144, 82, 80, 83, 94, 132, 82, 151, 152, 92, 82, 74, 134, 135, 85, 90, 100, 80, 159, 163, 161, 162, 166, 167, 87, 88, 89, 149, 91, 159, 93, 161, 95, 84, 96, 98, 97, 134, 135, 161, 103, 104, 105, 97, 116, 31, 109, 110, 97, 113, 100, 153, 115, 116, 114, 106, 128, 108, 128, 159, 128, 124, 113, -1, 161, 137, 117, 118, 133, -1, -1, 122, 138, 132, -1, -1, 127, 128, 129, 130, 131, -1, 137, 31, -1, -1, 158, -1, 74, 146, -1, 163, -1, 146, 80, 167, -1, 149, -1, 150, -1, 87, 88, 89, 149, 91, 149, 93, 159, 95, 161, 162, 98, 149, 149, 153, 155, 103, 104, 105, 154, 158, 162, 109, 110, 166, 74, 159, 159, 115, 116, 159, 80, 159, 1, 159, 159, 159, 124, 87, 88, 89, 159, 91, 159, 93, 159, 95, 159, 159, 98, 159, 159, 159, 159, 103, 104, 105, 74, 159, 161, 109, 110, 160, 80, 160, 160, 115, 116, 160, 160, 87, 88, 89, 161, 91, 124, 93, 160, 95, 161, 161, 98, 161, 161, 161, 102, 103, 104, 105, 74, 161, 161, 109, 110, 161, 80, 81, 161, 115, 116, 161, 163, 87, 88, 89, 162, 91, 124, 93, 162, 95, 162, 162, 98, 162, 162, 84, 162, 103, 104, 105, 162, 162, 162, 109, 110, 162, 162, 162, 162, 115, 116, 100, 101, 102, 162, 162, 162, 106, 124, 162, 162, 162, 162, 162, -1, 162, 162, 162, 117, 118, 163, 70, 163, 122, 163, 163, 163, 163, 127, 128, 129, 130, 131, 82, 163, 74, 163, 86, 163, 163, 163, 80, 163, 163, 163, 163, 163, -1, 87, 88, 89, 163, 91, 163, 93, 163, 95, 163, 163, 98, 159, 163, 161, 162, 103, 104, 105, 116, 163, 163, 109, 110, 163, 163, 163, 163, 115, 116, 163, 163, 163, 163, 163, 163, 163, 124, 164, 164, 137, 164, 139, 140, 141, 142, 143, 144, 164, 164, 164, 164, 164, 164, 151, 152, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 163, 165, 164, 166, 167, 164, 164, 164, 164, 164, 164, -1, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, -1, 165, -1, -1, 167); + protected $actionBase = array(0, -2, 154, 542, 785, 695, 969, 549, 53, 420, 831, 307, 307, 67, 307, 307, 307, 496, 538, 538, 565, 538, 204, 504, 706, 706, 706, 651, 651, 651, 651, 773, 773, 920, 920, 952, 888, 850, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 1029, 211, 344, 288, 691, 1038, 1044, 1040, 1045, 1036, 1035, 1039, 1041, 1046, 917, 918, 751, 919, 921, 922, 923, 1042, 854, 1037, 1043, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 641, 159, 473, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, 54, 54, 54, 341, 692, 692, 190, 184, 658, 47, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 1019, 144, 144, 7, 7, 7, 7, 7, 371, -25, -25, -25, -25, 574, 347, 764, 474, 584, 266, 241, 338, 470, 470, 591, 591, 396, -116, 396, 348, 348, 396, 396, 396, 770, 770, 770, 770, 443, 559, 452, 86, 514, 479, 479, 479, 479, 514, 514, 514, 514, 783, 795, 514, 514, 514, 642, 653, 653, 714, 300, 300, 300, 653, 390, 765, 90, 390, 90, 37, 156, 781, -55, -40, 292, 768, 781, 320, 739, 314, 143, 797, 546, 797, 1034, 745, 733, 705, 836, 876, 1047, 752, 915, 786, 916, 62, 704, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1049, 469, 1034, 65, 1049, 1049, 1049, 469, 469, 469, 469, 469, 469, 469, 469, 469, 469, 533, 65, 466, 552, 65, 763, 469, 211, 791, 211, 211, 211, 211, 973, 211, 211, 211, 211, 211, 211, 980, 737, 29, 211, 344, 52, 52, 428, 58, 52, 52, 52, 52, 211, 211, 211, 546, 743, 734, 555, 798, 195, 743, 743, 743, 345, 135, 192, 194, 710, 713, 280, 758, 758, 760, 931, 931, 758, 755, 758, 760, 944, 758, 931, 799, 433, 627, 571, 603, 631, 931, 494, 758, 758, 758, 758, 639, 758, 491, 445, 758, 758, 709, 741, 777, 46, 931, 931, 931, 777, 585, 771, 771, 771, 805, 808, 772, 740, 540, 507, 650, 138, 780, 740, 740, 758, 612, 772, 740, 772, 740, 802, 740, 740, 740, 772, 740, 755, 583, 740, 703, 646, 60, 740, 6, 945, 947, 636, 948, 941, 949, 989, 950, 951, 856, 963, 943, 956, 939, 932, 750, 690, 693, 793, 784, 930, 747, 747, 747, 927, 747, 747, 747, 747, 747, 747, 747, 747, 690, 839, 801, 766, 731, 974, 697, 698, 779, 880, 1018, 1048, 973, 1024, 958, 736, 699, 1004, 977, 796, 849, 978, 979, 1005, 1025, 1026, 884, 757, 886, 887, 841, 983, 858, 747, 945, 951, 943, 956, 939, 932, 732, 728, 726, 727, 722, 721, 712, 719, 738, 1027, 925, 875, 842, 980, 929, 690, 845, 1000, 835, 1008, 1009, 855, 782, 756, 846, 889, 984, 985, 986, 859, 1028, 804, 1001, 990, 1010, 787, 890, 1011, 1012, 1013, 1014, 892, 860, 866, 867, 810, 761, 991, 774, 896, 48, 754, 759, 778, 988, 654, 966, 870, 897, 898, 1015, 1016, 1017, 901, 960, 812, 1002, 746, 1003, 993, 813, 814, 677, 769, 1030, 735, 748, 767, 678, 681, 902, 903, 904, 962, 742, 744, 819, 821, 1031, 762, 1032, 910, 684, 823, 711, 911, 1023, 717, 718, 753, 873, 800, 776, 775, 987, 749, 825, 912, 826, 828, 829, 1020, 830, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 458, 458, 458, 458, 458, 458, 307, 307, 307, 307, 0, 0, 307, 0, 0, 0, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 415, 415, 291, 291, 0, 291, 415, 415, 415, 415, 415, 415, 415, 415, 415, 415, 291, 291, 291, 291, 291, 291, 291, 799, 300, 300, 300, 300, 415, 415, 415, 415, 415, -88, -88, 415, 415, 415, 300, 300, 415, 244, 415, 415, 415, 415, 415, 415, 415, 415, 415, 415, 0, 0, 65, 90, 415, 755, 755, 755, 755, 415, 415, 415, 415, 90, 90, 415, 415, 415, 0, 0, 0, 0, 0, 0, 0, 0, 65, 90, 0, 65, 0, 755, 755, 415, 799, 799, 232, 244, 415, 0, 0, 0, 0, 65, 755, 65, 469, 90, 469, 469, 52, 211, 232, 453, 453, 453, 453, 0, 546, 799, 799, 799, 799, 799, 799, 799, 799, 799, 799, 799, 755, 0, 799, 0, 755, 755, 755, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 755, 0, 0, 931, 0, 0, 0, 0, 758, 0, 0, 0, 0, 0, 0, 758, 944, 0, 0, 0, 0, 0, 0, 755, 0, 0, 0, 0, 0, 0, 0, 0, 747, 782, 0, 782, 0, 747, 747, 747, 0, 0, 0, 0, 769, 762); + protected $actionDefault = array(3, 32767, 103, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 101, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 588, 588, 588, 588, 32767, 32767, 250, 103, 32767, 32767, 464, 382, 382, 382, 32767, 32767, 532, 532, 532, 532, 532, 532, 32767, 32767, 32767, 32767, 32767, 32767, 464, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 101, 32767, 32767, 32767, 37, 7, 8, 10, 11, 50, 17, 320, 32767, 32767, 32767, 32767, 103, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 581, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 468, 447, 448, 450, 451, 381, 533, 587, 323, 584, 380, 146, 335, 325, 238, 326, 254, 469, 255, 470, 473, 474, 211, 283, 377, 150, 411, 465, 413, 463, 467, 412, 387, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 385, 386, 466, 444, 443, 442, 409, 32767, 32767, 410, 414, 384, 417, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 103, 32767, 415, 416, 433, 434, 431, 432, 435, 32767, 436, 437, 438, 439, 32767, 312, 32767, 32767, 32767, 361, 359, 312, 32767, 32767, 424, 425, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 526, 441, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 103, 32767, 101, 528, 406, 408, 496, 419, 420, 418, 388, 32767, 503, 32767, 103, 505, 32767, 32767, 32767, 112, 32767, 32767, 32767, 32767, 527, 32767, 534, 534, 32767, 489, 101, 194, 32767, 194, 194, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 595, 489, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 32767, 194, 111, 32767, 32767, 32767, 101, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 189, 32767, 264, 266, 103, 549, 194, 32767, 508, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 501, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 489, 429, 139, 32767, 139, 534, 421, 422, 423, 491, 534, 534, 534, 308, 285, 32767, 32767, 32767, 32767, 506, 506, 101, 101, 101, 101, 501, 32767, 32767, 112, 100, 100, 100, 100, 100, 104, 102, 32767, 32767, 32767, 32767, 100, 32767, 102, 102, 32767, 32767, 221, 208, 219, 102, 32767, 553, 554, 219, 102, 223, 223, 223, 243, 243, 480, 314, 102, 100, 102, 102, 196, 314, 314, 32767, 102, 480, 314, 480, 314, 198, 314, 314, 314, 480, 314, 32767, 102, 314, 210, 100, 100, 314, 32767, 32767, 32767, 491, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 521, 32767, 538, 551, 427, 428, 430, 536, 452, 453, 454, 455, 456, 457, 458, 460, 583, 32767, 495, 32767, 32767, 32767, 32767, 334, 593, 32767, 593, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 594, 32767, 534, 32767, 32767, 32767, 32767, 426, 9, 76, 43, 44, 52, 58, 512, 513, 514, 515, 509, 510, 516, 511, 32767, 32767, 517, 559, 32767, 32767, 535, 586, 32767, 32767, 32767, 32767, 32767, 32767, 139, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 521, 32767, 137, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 534, 32767, 32767, 32767, 32767, 310, 307, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 534, 32767, 32767, 32767, 32767, 32767, 287, 32767, 304, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 282, 32767, 32767, 376, 32767, 32767, 32767, 32767, 355, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 152, 152, 3, 3, 337, 152, 152, 152, 337, 152, 337, 337, 337, 152, 152, 152, 152, 152, 152, 276, 184, 258, 261, 243, 243, 152, 347, 152); + protected $goto = array(194, 194, 677, 466, 1281, 1282, 345, 428, 325, 325, 325, 325, 536, 536, 536, 536, 665, 591, 926, 1039, 685, 1003, 1019, 1020, 1080, 1081, 165, 165, 165, 165, 218, 195, 191, 191, 175, 177, 213, 191, 191, 191, 191, 191, 192, 192, 192, 192, 192, 192, 186, 187, 188, 189, 190, 215, 213, 216, 524, 525, 415, 526, 528, 529, 530, 531, 532, 533, 534, 535, 1108, 166, 167, 168, 193, 169, 170, 171, 164, 172, 173, 174, 176, 212, 214, 217, 235, 238, 241, 242, 244, 255, 256, 257, 258, 259, 260, 261, 263, 264, 265, 266, 277, 278, 313, 314, 315, 420, 421, 422, 569, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 178, 234, 179, 196, 197, 198, 236, 186, 187, 188, 189, 190, 215, 1108, 199, 180, 181, 182, 200, 196, 183, 237, 201, 199, 163, 202, 203, 184, 204, 205, 206, 185, 207, 208, 209, 210, 211, 834, 587, 425, 645, 548, 541, 830, 831, 419, 310, 311, 332, 564, 316, 424, 333, 426, 623, 832, 973, 947, 947, 945, 947, 710, 808, 540, 982, 977, 827, 827, 607, 642, 391, 541, 548, 557, 558, 398, 567, 589, 603, 604, 839, 865, 887, 882, 883, 896, 15, 840, 884, 837, 885, 886, 838, 457, 457, 639, 890, 656, 657, 658, 987, 987, 457, 609, 609, 806, 1060, 1056, 1057, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1256, 1256, 346, 347, 812, 949, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1014, 1013, 1207, 1008, 1207, 1008, 1207, 561, 442, 1008, 1008, 1008, 343, 442, 1008, 442, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 1008, 251, 251, 1296, 812, 1207, 812, 1307, 1307, 970, 1207, 1207, 1207, 1207, 1017, 1018, 1207, 1207, 1207, 1288, 1288, 1288, 1288, 827, 1307, 321, 305, 248, 248, 248, 248, 250, 252, 387, 903, 1254, 1254, 619, 620, 904, 1203, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 527, 527, 280, 280, 280, 280, 527, 527, 527, 527, 527, 527, 527, 527, 527, 527, 941, 405, 684, 560, 442, 442, 442, 442, 442, 442, 442, 442, 442, 442, 442, 348, 644, 442, 389, 393, 549, 588, 592, 847, 1157, 348, 348, 538, 1204, 538, 891, 538, 892, 432, 418, 822, 598, 666, 859, 348, 348, 846, 348, 5, 1323, 6, 824, 554, 1283, 1284, 650, 1205, 1264, 1265, 602, 448, 543, 565, 601, 348, 943, 943, 943, 943, 334, 932, 448, 937, 944, 403, 404, 1278, 852, 1278, 654, 1278, 655, 397, 407, 408, 409, 1200, 668, 849, 1045, 410, 542, 552, 992, 341, 490, 542, 491, 552, 714, 467, 390, 861, 497, 1049, 1290, 1290, 1290, 1290, 1267, 954, 568, 460, 461, 462, 1091, 857, 471, 0, 1314, 1315, 555, 0, 0, 0, 711, 622, 624, 0, 643, 0, 1274, 670, 667, 671, 984, 675, 683, 980, 0, 0, 0, 0, 0, 855, 596, 610, 613, 614, 615, 616, 636, 637, 638, 687, 860, 848, 1044, 1048, 908, 1096, 0, 543, 0, 0, 952, 606, 1306, 1306, 0, 1047, 989, 0, 0, 1276, 1276, 1047, 254, 254, 851, 0, 648, 968, 427, 1306, 0, 0, 845, 942, 427, 0, 0, 0, 0, 0, 0, 0, 1015, 1015, 1199, 0, 1309, 649, 1026, 1022, 1023, 0, 0, 0, 0, 1089, 864, 0, 0, 0, 586, 1073, 0, 688, 674, 674, 1202, 498, 680, 1071, 1188, 919, 0, 0, 1189, 1192, 920, 1193, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 539, 0, 539); + protected $gotoCheck = array(42, 42, 72, 172, 172, 172, 95, 87, 23, 23, 23, 23, 105, 105, 105, 105, 87, 105, 87, 125, 9, 87, 87, 87, 142, 142, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 15, 128, 65, 65, 75, 75, 25, 26, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 27, 25, 25, 25, 25, 25, 25, 7, 25, 25, 25, 22, 22, 55, 55, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 15, 45, 15, 15, 15, 15, 75, 15, 15, 15, 15, 15, 15, 147, 147, 84, 15, 84, 84, 84, 105, 105, 147, 106, 106, 6, 15, 15, 15, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 166, 166, 95, 95, 12, 49, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166, 116, 116, 72, 72, 72, 72, 72, 168, 23, 72, 72, 72, 175, 23, 72, 23, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 5, 5, 177, 12, 72, 12, 179, 179, 101, 72, 72, 72, 72, 117, 117, 72, 72, 72, 9, 9, 9, 9, 22, 179, 165, 165, 5, 5, 5, 5, 5, 5, 61, 72, 167, 167, 83, 83, 72, 20, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 169, 24, 24, 24, 24, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 91, 91, 91, 102, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 14, 63, 23, 58, 58, 58, 58, 58, 35, 149, 14, 14, 19, 20, 19, 64, 19, 64, 111, 13, 20, 13, 114, 35, 14, 14, 35, 14, 46, 14, 46, 18, 9, 174, 174, 118, 20, 20, 20, 9, 19, 14, 2, 2, 14, 19, 19, 19, 19, 29, 90, 19, 19, 19, 80, 80, 128, 39, 128, 80, 128, 80, 28, 80, 80, 80, 158, 80, 37, 127, 80, 9, 9, 108, 80, 153, 9, 153, 9, 97, 155, 9, 41, 153, 130, 128, 128, 128, 128, 14, 94, 9, 9, 9, 9, 145, 9, 82, -1, 9, 9, 48, -1, -1, -1, 48, 48, 48, -1, 48, -1, 128, 14, 48, 48, 48, 48, 48, 48, -1, -1, -1, -1, -1, 9, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 16, 16, 16, 16, 17, 17, -1, 14, -1, -1, 16, 17, 178, 178, -1, 128, 17, -1, -1, 128, 128, 128, 5, 5, 17, -1, 17, 17, 115, 178, -1, -1, 17, 16, 115, -1, -1, -1, -1, -1, -1, -1, 115, 115, 17, -1, 178, 115, 115, 115, 115, -1, -1, -1, -1, 16, 16, -1, -1, -1, 8, 8, -1, 8, 8, 8, 14, 8, 8, 8, 78, 78, -1, -1, 78, 78, 78, 78, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, 24, -1, 24); + protected $gotoBase = array(0, 0, -283, 0, 0, 284, 216, 177, 554, 7, 0, 0, -46, 51, 72, -181, 57, 49, 91, 111, -62, 0, -135, 5, 334, 163, 164, 175, 94, 122, 0, 0, 0, 0, 0, 10, 0, 98, 0, 103, 0, 13, -1, 0, 0, 193, -320, 0, -223, 225, 0, 0, 0, 0, 0, 153, 0, 0, 325, 0, 0, 276, 0, 127, 362, -76, 0, 0, 0, 0, 0, 0, -6, 0, 0, -174, 0, 0, 168, 140, -61, 0, -4, -149, -478, 0, 0, -263, 0, 0, 88, 50, 0, 0, 19, -467, 0, 43, 0, 0, 0, 259, 312, 0, 0, -15, -12, 0, 76, 0, 0, 110, 0, 0, 109, 261, -16, 16, 114, 0, 0, 0, 0, 0, 0, 17, 0, 68, 155, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -248, 0, 0, 23, 0, 184, 0, 104, 0, 0, 0, -44, 0, 12, 0, 0, 70, 0, 0, 0, 0, 0, 0, -9, 4, 80, 238, 96, 0, 0, -294, 0, 34, 242, 0, 257, 209, -13, 0, 0); + protected $gotoDefault = array(-32768, 502, 718, 4, 719, 912, 795, 804, 584, 518, 686, 342, 611, 416, 1272, 889, 1095, 566, 823, 1216, 1224, 449, 826, 326, 708, 871, 872, 873, 394, 379, 385, 392, 634, 612, 484, 858, 445, 850, 476, 853, 444, 862, 162, 413, 500, 866, 3, 868, 545, 899, 380, 876, 381, 661, 878, 551, 880, 881, 388, 395, 396, 1100, 559, 608, 893, 243, 553, 894, 378, 895, 902, 383, 386, 672, 456, 495, 489, 406, 1075, 595, 631, 453, 470, 618, 617, 605, 469, 1011, 411, 328, 931, 939, 477, 454, 953, 344, 961, 716, 1107, 625, 479, 969, 626, 976, 979, 519, 520, 468, 991, 268, 994, 480, 1032, 651, 1005, 1006, 652, 627, 1028, 628, 653, 629, 1030, 463, 585, 1038, 446, 1046, 1260, 447, 1050, 262, 1053, 274, 412, 429, 1058, 1059, 8, 1065, 678, 679, 10, 273, 499, 1090, 673, 443, 1106, 433, 1176, 1178, 547, 481, 1196, 1195, 664, 496, 1201, 1263, 441, 521, 464, 312, 522, 304, 330, 309, 537, 291, 331, 523, 465, 1269, 1277, 327, 30, 1297, 1308, 338, 563, 600); + protected $ruleToNonTerminal = array(0, 1, 3, 3, 2, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 25, 25, 68, 68, 71, 71, 70, 69, 69, 62, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 26, 26, 27, 27, 27, 27, 86, 86, 88, 88, 81, 81, 89, 89, 90, 90, 90, 82, 82, 85, 85, 83, 83, 91, 92, 92, 56, 56, 64, 64, 67, 67, 67, 66, 93, 93, 94, 57, 57, 57, 57, 95, 95, 96, 96, 97, 97, 98, 99, 99, 100, 100, 101, 101, 54, 54, 50, 50, 103, 52, 52, 104, 51, 51, 53, 53, 63, 63, 63, 63, 79, 79, 107, 107, 109, 109, 110, 110, 110, 110, 108, 108, 108, 112, 112, 112, 112, 87, 87, 115, 115, 115, 116, 116, 113, 113, 117, 117, 119, 119, 120, 120, 114, 121, 121, 118, 122, 122, 122, 122, 111, 111, 80, 80, 80, 20, 20, 20, 124, 123, 123, 125, 125, 125, 125, 59, 126, 126, 127, 60, 129, 129, 130, 130, 131, 131, 84, 132, 132, 132, 132, 132, 132, 137, 137, 138, 138, 139, 139, 139, 139, 139, 140, 141, 141, 136, 136, 133, 133, 135, 135, 143, 143, 142, 142, 142, 142, 142, 142, 142, 134, 144, 144, 146, 145, 145, 61, 102, 147, 147, 55, 55, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 154, 148, 148, 153, 153, 156, 157, 157, 158, 159, 159, 159, 19, 19, 72, 72, 72, 72, 149, 149, 149, 149, 161, 161, 150, 150, 152, 152, 152, 155, 155, 166, 166, 166, 166, 166, 166, 166, 166, 166, 167, 167, 106, 169, 169, 169, 169, 151, 151, 151, 151, 151, 151, 151, 151, 58, 58, 164, 164, 164, 164, 170, 170, 160, 160, 160, 171, 171, 171, 171, 171, 171, 73, 73, 65, 65, 65, 65, 128, 128, 128, 128, 174, 173, 163, 163, 163, 163, 163, 163, 163, 162, 162, 162, 172, 172, 172, 172, 105, 168, 176, 176, 175, 175, 177, 177, 177, 177, 177, 177, 177, 177, 165, 165, 165, 165, 179, 180, 178, 178, 178, 178, 178, 178, 178, 178, 181, 181, 181, 181); + protected $ruleToLength = array(1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, 0, 1, 1, 1, 1, 1, 3, 5, 4, 3, 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, 2, 1, 1, 1, 0, 2, 1, 3, 8, 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, 1, 3, 1, 8, 9, 8, 7, 6, 8, 0, 2, 0, 2, 1, 2, 1, 2, 1, 1, 1, 0, 2, 0, 2, 0, 2, 2, 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 3, 3, 3, 3, 3, 1, 3, 3, 1, 1, 2, 1, 1, 0, 1, 0, 2, 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, 2, 0, 1, 5, 5, 10, 3, 5, 1, 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, 3, 3, 3, 4, 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, 10, 9, 10, 8, 3, 2, 0, 4, 2, 1, 3, 2, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 0, 3, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 4, 1, 1, 3, 1, 1, 1, 1, 1, 3, 2, 3, 0, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 4, 4, 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, 1); + protected function initReduceCallbacks() + { + $this->reduceCallbacks = [0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos - (1 - 1)]); + }, 2 => function ($stackPos) { + if (\is_array($this->semStack[$stackPos - (2 - 2)])) { + $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); + } else { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + } + }, 3 => function ($stackPos) { + $this->semValue = array(); + }, 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; + } + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 82 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 83 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 84 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 85 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 86 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 87 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 88 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 89 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 90 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 91 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 92 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 93 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 94 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 95 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 96 => function ($stackPos) { + $this->semValue = new Name(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 97 => function ($stackPos) { + $this->semValue = new Expr\Variable(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 98 => function ($stackPos) { + /* nothing */ + }, 99 => function ($stackPos) { + /* nothing */ + }, 100 => function ($stackPos) { + /* nothing */ + }, 101 => function ($stackPos) { + $this->emitError(new Error('A trailing comma is not allowed here', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); + }, 102 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 103 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 104 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos - (1 - 1)], [], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 105 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 106 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 107 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 108 => function ($stackPos) { + $this->semValue = new Node\AttributeGroup($this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 109 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 110 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 111 => function ($stackPos) { + $this->semValue = []; + }, 112 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 113 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 114 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 115 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 116 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 117 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (3 - 2)], null, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, 118 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, 119 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, 120 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (3 - 2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 121 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 122 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 123 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 124 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, 125 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, 126 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->semStack[$stackPos - (7 - 2)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 127 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 128 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 129 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 130 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 131 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 132 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 133 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 134 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 135 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 136 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 137 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); + }, 138 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); + }, 139 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (1 - 1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (1 - 1)); + }, 140 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->checkUseUse($this->semValue, $stackPos - (3 - 3)); + }, 141 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, 142 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue->type = $this->semStack[$stackPos - (2 - 1)]; + }, 143 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 144 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 145 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 146 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 147 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 148 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 149 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 150 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 151 => function ($stackPos) { + if (\is_array($this->semStack[$stackPos - (2 - 2)])) { + $this->semValue = \array_merge($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); + } else { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + } + }, 152 => function ($stackPos) { + $this->semValue = array(); + }, 153 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; + } + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 154 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 155 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 156 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 157 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 158 => function ($stackPos) { + if ($this->semStack[$stackPos - (3 - 2)]) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)]; + $stmts = $this->semValue; + if (!empty($attrs['comments'])) { + $stmts[0]->setAttribute('comments', \array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); + } + } else { + $startAttributes = $this->startAttributeStack[$stackPos - (3 - 1)]; + if (isset($startAttributes['comments'])) { + $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); + } else { + $this->semValue = null; + } + if (null === $this->semValue) { + $this->semValue = array(); + } + } + }, 159 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos - (7 - 3)], ['stmts' => \is_array($this->semStack[$stackPos - (7 - 5)]) ? $this->semStack[$stackPos - (7 - 5)] : array($this->semStack[$stackPos - (7 - 5)]), 'elseifs' => $this->semStack[$stackPos - (7 - 6)], 'else' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 160 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos - (10 - 3)], ['stmts' => $this->semStack[$stackPos - (10 - 6)], 'elseifs' => $this->semStack[$stackPos - (10 - 7)], 'else' => $this->semStack[$stackPos - (10 - 8)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + }, 161 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 162 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos - (7 - 5)], \is_array($this->semStack[$stackPos - (7 - 2)]) ? $this->semStack[$stackPos - (7 - 2)] : array($this->semStack[$stackPos - (7 - 2)]), $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 163 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos - (9 - 3)], 'cond' => $this->semStack[$stackPos - (9 - 5)], 'loop' => $this->semStack[$stackPos - (9 - 7)], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 164 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 165 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 166 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 167 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 168 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 169 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 170 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 171 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 172 => function ($stackPos) { + $e = $this->semStack[$stackPos - (2 - 1)]; + if ($e instanceof Expr\Throw_) { + // For backwards-compatibility reasons, convert throw in statement position into + // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). + $this->semValue = new Stmt\Throw_($e->expr, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + } else { + $this->semValue = new Stmt\Expression($e, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + } + }, 173 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos - (5 - 3)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 174 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos - (7 - 5)][1], 'stmts' => $this->semStack[$stackPos - (7 - 7)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 175 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (9 - 3)], $this->semStack[$stackPos - (9 - 7)][0], ['keyVar' => $this->semStack[$stackPos - (9 - 5)], 'byRef' => $this->semStack[$stackPos - (9 - 7)][1], 'stmts' => $this->semStack[$stackPos - (9 - 9)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 176 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos - (6 - 3)], new Expr\Error($this->startAttributeStack[$stackPos - (6 - 4)] + $this->endAttributeStack[$stackPos - (6 - 4)]), ['stmts' => $this->semStack[$stackPos - (6 - 6)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 177 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 178 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 5)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + $this->checkTryCatch($this->semValue); + }, 179 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 180 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 181 => function ($stackPos) { + $this->semValue = array(); + /* means: no statement */ + }, 182 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 183 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos - (1 - 1)]; + if (isset($startAttributes['comments'])) { + $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); + } else { + $this->semValue = null; + } + if ($this->semValue === null) { + $this->semValue = array(); + } + /* means: no statement */ + }, 184 => function ($stackPos) { + $this->semValue = array(); + }, 185 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 186 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 187 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 188 => function ($stackPos) { + $this->semValue = new Stmt\Catch_($this->semStack[$stackPos - (8 - 3)], $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 7)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 189 => function ($stackPos) { + $this->semValue = null; + }, 190 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 191 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 192 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 193 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 194 => function ($stackPos) { + $this->semValue = \false; + }, 195 => function ($stackPos) { + $this->semValue = \true; + }, 196 => function ($stackPos) { + $this->semValue = \false; + }, 197 => function ($stackPos) { + $this->semValue = \true; + }, 198 => function ($stackPos) { + $this->semValue = \false; + }, 199 => function ($stackPos) { + $this->semValue = \true; + }, 200 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 201 => function ($stackPos) { + $this->semValue = []; + }, 202 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (8 - 3)], ['byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 5)], 'returnType' => $this->semStack[$stackPos - (8 - 7)], 'stmts' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 203 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos - (9 - 4)], ['byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 6)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 204 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos - (8 - 3)], ['type' => $this->semStack[$stackPos - (8 - 2)], 'extends' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos - (8 - 3)); + }, 205 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos - (7 - 3)], ['extends' => $this->semStack[$stackPos - (7 - 4)], 'stmts' => $this->semStack[$stackPos - (7 - 6)], 'attrGroups' => $this->semStack[$stackPos - (7 - 1)]], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos - (7 - 3)); + }, 206 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos - (6 - 3)], ['stmts' => $this->semStack[$stackPos - (6 - 5)], 'attrGroups' => $this->semStack[$stackPos - (6 - 1)]], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 207 => function ($stackPos) { + $this->semValue = new Stmt\Enum_($this->semStack[$stackPos - (8 - 3)], ['scalarType' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + $this->checkEnum($this->semValue, $stackPos - (8 - 3)); + }, 208 => function ($stackPos) { + $this->semValue = null; + }, 209 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 210 => function ($stackPos) { + $this->semValue = null; + }, 211 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 212 => function ($stackPos) { + $this->semValue = 0; + }, 213 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 214 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 215 => function ($stackPos) { + $this->checkClassModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); + $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; + }, 216 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, 217 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, 218 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, 219 => function ($stackPos) { + $this->semValue = null; + }, 220 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 221 => function ($stackPos) { + $this->semValue = array(); + }, 222 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 223 => function ($stackPos) { + $this->semValue = array(); + }, 224 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 225 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 226 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 227 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 228 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 229 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 230 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 231 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 232 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 233 => function ($stackPos) { + $this->semValue = null; + }, 234 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 235 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 236 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 237 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 238 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 239 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 240 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 3)]; + }, 241 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 242 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (5 - 3)]; + }, 243 => function ($stackPos) { + $this->semValue = array(); + }, 244 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 245 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos - (4 - 2)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 246 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 247 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 248 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 249 => function ($stackPos) { + $this->semValue = new Expr\Match_($this->semStack[$stackPos - (7 - 3)], $this->semStack[$stackPos - (7 - 6)], $this->startAttributeStack[$stackPos - (7 - 1)] + $this->endAttributes); + }, 250 => function ($stackPos) { + $this->semValue = []; + }, 251 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 252 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 253 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 254 => function ($stackPos) { + $this->semValue = new Node\MatchArm($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 255 => function ($stackPos) { + $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 256 => function ($stackPos) { + $this->semValue = \is_array($this->semStack[$stackPos - (1 - 1)]) ? $this->semStack[$stackPos - (1 - 1)] : array($this->semStack[$stackPos - (1 - 1)]); + }, 257 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 258 => function ($stackPos) { + $this->semValue = array(); + }, 259 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 260 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (5 - 3)], \is_array($this->semStack[$stackPos - (5 - 5)]) ? $this->semStack[$stackPos - (5 - 5)] : array($this->semStack[$stackPos - (5 - 5)]), $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 261 => function ($stackPos) { + $this->semValue = array(); + }, 262 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 263 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 6)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 264 => function ($stackPos) { + $this->semValue = null; + }, 265 => function ($stackPos) { + $this->semValue = new Stmt\Else_(\is_array($this->semStack[$stackPos - (2 - 2)]) ? $this->semStack[$stackPos - (2 - 2)] : array($this->semStack[$stackPos - (2 - 2)]), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 266 => function ($stackPos) { + $this->semValue = null; + }, 267 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 268 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); + }, 269 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (2 - 2)], \true); + }, 270 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); + }, 271 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)], \false); + }, 272 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 273 => function ($stackPos) { + $this->semValue = array(); + }, 274 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 275 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 276 => function ($stackPos) { + $this->semValue = 0; + }, 277 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); + $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; + }, 278 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, 279 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, 280 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, 281 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, 282 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos - (6 - 6)], null, $this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 1)]); + $this->checkParam($this->semValue); + }, 283 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos - (8 - 6)], $this->semStack[$stackPos - (8 - 8)], $this->semStack[$stackPos - (8 - 3)], $this->semStack[$stackPos - (8 - 4)], $this->semStack[$stackPos - (8 - 5)], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (8 - 2)], $this->semStack[$stackPos - (8 - 1)]); + $this->checkParam($this->semValue); + }, 284 => function ($stackPos) { + $this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes), null, $this->semStack[$stackPos - (6 - 3)], $this->semStack[$stackPos - (6 - 4)], $this->semStack[$stackPos - (6 - 5)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 1)]); + }, 285 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 286 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 287 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 288 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 289 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 290 => function ($stackPos) { + $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 291 => function ($stackPos) { + $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos - (1 - 1)]); + }, 292 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 293 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 294 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 295 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 296 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 297 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 298 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 299 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 300 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 301 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 302 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 303 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 304 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 305 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 306 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 307 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 308 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 309 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 310 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 311 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 312 => function ($stackPos) { + $this->semValue = null; + }, 313 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 314 => function ($stackPos) { + $this->semValue = null; + }, 315 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 2)]; + }, 316 => function ($stackPos) { + $this->semValue = null; + }, 317 => function ($stackPos) { + $this->semValue = array(); + }, 318 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 2)]; + }, 319 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 2)]); + }, 320 => function ($stackPos) { + $this->semValue = new Node\VariadicPlaceholder($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 321 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 322 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 323 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (1 - 1)], \false, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 324 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \true, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 325 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (2 - 2)], \false, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 326 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos - (3 - 3)], \false, \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (3 - 1)]); + }, 327 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 328 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 329 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 330 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 331 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 332 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 333 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 334 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 335 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 336 => function ($stackPos) { + if ($this->semStack[$stackPos - (2 - 2)] !== null) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + } + }, 337 => function ($stackPos) { + $this->semValue = array(); + }, 338 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; + if (isset($startAttributes['comments'])) { + $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); + } else { + $nop = null; + } + if ($nop !== null) { + $this->semStack[$stackPos - (1 - 1)][] = $nop; + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 339 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos - (5 - 2)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 1)]); + $this->checkProperty($this->semValue, $stackPos - (5 - 2)); + }, 340 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos - (5 - 4)], $this->semStack[$stackPos - (5 - 2)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes, $this->semStack[$stackPos - (5 - 1)]); + $this->checkClassConst($this->semValue, $stackPos - (5 - 2)); + }, 341 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos - (10 - 5)], ['type' => $this->semStack[$stackPos - (10 - 2)], 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 7)], 'returnType' => $this->semStack[$stackPos - (10 - 9)], 'stmts' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos - (10 - 2)); + }, 342 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 343 => function ($stackPos) { + $this->semValue = new Stmt\EnumCase($this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->semStack[$stackPos - (5 - 1)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 344 => function ($stackPos) { + $this->semValue = null; + /* will be skipped */ + }, 345 => function ($stackPos) { + $this->semValue = array(); + }, 346 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 347 => function ($stackPos) { + $this->semValue = array(); + }, 348 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 349 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 350 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (5 - 1)][0], $this->semStack[$stackPos - (5 - 1)][1], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 4)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 351 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], $this->semStack[$stackPos - (4 - 3)], null, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 352 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 353 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos - (4 - 1)][0], $this->semStack[$stackPos - (4 - 1)][1], null, $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 354 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)]); + }, 355 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 356 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos - (1 - 1)]); + }, 357 => function ($stackPos) { + $this->semValue = null; + }, 358 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 359 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 360 => function ($stackPos) { + $this->semValue = 0; + }, 361 => function ($stackPos) { + $this->semValue = 0; + }, 362 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 363 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 364 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $stackPos - (2 - 2)); + $this->semValue = $this->semStack[$stackPos - (2 - 1)] | $this->semStack[$stackPos - (2 - 2)]; + }, 365 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, 366 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, 367 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, 368 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, 369 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, 370 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, 371 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, 372 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 373 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 374 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 375 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 376 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (1 - 1)], null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 377 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 378 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 379 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 380 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 381 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 382 => function ($stackPos) { + $this->semValue = array(); + }, 383 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 384 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 385 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 386 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 387 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 388 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 389 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 390 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 391 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 392 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 393 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 394 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 395 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 396 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 397 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 398 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 399 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 400 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 401 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 402 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 403 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 404 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 405 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 406 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 407 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 408 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 409 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 410 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 411 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 412 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 413 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 414 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 415 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 416 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 417 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 418 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 419 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 420 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 421 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 422 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 423 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 424 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 425 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 426 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 427 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 428 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 429 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 430 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 431 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 432 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 433 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 434 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 435 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 436 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 437 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 438 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 439 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 440 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 441 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 442 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (5 - 1)], $this->semStack[$stackPos - (5 - 3)], $this->semStack[$stackPos - (5 - 5)], $this->startAttributeStack[$stackPos - (5 - 1)] + $this->endAttributes); + }, 443 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos - (4 - 1)], null, $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 444 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 445 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 446 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 447 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 448 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 449 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 450 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 451 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos - (2 - 2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 452 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 453 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos - (2 - 1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos - (2 - 2)], $attrs); + }, 454 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 455 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 456 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 457 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 458 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 459 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes; + $attrs['kind'] = \strtolower($this->semStack[$stackPos - (2 - 1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos - (2 - 2)], $attrs); + }, 460 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 461 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 462 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 463 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 464 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 465 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (2 - 2)], null, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 466 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 2)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 467 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 468 => function ($stackPos) { + $this->semValue = new Expr\Throw_($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 469 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => \false, 'byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 4)], 'returnType' => $this->semStack[$stackPos - (8 - 6)], 'expr' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 470 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => \true, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'returnType' => $this->semStack[$stackPos - (9 - 7)], 'expr' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 471 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (8 - 2)], 'params' => $this->semStack[$stackPos - (8 - 4)], 'uses' => $this->semStack[$stackPos - (8 - 6)], 'returnType' => $this->semStack[$stackPos - (8 - 7)], 'stmts' => $this->semStack[$stackPos - (8 - 8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes); + }, 472 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'uses' => $this->semStack[$stackPos - (9 - 7)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 473 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => \false, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'returnType' => $this->semStack[$stackPos - (9 - 7)], 'expr' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 474 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => \true, 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 6)], 'returnType' => $this->semStack[$stackPos - (10 - 8)], 'expr' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + }, 475 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \false, 'byRef' => $this->semStack[$stackPos - (9 - 3)], 'params' => $this->semStack[$stackPos - (9 - 5)], 'uses' => $this->semStack[$stackPos - (9 - 7)], 'returnType' => $this->semStack[$stackPos - (9 - 8)], 'stmts' => $this->semStack[$stackPos - (9 - 9)], 'attrGroups' => $this->semStack[$stackPos - (9 - 1)]], $this->startAttributeStack[$stackPos - (9 - 1)] + $this->endAttributes); + }, 476 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => \true, 'byRef' => $this->semStack[$stackPos - (10 - 4)], 'params' => $this->semStack[$stackPos - (10 - 6)], 'uses' => $this->semStack[$stackPos - (10 - 8)], 'returnType' => $this->semStack[$stackPos - (10 - 9)], 'stmts' => $this->semStack[$stackPos - (10 - 10)], 'attrGroups' => $this->semStack[$stackPos - (10 - 1)]], $this->startAttributeStack[$stackPos - (10 - 1)] + $this->endAttributes); + }, 477 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos - (8 - 4)], 'implements' => $this->semStack[$stackPos - (8 - 5)], 'stmts' => $this->semStack[$stackPos - (8 - 7)], 'attrGroups' => $this->semStack[$stackPos - (8 - 1)]], $this->startAttributeStack[$stackPos - (8 - 1)] + $this->endAttributes), $this->semStack[$stackPos - (8 - 3)]); + $this->checkClass($this->semValue[0], -1); + }, 478 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 479 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 480 => function ($stackPos) { + $this->semValue = array(); + }, 481 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (4 - 3)]; + }, 482 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 483 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 484 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 485 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos - (2 - 2)], $this->semStack[$stackPos - (2 - 1)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 486 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 487 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 488 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 489 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 490 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 491 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 492 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 493 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(\substr($this->semStack[$stackPos - (1 - 1)], 1), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 494 => function ($stackPos) { + $this->semValue = new Name\Relative(\substr($this->semStack[$stackPos - (1 - 1)], 10), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 495 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 496 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 497 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 498 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->errorState = 2; + }, 499 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 500 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 501 => function ($stackPos) { + $this->semValue = null; + }, 502 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 503 => function ($stackPos) { + $this->semValue = array(); + }, 504 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos - (1 - 1)], '`'), $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes)); + }, 505 => function ($stackPos) { + foreach ($this->semStack[$stackPos - (1 - 1)] as $s) { + if ($s instanceof Node\Scalar\EncapsedStringPart) { + $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', \true); + } + } + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 506 => function ($stackPos) { + $this->semValue = array(); + }, 507 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 508 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 509 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 510 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 511 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 512 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 513 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 514 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 515 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 516 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 517 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 518 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos - (3 - 1)], new Expr\Error($this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)]), $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + $this->errorState = 2; + }, 519 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; + $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (3 - 2)], $attrs); + }, 520 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes; + $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos - (4 - 3)], $attrs); + }, 521 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 522 => function ($stackPos) { + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 523 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes; + $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos - (3 - 2)] as $s) { + if ($s instanceof Node\Scalar\EncapsedStringPart) { + $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', \true); + } + } + $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos - (3 - 2)], $attrs); + }, 524 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 525 => function ($stackPos) { + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 526 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 527 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 528 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 529 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); + }, 530 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (2 - 1)], '', $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (2 - 2)] + $this->endAttributeStack[$stackPos - (2 - 2)], \true); + }, 531 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 2)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes, $this->startAttributeStack[$stackPos - (3 - 3)] + $this->endAttributeStack[$stackPos - (3 - 3)], \true); + }, 532 => function ($stackPos) { + $this->semValue = null; + }, 533 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 534 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 535 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 536 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 537 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 538 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 539 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 540 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 541 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 542 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 543 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 544 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 545 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 546 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 547 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 548 => function ($stackPos) { + $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->semStack[$stackPos - (4 - 4)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 549 => function ($stackPos) { + $this->semValue = null; + }, 550 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 551 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 552 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 553 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 554 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 555 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 556 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 557 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 558 => function ($stackPos) { + $this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes), $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + $this->errorState = 2; + }, 559 => function ($stackPos) { + $var = $this->semStack[$stackPos - (1 - 1)]->name; + $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes) : $var; + }, 560 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 561 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 562 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 563 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 564 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 565 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 566 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 567 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 568 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 569 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 570 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 571 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 572 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 573 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 574 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + $this->errorState = 2; + }, 575 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 576 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + $end = \count($this->semValue) - 1; + if ($this->semValue[$end] === null) { + \array_pop($this->semValue); + } + }, 577 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, 578 => function ($stackPos) { + /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ + }, 579 => function ($stackPos) { + $this->semStack[$stackPos - (3 - 1)][] = $this->semStack[$stackPos - (3 - 3)]; + $this->semValue = $this->semStack[$stackPos - (3 - 1)]; + }, 580 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 581 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 582 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 583 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (1 - 1)], null, \false, $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 584 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 585 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (4 - 4)], $this->semStack[$stackPos - (4 - 1)], \true, $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 586 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (3 - 3)], $this->semStack[$stackPos - (3 - 1)], \false, $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 587 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos - (2 - 2)], null, \false, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes, \true, $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 588 => function ($stackPos) { + $this->semValue = null; + }, 589 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 590 => function ($stackPos) { + $this->semStack[$stackPos - (2 - 1)][] = $this->semStack[$stackPos - (2 - 2)]; + $this->semValue = $this->semStack[$stackPos - (2 - 1)]; + }, 591 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (1 - 1)]); + }, 592 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos - (2 - 1)], $this->semStack[$stackPos - (2 - 2)]); + }, 593 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 594 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 595 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }, 596 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (4 - 1)], $this->semStack[$stackPos - (4 - 3)], $this->startAttributeStack[$stackPos - (4 - 1)] + $this->endAttributes); + }, 597 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 598 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos - (3 - 1)], $this->semStack[$stackPos - (3 - 3)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 599 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 600 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos - (3 - 2)], $this->startAttributeStack[$stackPos - (3 - 1)] + $this->endAttributes); + }, 601 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos - (6 - 2)], $this->semStack[$stackPos - (6 - 4)], $this->startAttributeStack[$stackPos - (6 - 1)] + $this->endAttributes); + }, 602 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (3 - 2)]; + }, 603 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 604 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos - (1 - 1)], $this->startAttributeStack[$stackPos - (1 - 1)] + $this->endAttributes); + }, 605 => function ($stackPos) { + $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos - (2 - 2)], $this->startAttributeStack[$stackPos - (2 - 1)] + $this->endAttributes); + }, 606 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos - (1 - 1)]; + }]; + } +} +lexer = $lexer; + if (isset($options['throwOnError'])) { + throw new \LogicException('"throwOnError" is no longer supported, use "errorHandler" instead'); + } + $this->initReduceCallbacks(); + } + /** + * Parses PHP code into a node tree. + * + * If a non-throwing error handler is used, the parser will continue parsing after an error + * occurred and attempt to build a partial AST. + * + * @param string $code The source code to parse + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults + * to ErrorHandler\Throwing. + * + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and + * the parser was unable to recover from an error). + */ + public function parse(string $code, ErrorHandler $errorHandler = null) + { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing(); + $this->lexer->startLexing($code, $this->errorHandler); + $result = $this->doParse(); + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->startAttributeStack = []; + $this->endAttributeStack = []; + $this->semStack = []; + $this->semValue = null; + return $result; + } + protected function doParse() + { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + // The attributes for a node are taken from the first and last token of the node. + // From the first token only the startAttributes are taken and from the last only + // the endAttributes. Both are merged using the array union operator (+). + $startAttributes = []; + $endAttributes = []; + $this->endAttributes = $endAttributes; + // Keep stack of start and end attributes + $this->startAttributeStack = []; + $this->endAttributeStack = [$endAttributes]; + // Start off in the initial state and keep a stack of previous states + $state = 0; + $stateStack = [$state]; + // Semantic value stack (contains values of tokens and semantic action results) + $this->semStack = []; + // Current position in the stack(s) + $stackPos = 0; + $this->errorState = 0; + for (;;) { + //$this->traceNewState($state, $symbol); + if ($this->actionBase[$state] === 0) { + $rule = $this->actionDefault[$state]; + } else { + if ($symbol === self::SYMBOL_NONE) { + // Fetch the next token id from the lexer and fetch additional info by-ref. + // The end attributes are fetched into a temporary variable and only set once the token is really + // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is + // reduced after a token was read but not yet shifted. + $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes); + // map the lexer token id to the internally used symbols + $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize ? $this->tokenToSymbol[$tokenId] : $this->invalidSymbol; + if ($symbol === $this->invalidSymbol) { + throw new \RangeException(\sprintf('The lexer returned an invalid token (id=%d, value=%s)', $tokenId, $tokenValue)); + } + // Allow productions to access the start attributes of the lookahead token. + $this->lookaheadStartAttributes = $startAttributes; + //$this->traceRead($symbol); + } + $idx = $this->actionBase[$state] + $symbol; + if (($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) && ($action = $this->action[$idx]) !== $this->defaultAction) { + /* + * >= numNonLeafStates: shift and reduce + * > 0: shift + * = 0: accept + * < 0: reduce + * = -YYUNEXPECTED: error + */ + if ($action > 0) { + /* shift */ + //$this->traceShift($symbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + $this->semStack[$stackPos] = $tokenValue; + $this->startAttributeStack[$stackPos] = $startAttributes; + $this->endAttributeStack[$stackPos] = $endAttributes; + $this->endAttributes = $endAttributes; + $symbol = self::SYMBOL_NONE; + if ($this->errorState) { + --$this->errorState; + } + if ($action < $this->numNonLeafStates) { + continue; + } + /* $yyn >= numNonLeafStates means shift-and-reduce */ + $rule = $action - $this->numNonLeafStates; + } else { + $rule = -$action; + } + } else { + $rule = $this->actionDefault[$state]; + } + } + for (;;) { + if ($rule === 0) { + /* accept */ + //$this->traceAccept(); + return $this->semValue; + } elseif ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + try { + $this->reduceCallbacks[$rule]($stackPos); + } catch (Error $e) { + if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) { + $e->setStartLine($startAttributes['startLine']); + } + $this->emitError($e); + // Can't recover from this type of error + return null; + } + /* Goto - shift nonterminal */ + $lastEndAttributes = $this->endAttributeStack[$stackPos]; + $ruleLength = $this->ruleToLength[$rule]; + $stackPos -= $ruleLength; + $nonTerminal = $this->ruleToNonTerminal[$rule]; + $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; + if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { + $state = $this->goto[$idx]; + } else { + $state = $this->gotoDefault[$nonTerminal]; + } + ++$stackPos; + $stateStack[$stackPos] = $state; + $this->semStack[$stackPos] = $this->semValue; + $this->endAttributeStack[$stackPos] = $lastEndAttributes; + if ($ruleLength === 0) { + // Empty productions use the start attributes of the lookahead token. + $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; + } + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $startAttributes + $endAttributes)); + // Break missing intentionally + case 1: + case 2: + $this->errorState = 3; + // Pop until error-expecting state uncovered + while (!(($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) || ($action = $this->action[$idx]) === $this->defaultAction) { + // Not totally sure about this + if ($stackPos <= 0) { + // Could not recover from error + return null; + } + $state = $stateStack[--$stackPos]; + //$this->tracePop($state); + } + //$this->traceShift($this->errorSymbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + // We treat the error symbol as being empty, so we reset the end attributes + // to the end attributes of the last non-error symbol + $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; + $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1]; + $this->endAttributes = $this->endAttributeStack[$stackPos - 1]; + break; + case 3: + if ($symbol === 0) { + // Reached EOF without recovering from error + return null; + } + //$this->traceDiscard($symbol); + $symbol = self::SYMBOL_NONE; + break 2; + } + } + if ($state < $this->numNonLeafStates) { + break; + } + /* >= numNonLeafStates means shift-and-reduce */ + $rule = $state - $this->numNonLeafStates; + } + } + throw new \RuntimeException('Reached end of parser loop'); + } + protected function emitError(Error $error) + { + $this->errorHandler->handleError($error); + } + /** + * Format error message including expected tokens. + * + * @param int $symbol Unexpected symbol + * @param int $state State at time of error + * + * @return string Formatted error message + */ + protected function getErrorMessage(int $symbol, int $state) : string + { + $expectedString = ''; + if ($expected = $this->getExpectedTokens($state)) { + $expectedString = ', expecting ' . \implode(' or ', $expected); + } + return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + } + /** + * Get limited number of expected tokens in given state. + * + * @param int $state State + * + * @return string[] Expected tokens. If too many, an empty array is returned. + */ + protected function getExpectedTokens(int $state) : array + { + $expected = []; + $base = $this->actionBase[$state]; + foreach ($this->symbolToName as $symbol => $name) { + $idx = $base + $symbol; + if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol || $state < $this->YY2TBLSTATE && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) { + if ($this->action[$idx] !== $this->unexpectedTokenRule && $this->action[$idx] !== $this->defaultAction && $symbol !== $this->errorSymbol) { + if (\count($expected) === 4) { + /* Too many expected tokens */ + return []; + } + $expected[] = $name; + } + } + } + return $expected; + } + /* + * Tracing functions used for debugging the parser. + */ + /* + protected function traceNewState($state, $symbol) { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol) { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol) { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept() { + echo "% Accepted.\n"; + } + + protected function traceReduce($n) { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state) { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol) { + echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + } + */ + /* + * Helper functions invoked by semantic actions + */ + /** + * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions. + * + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + protected function handleNamespaces(array $stmts) : array + { + $hasErrored = \false; + $style = $this->getNamespacingStyle($stmts); + if (null === $style) { + // not namespaced, nothing to do + return $stmts; + } elseif ('brace' === $style) { + // For braced namespaces we only have to check that there are no invalid statements between the namespaces + $afterFirstNamespace = \false; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $afterFirstNamespace = \true; + } elseif (!$stmt instanceof Node\Stmt\HaltCompiler && !$stmt instanceof Node\Stmt\Nop && $afterFirstNamespace && !$hasErrored) { + $this->emitError(new Error('No code may exist outside of namespace {}', $stmt->getAttributes())); + $hasErrored = \true; + // Avoid one error for every statement + } + } + return $stmts; + } else { + // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts + $resultStmts = []; + $targetStmts =& $resultStmts; + $lastNs = null; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + if ($stmt->stmts === null) { + $stmt->stmts = []; + $targetStmts =& $stmt->stmts; + $resultStmts[] = $stmt; + } else { + // This handles the invalid case of mixed style namespaces + $resultStmts[] = $stmt; + $targetStmts =& $resultStmts; + } + $lastNs = $stmt; + } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { + // __halt_compiler() is not moved into the namespace + $resultStmts[] = $stmt; + } else { + $targetStmts[] = $stmt; + } + } + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + return $resultStmts; + } + } + private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) + { + // We moved the statements into the namespace node, as such the end of the namespace node + // needs to be extended to the end of the statements. + if (empty($stmt->stmts)) { + return; + } + // We only move the builtin end attributes here. This is the best we can do with the + // knowledge we have. + $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; + $lastStmt = $stmt->stmts[\count($stmt->stmts) - 1]; + foreach ($endAttributes as $endAttribute) { + if ($lastStmt->hasAttribute($endAttribute)) { + $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); + } + } + } + /** + * Determine namespacing style (semicolon or brace) + * + * @param Node[] $stmts Top-level statements. + * + * @return null|string One of "semicolon", "brace" or null (no namespaces) + */ + private function getNamespacingStyle(array $stmts) + { + $style = null; + $hasNotAllowedStmts = \false; + foreach ($stmts as $i => $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; + if (null === $style) { + $style = $currentStyle; + if ($hasNotAllowedStmts) { + $this->emitError(new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine())); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine())); + // Treat like semicolon style for namespace normalization + return 'semicolon'; + } + continue; + } + /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ + if ($stmt instanceof Node\Stmt\Declare_ || $stmt instanceof Node\Stmt\HaltCompiler || $stmt instanceof Node\Stmt\Nop) { + continue; + } + /* There may be a hashbang line at the very start of the file */ + if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && \preg_match('/\\A#!.*\\r?\\n\\z/', $stmt->value)) { + continue; + } + /* Everything else if forbidden before namespace declarations */ + $hasNotAllowedStmts = \true; + } + return $style; + } + /** + * Fix up parsing of static property calls in PHP 5. + * + * In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is + * interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the + * latter as the former initially and this method fixes the AST into the correct form when we + * encounter the "()". + * + * @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop + * @param Node\Arg[] $args + * @param array $attributes + * + * @return Expr\StaticCall + */ + protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall + { + if ($prop instanceof Node\Expr\StaticPropertyFetch) { + $name = $prop->name instanceof VarLikeIdentifier ? $prop->name->toString() : $prop->name; + $var = new Expr\Variable($name, $prop->name->getAttributes()); + return new Expr\StaticCall($prop->class, $var, $args, $attributes); + } elseif ($prop instanceof Node\Expr\ArrayDimFetch) { + $tmp = $prop; + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + } + /** @var Expr\StaticPropertyFetch $staticProp */ + $staticProp = $tmp->var; + // Set start attributes to attributes of innermost node + $tmp = $prop; + $this->fixupStartAttributes($tmp, $staticProp->name); + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + $this->fixupStartAttributes($tmp, $staticProp->name); + } + $name = $staticProp->name instanceof VarLikeIdentifier ? $staticProp->name->toString() : $staticProp->name; + $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes()); + return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes); + } else { + throw new \Exception(); + } + } + protected function fixupStartAttributes(Node $to, Node $from) + { + $startAttributes = ['startLine', 'startFilePos', 'startTokenPos']; + foreach ($startAttributes as $startAttribute) { + if ($from->hasAttribute($startAttribute)) { + $to->setAttribute($startAttribute, $from->getAttribute($startAttribute)); + } + } + } + protected function handleBuiltinTypes(Name $name) + { + $builtinTypes = ['bool' => \true, 'int' => \true, 'float' => \true, 'string' => \true, 'iterable' => \true, 'void' => \true, 'object' => \true, 'null' => \true, 'false' => \true, 'mixed' => \true, 'never' => \true, 'true' => \true]; + if (!$name->isUnqualified()) { + return $name; + } + $lowerName = $name->toLowerString(); + if (!isset($builtinTypes[$lowerName])) { + return $name; + } + return new Node\Identifier($lowerName, $name->getAttributes()); + } + /** + * Get combined start and end attributes at a stack location + * + * @param int $pos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $pos) : array + { + return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos]; + } + protected function getFloatCastKind(string $cast) : int + { + $cast = \strtolower($cast); + if (\strpos($cast, 'float') !== \false) { + return Double::KIND_FLOAT; + } + if (\strpos($cast, 'real') !== \false) { + return Double::KIND_REAL; + } + return Double::KIND_DOUBLE; + } + protected function parseLNumber($str, $attributes, $allowInvalidOctal = \false) + { + try { + return LNumber::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new LNumber(0, $attributes); + } + } + /** + * Parse a T_NUM_STRING token into either an integer or string node. + * + * @param string $str Number string + * @param array $attributes Attributes + * + * @return LNumber|String_ Integer or string node. + */ + protected function parseNumString(string $str, array $attributes) + { + if (!\preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { + return new String_($str, $attributes); + } + $num = +$str; + if (!\is_int($num)) { + return new String_($str, $attributes); + } + return new LNumber($num, $attributes); + } + protected function stripIndentation(string $string, int $indentLen, string $indentChar, bool $newlineAtStart, bool $newlineAtEnd, array $attributes) + { + if ($indentLen === 0) { + return $string; + } + $start = $newlineAtStart ? '(?:(?<=\\n)|\\A)' : '(?<=\\n)'; + $end = $newlineAtEnd ? '(?:(?=[\\r\\n])|\\z)' : '(?=[\\r\\n])'; + $regex = '/' . $start . '([ \\t]*)(' . $end . ')?/'; + return \preg_replace_callback($regex, function ($matches) use($indentLen, $indentChar, $attributes) { + $prefix = \substr($matches[1], 0, $indentLen); + if (\false !== \strpos($prefix, $indentChar === " " ? "\t" : " ")) { + $this->emitError(new Error('Invalid indentation - tabs and spaces cannot be mixed', $attributes)); + } elseif (\strlen($prefix) < $indentLen && !isset($matches[2])) { + $this->emitError(new Error('Invalid body indentation level ' . '(expecting an indentation level of at least ' . $indentLen . ')', $attributes)); + } + return \substr($matches[0], \strlen($prefix)); + }, $string); + } + protected function parseDocString(string $startToken, $contents, string $endToken, array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape) + { + $kind = \strpos($startToken, "'") === \false ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; + $regex = '/\\A[bB]?<<<[ \\t]*[\'"]?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)[\'"]?(?:\\r\\n|\\n|\\r)\\z/'; + $result = \preg_match($regex, $startToken, $matches); + \assert($result === 1); + $label = $matches[1]; + $result = \preg_match('/\\A[ \\t]*/', $endToken, $matches); + \assert($result === 1); + $indentation = $matches[0]; + $attributes['kind'] = $kind; + $attributes['docLabel'] = $label; + $attributes['docIndentation'] = $indentation; + $indentHasSpaces = \false !== \strpos($indentation, " "); + $indentHasTabs = \false !== \strpos($indentation, "\t"); + if ($indentHasSpaces && $indentHasTabs) { + $this->emitError(new Error('Invalid indentation - tabs and spaces cannot be mixed', $endTokenAttributes)); + // Proceed processing as if this doc string is not indented + $indentation = ''; + } + $indentLen = \strlen($indentation); + $indentChar = $indentHasSpaces ? " " : "\t"; + if (\is_string($contents)) { + if ($contents === '') { + return new String_('', $attributes); + } + $contents = $this->stripIndentation($contents, $indentLen, $indentChar, \true, \true, $attributes); + $contents = \preg_replace('~(\\r\\n|\\n|\\r)\\z~', '', $contents); + if ($kind === String_::KIND_HEREDOC) { + $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); + } + return new String_($contents, $attributes); + } else { + \assert(\count($contents) > 0); + if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) { + // If there is no leading encapsed string part, pretend there is an empty one + $this->stripIndentation('', $indentLen, $indentChar, \true, \false, $contents[0]->getAttributes()); + } + $newContents = []; + foreach ($contents as $i => $part) { + if ($part instanceof Node\Scalar\EncapsedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation($part->value, $indentLen, $indentChar, $i === 0, $isLast, $part->getAttributes()); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ($isLast) { + $part->value = \preg_replace('~(\\r\\n|\\n|\\r)\\z~', '', $part->value); + } + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new Encapsed($newContents, $attributes); + } + } + /** + * Create attributes for a zero-length common-capturing nop. + * + * @param Comment[] $comments + * @return array + */ + protected function createCommentNopAttributes(array $comments) + { + $comment = $comments[\count($comments) - 1]; + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + $attributes = ['comments' => $comments]; + if (-1 !== $commentEndLine) { + $attributes['startLine'] = $commentEndLine; + $attributes['endLine'] = $commentEndLine; + } + if (-1 !== $commentEndFilePos) { + $attributes['startFilePos'] = $commentEndFilePos + 1; + $attributes['endFilePos'] = $commentEndFilePos; + } + if (-1 !== $commentEndTokenPos) { + $attributes['startTokenPos'] = $commentEndTokenPos + 1; + $attributes['endTokenPos'] = $commentEndTokenPos; + } + return $attributes; + } + protected function checkClassModifier($a, $b, $modifierPos) + { + try { + Class_::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + protected function checkModifier($a, $b, $modifierPos) + { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Class_::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + protected function checkParam(Param $node) + { + if ($node->variadic && null !== $node->default) { + $this->emitError(new Error('Variadic parameter cannot have a default value', $node->default->getAttributes())); + } + } + protected function checkTryCatch(TryCatch $node) + { + if (empty($node->catches) && null === $node->finally) { + $this->emitError(new Error('Cannot use try without catch or finally', $node->getAttributes())); + } + } + protected function checkNamespace(Namespace_ $node) + { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error('Namespace declarations cannot be nested', $stmt->getAttributes())); + } + } + } + } + private function checkClassName($name, $namePos) + { + if (null !== $name && $name->isSpecialClassName()) { + $this->emitError(new Error(\sprintf('Cannot use \'%s\' as class name as it is reserved', $name), $this->getAttributesAt($namePos))); + } + } + private function checkImplementedInterfaces(array $interfaces) + { + foreach ($interfaces as $interface) { + if ($interface->isSpecialClassName()) { + $this->emitError(new Error(\sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), $interface->getAttributes())); + } + } + } + protected function checkClass(Class_ $node, $namePos) + { + $this->checkClassName($node->name, $namePos); + if ($node->extends && $node->extends->isSpecialClassName()) { + $this->emitError(new Error(\sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), $node->extends->getAttributes())); + } + $this->checkImplementedInterfaces($node->implements); + } + protected function checkInterface(Interface_ $node, $namePos) + { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->extends); + } + protected function checkEnum(Enum_ $node, $namePos) + { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->implements); + } + protected function checkClassMethod(ClassMethod $node, $modifierPos) + { + if ($node->flags & Class_::MODIFIER_STATIC) { + switch ($node->name->toLowerString()) { + case '__construct': + $this->emitError(new Error(\sprintf('Constructor %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); + break; + case '__destruct': + $this->emitError(new Error(\sprintf('Destructor %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); + break; + case '__clone': + $this->emitError(new Error(\sprintf('Clone method %s() cannot be static', $node->name), $this->getAttributesAt($modifierPos))); + break; + } + } + if ($node->flags & Class_::MODIFIER_READONLY) { + $this->emitError(new Error(\sprintf('Method %s() cannot be readonly', $node->name), $this->getAttributesAt($modifierPos))); + } + } + protected function checkClassConst(ClassConst $node, $modifierPos) + { + if ($node->flags & Class_::MODIFIER_STATIC) { + $this->emitError(new Error("Cannot use 'static' as constant modifier", $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error("Cannot use 'abstract' as constant modifier", $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_READONLY) { + $this->emitError(new Error("Cannot use 'readonly' as constant modifier", $this->getAttributesAt($modifierPos))); + } + } + protected function checkProperty(Property $node, $modifierPos) + { + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error('Properties cannot be declared abstract', $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_FINAL) { + $this->emitError(new Error('Properties cannot be declared final', $this->getAttributesAt($modifierPos))); + } + } + protected function checkUseUse(UseUse $node, $namePos) + { + if ($node->alias && $node->alias->isSpecialClassName()) { + $this->emitError(new Error(\sprintf('Cannot use %s as %s because \'%2$s\' is a special class name', $node->name, $node->alias), $this->getAttributesAt($namePos))); + } + } +} +pAttrGroups($node->attrGroups, \true) . $this->pModifiers($node->flags) . ($node->type ? $this->p($node->type) . ' ' : '') . ($node->byRef ? '&' : '') . ($node->variadic ? '...' : '') . $this->p($node->var) . ($node->default ? ' = ' . $this->p($node->default) : ''); + } + protected function pArg(Node\Arg $node) + { + return ($node->name ? $node->name->toString() . ': ' : '') . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value); + } + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) + { + return '...'; + } + protected function pConst(Node\Const_ $node) + { + return $node->name . ' = ' . $this->p($node->value); + } + protected function pNullableType(Node\NullableType $node) + { + return '?' . $this->p($node->type); + } + protected function pUnionType(Node\UnionType $node) + { + $types = []; + foreach ($node->types as $typeNode) { + if ($typeNode instanceof Node\IntersectionType) { + $types[] = '(' . $this->p($typeNode) . ')'; + continue; + } + $types[] = $this->p($typeNode); + } + return \implode('|', $types); + } + protected function pIntersectionType(Node\IntersectionType $node) + { + return $this->pImplode($node->types, '&'); + } + protected function pIdentifier(Node\Identifier $node) + { + return $node->name; + } + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) + { + return '$' . $node->name; + } + protected function pAttribute(Node\Attribute $node) + { + return $this->p($node->name) . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + protected function pAttributeGroup(Node\AttributeGroup $node) + { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + // Names + protected function pName(Name $node) + { + return \implode('\\', $node->parts); + } + protected function pName_FullyQualified(Name\FullyQualified $node) + { + return '\\' . \implode('\\', $node->parts); + } + protected function pName_Relative(Name\Relative $node) + { + return 'namespace\\' . \implode('\\', $node->parts); + } + // Magic Constants + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node) + { + return '__CLASS__'; + } + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node) + { + return '__DIR__'; + } + protected function pScalar_MagicConst_File(MagicConst\File $node) + { + return '__FILE__'; + } + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node) + { + return '__FUNCTION__'; + } + protected function pScalar_MagicConst_Line(MagicConst\Line $node) + { + return '__LINE__'; + } + protected function pScalar_MagicConst_Method(MagicConst\Method $node) + { + return '__METHOD__'; + } + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) + { + return '__NAMESPACE__'; + } + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) + { + return '__TRAIT__'; + } + // Scalars + protected function pScalar_String(Scalar\String_ $node) + { + $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); + switch ($kind) { + case Scalar\String_::KIND_NOWDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + if ($node->value === '') { + return "<<<'{$label}'\n{$label}" . $this->docStringEndToken; + } + return "<<<'{$label}'\n{$node->value}\n{$label}" . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + if ($node->value === '') { + return "<<<{$label}\n{$label}" . $this->docStringEndToken; + } + $escaped = $this->escapeString($node->value, null); + return "<<<{$label}\n" . $escaped . "\n{$label}" . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + protected function pScalar_Encapsed(Scalar\Encapsed $node) + { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + if (\count($node->parts) === 1 && $node->parts[0] instanceof Scalar\EncapsedStringPart && $node->parts[0]->value === '') { + return "<<<{$label}\n{$label}" . $this->docStringEndToken; + } + return "<<<{$label}\n" . $this->pEncapsList($node->parts, null) . "\n{$label}" . $this->docStringEndToken; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + protected function pScalar_LNumber(Scalar\LNumber $node) + { + if ($node->value === -\PHP_INT_MAX - 1) { + // PHP_INT_MIN cannot be represented as a literal, + // because the sign is not part of the literal + return '(-' . \PHP_INT_MAX . '-1)'; + } + $kind = $node->getAttribute('kind', Scalar\LNumber::KIND_DEC); + if (Scalar\LNumber::KIND_DEC === $kind) { + return (string) $node->value; + } + if ($node->value < 0) { + $sign = '-'; + $str = (string) -$node->value; + } else { + $sign = ''; + $str = (string) $node->value; + } + switch ($kind) { + case Scalar\LNumber::KIND_BIN: + return $sign . '0b' . \base_convert($str, 10, 2); + case Scalar\LNumber::KIND_OCT: + return $sign . '0' . \base_convert($str, 10, 8); + case Scalar\LNumber::KIND_HEX: + return $sign . '0x' . \base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + protected function pScalar_DNumber(Scalar\DNumber $node) + { + if (!\is_finite($node->value)) { + if ($node->value === \INF) { + return '\\INF'; + } elseif ($node->value === -\INF) { + return '-\\INF'; + } else { + return '\\NAN'; + } + } + // Try to find a short full-precision representation + $stringValue = \sprintf('%.16G', $node->value); + if ($node->value !== (double) $stringValue) { + $stringValue = \sprintf('%.17G', $node->value); + } + // %G is locale dependent and there exists no locale-independent alternative. We don't want + // mess with switching locales here, so let's assume that a comma is the only non-standard + // decimal separator we may encounter... + $stringValue = \str_replace(',', '.', $stringValue); + // ensure that number is really printed as float + return \preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; + } + protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) + { + throw new \LogicException('Cannot directly print EncapsedStringPart'); + } + // Assignments + protected function pExpr_Assign(Expr\Assign $node) + { + return $this->pInfixOp(Expr\Assign::class, $node->var, ' = ', $node->expr); + } + protected function pExpr_AssignRef(Expr\AssignRef $node) + { + return $this->pInfixOp(Expr\AssignRef::class, $node->var, ' =& ', $node->expr); + } + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) + { + return $this->pInfixOp(AssignOp\Plus::class, $node->var, ' += ', $node->expr); + } + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) + { + return $this->pInfixOp(AssignOp\Minus::class, $node->var, ' -= ', $node->expr); + } + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) + { + return $this->pInfixOp(AssignOp\Mul::class, $node->var, ' *= ', $node->expr); + } + protected function pExpr_AssignOp_Div(AssignOp\Div $node) + { + return $this->pInfixOp(AssignOp\Div::class, $node->var, ' /= ', $node->expr); + } + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) + { + return $this->pInfixOp(AssignOp\Concat::class, $node->var, ' .= ', $node->expr); + } + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) + { + return $this->pInfixOp(AssignOp\Mod::class, $node->var, ' %= ', $node->expr); + } + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) + { + return $this->pInfixOp(AssignOp\BitwiseAnd::class, $node->var, ' &= ', $node->expr); + } + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) + { + return $this->pInfixOp(AssignOp\BitwiseOr::class, $node->var, ' |= ', $node->expr); + } + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) + { + return $this->pInfixOp(AssignOp\BitwiseXor::class, $node->var, ' ^= ', $node->expr); + } + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) + { + return $this->pInfixOp(AssignOp\ShiftLeft::class, $node->var, ' <<= ', $node->expr); + } + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) + { + return $this->pInfixOp(AssignOp\ShiftRight::class, $node->var, ' >>= ', $node->expr); + } + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) + { + return $this->pInfixOp(AssignOp\Pow::class, $node->var, ' **= ', $node->expr); + } + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node) + { + return $this->pInfixOp(AssignOp\Coalesce::class, $node->var, ' ??= ', $node->expr); + } + // Binary expressions + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) + { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right); + } + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) + { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right); + } + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) + { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right); + } + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node) + { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right); + } + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) + { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right); + } + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) + { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right); + } + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) + { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right); + } + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) + { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right); + } + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) + { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right); + } + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) + { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right); + } + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) + { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right); + } + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) + { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right); + } + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) + { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right); + } + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) + { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right); + } + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) + { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right); + } + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) + { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right); + } + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) + { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right); + } + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) + { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right); + } + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) + { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right); + } + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) + { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right); + } + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) + { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right); + } + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) + { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right); + } + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) + { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right); + } + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) + { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right); + } + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) + { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right); + } + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) + { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right); + } + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) + { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right); + } + protected function pExpr_Instanceof(Expr\Instanceof_ $node) + { + list($precedence, $associativity) = $this->precedenceMap[Expr\Instanceof_::class]; + return $this->pPrec($node->expr, $precedence, $associativity, -1) . ' instanceof ' . $this->pNewVariable($node->class); + } + // Unary expressions + protected function pExpr_BooleanNot(Expr\BooleanNot $node) + { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr); + } + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node) + { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr); + } + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node) + { + if ($node->expr instanceof Expr\UnaryMinus || $node->expr instanceof Expr\PreDec) { + // Enforce -(-$expr) instead of --$expr + return '-(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr); + } + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node) + { + if ($node->expr instanceof Expr\UnaryPlus || $node->expr instanceof Expr\PreInc) { + // Enforce +(+$expr) instead of ++$expr + return '+(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr); + } + protected function pExpr_PreInc(Expr\PreInc $node) + { + return $this->pPrefixOp(Expr\PreInc::class, '++', $node->var); + } + protected function pExpr_PreDec(Expr\PreDec $node) + { + return $this->pPrefixOp(Expr\PreDec::class, '--', $node->var); + } + protected function pExpr_PostInc(Expr\PostInc $node) + { + return $this->pPostfixOp(Expr\PostInc::class, $node->var, '++'); + } + protected function pExpr_PostDec(Expr\PostDec $node) + { + return $this->pPostfixOp(Expr\PostDec::class, $node->var, '--'); + } + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) + { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr); + } + protected function pExpr_YieldFrom(Expr\YieldFrom $node) + { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr); + } + protected function pExpr_Print(Expr\Print_ $node) + { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr); + } + // Casts + protected function pExpr_Cast_Int(Cast\Int_ $node) + { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr); + } + protected function pExpr_Cast_Double(Cast\Double $node) + { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } elseif ($kind === Cast\Double::KIND_REAL) { + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr); + } + protected function pExpr_Cast_String(Cast\String_ $node) + { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr); + } + protected function pExpr_Cast_Array(Cast\Array_ $node) + { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr); + } + protected function pExpr_Cast_Object(Cast\Object_ $node) + { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr); + } + protected function pExpr_Cast_Bool(Cast\Bool_ $node) + { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr); + } + protected function pExpr_Cast_Unset(Cast\Unset_ $node) + { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr); + } + // Function calls and similar constructs + protected function pExpr_FuncCall(Expr\FuncCall $node) + { + return $this->pCallLhs($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + protected function pExpr_MethodCall(Expr\MethodCall $node) + { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node) + { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + protected function pExpr_StaticCall(Expr\StaticCall $node) + { + return $this->pDereferenceLhs($node->class) . '::' . ($node->name instanceof Expr ? $node->name instanceof Expr\Variable ? $this->p($node->name) : '{' . $this->p($node->name) . '}' : $node->name) . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + protected function pExpr_Empty(Expr\Empty_ $node) + { + return 'empty(' . $this->p($node->expr) . ')'; + } + protected function pExpr_Isset(Expr\Isset_ $node) + { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + protected function pExpr_Eval(Expr\Eval_ $node) + { + return 'eval(' . $this->p($node->expr) . ')'; + } + protected function pExpr_Include(Expr\Include_ $node) + { + static $map = [Expr\Include_::TYPE_INCLUDE => 'include', Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', Expr\Include_::TYPE_REQUIRE => 'require', Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once']; + return $map[$node->type] . ' ' . $this->p($node->expr); + } + protected function pExpr_List(Expr\List_ $node) + { + return 'list(' . $this->pCommaSeparated($node->items) . ')'; + } + // Other + protected function pExpr_Error(Expr\Error $node) + { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + protected function pExpr_Variable(Expr\Variable $node) + { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + protected function pExpr_Array(Expr\Array_ $node) + { + $syntax = $node->getAttribute('kind', $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); + if ($syntax === Expr\Array_::KIND_SHORT) { + return '[' . $this->pMaybeMultiline($node->items, \true) . ']'; + } else { + return 'array(' . $this->pMaybeMultiline($node->items, \true) . ')'; + } + } + protected function pExpr_ArrayItem(Expr\ArrayItem $node) + { + return (null !== $node->key ? $this->p($node->key) . ' => ' : '') . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value); + } + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) + { + return $this->pDereferenceLhs($node->var) . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + protected function pExpr_ConstFetch(Expr\ConstFetch $node) + { + return $this->p($node->name); + } + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) + { + return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name); + } + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) + { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node) + { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) + { + return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + protected function pExpr_ShellExec(Expr\ShellExec $node) + { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + protected function pExpr_Closure(Expr\Closure $node) + { + return $this->pAttrGroups($node->attrGroups, \true) . ($node->static ? 'static ' : '') . 'function ' . ($node->byRef ? '&' : '') . '(' . $this->pCommaSeparated($node->params) . ')' . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '') . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pExpr_Match(Expr\Match_ $node) + { + return 'match (' . $this->p($node->cond) . ') {' . $this->pCommaSeparatedMultiline($node->arms, \true) . $this->nl . '}'; + } + protected function pMatchArm(Node\MatchArm $node) + { + return ($node->conds ? $this->pCommaSeparated($node->conds) : 'default') . ' => ' . $this->p($node->body); + } + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) + { + return $this->pAttrGroups($node->attrGroups, \true) . ($node->static ? 'static ' : '') . 'fn' . ($node->byRef ? '&' : '') . '(' . $this->pCommaSeparated($node->params) . ')' . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . ' => ' . $this->p($node->expr); + } + protected function pExpr_ClosureUse(Expr\ClosureUse $node) + { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + protected function pExpr_New(Expr\New_ $node) + { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewVariable($node->class) . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + protected function pExpr_Clone(Expr\Clone_ $node) + { + return 'clone ' . $this->p($node->expr); + } + protected function pExpr_Ternary(Expr\Ternary $node) + { + // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. + // this is okay because the part between ? and : never needs parentheses. + return $this->pInfixOp(Expr\Ternary::class, $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else); + } + protected function pExpr_Exit(Expr\Exit_ $node) + { + $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); + return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); + } + protected function pExpr_Throw(Expr\Throw_ $node) + { + return 'throw ' . $this->p($node->expr); + } + protected function pExpr_Yield(Expr\Yield_ $node) + { + if ($node->value === null) { + return 'yield'; + } else { + // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary + return '(yield ' . ($node->key !== null ? $this->p($node->key) . ' => ' : '') . $this->p($node->value) . ')'; + } + } + // Declarations + protected function pStmt_Namespace(Stmt\Namespace_ $node) + { + if ($this->canUseSemicolonNamespaces) { + return 'namespace ' . $this->p($node->name) . ';' . $this->nl . $this->pStmts($node->stmts, \false); + } else { + return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + } + protected function pStmt_Use(Stmt\Use_ $node) + { + return 'use ' . $this->pUseType($node->type) . $this->pCommaSeparated($node->uses) . ';'; + } + protected function pStmt_GroupUse(Stmt\GroupUse $node) + { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) . '\\{' . $this->pCommaSeparated($node->uses) . '};'; + } + protected function pStmt_UseUse(Stmt\UseUse $node) + { + return $this->pUseType($node->type) . $this->p($node->name) . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + protected function pUseType($type) + { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + protected function pStmt_Interface(Stmt\Interface_ $node) + { + return $this->pAttrGroups($node->attrGroups) . 'interface ' . $node->name . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Enum(Stmt\Enum_ $node) + { + return $this->pAttrGroups($node->attrGroups) . 'enum ' . $node->name . ($node->scalarType ? " : {$node->scalarType}" : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Class(Stmt\Class_ $node) + { + return $this->pClassCommon($node, ' ' . $node->name); + } + protected function pStmt_Trait(Stmt\Trait_ $node) + { + return $this->pAttrGroups($node->attrGroups) . 'trait ' . $node->name . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_EnumCase(Stmt\EnumCase $node) + { + return $this->pAttrGroups($node->attrGroups) . 'case ' . $node->name . ($node->expr ? ' = ' . $this->p($node->expr) : '') . ';'; + } + protected function pStmt_TraitUse(Stmt\TraitUse $node) + { + return 'use ' . $this->pCommaSeparated($node->traits) . (empty($node->adaptations) ? ';' : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) + { + return $this->p($node->trait) . '::' . $node->method . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) + { + return (null !== $node->trait ? $this->p($node->trait) . '::' : '') . $node->method . ' as' . (null !== $node->newModifier ? ' ' . \rtrim($this->pModifiers($node->newModifier), ' ') : '') . (null !== $node->newName ? ' ' . $node->newName : '') . ';'; + } + protected function pStmt_Property(Stmt\Property $node) + { + return $this->pAttrGroups($node->attrGroups) . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) . ($node->type ? $this->p($node->type) . ' ' : '') . $this->pCommaSeparated($node->props) . ';'; + } + protected function pStmt_PropertyProperty(Stmt\PropertyProperty $node) + { + return '$' . $node->name . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + protected function pStmt_ClassMethod(Stmt\ClassMethod $node) + { + return $this->pAttrGroups($node->attrGroups) . $this->pModifiers($node->flags) . 'function ' . ($node->byRef ? '&' : '') . $node->name . '(' . $this->pMaybeMultiline($node->params) . ')' . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . (null !== $node->stmts ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + protected function pStmt_ClassConst(Stmt\ClassConst $node) + { + return $this->pAttrGroups($node->attrGroups) . $this->pModifiers($node->flags) . 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + protected function pStmt_Function(Stmt\Function_ $node) + { + return $this->pAttrGroups($node->attrGroups) . 'function ' . ($node->byRef ? '&' : '') . $node->name . '(' . $this->pCommaSeparated($node->params) . ')' . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Const(Stmt\Const_ $node) + { + return 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + protected function pStmt_Declare(Stmt\Declare_ $node) + { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + protected function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) + { + return $node->key . '=' . $this->p($node->value); + } + // Control flow + protected function pStmt_If(Stmt\If_ $node) + { + return 'if (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}' . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') . (null !== $node->else ? ' ' . $this->p($node->else) : ''); + } + protected function pStmt_ElseIf(Stmt\ElseIf_ $node) + { + return 'elseif (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Else(Stmt\Else_ $node) + { + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_For(Stmt\For_ $node) + { + return 'for (' . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') . $this->pCommaSeparated($node->loop) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Foreach(Stmt\Foreach_ $node) + { + return 'foreach (' . $this->p($node->expr) . ' as ' . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_While(Stmt\While_ $node) + { + return 'while (' . $this->p($node->cond) . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Do(Stmt\Do_ $node) + { + return 'do {' . $this->pStmts($node->stmts) . $this->nl . '} while (' . $this->p($node->cond) . ');'; + } + protected function pStmt_Switch(Stmt\Switch_ $node) + { + return 'switch (' . $this->p($node->cond) . ') {' . $this->pStmts($node->cases) . $this->nl . '}'; + } + protected function pStmt_Case(Stmt\Case_ $node) + { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' . $this->pStmts($node->stmts); + } + protected function pStmt_TryCatch(Stmt\TryCatch $node) + { + return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); + } + protected function pStmt_Catch(Stmt\Catch_ $node) + { + return 'catch (' . $this->pImplode($node->types, '|') . ($node->var !== null ? ' ' . $this->p($node->var) : '') . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Finally(Stmt\Finally_ $node) + { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pStmt_Break(Stmt\Break_ $node) + { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + protected function pStmt_Continue(Stmt\Continue_ $node) + { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + protected function pStmt_Return(Stmt\Return_ $node) + { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + protected function pStmt_Throw(Stmt\Throw_ $node) + { + return 'throw ' . $this->p($node->expr) . ';'; + } + protected function pStmt_Label(Stmt\Label $node) + { + return $node->name . ':'; + } + protected function pStmt_Goto(Stmt\Goto_ $node) + { + return 'goto ' . $node->name . ';'; + } + // Other + protected function pStmt_Expression(Stmt\Expression $node) + { + return $this->p($node->expr) . ';'; + } + protected function pStmt_Echo(Stmt\Echo_ $node) + { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + protected function pStmt_Static(Stmt\Static_ $node) + { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + protected function pStmt_Global(Stmt\Global_ $node) + { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + protected function pStmt_StaticVar(Stmt\StaticVar $node) + { + return $this->p($node->var) . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + protected function pStmt_Unset(Stmt\Unset_ $node) + { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + protected function pStmt_InlineHTML(Stmt\InlineHTML $node) + { + $newline = $node->getAttribute('hasLeadingNewline', \true) ? "\n" : ''; + return '?>' . $newline . $node->value . 'remaining; + } + protected function pStmt_Nop(Stmt\Nop $node) + { + return ''; + } + // Helpers + protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) + { + return $this->pAttrGroups($node->attrGroups, $node->name === null) . $this->pModifiers($node->flags) . 'class' . $afterClassToken . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + protected function pObjectProperty($node) + { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + return $node; + } + } + protected function pEncapsList(array $encapsList, $quote) + { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Scalar\EncapsedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + return $return; + } + protected function pSingleQuotedString(string $string) + { + return '\'' . \addcslashes($string, '\'\\') . '\''; + } + protected function escapeString($string, $quote) + { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = \addcslashes($string, "\t\f\v\$\\"); + } else { + $escaped = \addcslashes($string, "\n\r\t\f\v\$" . $quote . "\\"); + } + // Escape control characters and non-UTF-8 characters. + // Regex based on https://stackoverflow.com/a/11709412/385378. + $regex = '/( + [\\x00-\\x08\\x0E-\\x1F] # Control characters + | [\\xC0-\\xC1] # Invalid UTF-8 Bytes + | [\\xF5-\\xFF] # Invalid UTF-8 Bytes + | \\xE0(?=[\\x80-\\x9F]) # Overlong encoding of prior code point + | \\xF0(?=[\\x80-\\x8F]) # Overlong encoding of prior code point + | [\\xC2-\\xDF](?![\\x80-\\xBF]) # Invalid UTF-8 Sequence Start + | [\\xE0-\\xEF](?![\\x80-\\xBF]{2}) # Invalid UTF-8 Sequence Start + | [\\xF0-\\xF4](?![\\x80-\\xBF]{3}) # Invalid UTF-8 Sequence Start + | (?<=[\\x00-\\x7F\\xF5-\\xFF])[\\x80-\\xBF] # Invalid UTF-8 Sequence Middle + | (? $part) { + $atStart = $i === 0; + $atEnd = $i === \count($parts) - 1; + if ($part instanceof Scalar\EncapsedStringPart && $this->containsEndLabel($part->value, $label, $atStart, $atEnd)) { + return \true; + } + } + return \false; + } + protected function pDereferenceLhs(Node $node) + { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + protected function pCallLhs(Node $node) + { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + protected function pNewVariable(Node $node) + { + // TODO: This is not fully accurate. + return $this->pDereferenceLhs($node); + } + /** + * @param Node[] $nodes + * @return bool + */ + protected function hasNodeWithComments(array $nodes) + { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return \true; + } + } + return \false; + } + protected function pMaybeMultiline(array $nodes, bool $trailingComma = \false) + { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + protected function pAttrGroups(array $nodes, bool $inline = \false) : string + { + $result = ''; + $sep = $inline ? ' ' : $this->nl; + foreach ($nodes as $node) { + $result .= $this->p($node) . $sep; + } + return $result; + } +} + [0, 1], + Expr\BitwiseNot::class => [10, 1], + Expr\PreInc::class => [10, 1], + Expr\PreDec::class => [10, 1], + Expr\PostInc::class => [10, -1], + Expr\PostDec::class => [10, -1], + Expr\UnaryPlus::class => [10, 1], + Expr\UnaryMinus::class => [10, 1], + Cast\Int_::class => [10, 1], + Cast\Double::class => [10, 1], + Cast\String_::class => [10, 1], + Cast\Array_::class => [10, 1], + Cast\Object_::class => [10, 1], + Cast\Bool_::class => [10, 1], + Cast\Unset_::class => [10, 1], + Expr\ErrorSuppress::class => [10, 1], + Expr\Instanceof_::class => [20, 0], + Expr\BooleanNot::class => [30, 1], + BinaryOp\Mul::class => [40, -1], + BinaryOp\Div::class => [40, -1], + BinaryOp\Mod::class => [40, -1], + BinaryOp\Plus::class => [50, -1], + BinaryOp\Minus::class => [50, -1], + BinaryOp\Concat::class => [50, -1], + BinaryOp\ShiftLeft::class => [60, -1], + BinaryOp\ShiftRight::class => [60, -1], + BinaryOp\Smaller::class => [70, 0], + BinaryOp\SmallerOrEqual::class => [70, 0], + BinaryOp\Greater::class => [70, 0], + BinaryOp\GreaterOrEqual::class => [70, 0], + BinaryOp\Equal::class => [80, 0], + BinaryOp\NotEqual::class => [80, 0], + BinaryOp\Identical::class => [80, 0], + BinaryOp\NotIdentical::class => [80, 0], + BinaryOp\Spaceship::class => [80, 0], + BinaryOp\BitwiseAnd::class => [90, -1], + BinaryOp\BitwiseXor::class => [100, -1], + BinaryOp\BitwiseOr::class => [110, -1], + BinaryOp\BooleanAnd::class => [120, -1], + BinaryOp\BooleanOr::class => [130, -1], + BinaryOp\Coalesce::class => [140, 1], + Expr\Ternary::class => [150, 0], + // parser uses %left for assignments, but they really behave as %right + Expr\Assign::class => [160, 1], + Expr\AssignRef::class => [160, 1], + AssignOp\Plus::class => [160, 1], + AssignOp\Minus::class => [160, 1], + AssignOp\Mul::class => [160, 1], + AssignOp\Div::class => [160, 1], + AssignOp\Concat::class => [160, 1], + AssignOp\Mod::class => [160, 1], + AssignOp\BitwiseAnd::class => [160, 1], + AssignOp\BitwiseOr::class => [160, 1], + AssignOp\BitwiseXor::class => [160, 1], + AssignOp\ShiftLeft::class => [160, 1], + AssignOp\ShiftRight::class => [160, 1], + AssignOp\Pow::class => [160, 1], + AssignOp\Coalesce::class => [160, 1], + Expr\YieldFrom::class => [165, 1], + Expr\Print_::class => [168, 1], + BinaryOp\LogicalAnd::class => [170, -1], + BinaryOp\LogicalXor::class => [180, -1], + BinaryOp\LogicalOr::class => [190, -1], + Expr\Include_::class => [200, -1], + ]; + /** @var int Current indentation level. */ + protected $indentLevel; + /** @var string Newline including current indentation. */ + protected $nl; + /** @var string Token placed at end of doc string to ensure it is followed by a newline. */ + protected $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected $canUseSemicolonNamespaces; + /** @var array Pretty printer options */ + protected $options; + /** @var TokenStream Original tokens for use in format-preserving pretty print */ + protected $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected $nodeListDiffer; + /** @var bool[] Map determining whether a certain character is a label character */ + protected $labelCharMap; + /** + * @var int[][] Map from token classes and subnode names to FIXUP_* constants. This is used + * during format-preserving prints to place additional parens/braces if necessary. + */ + protected $fixupMap; + /** + * @var int[][] Map from "{$node->getType()}->{$subNode}" to ['left' => $l, 'right' => $r], + * where $l and $r specify the token type that needs to be stripped when removing + * this node. + */ + protected $removalMap; + /** + * @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. + * $find is an optional token after which the insertion occurs. $extraLeft/Right + * are optionally added before/after the main insertions. + */ + protected $insertionMap; + /** + * @var string[] Map From "{$node->getType()}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected $listInsertionMap; + protected $emptyListInsertionMap; + /** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers + * should be reprinted. */ + protected $modifierChangeMap; + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. + * + * @param array $options Dictionary of formatting options + */ + public function __construct(array $options = []) + { + $this->docStringEndToken = '_DOC_STRING_END_' . \mt_rand(); + $defaultOptions = ['shortArraySyntax' => \false]; + $this->options = $options + $defaultOptions; + } + /** + * Reset pretty printing state. + */ + protected function resetState() + { + $this->indentLevel = 0; + $this->nl = "\n"; + $this->origTokens = null; + } + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level) + { + $this->indentLevel = $level; + $this->nl = "\n" . \str_repeat(' ', $level); + } + /** + * Increase indentation level. + */ + protected function indent() + { + $this->indentLevel += 4; + $this->nl .= ' '; + } + /** + * Decrease indentation level. + */ + protected function outdent() + { + \assert($this->indentLevel >= 4); + $this->indentLevel -= 4; + $this->nl = "\n" . \str_repeat(' ', $this->indentLevel); + } + /** + * Pretty prints an array of statements. + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrint(array $stmts) : string + { + $this->resetState(); + $this->preprocessNodes($stmts); + return \ltrim($this->handleMagicTokens($this->pStmts($stmts, \false))); + } + /** + * Pretty prints an expression. + * + * @param Expr $node Expression node + * + * @return string Pretty printed node + */ + public function prettyPrintExpr(Expr $node) : string + { + $this->resetState(); + return $this->handleMagicTokens($this->p($node)); + } + /** + * Pretty prints a file of statements (includes the opening prettyPrint($stmts); + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = \preg_replace('/^<\\?php\\s+\\?>\\n?/', '', $p); + } + if ($stmts[\count($stmts) - 1] instanceof Stmt\InlineHTML) { + $p = \preg_replace('/<\\?php$/', '', \rtrim($p)); + } + return $p; + } + /** + * Preprocesses the top-level nodes to initialize pretty printer state. + * + * @param Node[] $nodes Array of nodes + */ + protected function preprocessNodes(array $nodes) + { + /* We can use semicolon-namespaces unless there is a global namespace declaration */ + $this->canUseSemicolonNamespaces = \true; + foreach ($nodes as $node) { + if ($node instanceof Stmt\Namespace_ && null === $node->name) { + $this->canUseSemicolonNamespaces = \false; + break; + } + } + } + /** + * Handles (and removes) no-indent and doc-string-end tokens. + * + * @param string $str + * @return string + */ + protected function handleMagicTokens(string $str) : string + { + // Replace doc-string-end tokens with nothing or a newline + $str = \str_replace($this->docStringEndToken . ";\n", ";\n", $str); + $str = \str_replace($this->docStringEndToken, "\n", $str); + return $str; + } + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param Node[] $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes + * + * @return string Pretty printed statements + */ + protected function pStmts(array $nodes, bool $indent = \true) : string + { + if ($indent) { + $this->indent(); + } + $result = ''; + foreach ($nodes as $node) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + if ($node instanceof Stmt\Nop) { + continue; + } + } + $result .= $this->nl . $this->p($node); + } + if ($indent) { + $this->outdent(); + } + return $result; + } + /** + * Pretty-print an infix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param Node $leftNode Left-hand side node + * @param string $operatorString String representation of the operator + * @param Node $rightNode Right-hand side node + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp(string $class, Node $leftNode, string $operatorString, Node $rightNode) : string + { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $this->pPrec($leftNode, $precedence, $associativity, -1) . $operatorString . $this->pPrec($rightNode, $precedence, $associativity, 1); + } + /** + * Pretty-print a prefix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node) : string + { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $operatorString . $this->pPrec($node, $precedence, $associativity, 1); + } + /** + * Pretty-print a postfix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString) : string + { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString; + } + /** + * Prints an expression node with the least amount of parentheses necessary to preserve the meaning. + * + * @param Node $node Node to pretty print + * @param int $parentPrecedence Precedence of the parent operator + * @param int $parentAssociativity Associativity of parent operator + * (-1 is left, 0 is nonassoc, 1 is right) + * @param int $childPosition Position of the node relative to the operator + * (-1 is left, 1 is right) + * + * @return string The pretty printed node + */ + protected function pPrec(Node $node, int $parentPrecedence, int $parentAssociativity, int $childPosition) : string + { + $class = \get_class($node); + if (isset($this->precedenceMap[$class])) { + $childPrecedence = $this->precedenceMap[$class][0]; + if ($childPrecedence > $parentPrecedence || $parentPrecedence === $childPrecedence && $parentAssociativity !== $childPosition) { + return '(' . $this->p($node) . ')'; + } + } + return $this->p($node); + } + /** + * Pretty prints an array of nodes and implodes the printed values. + * + * @param Node[] $nodes Array of Nodes to be printed + * @param string $glue Character to implode with + * + * @return string Imploded pretty printed nodes + */ + protected function pImplode(array $nodes, string $glue = '') : string + { + $pNodes = []; + foreach ($nodes as $node) { + if (null === $node) { + $pNodes[] = ''; + } else { + $pNodes[] = $this->p($node); + } + } + return \implode($glue, $pNodes); + } + /** + * Pretty prints an array of nodes and implodes the printed values with commas. + * + * @param Node[] $nodes Array of Nodes to be printed + * + * @return string Comma separated pretty printed nodes + */ + protected function pCommaSeparated(array $nodes) : string + { + return $this->pImplode($nodes, ', '); + } + /** + * Pretty prints a comma-separated list of nodes in multiline style, including comments. + * + * The result includes a leading newline and one level of indentation (same as pStmts). + * + * @param Node[] $nodes Array of Nodes to be printed + * @param bool $trailingComma Whether to use a trailing comma + * + * @return string Comma separated pretty printed nodes in multiline style + */ + protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma) : string + { + $this->indent(); + $result = ''; + $lastIdx = \count($nodes) - 1; + foreach ($nodes as $idx => $node) { + if ($node !== null) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + $result .= $this->nl . $this->p($node); + } else { + $result .= $this->nl; + } + if ($trailingComma || $idx !== $lastIdx) { + $result .= ','; + } + } + $this->outdent(); + return $result; + } + /** + * Prints reformatted text of the passed comments. + * + * @param Comment[] $comments List of comments + * + * @return string Reformatted text of comments + */ + protected function pComments(array $comments) : string + { + $formattedComments = []; + foreach ($comments as $comment) { + $formattedComments[] = \str_replace("\n", $this->nl, $comment->getReformattedText()); + } + return \implode($this->nl, $formattedComments); + } + /** + * Perform a format-preserving pretty print of an AST. + * + * The format preservation is best effort. For some changes to the AST the formatting will not + * be preserved (at least not locally). + * + * In order to use this method a number of prerequisites must be satisfied: + * * The startTokenPos and endTokenPos attributes in the lexer must be enabled. + * * The CloningVisitor must be run on the AST prior to modification. + * * The original tokens must be provided, using the getTokens() method on the lexer. + * + * @param Node[] $stmts Modified AST with links to original AST + * @param Node[] $origStmts Original AST with token offset information + * @param array $origTokens Tokens of the original code + * + * @return string + */ + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens) : string + { + $this->initializeNodeListDiffer(); + $this->initializeLabelCharMap(); + $this->initializeFixupMap(); + $this->initializeRemovalMap(); + $this->initializeInsertionMap(); + $this->initializeListInsertionMap(); + $this->initializeEmptyListInsertionMap(); + $this->initializeModifierChangeMap(); + $this->resetState(); + $this->origTokens = new TokenStream($origTokens); + $this->preprocessNodes($stmts); + $pos = 0; + $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); + if (null !== $result) { + $result .= $this->origTokens->getTokenCode($pos, \count($origTokens), 0); + } else { + // Fallback + // TODO Add pStmts($stmts, \false); + } + return \ltrim($this->handleMagicTokens($result)); + } + protected function pFallback(Node $node) + { + return $this->{'p' . $node->getType()}($node); + } + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p(Node $node, $parentFormatPreserved = \false) : string + { + // No orig tokens means this is a normal pretty print without preservation of formatting + if (!$this->origTokens) { + return $this->{'p' . $node->getType()}($node); + } + /** @var Node $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node); + } + $class = \get_class($node); + \assert($class === \get_class($origNode)); + $startPos = $origNode->getStartTokenPos(); + $endPos = $origNode->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + $fallbackNode = $node; + if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { + // Normalize node structure of anonymous classes + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + } + // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting + // is not preserved, then we need to use the fallback code to make sure the tags are + // printed. + if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { + return $this->pFallback($fallbackNode); + } + $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); + $type = $node->getType(); + $fixupInfo = $this->fixupMap[$class] ?? null; + $result = ''; + $pos = $startPos; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->{$subNodeName}; + $origSubNode = $origNode->{$subNodeName}; + if (!$subNode instanceof Node && $subNode !== null || !$origSubNode instanceof Node && $origSubNode !== null) { + if ($subNode === $origSubNode) { + // Unchanged, can reuse old code + continue; + } + if (\is_array($subNode) && \is_array($origSubNode)) { + // Array subnode changed, we might be able to reconstruct it + $listResult = $this->pArray($subNode, $origSubNode, $pos, $indentAdjustment, $type, $subNodeName, $fixupInfo[$subNodeName] ?? null); + if (null === $listResult) { + return $this->pFallback($fallbackNode); + } + $result .= $listResult; + continue; + } + if (\is_int($subNode) && \is_int($origSubNode)) { + // Check if this is a modifier change + $key = $type . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode); + } + $findToken = $this->modifierChangeMap[$key]; + $result .= $this->pModifiers($subNode); + $pos = $this->origTokens->findRight($pos, $findToken); + continue; + } + // If a non-node, non-array subnode changed, we don't be able to do a partial + // reconstructions, as we don't have enough offset information. Pretty print the + // whole node instead. + return $this->pFallback($fallbackNode); + } + $extraLeft = ''; + $extraRight = ''; + if ($origSubNode !== null) { + $subStartPos = $origSubNode->getStartTokenPos(); + $subEndPos = $origSubNode->getEndTokenPos(); + \assert($subStartPos >= 0 && $subEndPos >= 0); + } else { + if ($subNode === null) { + // Both null, nothing to do + continue; + } + // A node has been inserted, check if we have insertion information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->insertionMap[$key])) { + return $this->pFallback($fallbackNode); + } + list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; + if (null !== $findToken) { + $subStartPos = $this->origTokens->findRight($pos, $findToken) + (int) (!$beforeToken); + } else { + $subStartPos = $pos; + } + if (null === $extraLeft && null !== $extraRight) { + // If inserting on the right only, skipping whitespace looks better + $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); + } + $subEndPos = $subStartPos - 1; + } + if (null === $subNode) { + // A node has been removed, check if we have removal information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->removalMap[$key])) { + return $this->pFallback($fallbackNode); + } + // Adjust positions to account for additional tokens that must be skipped + $removalInfo = $this->removalMap[$key]; + if (isset($removalInfo['left'])) { + $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; + } + if (isset($removalInfo['right'])) { + $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; + } + } + $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); + if (null !== $subNode) { + $result .= $extraLeft; + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment); + // If it's the same node that was previously in this position, it certainly doesn't + // need fixup. It's important to check this here, because our fixup checks are more + // conservative than strictly necessary. + if (isset($fixupInfo[$subNodeName]) && $subNode->getAttribute('origNode') !== $origSubNode) { + $fixup = $fixupInfo[$subNodeName]; + $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); + } else { + $res = $this->p($subNode, \true); + } + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + $result .= $extraRight; + } + $pos = $subEndPos + 1; + } + $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); + return $result; + } + /** + * Perform a format-preserving pretty print of an array. + * + * @param array $nodes New nodes + * @param array $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeType Type of the containing node. + * @param string $subNodeName Name of array subnode. + * @param null|int $fixup Fixup information for array item nodes + * + * @return null|string Result of pretty print or null if cannot preserve formatting + */ + protected function pArray(array $nodes, array $origNodes, int &$pos, int $indentAdjustment, string $parentNodeType, string $subNodeName, $fixup) + { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + $mapKey = $parentNodeType . '->' . $subNodeName; + $insertStr = $this->listInsertionMap[$mapKey] ?? null; + $isStmtList = $subNodeName === 'stmts'; + $beforeFirstKeepOrReplace = \true; + $skipRemovedNode = \false; + $delayedAdd = []; + $lastElemIndentLevel = $this->indentLevel; + $insertNewline = \false; + if ($insertStr === "\n") { + $insertStr = ''; + $insertNewline = \true; + } + if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { + $startPos = $origNodes[0]->getStartTokenPos(); + $endPos = $origNodes[0]->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + if (!$this->origTokens->haveBraces($startPos, $endPos)) { + // This was a single statement without braces, but either additional statements + // have been added, or the single statement has been removed. This requires the + // addition of braces. For now fall back. + // TODO: Try to preserve formatting + return null; + } + } + $result = ''; + foreach ($diff as $i => $diffElem) { + $diffType = $diffElem->type; + /** @var Node|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|null $origArrItem */ + $origArrItem = $diffElem->old; + if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { + $beforeFirstKeepOrReplace = \false; + if ($origArrItem === null || $arrItem === null) { + // We can only handle the case where both are null + if ($origArrItem === $arrItem) { + continue; + } + return null; + } + if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { + // We can only deal with nodes. This can occur for Names, which use string arrays. + return null; + } + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); + $origIndentLevel = $this->indentLevel; + $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; + $this->setIndentLevel($lastElemIndentLevel); + $comments = $arrItem->getComments(); + $origComments = $origArrItem->getComments(); + $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; + \assert($commentStartPos >= 0); + if ($commentStartPos < $pos) { + // Comments may be assigned to multiple nodes if they start at the same position. + // Make sure we don't try to print them multiple times. + $commentStartPos = $itemStartPos; + } + if ($skipRemovedNode) { + if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // TODO: Preserve formatting. + $this->setIndentLevel($origIndentLevel); + return null; + } + } else { + $result .= $this->origTokens->getTokenCode($pos, $commentStartPos, $indentAdjustment); + } + if (!empty($delayedAdd)) { + /** @var Node $delayedAddNode */ + foreach ($delayedAdd as $delayedAddNode) { + if ($insertNewline) { + $delayedAddComments = $delayedAddNode->getComments(); + if ($delayedAddComments) { + $result .= $this->pComments($delayedAddComments) . $this->nl; + } + } + $this->safeAppend($result, $this->p($delayedAddNode, \true)); + if ($insertNewline) { + $result .= $insertStr . $this->nl; + } else { + $result .= $insertStr; + } + } + $delayedAdd = []; + } + if ($comments !== $origComments) { + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $this->origTokens->getTokenCode($commentStartPos, $itemStartPos, $indentAdjustment); + } + // If we had to remove anything, we have done so now. + $skipRemovedNode = \false; + } elseif ($diffType === DiffElem::TYPE_ADD) { + if (null === $insertStr) { + // We don't have insertion information for this list type + return null; + } + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + if ($insertStr === ', ' && ($this->isMultiline($origNodes) || $arrItem->getComments())) { + $insertStr = ','; + $insertNewline = \true; + } + if ($beforeFirstKeepOrReplace) { + // Will be inserted at the next "replace" or "keep" element + $delayedAdd[] = $arrItem; + continue; + } + $itemStartPos = $pos; + $itemEndPos = $pos - 1; + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($lastElemIndentLevel); + if ($insertNewline) { + $result .= $insertStr . $this->nl; + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $insertStr; + } + } elseif ($diffType === DiffElem::TYPE_REMOVE) { + if (!$origArrItem instanceof Node) { + // We only support removal for nodes + return null; + } + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0); + // Consider comments part of the node. + $origComments = $origArrItem->getComments(); + if ($origComments) { + $itemStartPos = $origComments[0]->getStartTokenPos(); + } + if ($i === 0) { + // If we're removing from the start, keep the tokens before the node and drop those after it, + // instead of the other way around. + $result .= $this->origTokens->getTokenCode($pos, $itemStartPos, $indentAdjustment); + $skipRemovedNode = \true; + } else { + if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // TODO: Preserve formatting. + return null; + } + } + $pos = $itemEndPos + 1; + continue; + } else { + throw new \Exception("Shouldn't happen"); + } + if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { + $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); + } else { + $res = $this->p($arrItem, \true); + } + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + $pos = $itemEndPos + 1; + } + if ($skipRemovedNode) { + // TODO: Support removing single node. + return null; + } + if (!empty($delayedAdd)) { + if (!isset($this->emptyListInsertionMap[$mapKey])) { + return null; + } + list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; + if (null !== $findToken) { + $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; + $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); + $pos = $insertPos; + } + $first = \true; + $result .= $extraLeft; + foreach ($delayedAdd as $delayedAddNode) { + if (!$first) { + $result .= $insertStr; + if ($insertNewline) { + $result .= $this->nl; + } + } + $result .= $this->p($delayedAddNode, \true); + $first = \false; + } + $result .= $extraRight === "\n" ? $this->nl : $extraRight; + } + return $result; + } + /** + * Print node with fixups. + * + * Fixups here refer to the addition of extra parentheses, braces or other characters, that + * are required to preserve program semantics in a certain context (e.g. to maintain precedence + * or because only certain expressions are allowed in certain places). + * + * @param int $fixup Fixup type + * @param Node $subNode Subnode to print + * @param string|null $parentClass Class of parent node + * @param int $subStartPos Original start pos of subnode + * @param int $subEndPos Original end pos of subnode + * + * @return string Result of fixed-up print of subnode + */ + protected function pFixup(int $fixup, Node $subNode, $parentClass, int $subStartPos, int $subEndPos) : string + { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + list($precedence, $associativity) = $this->precedenceMap[$parentClass]; + return $this->pPrec($subNode, $precedence, $associativity, $fixup === self::FIXUP_PREC_LEFT ? -1 : 1); + } + break; + case self::FIXUP_CALL_LHS: + if ($this->callLhsRequiresParens($subNode) && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_DEREF_LHS: + if ($this->dereferenceLhsRequiresParens($subNode) && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_BRACED_NAME: + case self::FIXUP_VAR_BRACED_NAME: + if ($subNode instanceof Expr && !$this->origTokens->haveBraces($subStartPos, $subEndPos)) { + return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') . '{' . $this->p($subNode) . '}'; + } + break; + case self::FIXUP_ENCAPSED: + if (!$subNode instanceof Scalar\EncapsedStringPart && !$this->origTokens->haveBraces($subStartPos, $subEndPos)) { + return '{' . $this->p($subNode) . '}'; + } + break; + default: + throw new \Exception('Cannot happen'); + } + // Nothing special to do + return $this->p($subNode); + } + /** + * Appends to a string, ensuring whitespace between label characters. + * + * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". + * Without safeAppend the result would be "echox", which does not preserve semantics. + * + * @param string $str + * @param string $append + */ + protected function safeAppend(string &$str, string $append) + { + if ($str === "") { + $str = $append; + return; + } + if ($append === "") { + return; + } + if (!$this->labelCharMap[$append[0]] || !$this->labelCharMap[$str[\strlen($str) - 1]]) { + $str .= $append; + } else { + $str .= " " . $append; + } + } + /** + * Determines whether the LHS of a call must be wrapped in parenthesis. + * + * @param Node $node LHS of a call + * + * @return bool Whether parentheses are required + */ + protected function callLhsRequiresParens(Node $node) : bool + { + return !($node instanceof Node\Name || $node instanceof Expr\Variable || $node instanceof Expr\ArrayDimFetch || $node instanceof Expr\FuncCall || $node instanceof Expr\MethodCall || $node instanceof Expr\NullsafeMethodCall || $node instanceof Expr\StaticCall || $node instanceof Expr\Array_); + } + /** + * Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(Node $node) : bool + { + return !($node instanceof Expr\Variable || $node instanceof Node\Name || $node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch || $node instanceof Expr\NullsafePropertyFetch || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\FuncCall || $node instanceof Expr\MethodCall || $node instanceof Expr\NullsafeMethodCall || $node instanceof Expr\StaticCall || $node instanceof Expr\Array_ || $node instanceof Scalar\String_ || $node instanceof Expr\ConstFetch || $node instanceof Expr\ClassConstFetch); + } + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers) + { + return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC ? 'public ' : '') . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '') . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '') . ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '') . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '') . ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '') . ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : ''); + } + /** + * Determine whether a list of nodes uses multiline formatting. + * + * @param (Node|null)[] $nodes Node list + * + * @return bool Whether multiline formatting is used + */ + protected function isMultiline(array $nodes) : bool + { + if (\count($nodes) < 2) { + return \false; + } + $pos = -1; + foreach ($nodes as $node) { + if (null === $node) { + continue; + } + $endPos = $node->getEndTokenPos() + 1; + if ($pos >= 0) { + $text = $this->origTokens->getTokenCode($pos, $endPos, 0); + if (\false === \strpos($text, "\n")) { + // We require that a newline is present between *every* item. If the formatting + // is inconsistent, with only some items having newlines, we don't consider it + // as multiline + return \false; + } + } + $pos = $endPos; + } + return \true; + } + /** + * Lazily initializes label char map. + * + * The label char map determines whether a certain character may occur in a label. + */ + protected function initializeLabelCharMap() + { + if ($this->labelCharMap) { + return; + } + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + // Since PHP 7.1 The lower range is 0x80. However, we also want to support code for + // older versions. + $chr = \chr($i); + $this->labelCharMap[$chr] = $i >= 0x7f || \ctype_alnum($chr); + } + } + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer() + { + if ($this->nodeListDiffer) { + return; + } + $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { + if ($a instanceof Node && $b instanceof Node) { + return $a === $b->getAttribute('origNode'); + } + // Can happen for array destructuring + return $a === null && $b === null; + }); + } + /** + * Lazily initializes fixup map. + * + * The fixup map is used to determine whether a certain subnode of a certain node may require + * some kind of "fixup" operation, e.g. the addition of parenthesis or braces. + */ + protected function initializeFixupMap() + { + if ($this->fixupMap) { + return; + } + $this->fixupMap = [ + Expr\PreInc::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PreDec::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PostInc::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\Instanceof_::class => ['expr' => self::FIXUP_PREC_LEFT, 'class' => self::FIXUP_PREC_RIGHT], + Expr\Ternary::class => ['cond' => self::FIXUP_PREC_LEFT, 'else' => self::FIXUP_PREC_RIGHT], + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], + // TODO: FIXUP_NEW_VARIABLE + Expr\MethodCall::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], + Expr\NullsafeMethodCall::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], + Expr\StaticPropertyFetch::class => ['class' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_VAR_BRACED_NAME], + Expr\PropertyFetch::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], + Expr\NullsafePropertyFetch::class => ['var' => self::FIXUP_DEREF_LHS, 'name' => self::FIXUP_BRACED_NAME], + Scalar\Encapsed::class => ['parts' => self::FIXUP_ENCAPSED], + ]; + $binaryOps = [BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class]; + foreach ($binaryOps as $binaryOp) { + $this->fixupMap[$binaryOp] = ['left' => self::FIXUP_PREC_LEFT, 'right' => self::FIXUP_PREC_RIGHT]; + } + $assignOps = [Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class]; + foreach ($assignOps as $assignOp) { + $this->fixupMap[$assignOp] = ['var' => self::FIXUP_PREC_LEFT, 'expr' => self::FIXUP_PREC_RIGHT]; + } + $prefixOps = [Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_RIGHT]; + } + } + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be removed when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap() + { + if ($this->removalMap) { + return; + } + $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; + $stripLeft = ['left' => \T_WHITESPACE]; + $stripRight = ['right' => \T_WHITESPACE]; + $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; + $stripColon = ['left' => ':']; + $stripEquals = ['left' => '=']; + $this->removalMap = ['Expr_ArrayDimFetch->dim' => $stripBoth, 'Expr_ArrayItem->key' => $stripDoubleArrow, 'Expr_ArrowFunction->returnType' => $stripColon, 'Expr_Closure->returnType' => $stripColon, 'Expr_Exit->expr' => $stripBoth, 'Expr_Ternary->if' => $stripBoth, 'Expr_Yield->key' => $stripDoubleArrow, 'Expr_Yield->value' => $stripBoth, 'Param->type' => $stripRight, 'Param->default' => $stripEquals, 'Stmt_Break->num' => $stripBoth, 'Stmt_Catch->var' => $stripLeft, 'Stmt_ClassMethod->returnType' => $stripColon, 'Stmt_Class->extends' => ['left' => \T_EXTENDS], 'Stmt_Enum->scalarType' => $stripColon, 'Stmt_EnumCase->expr' => $stripEquals, 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], 'Stmt_Continue->num' => $stripBoth, 'Stmt_Foreach->keyVar' => $stripDoubleArrow, 'Stmt_Function->returnType' => $stripColon, 'Stmt_If->else' => $stripLeft, 'Stmt_Namespace->name' => $stripLeft, 'Stmt_Property->type' => $stripRight, 'Stmt_PropertyProperty->default' => $stripEquals, 'Stmt_Return->expr' => $stripBoth, 'Stmt_StaticVar->default' => $stripEquals, 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, 'Stmt_TryCatch->finally' => $stripLeft]; + } + protected function initializeInsertionMap() + { + if ($this->insertionMap) { + return; + } + // TODO: "yield" where both key and value are inserted doesn't work + // [$find, $beforeToken, $extraLeft, $extraRight] + $this->insertionMap = [ + 'Expr_ArrayDimFetch->dim' => ['[', \false, null, null], + 'Expr_ArrayItem->key' => [null, \false, null, ' => '], + 'Expr_ArrowFunction->returnType' => [')', \false, ' : ', null], + 'Expr_Closure->returnType' => [')', \false, ' : ', null], + 'Expr_Ternary->if' => ['?', \false, ' ', ' '], + 'Expr_Yield->key' => [\T_YIELD, \false, null, ' => '], + 'Expr_Yield->value' => [\T_YIELD, \false, ' ', null], + 'Param->type' => [null, \false, null, ' '], + 'Param->default' => [null, \false, ' = ', null], + 'Stmt_Break->num' => [\T_BREAK, \false, ' ', null], + 'Stmt_Catch->var' => [null, \false, ' ', null], + 'Stmt_ClassMethod->returnType' => [')', \false, ' : ', null], + 'Stmt_Class->extends' => [null, \false, ' extends ', null], + 'Stmt_Enum->scalarType' => [null, \false, ' : ', null], + 'Stmt_EnumCase->expr' => [null, \false, ' = ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null], + 'Stmt_Continue->num' => [\T_CONTINUE, \false, ' ', null], + 'Stmt_Foreach->keyVar' => [\T_AS, \false, null, ' => '], + 'Stmt_Function->returnType' => [')', \false, ' : ', null], + 'Stmt_If->else' => [null, \false, ' ', null], + 'Stmt_Namespace->name' => [\T_NAMESPACE, \false, ' ', null], + 'Stmt_Property->type' => [\T_VARIABLE, \true, null, ' '], + 'Stmt_PropertyProperty->default' => [null, \false, ' = ', null], + 'Stmt_Return->expr' => [\T_RETURN, \false, ' ', null], + 'Stmt_StaticVar->default' => [null, \false, ' = ', null], + //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO + 'Stmt_TryCatch->finally' => [null, \false, ' ', null], + ]; + } + protected function initializeListInsertionMap() + { + if ($this->listInsertionMap) { + return; + } + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_Encapsed->parts' => '', + 'Stmt_Catch->types' => '|', + 'UnionType->types' => '|', + 'IntersectionType->types' => '&', + 'Stmt_If->elseifs' => ' ', + 'Stmt_TryCatch->catches' => ' ', + // comma-separated lists + 'Expr_Array->items' => ', ', + 'Expr_ArrowFunction->params' => ', ', + 'Expr_Closure->params' => ', ', + 'Expr_Closure->uses' => ', ', + 'Expr_FuncCall->args' => ', ', + 'Expr_Isset->vars' => ', ', + 'Expr_List->items' => ', ', + 'Expr_MethodCall->args' => ', ', + 'Expr_NullsafeMethodCall->args' => ', ', + 'Expr_New->args' => ', ', + 'Expr_PrintableNewAnonClass->args' => ', ', + 'Expr_StaticCall->args' => ', ', + 'Stmt_ClassConst->consts' => ', ', + 'Stmt_ClassMethod->params' => ', ', + 'Stmt_Class->implements' => ', ', + 'Stmt_Enum->implements' => ', ', + 'Expr_PrintableNewAnonClass->implements' => ', ', + 'Stmt_Const->consts' => ', ', + 'Stmt_Declare->declares' => ', ', + 'Stmt_Echo->exprs' => ', ', + 'Stmt_For->init' => ', ', + 'Stmt_For->cond' => ', ', + 'Stmt_For->loop' => ', ', + 'Stmt_Function->params' => ', ', + 'Stmt_Global->vars' => ', ', + 'Stmt_GroupUse->uses' => ', ', + 'Stmt_Interface->extends' => ', ', + 'Stmt_Match->arms' => ', ', + 'Stmt_Property->props' => ', ', + 'Stmt_StaticVar->vars' => ', ', + 'Stmt_TraitUse->traits' => ', ', + 'Stmt_TraitUseAdaptation_Precedence->insteadof' => ', ', + 'Stmt_Unset->vars' => ', ', + 'Stmt_Use->uses' => ', ', + 'MatchArm->conds' => ', ', + 'AttributeGroup->attrs' => ', ', + // statement lists + 'Expr_Closure->stmts' => "\n", + 'Stmt_Case->stmts' => "\n", + 'Stmt_Catch->stmts' => "\n", + 'Stmt_Class->stmts' => "\n", + 'Stmt_Enum->stmts' => "\n", + 'Expr_PrintableNewAnonClass->stmts' => "\n", + 'Stmt_Interface->stmts' => "\n", + 'Stmt_Trait->stmts' => "\n", + 'Stmt_ClassMethod->stmts' => "\n", + 'Stmt_Declare->stmts' => "\n", + 'Stmt_Do->stmts' => "\n", + 'Stmt_ElseIf->stmts' => "\n", + 'Stmt_Else->stmts' => "\n", + 'Stmt_Finally->stmts' => "\n", + 'Stmt_Foreach->stmts' => "\n", + 'Stmt_For->stmts' => "\n", + 'Stmt_Function->stmts' => "\n", + 'Stmt_If->stmts' => "\n", + 'Stmt_Namespace->stmts' => "\n", + 'Stmt_Class->attrGroups' => "\n", + 'Stmt_Enum->attrGroups' => "\n", + 'Stmt_EnumCase->attrGroups' => "\n", + 'Stmt_Interface->attrGroups' => "\n", + 'Stmt_Trait->attrGroups' => "\n", + 'Stmt_Function->attrGroups' => "\n", + 'Stmt_ClassMethod->attrGroups' => "\n", + 'Stmt_ClassConst->attrGroups' => "\n", + 'Stmt_Property->attrGroups' => "\n", + 'Expr_PrintableNewAnonClass->attrGroups' => ' ', + 'Expr_Closure->attrGroups' => ' ', + 'Expr_ArrowFunction->attrGroups' => ' ', + 'Param->attrGroups' => ' ', + 'Stmt_Switch->cases' => "\n", + 'Stmt_TraitUse->adaptations' => "\n", + 'Stmt_TryCatch->stmts' => "\n", + 'Stmt_While->stmts' => "\n", + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + protected function initializeEmptyListInsertionMap() + { + if ($this->emptyListInsertionMap) { + return; + } + // TODO Insertion into empty statement lists. + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = ['Expr_ArrowFunction->params' => ['(', '', ''], 'Expr_Closure->uses' => [')', ' use(', ')'], 'Expr_Closure->params' => ['(', '', ''], 'Expr_FuncCall->args' => ['(', '', ''], 'Expr_MethodCall->args' => ['(', '', ''], 'Expr_NullsafeMethodCall->args' => ['(', '', ''], 'Expr_New->args' => ['(', '', ''], 'Expr_PrintableNewAnonClass->args' => ['(', '', ''], 'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''], 'Expr_StaticCall->args' => ['(', '', ''], 'Stmt_Class->implements' => [null, ' implements ', ''], 'Stmt_Enum->implements' => [null, ' implements ', ''], 'Stmt_ClassMethod->params' => ['(', '', ''], 'Stmt_Interface->extends' => [null, ' extends ', ''], 'Stmt_Function->params' => ['(', '', ''], 'Stmt_Interface->attrGroups' => [null, '', "\n"], 'Stmt_Class->attrGroups' => [null, '', "\n"], 'Stmt_ClassConst->attrGroups' => [null, '', "\n"], 'Stmt_ClassMethod->attrGroups' => [null, '', "\n"], 'Stmt_Function->attrGroups' => [null, '', "\n"], 'Stmt_Property->attrGroups' => [null, '', "\n"], 'Stmt_Trait->attrGroups' => [null, '', "\n"], 'Expr_ArrowFunction->attrGroups' => [null, '', ' '], 'Expr_Closure->attrGroups' => [null, '', ' '], 'Expr_PrintableNewAnonClass->attrGroups' => [\T_NEW, ' ', '']]; + } + protected function initializeModifierChangeMap() + { + if ($this->modifierChangeMap) { + return; + } + $this->modifierChangeMap = ['Stmt_ClassConst->flags' => \T_CONST, 'Stmt_ClassMethod->flags' => \T_FUNCTION, 'Stmt_Class->flags' => \T_CLASS, 'Stmt_Property->flags' => \T_VARIABLE, 'Param->flags' => \T_VARIABLE]; + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // Stmt_UseUse->type + } +} +Object Enumerator + +Copyright (c) 2016-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +Object Reflector + +Copyright (c) 2017-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +Phar.io - Manifest + +Copyright (c) 2016-2019 Arne Blankerts , Sebastian Heuer , Sebastian Bergmann , and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Arne Blankerts nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\Exception as VersionException; +use PHPUnit\PharIo\Version\Version; +use PHPUnit\PharIo\Version\VersionConstraintParser; +class ManifestDocumentMapper +{ + public function map(ManifestDocument $document) : Manifest + { + try { + $contains = $document->getContainsElement(); + $type = $this->mapType($contains); + $copyright = $this->mapCopyright($document->getCopyrightElement()); + $requirements = $this->mapRequirements($document->getRequiresElement()); + $bundledComponents = $this->mapBundledComponents($document); + return new Manifest(new ApplicationName($contains->getName()), new Version($contains->getVersion()), $type, $copyright, $requirements, $bundledComponents); + } catch (VersionException $e) { + throw new ManifestDocumentMapperException($e->getMessage(), (int) $e->getCode(), $e); + } catch (Exception $e) { + throw new ManifestDocumentMapperException($e->getMessage(), (int) $e->getCode(), $e); + } + } + private function mapType(ContainsElement $contains) : Type + { + switch ($contains->getType()) { + case 'application': + return Type::application(); + case 'library': + return Type::library(); + case 'extension': + return $this->mapExtension($contains->getExtensionElement()); + } + throw new ManifestDocumentMapperException(\sprintf('Unsupported type %s', $contains->getType())); + } + private function mapCopyright(CopyrightElement $copyright) : CopyrightInformation + { + $authors = new AuthorCollection(); + foreach ($copyright->getAuthorElements() as $authorElement) { + $authors->add(new Author($authorElement->getName(), new Email($authorElement->getEmail()))); + } + $licenseElement = $copyright->getLicenseElement(); + $license = new License($licenseElement->getType(), new Url($licenseElement->getUrl())); + return new CopyrightInformation($authors, $license); + } + private function mapRequirements(RequiresElement $requires) : RequirementCollection + { + $collection = new RequirementCollection(); + $phpElement = $requires->getPHPElement(); + $parser = new VersionConstraintParser(); + try { + $versionConstraint = $parser->parse($phpElement->getVersion()); + } catch (VersionException $e) { + throw new ManifestDocumentMapperException(\sprintf('Unsupported version constraint - %s', $e->getMessage()), (int) $e->getCode(), $e); + } + $collection->add(new PhpVersionRequirement($versionConstraint)); + if (!$phpElement->hasExtElements()) { + return $collection; + } + foreach ($phpElement->getExtElements() as $extElement) { + $collection->add(new PhpExtensionRequirement($extElement->getName())); + } + return $collection; + } + private function mapBundledComponents(ManifestDocument $document) : BundledComponentCollection + { + $collection = new BundledComponentCollection(); + if (!$document->hasBundlesElement()) { + return $collection; + } + foreach ($document->getBundlesElement()->getComponentElements() as $componentElement) { + $collection->add(new BundledComponent($componentElement->getName(), new Version($componentElement->getVersion()))); + } + return $collection; + } + private function mapExtension(ExtensionElement $extension) : Extension + { + try { + $versionConstraint = (new VersionConstraintParser())->parse($extension->getCompatible()); + return Type::extension(new ApplicationName($extension->getFor()), $versionConstraint); + } catch (VersionException $e) { + throw new ManifestDocumentMapperException(\sprintf('Unsupported version constraint - %s', $e->getMessage()), (int) $e->getCode(), $e); + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ManifestLoader +{ + public static function fromFile(string $filename) : Manifest + { + try { + return (new ManifestDocumentMapper())->map(ManifestDocument::fromFile($filename)); + } catch (Exception $e) { + throw new ManifestLoaderException(\sprintf('Loading %s failed.', $filename), (int) $e->getCode(), $e); + } + } + public static function fromPhar(string $filename) : Manifest + { + return self::fromFile('phar://' . $filename . '/manifest.xml'); + } + public static function fromString(string $manifest) : Manifest + { + try { + return (new ManifestDocumentMapper())->map(ManifestDocument::fromString($manifest)); + } catch (Exception $e) { + throw new ManifestLoaderException('Processing string failed', (int) $e->getCode(), $e); + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\AnyVersionConstraint; +use PHPUnit\PharIo\Version\Version; +use PHPUnit\PharIo\Version\VersionConstraint; +use XMLWriter; +/** @psalm-suppress MissingConstructor */ +class ManifestSerializer +{ + /** @var XMLWriter */ + private $xmlWriter; + public function serializeToFile(Manifest $manifest, string $filename) : void + { + \file_put_contents($filename, $this->serializeToString($manifest)); + } + public function serializeToString(Manifest $manifest) : string + { + $this->startDocument(); + $this->addContains($manifest->getName(), $manifest->getVersion(), $manifest->getType()); + $this->addCopyright($manifest->getCopyrightInformation()); + $this->addRequirements($manifest->getRequirements()); + $this->addBundles($manifest->getBundledComponents()); + return $this->finishDocument(); + } + private function startDocument() : void + { + $xmlWriter = new XMLWriter(); + $xmlWriter->openMemory(); + $xmlWriter->setIndent(\true); + $xmlWriter->setIndentString(\str_repeat(' ', 4)); + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('phar'); + $xmlWriter->writeAttribute('xmlns', 'https://phar.io/xml/manifest/1.0'); + $this->xmlWriter = $xmlWriter; + } + private function finishDocument() : string + { + $this->xmlWriter->endElement(); + $this->xmlWriter->endDocument(); + return $this->xmlWriter->outputMemory(); + } + private function addContains(ApplicationName $name, Version $version, Type $type) : void + { + $this->xmlWriter->startElement('contains'); + $this->xmlWriter->writeAttribute('name', $name->asString()); + $this->xmlWriter->writeAttribute('version', $version->getVersionString()); + switch (\true) { + case $type->isApplication(): + $this->xmlWriter->writeAttribute('type', 'application'); + break; + case $type->isLibrary(): + $this->xmlWriter->writeAttribute('type', 'library'); + break; + case $type->isExtension(): + $this->xmlWriter->writeAttribute('type', 'extension'); + /* @var $type Extension */ + $this->addExtension($type->getApplicationName(), $type->getVersionConstraint()); + break; + default: + $this->xmlWriter->writeAttribute('type', 'custom'); + } + $this->xmlWriter->endElement(); + } + private function addCopyright(CopyrightInformation $copyrightInformation) : void + { + $this->xmlWriter->startElement('copyright'); + foreach ($copyrightInformation->getAuthors() as $author) { + $this->xmlWriter->startElement('author'); + $this->xmlWriter->writeAttribute('name', $author->getName()); + $this->xmlWriter->writeAttribute('email', $author->getEmail()->asString()); + $this->xmlWriter->endElement(); + } + $license = $copyrightInformation->getLicense(); + $this->xmlWriter->startElement('license'); + $this->xmlWriter->writeAttribute('type', $license->getName()); + $this->xmlWriter->writeAttribute('url', $license->getUrl()->asString()); + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); + } + private function addRequirements(RequirementCollection $requirementCollection) : void + { + $phpRequirement = new AnyVersionConstraint(); + $extensions = []; + foreach ($requirementCollection as $requirement) { + if ($requirement instanceof PhpVersionRequirement) { + $phpRequirement = $requirement->getVersionConstraint(); + continue; + } + if ($requirement instanceof PhpExtensionRequirement) { + $extensions[] = $requirement->asString(); + } + } + $this->xmlWriter->startElement('requires'); + $this->xmlWriter->startElement('php'); + $this->xmlWriter->writeAttribute('version', $phpRequirement->asString()); + foreach ($extensions as $extension) { + $this->xmlWriter->startElement('ext'); + $this->xmlWriter->writeAttribute('name', $extension); + $this->xmlWriter->endElement(); + } + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); + } + private function addBundles(BundledComponentCollection $bundledComponentCollection) : void + { + if (\count($bundledComponentCollection) === 0) { + return; + } + $this->xmlWriter->startElement('bundles'); + foreach ($bundledComponentCollection as $bundledComponent) { + $this->xmlWriter->startElement('component'); + $this->xmlWriter->writeAttribute('name', $bundledComponent->getName()); + $this->xmlWriter->writeAttribute('version', $bundledComponent->getVersion()->getVersionString()); + $this->xmlWriter->endElement(); + } + $this->xmlWriter->endElement(); + } + private function addExtension(ApplicationName $applicationName, VersionConstraint $versionConstraint) : void + { + $this->xmlWriter->startElement('extension'); + $this->xmlWriter->writeAttribute('for', $applicationName->asString()); + $this->xmlWriter->writeAttribute('compatible', $versionConstraint->asString()); + $this->xmlWriter->endElement(); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ElementCollectionException extends \InvalidArgumentException implements Exception +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +interface Exception extends \Throwable +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class InvalidApplicationNameException extends \InvalidArgumentException implements Exception +{ + public const InvalidFormat = 2; +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class InvalidEmailException extends \InvalidArgumentException implements Exception +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class InvalidUrlException extends \InvalidArgumentException implements Exception +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use LibXMLError; +class ManifestDocumentLoadingException extends \Exception implements Exception +{ + /** @var LibXMLError[] */ + private $libxmlErrors; + /** + * ManifestDocumentLoadingException constructor. + * + * @param LibXMLError[] $libxmlErrors + */ + public function __construct(array $libxmlErrors) + { + $this->libxmlErrors = $libxmlErrors; + $first = $this->libxmlErrors[0]; + parent::__construct(\sprintf('%s (Line: %d / Column: %d / File: %s)', $first->message, $first->line, $first->column, $first->file), $first->code); + } + /** + * @return LibXMLError[] + */ + public function getLibxmlErrors() : array + { + return $this->libxmlErrors; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class Application extends Type +{ + public function isApplication() : bool + { + return \true; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ApplicationName +{ + /** @var string */ + private $name; + public function __construct(string $name) + { + $this->ensureValidFormat($name); + $this->name = $name; + } + public function asString() : string + { + return $this->name; + } + public function isEqual(ApplicationName $name) : bool + { + return $this->name === $name->name; + } + private function ensureValidFormat(string $name) : void + { + if (!\preg_match('#\\w/\\w#', $name)) { + throw new InvalidApplicationNameException(\sprintf('Format of name "%s" is not valid - expected: vendor/packagename', $name), InvalidApplicationNameException::InvalidFormat); + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class Author +{ + /** @var string */ + private $name; + /** @var Email */ + private $email; + public function __construct(string $name, Email $email) + { + $this->name = $name; + $this->email = $email; + } + public function asString() : string + { + return \sprintf('%s <%s>', $this->name, $this->email->asString()); + } + public function getName() : string + { + return $this->name; + } + public function getEmail() : Email + { + return $this->email; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class AuthorCollection implements \Countable, \IteratorAggregate +{ + /** @var Author[] */ + private $authors = []; + public function add(Author $author) : void + { + $this->authors[] = $author; + } + /** + * @return Author[] + */ + public function getAuthors() : array + { + return $this->authors; + } + public function count() : int + { + return \count($this->authors); + } + public function getIterator() : AuthorCollectionIterator + { + return new AuthorCollectionIterator($this); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class AuthorCollectionIterator implements \Iterator +{ + /** @var Author[] */ + private $authors; + /** @var int */ + private $position = 0; + public function __construct(AuthorCollection $authors) + { + $this->authors = $authors->getAuthors(); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < \count($this->authors); + } + public function key() : int + { + return $this->position; + } + public function current() : Author + { + return $this->authors[$this->position]; + } + public function next() : void + { + $this->position++; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\Version; +class BundledComponent +{ + /** @var string */ + private $name; + /** @var Version */ + private $version; + public function __construct(string $name, Version $version) + { + $this->name = $name; + $this->version = $version; + } + public function getName() : string + { + return $this->name; + } + public function getVersion() : Version + { + return $this->version; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class BundledComponentCollection implements \Countable, \IteratorAggregate +{ + /** @var BundledComponent[] */ + private $bundledComponents = []; + public function add(BundledComponent $bundledComponent) : void + { + $this->bundledComponents[] = $bundledComponent; + } + /** + * @return BundledComponent[] + */ + public function getBundledComponents() : array + { + return $this->bundledComponents; + } + public function count() : int + { + return \count($this->bundledComponents); + } + public function getIterator() : BundledComponentCollectionIterator + { + return new BundledComponentCollectionIterator($this); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class BundledComponentCollectionIterator implements \Iterator +{ + /** @var BundledComponent[] */ + private $bundledComponents; + /** @var int */ + private $position = 0; + public function __construct(BundledComponentCollection $bundledComponents) + { + $this->bundledComponents = $bundledComponents->getBundledComponents(); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < \count($this->bundledComponents); + } + public function key() : int + { + return $this->position; + } + public function current() : BundledComponent + { + return $this->bundledComponents[$this->position]; + } + public function next() : void + { + $this->position++; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class CopyrightInformation +{ + /** @var AuthorCollection */ + private $authors; + /** @var License */ + private $license; + public function __construct(AuthorCollection $authors, License $license) + { + $this->authors = $authors; + $this->license = $license; + } + public function getAuthors() : AuthorCollection + { + return $this->authors; + } + public function getLicense() : License + { + return $this->license; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class Email +{ + /** @var string */ + private $email; + public function __construct(string $email) + { + $this->ensureEmailIsValid($email); + $this->email = $email; + } + public function asString() : string + { + return $this->email; + } + private function ensureEmailIsValid(string $url) : void + { + if (\filter_var($url, \FILTER_VALIDATE_EMAIL) === \false) { + throw new InvalidEmailException(); + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\Version; +use PHPUnit\PharIo\Version\VersionConstraint; +class Extension extends Type +{ + /** @var ApplicationName */ + private $application; + /** @var VersionConstraint */ + private $versionConstraint; + public function __construct(ApplicationName $application, VersionConstraint $versionConstraint) + { + $this->application = $application; + $this->versionConstraint = $versionConstraint; + } + public function getApplicationName() : ApplicationName + { + return $this->application; + } + public function getVersionConstraint() : VersionConstraint + { + return $this->versionConstraint; + } + public function isExtension() : bool + { + return \true; + } + public function isExtensionFor(ApplicationName $name) : bool + { + return $this->application->isEqual($name); + } + public function isCompatibleWith(ApplicationName $name, Version $version) : bool + { + return $this->isExtensionFor($name) && $this->versionConstraint->complies($version); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class Library extends Type +{ + public function isLibrary() : bool + { + return \true; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class License +{ + /** @var string */ + private $name; + /** @var Url */ + private $url; + public function __construct(string $name, Url $url) + { + $this->name = $name; + $this->url = $url; + } + public function getName() : string + { + return $this->name; + } + public function getUrl() : Url + { + return $this->url; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\Version; +class Manifest +{ + /** @var ApplicationName */ + private $name; + /** @var Version */ + private $version; + /** @var Type */ + private $type; + /** @var CopyrightInformation */ + private $copyrightInformation; + /** @var RequirementCollection */ + private $requirements; + /** @var BundledComponentCollection */ + private $bundledComponents; + public function __construct(ApplicationName $name, Version $version, Type $type, CopyrightInformation $copyrightInformation, RequirementCollection $requirements, BundledComponentCollection $bundledComponents) + { + $this->name = $name; + $this->version = $version; + $this->type = $type; + $this->copyrightInformation = $copyrightInformation; + $this->requirements = $requirements; + $this->bundledComponents = $bundledComponents; + } + public function getName() : ApplicationName + { + return $this->name; + } + public function getVersion() : Version + { + return $this->version; + } + public function getType() : Type + { + return $this->type; + } + public function getCopyrightInformation() : CopyrightInformation + { + return $this->copyrightInformation; + } + public function getRequirements() : RequirementCollection + { + return $this->requirements; + } + public function getBundledComponents() : BundledComponentCollection + { + return $this->bundledComponents; + } + public function isApplication() : bool + { + return $this->type->isApplication(); + } + public function isLibrary() : bool + { + return $this->type->isLibrary(); + } + public function isExtension() : bool + { + return $this->type->isExtension(); + } + public function isExtensionFor(ApplicationName $application, Version $version = null) : bool + { + if (!$this->isExtension()) { + return \false; + } + /** @var Extension $type */ + $type = $this->type; + if ($version !== null) { + return $type->isCompatibleWith($application, $version); + } + return $type->isExtensionFor($application); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class PhpExtensionRequirement implements Requirement +{ + /** @var string */ + private $extension; + public function __construct(string $extension) + { + $this->extension = $extension; + } + public function asString() : string + { + return $this->extension; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\VersionConstraint; +class PhpVersionRequirement implements Requirement +{ + /** @var VersionConstraint */ + private $versionConstraint; + public function __construct(VersionConstraint $versionConstraint) + { + $this->versionConstraint = $versionConstraint; + } + public function getVersionConstraint() : VersionConstraint + { + return $this->versionConstraint; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +interface Requirement +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class RequirementCollection implements \Countable, \IteratorAggregate +{ + /** @var Requirement[] */ + private $requirements = []; + public function add(Requirement $requirement) : void + { + $this->requirements[] = $requirement; + } + /** + * @return Requirement[] + */ + public function getRequirements() : array + { + return $this->requirements; + } + public function count() : int + { + return \count($this->requirements); + } + public function getIterator() : RequirementCollectionIterator + { + return new RequirementCollectionIterator($this); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class RequirementCollectionIterator implements \Iterator +{ + /** @var Requirement[] */ + private $requirements; + /** @var int */ + private $position = 0; + public function __construct(RequirementCollection $requirements) + { + $this->requirements = $requirements->getRequirements(); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < \count($this->requirements); + } + public function key() : int + { + return $this->position; + } + public function current() : Requirement + { + return $this->requirements[$this->position]; + } + public function next() : void + { + $this->position++; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use PHPUnit\PharIo\Version\VersionConstraint; +abstract class Type +{ + public static function application() : Application + { + return new Application(); + } + public static function library() : Library + { + return new Library(); + } + public static function extension(ApplicationName $application, VersionConstraint $versionConstraint) : Extension + { + return new Extension($application, $versionConstraint); + } + /** @psalm-assert-if-true Application $this */ + public function isApplication() : bool + { + return \false; + } + /** @psalm-assert-if-true Library $this */ + public function isLibrary() : bool + { + return \false; + } + /** @psalm-assert-if-true Extension $this */ + public function isExtension() : bool + { + return \false; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class Url +{ + /** @var string */ + private $url; + public function __construct(string $url) + { + $this->ensureUrlIsValid($url); + $this->url = $url; + } + public function asString() : string + { + return $this->url; + } + /** + * @param string $url + * + * @throws InvalidUrlException + */ + private function ensureUrlIsValid($url) : void + { + if (\filter_var($url, \FILTER_VALIDATE_URL) === \false) { + throw new InvalidUrlException(); + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class AuthorElement extends ManifestElement +{ + public function getName() : string + { + return $this->getAttributeValue('name'); + } + public function getEmail() : string + { + return $this->getAttributeValue('email'); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class AuthorElementCollection extends ElementCollection +{ + public function current() : AuthorElement + { + return new AuthorElement($this->getCurrentElement()); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class BundlesElement extends ManifestElement +{ + public function getComponentElements() : ComponentElementCollection + { + return new ComponentElementCollection($this->getChildrenByName('component')); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ComponentElement extends ManifestElement +{ + public function getName() : string + { + return $this->getAttributeValue('name'); + } + public function getVersion() : string + { + return $this->getAttributeValue('version'); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ComponentElementCollection extends ElementCollection +{ + public function current() : ComponentElement + { + return new ComponentElement($this->getCurrentElement()); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ContainsElement extends ManifestElement +{ + public function getName() : string + { + return $this->getAttributeValue('name'); + } + public function getVersion() : string + { + return $this->getAttributeValue('version'); + } + public function getType() : string + { + return $this->getAttributeValue('type'); + } + public function getExtensionElement() : ExtensionElement + { + return new ExtensionElement($this->getChildByName('extension')); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class CopyrightElement extends ManifestElement +{ + public function getAuthorElements() : AuthorElementCollection + { + return new AuthorElementCollection($this->getChildrenByName('author')); + } + public function getLicenseElement() : LicenseElement + { + return new LicenseElement($this->getChildByName('license')); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use DOMElement; +use DOMNodeList; +abstract class ElementCollection implements \Iterator +{ + /** @var DOMElement[] */ + private $nodes = []; + /** @var int */ + private $position; + public function __construct(DOMNodeList $nodeList) + { + $this->position = 0; + $this->importNodes($nodeList); + } + #[\ReturnTypeWillChange] + public abstract function current(); + public function next() : void + { + $this->position++; + } + public function key() : int + { + return $this->position; + } + public function valid() : bool + { + return $this->position < \count($this->nodes); + } + public function rewind() : void + { + $this->position = 0; + } + protected function getCurrentElement() : DOMElement + { + return $this->nodes[$this->position]; + } + private function importNodes(DOMNodeList $nodeList) : void + { + foreach ($nodeList as $node) { + if (!$node instanceof DOMElement) { + throw new ElementCollectionException(\sprintf('\\DOMElement expected, got \\%s', \get_class($node))); + } + $this->nodes[] = $node; + } + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ExtElement extends ManifestElement +{ + public function getName() : string + { + return $this->getAttributeValue('name'); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ExtElementCollection extends ElementCollection +{ + public function current() : ExtElement + { + return new ExtElement($this->getCurrentElement()); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class ExtensionElement extends ManifestElement +{ + public function getFor() : string + { + return $this->getAttributeValue('for'); + } + public function getCompatible() : string + { + return $this->getAttributeValue('compatible'); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class LicenseElement extends ManifestElement +{ + public function getType() : string + { + return $this->getAttributeValue('type'); + } + public function getUrl() : string + { + return $this->getAttributeValue('url'); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use DOMDocument; +use DOMElement; +class ManifestDocument +{ + public const XMLNS = 'https://phar.io/xml/manifest/1.0'; + /** @var DOMDocument */ + private $dom; + public static function fromFile(string $filename) : ManifestDocument + { + if (!\file_exists($filename)) { + throw new ManifestDocumentException(\sprintf('File "%s" not found', $filename)); + } + return self::fromString(\file_get_contents($filename)); + } + public static function fromString(string $xmlString) : ManifestDocument + { + $prev = \libxml_use_internal_errors(\true); + \libxml_clear_errors(); + $dom = new DOMDocument(); + $dom->loadXML($xmlString); + $errors = \libxml_get_errors(); + \libxml_use_internal_errors($prev); + if (\count($errors) !== 0) { + throw new ManifestDocumentLoadingException($errors); + } + return new self($dom); + } + private function __construct(DOMDocument $dom) + { + $this->ensureCorrectDocumentType($dom); + $this->dom = $dom; + } + public function getContainsElement() : ContainsElement + { + return new ContainsElement($this->fetchElementByName('contains')); + } + public function getCopyrightElement() : CopyrightElement + { + return new CopyrightElement($this->fetchElementByName('copyright')); + } + public function getRequiresElement() : RequiresElement + { + return new RequiresElement($this->fetchElementByName('requires')); + } + public function hasBundlesElement() : bool + { + return $this->dom->getElementsByTagNameNS(self::XMLNS, 'bundles')->length === 1; + } + public function getBundlesElement() : BundlesElement + { + return new BundlesElement($this->fetchElementByName('bundles')); + } + private function ensureCorrectDocumentType(DOMDocument $dom) : void + { + $root = $dom->documentElement; + if ($root->localName !== 'phar' || $root->namespaceURI !== self::XMLNS) { + throw new ManifestDocumentException('Not a phar.io manifest document'); + } + } + private function fetchElementByName(string $elementName) : DOMElement + { + $element = $this->dom->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); + if (!$element instanceof DOMElement) { + throw new ManifestDocumentException(\sprintf('Element %s missing', $elementName)); + } + return $element; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +use DOMElement; +use DOMNodeList; +class ManifestElement +{ + public const XMLNS = 'https://phar.io/xml/manifest/1.0'; + /** @var DOMElement */ + private $element; + public function __construct(DOMElement $element) + { + $this->element = $element; + } + protected function getAttributeValue(string $name) : string + { + if (!$this->element->hasAttribute($name)) { + throw new ManifestElementException(\sprintf('Attribute %s not set on element %s', $name, $this->element->localName)); + } + return $this->element->getAttribute($name); + } + protected function getChildByName(string $elementName) : DOMElement + { + $element = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); + if (!$element instanceof DOMElement) { + throw new ManifestElementException(\sprintf('Element %s missing', $elementName)); + } + return $element; + } + protected function getChildrenByName(string $elementName) : DOMNodeList + { + $elementList = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName); + if ($elementList->length === 0) { + throw new ManifestElementException(\sprintf('Element(s) %s missing', $elementName)); + } + return $elementList; + } + protected function hasChild(string $elementName) : bool + { + return $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->length !== 0; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class PhpElement extends ManifestElement +{ + public function getVersion() : string + { + return $this->getAttributeValue('version'); + } + public function hasExtElements() : bool + { + return $this->hasChild('ext'); + } + public function getExtElements() : ExtElementCollection + { + return new ExtElementCollection($this->getChildrenByName('ext')); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Manifest; + +class RequiresElement extends ManifestElement +{ + public function getPHPElement() : PhpElement + { + return new PhpElement($this->getChildByName('php')); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class BuildMetaData +{ + /** @var string */ + private $value; + public function __construct(string $value) + { + $this->value = $value; + } + public function asString() : string + { + return $this->value; + } + public function equals(BuildMetaData $other) : bool + { + return $this->asString() === $other->asString(); + } +} +Copyright (c) 2016-2017 Arne Blankerts , Sebastian Heuer and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + 0, 'a' => 1, 'alpha' => 1, 'b' => 2, 'beta' => 2, 'rc' => 3, 'p' => 4, 'pl' => 4, 'patch' => 4]; + /** @var string */ + private $value; + /** @var int */ + private $valueScore; + /** @var int */ + private $number = 0; + /** @var string */ + private $full; + /** + * @throws InvalidPreReleaseSuffixException + */ + public function __construct(string $value) + { + $this->parseValue($value); + } + public function asString() : string + { + return $this->full; + } + public function getValue() : string + { + return $this->value; + } + public function getNumber() : ?int + { + return $this->number; + } + public function isGreaterThan(PreReleaseSuffix $suffix) : bool + { + if ($this->valueScore > $suffix->valueScore) { + return \true; + } + if ($this->valueScore < $suffix->valueScore) { + return \false; + } + return $this->getNumber() > $suffix->getNumber(); + } + private function mapValueToScore(string $value) : int + { + $value = \strtolower($value); + return self::valueScoreMap[$value]; + } + private function parseValue(string $value) : void + { + $regex = '/-?((dev|beta|b|rc|alpha|a|patch|p|pl)\\.?(\\d*)).*$/i'; + if (\preg_match($regex, $value, $matches) !== 1) { + throw new InvalidPreReleaseSuffixException(\sprintf('Invalid label %s', $value)); + } + $this->full = $matches[1]; + $this->value = $matches[2]; + if ($matches[3] !== '') { + $this->number = (int) $matches[3]; + } + $this->valueScore = $this->mapValueToScore($matches[2]); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class Version +{ + /** @var string */ + private $originalVersionString; + /** @var VersionNumber */ + private $major; + /** @var VersionNumber */ + private $minor; + /** @var VersionNumber */ + private $patch; + /** @var null|PreReleaseSuffix */ + private $preReleaseSuffix; + /** @var null|BuildMetaData */ + private $buildMetadata; + public function __construct(string $versionString) + { + $this->ensureVersionStringIsValid($versionString); + $this->originalVersionString = $versionString; + } + /** + * @throws NoPreReleaseSuffixException + */ + public function getPreReleaseSuffix() : PreReleaseSuffix + { + if ($this->preReleaseSuffix === null) { + throw new NoPreReleaseSuffixException('No pre-release suffix set'); + } + return $this->preReleaseSuffix; + } + public function getOriginalString() : string + { + return $this->originalVersionString; + } + public function getVersionString() : string + { + $str = \sprintf('%d.%d.%d', $this->getMajor()->getValue() ?? 0, $this->getMinor()->getValue() ?? 0, $this->getPatch()->getValue() ?? 0); + if (!$this->hasPreReleaseSuffix()) { + return $str; + } + return $str . '-' . $this->getPreReleaseSuffix()->asString(); + } + public function hasPreReleaseSuffix() : bool + { + return $this->preReleaseSuffix !== null; + } + public function equals(Version $other) : bool + { + if ($this->getVersionString() !== $other->getVersionString()) { + return \false; + } + if ($this->hasBuildMetaData() !== $other->hasBuildMetaData()) { + return \false; + } + if ($this->hasBuildMetaData() && $other->hasBuildMetaData() && !$this->getBuildMetaData()->equals($other->getBuildMetaData())) { + return \false; + } + return \true; + } + public function isGreaterThan(Version $version) : bool + { + if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { + return \false; + } + if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { + return \true; + } + if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { + return \false; + } + if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { + return \true; + } + if ($version->getPatch()->getValue() > $this->getPatch()->getValue()) { + return \false; + } + if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { + return \true; + } + if (!$version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { + return \false; + } + if ($version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { + return \true; + } + if (!$version->hasPreReleaseSuffix() && $this->hasPreReleaseSuffix()) { + return \false; + } + return $this->getPreReleaseSuffix()->isGreaterThan($version->getPreReleaseSuffix()); + } + public function getMajor() : VersionNumber + { + return $this->major; + } + public function getMinor() : VersionNumber + { + return $this->minor; + } + public function getPatch() : VersionNumber + { + return $this->patch; + } + /** + * @psalm-assert-if-true BuildMetaData $this->buildMetadata + * @psalm-assert-if-true BuildMetaData $this->getBuildMetaData() + */ + public function hasBuildMetaData() : bool + { + return $this->buildMetadata !== null; + } + /** + * @throws NoBuildMetaDataException + */ + public function getBuildMetaData() : BuildMetaData + { + if (!$this->hasBuildMetaData()) { + throw new NoBuildMetaDataException('No build metadata set'); + } + return $this->buildMetadata; + } + /** + * @param string[] $matches + * + * @throws InvalidPreReleaseSuffixException + */ + private function parseVersion(array $matches) : void + { + $this->major = new VersionNumber((int) $matches['Major']); + $this->minor = new VersionNumber((int) $matches['Minor']); + $this->patch = isset($matches['Patch']) ? new VersionNumber((int) $matches['Patch']) : new VersionNumber(0); + if (isset($matches['PreReleaseSuffix']) && $matches['PreReleaseSuffix'] !== '') { + $this->preReleaseSuffix = new PreReleaseSuffix($matches['PreReleaseSuffix']); + } + if (isset($matches['BuildMetadata'])) { + $this->buildMetadata = new BuildMetaData($matches['BuildMetadata']); + } + } + /** + * @param string $version + * + * @throws InvalidVersionException + */ + private function ensureVersionStringIsValid($version) : void + { + $regex = '/^v? + (?P0|[1-9]\\d*) + \\. + (?P0|[1-9]\\d*) + (\\. + (?P0|[1-9]\\d*) + )? + (?: + - + (?(?:(dev|beta|b|rc|alpha|a|patch|p|pl)\\.?\\d*)) + )? + (?: + \\+ + (?P[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-@]+)*) + )? + $/xi'; + if (\preg_match($regex, $version, $matches) !== 1) { + throw new InvalidVersionException(\sprintf("Version string '%s' does not follow SemVer semantics", $version)); + } + $this->parseVersion($matches); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class VersionConstraintParser +{ + /** + * @throws UnsupportedVersionConstraintException + */ + public function parse(string $value) : VersionConstraint + { + if (\strpos($value, '|') !== \false) { + return $this->handleOrGroup($value); + } + if (!\preg_match('/^[\\^~*]?v?[\\d.*]+(?:-.*)?$/i', $value)) { + throw new UnsupportedVersionConstraintException(\sprintf('Version constraint %s is not supported.', $value)); + } + switch ($value[0]) { + case '~': + return $this->handleTildeOperator($value); + case '^': + return $this->handleCaretOperator($value); + } + $constraint = new VersionConstraintValue($value); + if ($constraint->getMajor()->isAny()) { + return new AnyVersionConstraint(); + } + if ($constraint->getMinor()->isAny()) { + return new SpecificMajorVersionConstraint($constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0); + } + if ($constraint->getPatch()->isAny()) { + return new SpecificMajorAndMinorVersionConstraint($constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0, $constraint->getMinor()->getValue() ?? 0); + } + return new ExactVersionConstraint($constraint->getVersionString()); + } + private function handleOrGroup(string $value) : OrVersionConstraintGroup + { + $constraints = []; + foreach (\preg_split('{\\s*\\|\\|?\\s*}', \trim($value)) as $groupSegment) { + $constraints[] = $this->parse(\trim($groupSegment)); + } + return new OrVersionConstraintGroup($value, $constraints); + } + private function handleTildeOperator(string $value) : AndVersionConstraintGroup + { + $constraintValue = new VersionConstraintValue(\substr($value, 1)); + if ($constraintValue->getPatch()->isAny()) { + return $this->handleCaretOperator($value); + } + $constraints = [new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1))), new SpecificMajorAndMinorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0)]; + return new AndVersionConstraintGroup($value, $constraints); + } + private function handleCaretOperator(string $value) : AndVersionConstraintGroup + { + $constraintValue = new VersionConstraintValue(\substr($value, 1)); + $constraints = [new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1)))]; + if ($constraintValue->getMajor()->getValue() === 0) { + $constraints[] = new SpecificMajorAndMinorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0); + } else { + $constraints[] = new SpecificMajorVersionConstraint($value, $constraintValue->getMajor()->getValue() ?? 0); + } + return new AndVersionConstraintGroup($value, $constraints); + } +} +versionString = $versionString; + $this->parseVersion($versionString); + } + public function getLabel() : string + { + return $this->label; + } + public function getBuildMetaData() : string + { + return $this->buildMetaData; + } + public function getVersionString() : string + { + return $this->versionString; + } + public function getMajor() : VersionNumber + { + return $this->major; + } + public function getMinor() : VersionNumber + { + return $this->minor; + } + public function getPatch() : VersionNumber + { + return $this->patch; + } + private function parseVersion(string $versionString) : void + { + $this->extractBuildMetaData($versionString); + $this->extractLabel($versionString); + $this->stripPotentialVPrefix($versionString); + $versionSegments = \explode('.', $versionString); + $this->major = new VersionNumber(\is_numeric($versionSegments[0]) ? (int) $versionSegments[0] : null); + $minorValue = isset($versionSegments[1]) && \is_numeric($versionSegments[1]) ? (int) $versionSegments[1] : null; + $patchValue = isset($versionSegments[2]) && \is_numeric($versionSegments[2]) ? (int) $versionSegments[2] : null; + $this->minor = new VersionNumber($minorValue); + $this->patch = new VersionNumber($patchValue); + } + private function extractBuildMetaData(string &$versionString) : void + { + if (\preg_match('/\\+(.*)/', $versionString, $matches) === 1) { + $this->buildMetaData = $matches[1]; + $versionString = \str_replace($matches[0], '', $versionString); + } + } + private function extractLabel(string &$versionString) : void + { + if (\preg_match('/-(.*)/', $versionString, $matches) === 1) { + $this->label = $matches[1]; + $versionString = \str_replace($matches[0], '', $versionString); + } + } + private function stripPotentialVPrefix(string &$versionString) : void + { + if ($versionString[0] !== 'v') { + return; + } + $versionString = \substr($versionString, 1); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class VersionNumber +{ + /** @var ?int */ + private $value; + public function __construct(?int $value) + { + $this->value = $value; + } + public function isAny() : bool + { + return $this->value === null; + } + public function getValue() : ?int + { + return $this->value; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +abstract class AbstractVersionConstraint implements VersionConstraint +{ + /** @var string */ + private $originalValue; + public function __construct(string $originalValue) + { + $this->originalValue = $originalValue; + } + public function asString() : string + { + return $this->originalValue; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class AndVersionConstraintGroup extends AbstractVersionConstraint +{ + /** @var VersionConstraint[] */ + private $constraints = []; + /** + * @param VersionConstraint[] $constraints + */ + public function __construct(string $originalValue, array $constraints) + { + parent::__construct($originalValue); + $this->constraints = $constraints; + } + public function complies(Version $version) : bool + { + foreach ($this->constraints as $constraint) { + if (!$constraint->complies($version)) { + return \false; + } + } + return \true; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class AnyVersionConstraint implements VersionConstraint +{ + public function complies(Version $version) : bool + { + return \true; + } + public function asString() : string + { + return '*'; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class ExactVersionConstraint extends AbstractVersionConstraint +{ + public function complies(Version $version) : bool + { + $other = $version->getVersionString(); + if ($version->hasBuildMetaData()) { + $other .= '+' . $version->getBuildMetaData()->asString(); + } + return $this->asString() === $other; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class GreaterThanOrEqualToVersionConstraint extends AbstractVersionConstraint +{ + /** @var Version */ + private $minimalVersion; + public function __construct(string $originalValue, Version $minimalVersion) + { + parent::__construct($originalValue); + $this->minimalVersion = $minimalVersion; + } + public function complies(Version $version) : bool + { + return $version->getVersionString() === $this->minimalVersion->getVersionString() || $version->isGreaterThan($this->minimalVersion); + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class OrVersionConstraintGroup extends AbstractVersionConstraint +{ + /** @var VersionConstraint[] */ + private $constraints = []; + /** + * @param string $originalValue + * @param VersionConstraint[] $constraints + */ + public function __construct($originalValue, array $constraints) + { + parent::__construct($originalValue); + $this->constraints = $constraints; + } + public function complies(Version $version) : bool + { + foreach ($this->constraints as $constraint) { + if ($constraint->complies($version)) { + return \true; + } + } + return \false; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class SpecificMajorAndMinorVersionConstraint extends AbstractVersionConstraint +{ + /** @var int */ + private $major; + /** @var int */ + private $minor; + public function __construct(string $originalValue, int $major, int $minor) + { + parent::__construct($originalValue); + $this->major = $major; + $this->minor = $minor; + } + public function complies(Version $version) : bool + { + if ($version->getMajor()->getValue() !== $this->major) { + return \false; + } + return $version->getMinor()->getValue() === $this->minor; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +class SpecificMajorVersionConstraint extends AbstractVersionConstraint +{ + /** @var int */ + private $major; + public function __construct(string $originalValue, int $major) + { + parent::__construct($originalValue); + $this->major = $major; + } + public function complies(Version $version) : bool + { + return $version->getMajor()->getValue() === $this->major; + } +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +interface VersionConstraint +{ + public function complies(Version $version) : bool; + public function asString() : string; +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +use Throwable; +interface Exception extends Throwable +{ +} +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\PharIo\Version; + +final class UnsupportedVersionConstraintException extends \RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use function array_diff; +use function array_diff_key; +use function array_flip; +use function array_keys; +use function array_merge; +use function array_unique; +use function array_values; +use function count; +use function explode; +use function get_class; +use function is_array; +use function sort; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Test; +use ReflectionClass; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Builder; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory; +use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser; +use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; +use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser; +use PHPUnit\SebastianBergmann\CodeUnitReverseLookup\Wizard; +/** + * Provides collection functionality for PHP code coverage information. + */ +final class CodeCoverage +{ + private const UNCOVERED_FILES = 'UNCOVERED_FILES'; + /** + * @var Driver + */ + private $driver; + /** + * @var Filter + */ + private $filter; + /** + * @var Wizard + */ + private $wizard; + /** + * @var bool + */ + private $checkForUnintentionallyCoveredCode = \false; + /** + * @var bool + */ + private $includeUncoveredFiles = \true; + /** + * @var bool + */ + private $processUncoveredFiles = \false; + /** + * @var bool + */ + private $ignoreDeprecatedCode = \false; + /** + * @var null|PhptTestCase|string|TestCase + */ + private $currentId; + /** + * Code coverage data. + * + * @var ProcessedCodeCoverageData + */ + private $data; + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode = \true; + /** + * Test data. + * + * @var array + */ + private $tests = []; + /** + * @psalm-var list + */ + private $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; + /** + * @var ?FileAnalyser + */ + private $analyser; + /** + * @var ?string + */ + private $cacheDirectory; + public function __construct(Driver $driver, Filter $filter) + { + $this->driver = $driver; + $this->filter = $filter; + $this->data = new ProcessedCodeCoverageData(); + $this->wizard = new Wizard(); + } + /** + * Returns the code coverage information as a graph of node objects. + */ + public function getReport() : Directory + { + return (new Builder($this->analyser()))->build($this); + } + /** + * Clears collected code coverage data. + */ + public function clear() : void + { + $this->currentId = null; + $this->data = new ProcessedCodeCoverageData(); + $this->tests = []; + } + /** + * Returns the filter object used. + */ + public function filter() : Filter + { + return $this->filter; + } + /** + * Returns the collected code coverage data. + */ + public function getData(bool $raw = \false) : ProcessedCodeCoverageData + { + if (!$raw) { + if ($this->processUncoveredFiles) { + $this->processUncoveredFilesFromFilter(); + } elseif ($this->includeUncoveredFiles) { + $this->addUncoveredFilesFromFilter(); + } + } + return $this->data; + } + /** + * Sets the coverage data. + */ + public function setData(ProcessedCodeCoverageData $data) : void + { + $this->data = $data; + } + /** + * Returns the test data. + */ + public function getTests() : array + { + return $this->tests; + } + /** + * Sets the test data. + */ + public function setTests(array $tests) : void + { + $this->tests = $tests; + } + /** + * Start collection of code coverage information. + * + * @param PhptTestCase|string|TestCase $id + */ + public function start($id, bool $clear = \false) : void + { + if ($clear) { + $this->clear(); + } + $this->currentId = $id; + $this->driver->start(); + } + /** + * Stop collection of code coverage information. + * + * @param array|false $linesToBeCovered + */ + public function stop(bool $append = \true, $linesToBeCovered = [], array $linesToBeUsed = []) : RawCodeCoverageData + { + if (!is_array($linesToBeCovered) && $linesToBeCovered !== \false) { + throw new InvalidArgumentException('$linesToBeCovered must be an array or false'); + } + $data = $this->driver->stop(); + $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); + $this->currentId = null; + return $data; + } + /** + * Appends code coverage data. + * + * @param PhptTestCase|string|TestCase $id + * @param array|false $linesToBeCovered + * + * @throws ReflectionException + * @throws TestIdMissingException + * @throws UnintentionallyCoveredCodeException + */ + public function append(RawCodeCoverageData $rawData, $id = null, bool $append = \true, $linesToBeCovered = [], array $linesToBeUsed = []) : void + { + if ($id === null) { + $id = $this->currentId; + } + if ($id === null) { + throw new TestIdMissingException(); + } + $this->applyFilter($rawData); + $this->applyExecutableLinesFilter($rawData); + if ($this->useAnnotationsForIgnoringCode) { + $this->applyIgnoredLinesFilter($rawData); + } + $this->data->initializeUnseenData($rawData); + if (!$append) { + return; + } + if ($id !== self::UNCOVERED_FILES) { + $this->applyCoversAnnotationFilter($rawData, $linesToBeCovered, $linesToBeUsed); + if (empty($rawData->lineCoverage())) { + return; + } + $size = 'unknown'; + $status = -1; + $fromTestcase = \false; + if ($id instanceof TestCase) { + $fromTestcase = \true; + $_size = $id->getSize(); + if ($_size === Test::SMALL) { + $size = 'small'; + } elseif ($_size === Test::MEDIUM) { + $size = 'medium'; + } elseif ($_size === Test::LARGE) { + $size = 'large'; + } + $status = $id->getStatus(); + $id = get_class($id) . '::' . $id->getName(); + } elseif ($id instanceof PhptTestCase) { + $fromTestcase = \true; + $size = 'large'; + $id = $id->getName(); + } + $this->tests[$id] = ['size' => $size, 'status' => $status, 'fromTestcase' => $fromTestcase]; + $this->data->markCodeAsExecutedByTestCase($id, $rawData); + } + } + /** + * Merges the data from another instance. + */ + public function merge(self $that) : void + { + $this->filter->includeFiles($that->filter()->files()); + $this->data->merge($that->data); + $this->tests = array_merge($this->tests, $that->getTests()); + } + public function enableCheckForUnintentionallyCoveredCode() : void + { + $this->checkForUnintentionallyCoveredCode = \true; + } + public function disableCheckForUnintentionallyCoveredCode() : void + { + $this->checkForUnintentionallyCoveredCode = \false; + } + public function includeUncoveredFiles() : void + { + $this->includeUncoveredFiles = \true; + } + public function excludeUncoveredFiles() : void + { + $this->includeUncoveredFiles = \false; + } + public function processUncoveredFiles() : void + { + $this->processUncoveredFiles = \true; + } + public function doNotProcessUncoveredFiles() : void + { + $this->processUncoveredFiles = \false; + } + public function enableAnnotationsForIgnoringCode() : void + { + $this->useAnnotationsForIgnoringCode = \true; + } + public function disableAnnotationsForIgnoringCode() : void + { + $this->useAnnotationsForIgnoringCode = \false; + } + public function ignoreDeprecatedCode() : void + { + $this->ignoreDeprecatedCode = \true; + } + public function doNotIgnoreDeprecatedCode() : void + { + $this->ignoreDeprecatedCode = \false; + } + /** + * @psalm-assert-if-true !null $this->cacheDirectory + */ + public function cachesStaticAnalysis() : bool + { + return $this->cacheDirectory !== null; + } + public function cacheStaticAnalysis(string $directory) : void + { + $this->cacheDirectory = $directory; + } + public function doNotCacheStaticAnalysis() : void + { + $this->cacheDirectory = null; + } + /** + * @throws StaticAnalysisCacheNotConfiguredException + */ + public function cacheDirectory() : string + { + if (!$this->cachesStaticAnalysis()) { + throw new StaticAnalysisCacheNotConfiguredException('The static analysis cache is not configured'); + } + return $this->cacheDirectory; + } + /** + * @psalm-param class-string $className + */ + public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(string $className) : void + { + $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck[] = $className; + } + public function enableBranchAndPathCoverage() : void + { + $this->driver->enableBranchAndPathCoverage(); + } + public function disableBranchAndPathCoverage() : void + { + $this->driver->disableBranchAndPathCoverage(); + } + public function collectsBranchAndPathCoverage() : bool + { + return $this->driver->collectsBranchAndPathCoverage(); + } + public function detectsDeadCode() : bool + { + return $this->driver->detectsDeadCode(); + } + /** + * Applies the @covers annotation filtering. + * + * @param array|false $linesToBeCovered + * + * @throws ReflectionException + * @throws UnintentionallyCoveredCodeException + */ + private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $linesToBeCovered, array $linesToBeUsed) : void + { + if ($linesToBeCovered === \false) { + $rawData->clear(); + return; + } + if (empty($linesToBeCovered)) { + return; + } + if ($this->checkForUnintentionallyCoveredCode && (!$this->currentId instanceof TestCase || !$this->currentId->isMedium() && !$this->currentId->isLarge())) { + $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed); + } + $rawLineData = $rawData->lineCoverage(); + $filesWithNoCoverage = array_diff_key($rawLineData, $linesToBeCovered); + foreach (array_keys($filesWithNoCoverage) as $fileWithNoCoverage) { + $rawData->removeCoverageDataForFile($fileWithNoCoverage); + } + if (is_array($linesToBeCovered)) { + foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) { + $rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines); + $rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines); + } + } + } + private function applyFilter(RawCodeCoverageData $data) : void + { + if ($this->filter->isEmpty()) { + return; + } + foreach (array_keys($data->lineCoverage()) as $filename) { + if ($this->filter->isExcluded($filename)) { + $data->removeCoverageDataForFile($filename); + } + } + } + private function applyExecutableLinesFilter(RawCodeCoverageData $data) : void + { + foreach (array_keys($data->lineCoverage()) as $filename) { + if (!$this->filter->isFile($filename)) { + continue; + } + $data->keepLineCoverageDataOnlyForLines($filename, $this->analyser()->executableLinesIn($filename)); + } + } + private function applyIgnoredLinesFilter(RawCodeCoverageData $data) : void + { + foreach (array_keys($data->lineCoverage()) as $filename) { + if (!$this->filter->isFile($filename)) { + continue; + } + $data->removeCoverageDataForLines($filename, $this->analyser()->ignoredLinesFor($filename)); + } + } + /** + * @throws UnintentionallyCoveredCodeException + */ + private function addUncoveredFilesFromFilter() : void + { + $uncoveredFiles = array_diff($this->filter->files(), $this->data->coveredFiles()); + foreach ($uncoveredFiles as $uncoveredFile) { + if ($this->filter->isFile($uncoveredFile)) { + $this->append(RawCodeCoverageData::fromUncoveredFile($uncoveredFile, $this->analyser()), self::UNCOVERED_FILES); + } + } + } + /** + * @throws UnintentionallyCoveredCodeException + */ + private function processUncoveredFilesFromFilter() : void + { + $uncoveredFiles = array_diff($this->filter->files(), $this->data->coveredFiles()); + $this->driver->start(); + foreach ($uncoveredFiles as $uncoveredFile) { + if ($this->filter->isFile($uncoveredFile)) { + include_once $uncoveredFile; + } + } + $this->append($this->driver->stop(), self::UNCOVERED_FILES); + } + /** + * @throws ReflectionException + * @throws UnintentionallyCoveredCodeException + */ + private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed) : void + { + $allowedLines = $this->getAllowedLines($linesToBeCovered, $linesToBeUsed); + $unintentionallyCoveredUnits = []; + foreach ($data->lineCoverage() as $file => $_data) { + foreach ($_data as $line => $flag) { + if ($flag === 1 && !isset($allowedLines[$file][$line])) { + $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); + } + } + } + $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); + if (!empty($unintentionallyCoveredUnits)) { + throw new UnintentionallyCoveredCodeException($unintentionallyCoveredUnits); + } + } + private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) : array + { + $allowedLines = []; + foreach (array_keys($linesToBeCovered) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + $allowedLines[$file] = array_merge($allowedLines[$file], $linesToBeCovered[$file]); + } + foreach (array_keys($linesToBeUsed) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + $allowedLines[$file] = array_merge($allowedLines[$file], $linesToBeUsed[$file]); + } + foreach (array_keys($allowedLines) as $file) { + $allowedLines[$file] = array_flip(array_unique($allowedLines[$file])); + } + return $allowedLines; + } + /** + * @throws ReflectionException + */ + private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits) : array + { + $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); + sort($unintentionallyCoveredUnits); + foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { + $unit = explode('::', $unintentionallyCoveredUnits[$k]); + if (count($unit) !== 2) { + continue; + } + try { + $class = new ReflectionClass($unit[0]); + foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) { + if ($class->isSubclassOf($parentClass)) { + unset($unintentionallyCoveredUnits[$k]); + break; + } + } + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + } + return array_values($unintentionallyCoveredUnits); + } + private function analyser() : FileAnalyser + { + if ($this->analyser !== null) { + return $this->analyser; + } + $this->analyser = new ParsingFileAnalyser($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); + if ($this->cachesStaticAnalysis()) { + $this->analyser = new CachingFileAnalyser($this->cacheDirectory, $this->analyser); + } + return $this->analyser; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use PHPUnit\SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException; +use PHPUnit\SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; +use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Driver +{ + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_NOT_EXECUTABLE = -2; + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_NOT_EXECUTED = -1; + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_EXECUTED = 1; + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const BRANCH_NOT_HIT = 0; + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const BRANCH_HIT = 1; + /** + * @var bool + */ + private $collectBranchAndPathCoverage = \false; + /** + * @var bool + */ + private $detectDeadCode = \false; + /** + * @throws NoCodeCoverageDriverAvailableException + * @throws PcovNotAvailableException + * @throws PhpdbgNotAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + * + * @deprecated Use DriverSelector::forLineCoverage() instead + */ + public static function forLineCoverage(Filter $filter) : self + { + return (new Selector())->forLineCoverage($filter); + } + /** + * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + * + * @deprecated Use DriverSelector::forLineAndPathCoverage() instead + */ + public static function forLineAndPathCoverage(Filter $filter) : self + { + return (new Selector())->forLineAndPathCoverage($filter); + } + public function canCollectBranchAndPathCoverage() : bool + { + return \false; + } + public function collectsBranchAndPathCoverage() : bool + { + return $this->collectBranchAndPathCoverage; + } + /** + * @throws BranchAndPathCoverageNotSupportedException + */ + public function enableBranchAndPathCoverage() : void + { + if (!$this->canCollectBranchAndPathCoverage()) { + throw new BranchAndPathCoverageNotSupportedException(sprintf('%s does not support branch and path coverage', $this->nameAndVersion())); + } + $this->collectBranchAndPathCoverage = \true; + } + public function disableBranchAndPathCoverage() : void + { + $this->collectBranchAndPathCoverage = \false; + } + public function canDetectDeadCode() : bool + { + return \false; + } + public function detectsDeadCode() : bool + { + return $this->detectDeadCode; + } + /** + * @throws DeadCodeDetectionNotSupportedException + */ + public function enableDeadCodeDetection() : void + { + if (!$this->canDetectDeadCode()) { + throw new DeadCodeDetectionNotSupportedException(sprintf('%s does not support dead code detection', $this->nameAndVersion())); + } + $this->detectDeadCode = \true; + } + public function disableDeadCodeDetection() : void + { + $this->detectDeadCode = \false; + } + public abstract function nameAndVersion() : string; + public abstract function start() : void; + public abstract function stop() : RawCodeCoverageData; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use const pcov\inclusive; +use function array_intersect; +use function extension_loaded; +use function pcov\clear; +use function pcov\collect; +use function pcov\start; +use function pcov\stop; +use function pcov\waiting; +use function phpversion; +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class PcovDriver extends Driver +{ + /** + * @var Filter + */ + private $filter; + /** + * @throws PcovNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('pcov')) { + throw new PcovNotAvailableException(); + } + $this->filter = $filter; + } + public function start() : void + { + start(); + } + public function stop() : RawCodeCoverageData + { + stop(); + $filesToCollectCoverageFor = waiting(); + $collected = []; + if ($filesToCollectCoverageFor) { + if (!$this->filter->isEmpty()) { + $filesToCollectCoverageFor = array_intersect($filesToCollectCoverageFor, $this->filter->files()); + } + $collected = collect(inclusive, $filesToCollectCoverageFor); + clear(); + } + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collected); + } + public function nameAndVersion() : string + { + return 'PCOV ' . phpversion('pcov'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use const PHP_SAPI; +use const PHP_VERSION; +use function array_diff; +use function array_keys; +use function array_merge; +use function get_included_files; +use function phpdbg_end_oplog; +use function phpdbg_get_executable; +use function phpdbg_start_oplog; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class PhpdbgDriver extends Driver +{ + /** + * @throws PhpdbgNotAvailableException + */ + public function __construct() + { + if (PHP_SAPI !== 'phpdbg') { + throw new PhpdbgNotAvailableException(); + } + } + public function start() : void + { + phpdbg_start_oplog(); + } + public function stop() : RawCodeCoverageData + { + static $fetchedLines = []; + $dbgData = phpdbg_end_oplog(); + if ($fetchedLines === []) { + $sourceLines = phpdbg_get_executable(); + } else { + $newFiles = array_diff(get_included_files(), array_keys($fetchedLines)); + $sourceLines = []; + if ($newFiles) { + $sourceLines = phpdbg_get_executable(['files' => $newFiles]); + } + } + foreach ($sourceLines as $file => $lines) { + foreach ($lines as $lineNo => $numExecuted) { + $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; + } + } + $fetchedLines = array_merge($fetchedLines, $sourceLines); + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($this->detectExecutedLines($fetchedLines, $dbgData)); + } + public function nameAndVersion() : string + { + return 'PHPDBG ' . PHP_VERSION; + } + private function detectExecutedLines(array $sourceLines, array $dbgData) : array + { + foreach ($dbgData as $file => $coveredLines) { + foreach ($coveredLines as $lineNo => $numExecuted) { + // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. + // make sure we only mark lines executed which are actually executable. + if (isset($sourceLines[$file][$lineNo])) { + $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; + } + } + } + return $sourceLines; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use function phpversion; +use function version_compare; +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; +use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; +use PHPUnit\SebastianBergmann\Environment\Runtime; +final class Selector +{ + /** + * @throws NoCodeCoverageDriverAvailableException + * @throws PcovNotAvailableException + * @throws PhpdbgNotAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function forLineCoverage(Filter $filter) : Driver + { + $runtime = new Runtime(); + if ($runtime->hasPHPDBGCodeCoverage()) { + return new PhpdbgDriver(); + } + if ($runtime->hasPCOV()) { + return new PcovDriver($filter); + } + if ($runtime->hasXdebug()) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + $driver = new Xdebug3Driver($filter); + } else { + $driver = new Xdebug2Driver($filter); + } + $driver->enableDeadCodeDetection(); + return $driver; + } + throw new NoCodeCoverageDriverAvailableException(); + } + /** + * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function forLineAndPathCoverage(Filter $filter) : Driver + { + if ((new Runtime())->hasXdebug()) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + $driver = new Xdebug3Driver($filter); + } else { + $driver = new Xdebug2Driver($filter); + } + $driver->enableDeadCodeDetection(); + $driver->enableBranchAndPathCoverage(); + return $driver; + } + throw new NoCodeCoverageDriverWithPathCoverageSupportAvailableException(); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use const XDEBUG_CC_BRANCH_CHECK; +use const XDEBUG_CC_DEAD_CODE; +use const XDEBUG_CC_UNUSED; +use const XDEBUG_FILTER_CODE_COVERAGE; +use const XDEBUG_PATH_INCLUDE; +use const XDEBUG_PATH_WHITELIST; +use function defined; +use function extension_loaded; +use function ini_get; +use function phpversion; +use function sprintf; +use function version_compare; +use function xdebug_get_code_coverage; +use function xdebug_set_filter; +use function xdebug_start_code_coverage; +use function xdebug_stop_code_coverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Xdebug2Driver extends Driver +{ + /** + * @var bool + */ + private $pathCoverageIsMixedCoverage; + /** + * @throws WrongXdebugVersionException + * @throws Xdebug2NotEnabledException + * @throws XdebugNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('xdebug')) { + throw new XdebugNotAvailableException(); + } + if (version_compare(phpversion('xdebug'), '3', '>=')) { + throw new WrongXdebugVersionException(sprintf('This driver requires Xdebug 2 but version %s is loaded', phpversion('xdebug'))); + } + if (!ini_get('xdebug.coverage_enable')) { + throw new Xdebug2NotEnabledException(); + } + if (!$filter->isEmpty()) { + if (defined('XDEBUG_PATH_WHITELIST')) { + $listType = XDEBUG_PATH_WHITELIST; + } else { + $listType = XDEBUG_PATH_INCLUDE; + } + xdebug_set_filter(XDEBUG_FILTER_CODE_COVERAGE, $listType, $filter->files()); + } + $this->pathCoverageIsMixedCoverage = version_compare(phpversion('xdebug'), '2.9.6', '<'); + } + public function canCollectBranchAndPathCoverage() : bool + { + return \true; + } + public function canDetectDeadCode() : bool + { + return \true; + } + public function start() : void + { + $flags = XDEBUG_CC_UNUSED; + if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_DEAD_CODE; + } + if ($this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_BRANCH_CHECK; + } + xdebug_start_code_coverage($flags); + } + public function stop() : RawCodeCoverageData + { + $data = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + if ($this->collectsBranchAndPathCoverage()) { + if ($this->pathCoverageIsMixedCoverage) { + return RawCodeCoverageData::fromXdebugWithMixedCoverage($data); + } + return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + } + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); + } + public function nameAndVersion() : string + { + return 'Xdebug ' . phpversion('xdebug'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use const XDEBUG_CC_BRANCH_CHECK; +use const XDEBUG_CC_DEAD_CODE; +use const XDEBUG_CC_UNUSED; +use const XDEBUG_FILTER_CODE_COVERAGE; +use const XDEBUG_PATH_INCLUDE; +use function explode; +use function extension_loaded; +use function getenv; +use function in_array; +use function ini_get; +use function phpversion; +use function sprintf; +use function version_compare; +use function xdebug_get_code_coverage; +use function xdebug_set_filter; +use function xdebug_start_code_coverage; +use function xdebug_stop_code_coverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Xdebug3Driver extends Driver +{ + /** + * @throws WrongXdebugVersionException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('xdebug')) { + throw new XdebugNotAvailableException(); + } + if (version_compare(phpversion('xdebug'), '3', '<')) { + throw new WrongXdebugVersionException(sprintf('This driver requires Xdebug 3 but version %s is loaded', phpversion('xdebug'))); + } + $mode = getenv('XDEBUG_MODE'); + if ($mode === \false || $mode === '') { + $mode = ini_get('xdebug.mode'); + } + if ($mode === \false || !in_array('coverage', explode(',', $mode), \true)) { + throw new Xdebug3NotEnabledException(); + } + if (!$filter->isEmpty()) { + xdebug_set_filter(XDEBUG_FILTER_CODE_COVERAGE, XDEBUG_PATH_INCLUDE, $filter->files()); + } + } + public function canCollectBranchAndPathCoverage() : bool + { + return \true; + } + public function canDetectDeadCode() : bool + { + return \true; + } + public function start() : void + { + $flags = XDEBUG_CC_UNUSED; + if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_DEAD_CODE; + } + if ($this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_BRANCH_CHECK; + } + xdebug_start_code_coverage($flags); + } + public function stop() : RawCodeCoverageData + { + $data = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + if ($this->collectsBranchAndPathCoverage()) { + return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + } + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); + } + public function nameAndVersion() : string + { + return 'Xdebug ' . phpversion('xdebug'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class BranchAndPathCoverageNotSupportedException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class DeadCodeDetectionNotSupportedException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Util; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class DirectoryCouldNotBeCreatedException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class NoCodeCoverageDriverAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('No code coverage driver available'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class NoCodeCoverageDriverWithPathCoverageSupportAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('No code coverage driver with path coverage support available'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class ParserException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class PathExistsButIsNotDirectoryException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct(sprintf('"%s" exists but is not a directory', $path)); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class PcovNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The PCOV extension is not available'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class PhpdbgNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The PHPDBG SAPI is not available'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class ReflectionException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class ReportAlreadyFinalizedException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The code coverage report has already been finalized'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class StaticAnalysisCacheNotConfiguredException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class TestIdMissingException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('Test ID is missing'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class UnintentionallyCoveredCodeException extends RuntimeException implements Exception +{ + /** + * @var array + */ + private $unintentionallyCoveredUnits; + public function __construct(array $unintentionallyCoveredUnits) + { + $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits; + parent::__construct($this->toString()); + } + public function getUnintentionallyCoveredUnits() : array + { + return $this->unintentionallyCoveredUnits; + } + private function toString() : string + { + $message = ''; + foreach ($this->unintentionallyCoveredUnits as $unit) { + $message .= '- ' . $unit . "\n"; + } + return $message; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class WriteOperationFailedException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct(sprintf('Cannot write to "%s"', $path)); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class WrongXdebugVersionException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class Xdebug2NotEnabledException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('xdebug.coverage_enable=On has to be set'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class Xdebug3NotEnabledException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use PHPUnit\SebastianBergmann\CodeCoverage\Exception; +final class XdebugNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The Xdebug extension is not available'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use RuntimeException; +final class XmlException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage; + +use function array_keys; +use function is_file; +use function realpath; +use function strpos; +use PHPUnit\SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +final class Filter +{ + /** + * @psalm-var array + */ + private $files = []; + /** + * @psalm-var array + */ + private $isFileCache = []; + public function includeDirectory(string $directory, string $suffix = '.php', string $prefix = '') : void + { + foreach ((new FileIteratorFacade())->getFilesAsArray($directory, $suffix, $prefix) as $file) { + $this->includeFile($file); + } + } + /** + * @psalm-param list $files + */ + public function includeFiles(array $filenames) : void + { + foreach ($filenames as $filename) { + $this->includeFile($filename); + } + } + public function includeFile(string $filename) : void + { + $filename = realpath($filename); + if (!$filename) { + return; + } + $this->files[$filename] = \true; + } + public function excludeDirectory(string $directory, string $suffix = '.php', string $prefix = '') : void + { + foreach ((new FileIteratorFacade())->getFilesAsArray($directory, $suffix, $prefix) as $file) { + $this->excludeFile($file); + } + } + public function excludeFile(string $filename) : void + { + $filename = realpath($filename); + if (!$filename || !isset($this->files[$filename])) { + return; + } + unset($this->files[$filename]); + } + public function isFile(string $filename) : bool + { + if (isset($this->isFileCache[$filename])) { + return $this->isFileCache[$filename]; + } + if ($filename === '-' || strpos($filename, 'vfs://') === 0 || strpos($filename, 'xdebug://debug-eval') !== \false || strpos($filename, 'eval()\'d code') !== \false || strpos($filename, 'runtime-created function') !== \false || strpos($filename, 'runkit created function') !== \false || strpos($filename, 'assert code') !== \false || strpos($filename, 'regexp code') !== \false || strpos($filename, 'Standard input code') !== \false) { + $isFile = \false; + } else { + $isFile = is_file($filename); + } + $this->isFileCache[$filename] = $isFile; + return $isFile; + } + public function isExcluded(string $filename) : bool + { + return !isset($this->files[$filename]) || !$this->isFile($filename); + } + /** + * @psalm-return list + */ + public function files() : array + { + return array_keys($this->files); + } + public function isEmpty() : bool + { + return empty($this->files); + } +} +php-code-coverage + +Copyright (c) 2009-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; + +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function str_replace; +use function substr; +use Countable; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Percentage; /** - * This file is part of phpDocumentor. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class AbstractNode implements Countable +{ + /** + * @var string + */ + private $name; + /** + * @var string + */ + private $pathAsString; + /** + * @var array + */ + private $pathAsArray; + /** + * @var AbstractNode + */ + private $parent; + /** + * @var string + */ + private $id; + public function __construct(string $name, self $parent = null) + { + if (substr($name, -1) === DIRECTORY_SEPARATOR) { + $name = substr($name, 0, -1); + } + $this->name = $name; + $this->parent = $parent; + } + public function name() : string + { + return $this->name; + } + public function id() : string + { + if ($this->id === null) { + $parent = $this->parent(); + if ($parent === null) { + $this->id = 'index'; + } else { + $parentId = $parent->id(); + if ($parentId === 'index') { + $this->id = str_replace(':', '_', $this->name); + } else { + $this->id = $parentId . '/' . $this->name; + } + } + } + return $this->id; + } + public function pathAsString() : string + { + if ($this->pathAsString === null) { + if ($this->parent === null) { + $this->pathAsString = $this->name; + } else { + $this->pathAsString = $this->parent->pathAsString() . DIRECTORY_SEPARATOR . $this->name; + } + } + return $this->pathAsString; + } + public function pathAsArray() : array + { + if ($this->pathAsArray === null) { + if ($this->parent === null) { + $this->pathAsArray = []; + } else { + $this->pathAsArray = $this->parent->pathAsArray(); + } + $this->pathAsArray[] = $this; + } + return $this->pathAsArray; + } + public function parent() : ?self + { + return $this->parent; + } + public function percentageOfTestedClasses() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedClasses(), $this->numberOfClasses()); + } + public function percentageOfTestedTraits() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedTraits(), $this->numberOfTraits()); + } + public function percentageOfTestedClassesAndTraits() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedClassesAndTraits(), $this->numberOfClassesAndTraits()); + } + public function percentageOfTestedFunctions() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedFunctions(), $this->numberOfFunctions()); + } + public function percentageOfTestedMethods() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedMethods(), $this->numberOfMethods()); + } + public function percentageOfTestedFunctionsAndMethods() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfTestedFunctionsAndMethods(), $this->numberOfFunctionsAndMethods()); + } + public function percentageOfExecutedLines() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfExecutedLines(), $this->numberOfExecutableLines()); + } + public function percentageOfExecutedBranches() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfExecutedBranches(), $this->numberOfExecutableBranches()); + } + public function percentageOfExecutedPaths() : Percentage + { + return Percentage::fromFractionAndTotal($this->numberOfExecutedPaths(), $this->numberOfExecutablePaths()); + } + public function numberOfClassesAndTraits() : int + { + return $this->numberOfClasses() + $this->numberOfTraits(); + } + public function numberOfTestedClassesAndTraits() : int + { + return $this->numberOfTestedClasses() + $this->numberOfTestedTraits(); + } + public function classesAndTraits() : array + { + return array_merge($this->classes(), $this->traits()); + } + public function numberOfFunctionsAndMethods() : int + { + return $this->numberOfFunctions() + $this->numberOfMethods(); + } + public function numberOfTestedFunctionsAndMethods() : int + { + return $this->numberOfTestedFunctions() + $this->numberOfTestedMethods(); + } + public abstract function classes() : array; + public abstract function traits() : array; + public abstract function functions() : array; + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public abstract function linesOfCode() : array; + public abstract function numberOfExecutableLines() : int; + public abstract function numberOfExecutedLines() : int; + public abstract function numberOfExecutableBranches() : int; + public abstract function numberOfExecutedBranches() : int; + public abstract function numberOfExecutablePaths() : int; + public abstract function numberOfExecutedPaths() : int; + public abstract function numberOfClasses() : int; + public abstract function numberOfTestedClasses() : int; + public abstract function numberOfTraits() : int; + public abstract function numberOfTestedTraits() : int; + public abstract function numberOfMethods() : int; + public abstract function numberOfTestedMethods() : int; + public abstract function numberOfFunctions() : int; + public abstract function numberOfTestedFunctions() : int; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; + +use const DIRECTORY_SEPARATOR; +use function array_shift; +use function basename; +use function count; +use function dirname; +use function explode; +use function implode; +use function is_file; +use function str_replace; +use function strpos; +use function substr; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Builder +{ + /** + * @var FileAnalyser + */ + private $analyser; + public function __construct(FileAnalyser $analyser) + { + $this->analyser = $analyser; + } + public function build(CodeCoverage $coverage) : Directory + { + $data = clone $coverage->getData(); + // clone because path munging is destructive to the original data + $commonPath = $this->reducePaths($data); + $root = new Directory($commonPath, null); + $this->addItems($root, $this->buildDirectoryStructure($data), $coverage->getTests()); + return $root; + } + private function addItems(Directory $root, array $items, array $tests) : void + { + foreach ($items as $key => $value) { + $key = (string) $key; + if (substr($key, -2) === '/f') { + $key = substr($key, 0, -2); + $filename = $root->pathAsString() . DIRECTORY_SEPARATOR . $key; + if (is_file($filename)) { + $root->addFile(new File($key, $root, $value['lineCoverage'], $value['functionCoverage'], $tests, $this->analyser->classesIn($filename), $this->analyser->traitsIn($filename), $this->analyser->functionsIn($filename), $this->analyser->linesOfCodeFor($filename))); + } + } else { + $child = $root->addDirectory($key); + $this->addItems($child, $value, $tests); + } + } + } + /** + * Builds an array representation of the directory structure. + * + * For instance, + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is transformed into + * + * + * Array + * ( + * [.] => Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * ) + * + */ + private function buildDirectoryStructure(ProcessedCodeCoverageData $data) : array + { + $result = []; + foreach ($data->coveredFiles() as $originalPath) { + $path = explode(DIRECTORY_SEPARATOR, $originalPath); + $pointer =& $result; + $max = count($path); + for ($i = 0; $i < $max; $i++) { + $type = ''; + if ($i === $max - 1) { + $type = '/f'; + } + $pointer =& $pointer[$path[$i] . $type]; + } + $pointer = ['lineCoverage' => $data->lineCoverage()[$originalPath] ?? [], 'functionCoverage' => $data->functionCoverage()[$originalPath] ?? []]; + } + return $result; + } + /** + * Reduces the paths by cutting the longest common start path. + * + * For instance, + * + * + * Array + * ( + * [/home/sb/Money/Money.php] => Array + * ( + * ... + * ) + * + * [/home/sb/Money/MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is reduced to + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + */ + private function reducePaths(ProcessedCodeCoverageData $coverage) : string + { + if (empty($coverage->coveredFiles())) { + return '.'; + } + $commonPath = ''; + $paths = $coverage->coveredFiles(); + if (count($paths) === 1) { + $commonPath = dirname($paths[0]) . DIRECTORY_SEPARATOR; + $coverage->renameFile($paths[0], basename($paths[0])); + return $commonPath; + } + $max = count($paths); + for ($i = 0; $i < $max; $i++) { + // strip phar:// prefixes + if (strpos($paths[$i], 'phar://') === 0) { + $paths[$i] = substr($paths[$i], 7); + $paths[$i] = str_replace('/', DIRECTORY_SEPARATOR, $paths[$i]); + } + $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); + if (empty($paths[$i][0])) { + $paths[$i][0] = DIRECTORY_SEPARATOR; + } + } + $done = \false; + $max = count($paths); + while (!$done) { + for ($i = 0; $i < $max - 1; $i++) { + if (!isset($paths[$i][0]) || !isset($paths[$i + 1][0]) || $paths[$i][0] !== $paths[$i + 1][0]) { + $done = \true; + break; + } + } + if (!$done) { + $commonPath .= $paths[0][0]; + if ($paths[0][0] !== DIRECTORY_SEPARATOR) { + $commonPath .= DIRECTORY_SEPARATOR; + } + for ($i = 0; $i < $max; $i++) { + array_shift($paths[$i]); + } + } + } + $original = $coverage->coveredFiles(); + $max = count($original); + for ($i = 0; $i < $max; $i++) { + $coverage->renameFile($original[$i], implode(DIRECTORY_SEPARATOR, $paths[$i])); + } + return substr($commonPath, 0, -1); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; + +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CrapIndex +{ + /** + * @var int + */ + private $cyclomaticComplexity; + /** + * @var float + */ + private $codeCoverage; + public function __construct(int $cyclomaticComplexity, float $codeCoverage) + { + $this->cyclomaticComplexity = $cyclomaticComplexity; + $this->codeCoverage = $codeCoverage; + } + public function asString() : string + { + if ($this->codeCoverage === 0.0) { + return (string) ($this->cyclomaticComplexity ** 2 + $this->cyclomaticComplexity); + } + if ($this->codeCoverage >= 95) { + return (string) $this->cyclomaticComplexity; + } + return sprintf('%01.2F', $this->cyclomaticComplexity ** 2 * (1 - $this->codeCoverage / 100) ** 3 + $this->cyclomaticComplexity); + } +} + * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; -use InvalidArgumentException; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Author; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Covers; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Deprecated; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Generic; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Param; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Property; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Return_; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\See as SeeTag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Since; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Source; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Throws; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Uses; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Var_; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Version; -use PHPUnit\phpDocumentor\Reflection\FqsenResolver; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionParameter; -use PHPUnit\Webmozart\Assert\Assert; use function array_merge; -use function array_slice; -use function call_user_func_array; use function count; -use function get_class; -use function preg_match; -use function strpos; -use function trim; +use IteratorAggregate; +use RecursiveIteratorIterator; /** - * Creates a Tag object given the contents of a tag. - * - * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create` - * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can - * pass the dependencies that you need to construct a tag object. - * - * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise - * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to - * > verify that a dependency is actually passed. - * - * This Factory also features a Service Locator component that is used to pass the right dependencies to the - * `create` method of a tag; each dependency should be registered as a service or as a parameter. - * - * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass - * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class StandardTagFactory implements TagFactory +final class Directory extends AbstractNode implements IteratorAggregate { - /** PCRE regular expression matching a tag name. */ - public const REGEX_TAGNAME = '[\\w\\-\\_\\\\:]+'; /** - * @var array> An array with a tag as a key, and an - * FQCN to a class that handles it as an array value. + * @var AbstractNode[] */ - private $tagHandlerMappings = [ - 'author' => Author::class, - 'covers' => Covers::class, - 'deprecated' => Deprecated::class, - // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', - 'link' => LinkTag::class, - 'method' => Method::class, - 'param' => Param::class, - 'property-read' => PropertyRead::class, - 'property' => Property::class, - 'property-write' => PropertyWrite::class, - 'return' => Return_::class, - 'see' => SeeTag::class, - 'since' => Since::class, - 'source' => Source::class, - 'throw' => Throws::class, - 'throws' => Throws::class, - 'uses' => Uses::class, - 'var' => Var_::class, - 'version' => Version::class, - ]; + private $children = []; /** - * @var array> An array with a anotation s a key, and an - * FQCN to a class that handles it as an array value. + * @var Directory[] */ - private $annotationMappings = []; + private $directories = []; /** - * @var ReflectionParameter[][] a lazy-loading cache containing parameters - * for each tagHandler that has been used. + * @var File[] */ - private $tagHandlerParameterCache = []; - /** @var FqsenResolver */ - private $fqsenResolver; + private $files = []; /** - * @var mixed[] an array representing a simple Service Locator where we can store parameters and - * services that can be inserted into the Factory Methods of Tag Handlers. + * @var array */ - private $serviceLocator = []; + private $classes; /** - * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. - * - * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property - * is used. - * - * @see self::registerTagHandler() to add a new tag handler to the existing default list. - * - * @param array> $tagHandlers + * @var array */ - public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null) + private $traits; + /** + * @var array + */ + private $functions; + /** + * @psalm-var null|array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + private $linesOfCode; + /** + * @var int + */ + private $numFiles = -1; + /** + * @var int + */ + private $numExecutableLines = -1; + /** + * @var int + */ + private $numExecutedLines = -1; + /** + * @var int + */ + private $numExecutableBranches = -1; + /** + * @var int + */ + private $numExecutedBranches = -1; + /** + * @var int + */ + private $numExecutablePaths = -1; + /** + * @var int + */ + private $numExecutedPaths = -1; + /** + * @var int + */ + private $numClasses = -1; + /** + * @var int + */ + private $numTestedClasses = -1; + /** + * @var int + */ + private $numTraits = -1; + /** + * @var int + */ + private $numTestedTraits = -1; + /** + * @var int + */ + private $numMethods = -1; + /** + * @var int + */ + private $numTestedMethods = -1; + /** + * @var int + */ + private $numFunctions = -1; + /** + * @var int + */ + private $numTestedFunctions = -1; + public function count() : int { - $this->fqsenResolver = $fqsenResolver; - if ($tagHandlers !== null) { - $this->tagHandlerMappings = $tagHandlers; + if ($this->numFiles === -1) { + $this->numFiles = 0; + foreach ($this->children as $child) { + $this->numFiles += count($child); + } } - $this->addService($fqsenResolver, FqsenResolver::class); + return $this->numFiles; } - public function create(string $tagLine, ?TypeContext $context = null) : Tag + public function getIterator() : RecursiveIteratorIterator { - if (!$context) { - $context = new TypeContext(''); - } - [$tagName, $tagBody] = $this->extractTagParts($tagLine); - return $this->createTag(trim($tagBody), $tagName, $context); + return new RecursiveIteratorIterator(new Iterator($this), RecursiveIteratorIterator::SELF_FIRST); } - /** - * @param mixed $value - */ - public function addParameter(string $name, $value) : void + public function addDirectory(string $name) : self { - $this->serviceLocator[$name] = $value; + $directory = new self($name, $this); + $this->children[] = $directory; + $this->directories[] =& $this->children[count($this->children) - 1]; + return $directory; } - public function addService(object $service, ?string $alias = null) : void + public function addFile(File $file) : void { - $this->serviceLocator[$alias ?: get_class($service)] = $service; + $this->children[] = $file; + $this->files[] =& $this->children[count($this->children) - 1]; + $this->numExecutableLines = -1; + $this->numExecutedLines = -1; } - public function registerTagHandler(string $tagName, string $handler) : void + public function directories() : array { - Assert::stringNotEmpty($tagName); - Assert::classExists($handler); - Assert::implementsInterface($handler, Tag::class); - if (strpos($tagName, '\\') && $tagName[0] !== '\\') { - throw new InvalidArgumentException('A namespaced tag must have a leading backslash as it must be fully qualified'); + return $this->directories; + } + public function files() : array + { + return $this->files; + } + public function children() : array + { + return $this->children; + } + public function classes() : array + { + if ($this->classes === null) { + $this->classes = []; + foreach ($this->children as $child) { + $this->classes = array_merge($this->classes, $child->classes()); + } } - $this->tagHandlerMappings[$tagName] = $handler; + return $this->classes; } - /** - * Extracts all components for a tag. - * - * @return string[] - */ - private function extractTagParts(string $tagLine) : array + public function traits() : array { - $matches = []; - if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\\s\\(\\{])\\s*([^\\s].*)|$)/us', $tagLine, $matches)) { - throw new InvalidArgumentException('The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'); + if ($this->traits === null) { + $this->traits = []; + foreach ($this->children as $child) { + $this->traits = array_merge($this->traits, $child->traits()); + } } - if (count($matches) < 3) { - $matches[] = ''; + return $this->traits; + } + public function functions() : array + { + if ($this->functions === null) { + $this->functions = []; + foreach ($this->children as $child) { + $this->functions = array_merge($this->functions, $child->functions()); + } } - return array_slice($matches, 1); + return $this->functions; } /** - * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the - * body was invalid. + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} */ - private function createTag(string $body, string $name, TypeContext $context) : Tag + public function linesOfCode() : array { - $handlerClassName = $this->findHandlerClassName($name, $context); - $arguments = $this->getArgumentsForParametersFromWiring($this->fetchParametersForHandlerFactoryMethod($handlerClassName), $this->getServiceLocatorWithDynamicParameters($context, $name, $body)); - try { - $callable = [$handlerClassName, 'create']; - Assert::isCallable($callable); - /** @phpstan-var callable(string): ?Tag $callable */ - $tag = call_user_func_array($callable, $arguments); - return $tag ?? InvalidTag::create($body, $name); - } catch (InvalidArgumentException $e) { - return InvalidTag::create($body, $name)->withError($e); + if ($this->linesOfCode === null) { + $this->linesOfCode = ['linesOfCode' => 0, 'commentLinesOfCode' => 0, 'nonCommentLinesOfCode' => 0]; + foreach ($this->children as $child) { + $childLinesOfCode = $child->linesOfCode(); + $this->linesOfCode['linesOfCode'] += $childLinesOfCode['linesOfCode']; + $this->linesOfCode['commentLinesOfCode'] += $childLinesOfCode['commentLinesOfCode']; + $this->linesOfCode['nonCommentLinesOfCode'] += $childLinesOfCode['nonCommentLinesOfCode']; + } } + return $this->linesOfCode; } - /** - * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). - * - * @return class-string - */ - private function findHandlerClassName(string $tagName, TypeContext $context) : string + public function numberOfExecutableLines() : int { - $handlerClassName = Generic::class; - if (isset($this->tagHandlerMappings[$tagName])) { - $handlerClassName = $this->tagHandlerMappings[$tagName]; - } elseif ($this->isAnnotation($tagName)) { - // TODO: Annotation support is planned for a later stage and as such is disabled for now - $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); - if (isset($this->annotationMappings[$tagName])) { - $handlerClassName = $this->annotationMappings[$tagName]; + if ($this->numExecutableLines === -1) { + $this->numExecutableLines = 0; + foreach ($this->children as $child) { + $this->numExecutableLines += $child->numberOfExecutableLines(); } } - return $handlerClassName; + return $this->numExecutableLines; } - /** - * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. - * - * @param ReflectionParameter[] $parameters - * @param mixed[] $locator - * - * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters - * is provided with this method. - */ - private function getArgumentsForParametersFromWiring(array $parameters, array $locator) : array + public function numberOfExecutedLines() : int { - $arguments = []; - foreach ($parameters as $parameter) { - $type = $parameter->getType(); - $typeHint = null; - if ($type instanceof ReflectionNamedType) { - $typeHint = $type->getName(); - if ($typeHint === 'self') { - $declaringClass = $parameter->getDeclaringClass(); - if ($declaringClass !== null) { - $typeHint = $declaringClass->getName(); - } - } + if ($this->numExecutedLines === -1) { + $this->numExecutedLines = 0; + foreach ($this->children as $child) { + $this->numExecutedLines += $child->numberOfExecutedLines(); } - if (isset($locator[$typeHint])) { - $arguments[] = $locator[$typeHint]; - continue; + } + return $this->numExecutedLines; + } + public function numberOfExecutableBranches() : int + { + if ($this->numExecutableBranches === -1) { + $this->numExecutableBranches = 0; + foreach ($this->children as $child) { + $this->numExecutableBranches += $child->numberOfExecutableBranches(); } - $parameterName = $parameter->getName(); - if (isset($locator[$parameterName])) { - $arguments[] = $locator[$parameterName]; - continue; + } + return $this->numExecutableBranches; + } + public function numberOfExecutedBranches() : int + { + if ($this->numExecutedBranches === -1) { + $this->numExecutedBranches = 0; + foreach ($this->children as $child) { + $this->numExecutedBranches += $child->numberOfExecutedBranches(); } - $arguments[] = null; } - return $arguments; + return $this->numExecutedBranches; } - /** - * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given - * tag handler class name. - * - * @param class-string $handlerClassName - * - * @return ReflectionParameter[] - */ - private function fetchParametersForHandlerFactoryMethod(string $handlerClassName) : array + public function numberOfExecutablePaths() : int { - if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { - $methodReflection = new ReflectionMethod($handlerClassName, 'create'); - $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + if ($this->numExecutablePaths === -1) { + $this->numExecutablePaths = 0; + foreach ($this->children as $child) { + $this->numExecutablePaths += $child->numberOfExecutablePaths(); + } } - return $this->tagHandlerParameterCache[$handlerClassName]; + return $this->numExecutablePaths; } - /** - * Returns a copy of this class' Service Locator with added dynamic parameters, - * such as the tag's name, body and Context. - * - * @param TypeContext $context The Context (namespace and aliasses) that may be - * passed and is used to resolve FQSENs. - * @param string $tagName The name of the tag that may be - * passed onto the factory method of the Tag class. - * @param string $tagBody The body of the tag that may be - * passed onto the factory method of the Tag class. - * - * @return mixed[] - */ - private function getServiceLocatorWithDynamicParameters(TypeContext $context, string $tagName, string $tagBody) : array + public function numberOfExecutedPaths() : int { - return array_merge($this->serviceLocator, ['name' => $tagName, 'body' => $tagBody, TypeContext::class => $context]); + if ($this->numExecutedPaths === -1) { + $this->numExecutedPaths = 0; + foreach ($this->children as $child) { + $this->numExecutedPaths += $child->numberOfExecutedPaths(); + } + } + return $this->numExecutedPaths; } - /** - * Returns whether the given tag belongs to an annotation. - * - * @todo this method should be populated once we implement Annotation notation support. - */ - private function isAnnotation(string $tagContent) : bool + public function numberOfClasses() : int { - // 1. Contains a namespace separator - // 2. Contains parenthesis - // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part - // of the annotation class name matches the found tag name - return \false; + if ($this->numClasses === -1) { + $this->numClasses = 0; + foreach ($this->children as $child) { + $this->numClasses += $child->numberOfClasses(); + } + } + return $this->numClasses; } -} -indent = $indent; - $this->indentString = $indentString; - $this->isFirstLineIndented = $indentFirstLine; - $this->lineLength = $lineLength; - $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + if ($this->numTestedClasses === -1) { + $this->numTestedClasses = 0; + foreach ($this->children as $child) { + $this->numTestedClasses += $child->numberOfTestedClasses(); + } + } + return $this->numTestedClasses; } - /** - * Generate a DocBlock comment. - * - * @param DocBlock $docblock The DocBlock to serialize. - * - * @return string The serialized doc block. - */ - public function getDocComment(DocBlock $docblock) : string + public function numberOfTraits() : int { - $indent = str_repeat($this->indentString, $this->indent); - $firstIndent = $this->isFirstLineIndented ? $indent : ''; - // 3 === strlen(' * ') - $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; - $text = $this->removeTrailingSpaces($indent, $this->addAsterisksForEachLine($indent, $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength))); - $comment = $firstIndent . "/**\n"; - if ($text) { - $comment .= $indent . ' * ' . $text . "\n"; - $comment .= $indent . " *\n"; + if ($this->numTraits === -1) { + $this->numTraits = 0; + foreach ($this->children as $child) { + $this->numTraits += $child->numberOfTraits(); + } + } + return $this->numTraits; + } + public function numberOfTestedTraits() : int + { + if ($this->numTestedTraits === -1) { + $this->numTestedTraits = 0; + foreach ($this->children as $child) { + $this->numTestedTraits += $child->numberOfTestedTraits(); + } } - $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); - return $comment . $indent . ' */'; + return $this->numTestedTraits; } - private function removeTrailingSpaces(string $indent, string $text) : string + public function numberOfMethods() : int { - return str_replace(sprintf("\n%s * \n", $indent), sprintf("\n%s *\n", $indent), $text); + if ($this->numMethods === -1) { + $this->numMethods = 0; + foreach ($this->children as $child) { + $this->numMethods += $child->numberOfMethods(); + } + } + return $this->numMethods; } - private function addAsterisksForEachLine(string $indent, string $text) : string + public function numberOfTestedMethods() : int { - return str_replace("\n", sprintf("\n%s * ", $indent), $text); + if ($this->numTestedMethods === -1) { + $this->numTestedMethods = 0; + foreach ($this->children as $child) { + $this->numTestedMethods += $child->numberOfTestedMethods(); + } + } + return $this->numTestedMethods; } - private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength) : string + public function numberOfFunctions() : int { - $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() : ''); - if ($wrapLength !== null) { - $text = wordwrap($text, $wrapLength); - return $text; + if ($this->numFunctions === -1) { + $this->numFunctions = 0; + foreach ($this->children as $child) { + $this->numFunctions += $child->numberOfFunctions(); + } } - return $text; + return $this->numFunctions; } - private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment) : string + public function numberOfTestedFunctions() : int { - foreach ($docblock->getTags() as $tag) { - $tagText = $this->tagFormatter->format($tag); - if ($wrapLength !== null) { - $tagText = wordwrap($tagText, $wrapLength); + if ($this->numTestedFunctions === -1) { + $this->numTestedFunctions = 0; + foreach ($this->children as $child) { + $this->numTestedFunctions += $child->numberOfTestedFunctions(); } - $tagText = str_replace("\n", sprintf("\n%s * ", $indent), $tagText); - $comment .= sprintf("%s * %s\n", $indent, $tagText); } - return $comment; + return $this->numTestedFunctions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; -use InvalidArgumentException; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -interface TagFactory +use function array_filter; +use function count; +use function range; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class File extends AbstractNode { /** - * Adds a parameter to the service locator that can be injected in a tag's factory method. - * - * When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to - * typehint a parameter in the signature so that we can use that interface or class name to inject a dependency - * (see {@see addService()} for more information on that). - * - * Another way is to check the name of the argument against the names in the Service Locator. With this method - * you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching - * name. - * - * Be aware that there are two reserved names: - * - * - name, representing the name of the tag. - * - body, representing the complete body of the tag. - * - * These parameters are injected at the last moment and will override any existing parameter with those names. - * - * @param mixed $value + * @var array */ - public function addParameter(string $name, $value) : void; + private $lineCoverageData; /** - * Factory method responsible for instantiating the correct sub type. - * - * @param string $tagLine The text for this tag, including description. - * - * @return Tag A new tag object. - * - * @throws InvalidArgumentException If an invalid tag line was presented. + * @var array */ - public function create(string $tagLine, ?TypeContext $context = null) : Tag; + private $functionCoverageData; /** - * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided. - * - * When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter - * has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint. - * - * Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the - * interface is passed as alias then every time that interface is requested the provided service will be returned. + * @var array */ - public function addService(object $service) : void; + private $testData; /** - * Registers a handler for tags. - * - * If you want to use your own tags then you can use this method to instruct the TagFactory - * to register the name of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement - * the {@see Tag} interface (and thus the create method). - * - * @param string $tagName Name of tag to register a handler for. When registering a namespaced - * tag, the full name, along with a prefixing slash MUST be provided. - * @param class-string $handler FQCN of handler. - * - * @throws InvalidArgumentException If the tag name is not a string. - * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but - * does not start with a backslash. - * @throws InvalidArgumentException If the handler is not a string. - * @throws InvalidArgumentException If the handler is not an existing class. - * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + * @var int */ - public function registerTagHandler(string $tagName, string $handler) : void; -} -refers = $refers; - $this->description = $description; - } - public static function create(string $body, ?FqsenResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self - { - Assert::notNull($descriptionFactory); - $parts = Utils::pregSplit('/\\s+/Su', $body, 2); - $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; - // https://tools.ietf.org/html/rfc2396#section-3 - if (preg_match('/\\w:\\/\\/\\w/i', $parts[0])) { - return new static(new Url($parts[0]), $description); - } - return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); - } - private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen - { - Assert::notNull($fqsenResolver); - $fqsenParts = explode('::', $parts); - $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); - if (!array_key_exists(1, $fqsenParts)) { - return $resolved; - } - return new Fqsen($resolved . '::' . $fqsenParts[1]); - } + private $numExecutedLines = 0; /** - * Returns the ref of this tag. + * @var int */ - public function getReference() : Reference - { - return $this->refers; - } + private $numExecutableBranches = 0; /** - * Returns a string representation of this tag. + * @var int */ - public function __toString() : string - { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; - } - $refers = (string) $this->refers; - return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); - } -} -validateTagName($name); - $this->name = $name; - $this->description = $description; - } + private $numExecutablePaths = 0; /** - * Creates a new tag that represents any unknown tag type. - * - * @return static + * @var int */ - public static function create(string $body, string $name = '', ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self - { - Assert::stringNotEmpty($name); - Assert::notNull($descriptionFactory); - $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; - return new static($name, $description); - } + private $numExecutedPaths = 0; /** - * Returns the tag as a serialized string + * @var array */ - public function __toString() : string - { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; - } - return $description; - } + private $classes = []; /** - * Validates if the tag name matches the expected format, otherwise throws an exception. + * @var array */ - private function validateTagName(string $name) : void + private $traits = []; + /** + * @var array + */ + private $functions = []; + /** + * @psalm-var array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + private $linesOfCode; + /** + * @var int + */ + private $numClasses; + /** + * @var int + */ + private $numTestedClasses = 0; + /** + * @var int + */ + private $numTraits; + /** + * @var int + */ + private $numTestedTraits = 0; + /** + * @var int + */ + private $numMethods; + /** + * @var int + */ + private $numTestedMethods; + /** + * @var int + */ + private $numTestedFunctions; + /** + * @var array + */ + private $codeUnitsByLine = []; + /** + * @psalm-param array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} $linesOfCode + */ + public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, array $linesOfCode) { - if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { - throw new InvalidArgumentException('The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' . 'hyphens and backslashes.'); - } + parent::__construct($name, $parent); + $this->lineCoverageData = $lineCoverageData; + $this->functionCoverageData = $functionCoverageData; + $this->testData = $testData; + $this->linesOfCode = $linesOfCode; + $this->calculateStatistics($classes, $traits, $functions); } -} -name = $name; - $this->body = $body; + return 1; } - public function getException() : ?Throwable + public function lineCoverageData() : array { - return $this->throwable; + return $this->lineCoverageData; } - public function getName() : string + public function functionCoverageData() : array { - return $this->name; + return $this->functionCoverageData; } - public static function create(string $body, string $name = '') : self + public function testData() : array { - return new self($name, $body); + return $this->testData; } - public function withError(Throwable $exception) : self + public function classes() : array { - $this->flattenExceptionBacktrace($exception); - $tag = new self($this->name, $this->body); - $tag->throwable = $exception; - return $tag; + return $this->classes; } - /** - * Removes all complex types from backtrace - * - * Not all objects are serializable. So we need to remove them from the - * stored exception to be sure that we do not break existing library usage. - */ - private function flattenExceptionBacktrace(Throwable $exception) : void + public function traits() : array { - $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); - $traceProperty->setAccessible(\true); - do { - $trace = $exception->getTrace(); - if (isset($trace[0]['args'])) { - $trace = array_map(function (array $call) : array { - $call['args'] = array_map([$this, 'flattenArguments'], $call['args']); - return $call; - }, $trace); - } - $traceProperty->setValue($exception, $trace); - $exception = $exception->getPrevious(); - } while ($exception !== null); - $traceProperty->setAccessible(\false); + return $this->traits; } - /** - * @param mixed $value - * - * @return mixed - * - * @throws ReflectionException - */ - private function flattenArguments($value) + public function functions() : array { - if ($value instanceof Closure) { - $closureReflection = new ReflectionFunction($value); - $value = sprintf('(Closure at %s:%s)', $closureReflection->getFileName(), $closureReflection->getStartLine()); - } elseif (is_object($value)) { - $value = sprintf('object(%s)', get_class($value)); - } elseif (is_resource($value)) { - $value = sprintf('resource(%s)', get_resource_type($value)); - } elseif (is_array($value)) { - $value = array_map([$this, 'flattenArguments'], $value); - } - return $value; + return $this->functions; } - public function render(?Formatter $formatter = null) : string + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCode() : array { - if ($formatter === null) { - $formatter = new Formatter\PassthroughFormatter(); - } - return $formatter->format($this); + return $this->linesOfCode; } - public function __toString() : string + public function numberOfExecutableLines() : int { - return $this->body; + return $this->numExecutableLines; } -} -startingLine = (int) $startingLine; - $this->lineCount = $lineCount !== null ? (int) $lineCount : null; - $this->description = $description; + return $this->numExecutedLines; } - public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self + public function numberOfExecutableBranches() : int { - Assert::stringNotEmpty($body); - Assert::notNull($descriptionFactory); - $startingLine = 1; - $lineCount = null; - $description = null; - // Starting line / Number of lines / Description - if (preg_match('/^([1-9]\\d*)\\s*(?:((?1))\\s+)?(.*)$/sux', $body, $matches)) { - $startingLine = (int) $matches[1]; - if (isset($matches[2]) && $matches[2] !== '') { - $lineCount = (int) $matches[2]; - } - $description = $matches[3]; - } - return new static($startingLine, $lineCount, $descriptionFactory->create($description ?? '', $context)); + return $this->numExecutableBranches; } - /** - * Gets the starting line. - * - * @return int The starting line, relative to the structural element's - * location. - */ - public function getStartingLine() : int + public function numberOfExecutedBranches() : int { - return $this->startingLine; + return $this->numExecutedBranches; } - /** - * Returns the number of lines. - * - * @return int|null The number of lines, relative to the starting line. NULL - * means "to the end". - */ - public function getLineCount() : ?int + public function numberOfExecutablePaths() : int { - return $this->lineCount; + return $this->numExecutablePaths; } - public function __toString() : string + public function numberOfExecutedPaths() : int { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; - } - $startingLine = (string) $this->startingLine; - $lineCount = $this->lineCount !== null ? '' . $this->lineCount : ''; - return $startingLine . ($lineCount !== '' ? ($startingLine || $startingLine === '0' ? ' ' : '') . $lineCount : '') . ($description !== '' ? ($startingLine || $startingLine === '0' || $lineCount !== '' ? ' ' : '') . $description : ''); + return $this->numExecutedPaths; } -} - - * @var array> - */ - private $arguments; - /** @var bool */ - private $isStatic; - /** @var Type */ - private $returnType; - /** - * @param array> $arguments - * - * @phpstan-param array $arguments - */ - public function __construct(string $methodName, array $arguments = [], ?Type $returnType = null, bool $static = \false, ?Description $description = null) + public function numberOfClasses() : int { - Assert::stringNotEmpty($methodName); - if ($returnType === null) { - $returnType = new Void_(); + if ($this->numClasses === null) { + $this->numClasses = 0; + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numClasses++; + continue 2; + } + } + } } - $this->methodName = $methodName; - $this->arguments = $this->filterArguments($arguments); - $this->returnType = $returnType; - $this->isStatic = $static; - $this->description = $description; + return $this->numClasses; } - public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self + public function numberOfTestedClasses() : int { - Assert::stringNotEmpty($body); - Assert::notNull($typeResolver); - Assert::notNull($descriptionFactory); - // 1. none or more whitespace - // 2. optionally the keyword "static" followed by whitespace - // 3. optionally a word with underscores followed by whitespace : as - // type for the return value - // 4. then optionally a word with underscores followed by () and - // whitespace : as method name as used by phpDocumentor - // 5. then a word with underscores, followed by ( and any character - // until a ) and whitespace : as method name with signature - // 6. any remaining text : as description - if (!preg_match('/^ - # Static keyword - # Declares a static method ONLY if type is also present - (?: - (static) - \\s+ - )? - # Return type - (?: - ( - (?:[\\w\\|_\\\\]*\\$this[\\w\\|_\\\\]*) - | - (?: - (?:[\\w\\|_\\\\]+) - # array notation - (?:\\[\\])* - )*+ - ) - \\s+ - )? - # Method name - ([\\w_]+) - # Arguments - (?: - \\(([^\\)]*)\\) - )? - \\s* - # Description - (.*) - $/sux', $body, $matches)) { - return null; - } - [, $static, $returnType, $methodName, $argumentLines, $description] = $matches; - $static = $static === 'static'; - if ($returnType === '') { - $returnType = 'void'; - } - $returnType = $typeResolver->resolve($returnType, $context); - $description = $descriptionFactory->create($description, $context); - /** @phpstan-var array $arguments */ - $arguments = []; - if ($argumentLines !== '') { - $argumentsExploded = explode(',', $argumentLines); - foreach ($argumentsExploded as $argument) { - $argument = explode(' ', self::stripRestArg(trim($argument)), 2); - if (strpos($argument[0], '$') === 0) { - $argumentName = substr($argument[0], 1); - $argumentType = new Mixed_(); - } else { - $argumentType = $typeResolver->resolve($argument[0], $context); - $argumentName = ''; - if (isset($argument[1])) { - $argument[1] = self::stripRestArg($argument[1]); - $argumentName = substr($argument[1], 1); + return $this->numTestedClasses; + } + public function numberOfTraits() : int + { + if ($this->numTraits === null) { + $this->numTraits = 0; + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numTraits++; + continue 2; } } - $arguments[] = ['name' => $argumentName, 'type' => $argumentType]; } } - return new static($methodName, $arguments, $returnType, $static, $description); + return $this->numTraits; } - /** - * Retrieves the method name. - */ - public function getMethodName() : string + public function numberOfTestedTraits() : int { - return $this->methodName; + return $this->numTestedTraits; } - /** - * @return array> - * - * @phpstan-return array - */ - public function getArguments() : array + public function numberOfMethods() : int { - return $this->arguments; + if ($this->numMethods === null) { + $this->numMethods = 0; + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + } + return $this->numMethods; } - /** - * Checks whether the method tag describes a static method or not. - * - * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. - */ - public function isStatic() : bool + public function numberOfTestedMethods() : int { - return $this->isStatic; + if ($this->numTestedMethods === null) { + $this->numTestedMethods = 0; + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0 && $method['coverage'] === 100) { + $this->numTestedMethods++; + } + } + } + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0 && $method['coverage'] === 100) { + $this->numTestedMethods++; + } + } + } + } + return $this->numTestedMethods; } - public function getReturnType() : Type + public function numberOfFunctions() : int { - return $this->returnType; + return count($this->functions); } - public function __toString() : string + public function numberOfTestedFunctions() : int { - $arguments = []; - foreach ($this->arguments as $argument) { - $arguments[] = $argument['type'] . ' $' . $argument['name']; - } - $argumentStr = '(' . implode(', ', $arguments) . ')'; - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + if ($this->numTestedFunctions === null) { + $this->numTestedFunctions = 0; + foreach ($this->functions as $function) { + if ($function['executableLines'] > 0 && $function['coverage'] === 100) { + $this->numTestedFunctions++; + } + } } - $static = $this->isStatic ? 'static' : ''; - $returnType = (string) $this->returnType; - $methodName = (string) $this->methodName; - return $static . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '') . $argumentStr . ($description !== '' ? ' ' . $description : ''); + return $this->numTestedFunctions; } - /** - * @param mixed[][]|string[] $arguments - * - * @return mixed[][] - * - * @phpstan-param array $arguments - * @phpstan-return array - */ - private function filterArguments(array $arguments = []) : array + private function calculateStatistics(array $classes, array $traits, array $functions) : void { - $result = []; - foreach ($arguments as $argument) { - if (is_string($argument)) { - $argument = ['name' => $argument]; + foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = []; + } + $this->processClasses($classes); + $this->processTraits($traits); + $this->processFunctions($functions); + foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) { + if (isset($this->lineCoverageData[$lineNumber])) { + foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { + $codeUnit['executableLines']++; + } + unset($codeUnit); + $this->numExecutableLines++; + if (count($this->lineCoverageData[$lineNumber]) > 0) { + foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { + $codeUnit['executedLines']++; + } + unset($codeUnit); + $this->numExecutedLines++; + } } - if (!isset($argument['type'])) { - $argument['type'] = new Mixed_(); + } + foreach ($this->traits as &$trait) { + foreach ($trait['methods'] as &$method) { + $methodLineCoverage = $method['executableLines'] ? $method['executedLines'] / $method['executableLines'] * 100 : 100; + $methodBranchCoverage = $method['executableBranches'] ? $method['executedBranches'] / $method['executableBranches'] * 100 : 0; + $methodPathCoverage = $method['executablePaths'] ? $method['executedPaths'] / $method['executablePaths'] * 100 : 0; + $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; + $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); + $trait['ccn'] += $method['ccn']; } - $keys = array_keys($argument); - sort($keys); - if ($keys !== ['name', 'type']) { - throw new InvalidArgumentException('Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, \true)); + unset($method); + $traitLineCoverage = $trait['executableLines'] ? $trait['executedLines'] / $trait['executableLines'] * 100 : 100; + $traitBranchCoverage = $trait['executableBranches'] ? $trait['executedBranches'] / $trait['executableBranches'] * 100 : 0; + $traitPathCoverage = $trait['executablePaths'] ? $trait['executedPaths'] / $trait['executablePaths'] * 100 : 0; + $trait['coverage'] = $traitBranchCoverage ?: $traitLineCoverage; + $trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage ?: $traitLineCoverage))->asString(); + if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) { + $this->numTestedClasses++; } - $result[] = $argument; } - return $result; - } - private static function stripRestArg(string $argument) : string - { - if (strpos($argument, '...') === 0) { - $argument = trim(substr($argument, 3)); + unset($trait); + foreach ($this->classes as &$class) { + foreach ($class['methods'] as &$method) { + $methodLineCoverage = $method['executableLines'] ? $method['executedLines'] / $method['executableLines'] * 100 : 100; + $methodBranchCoverage = $method['executableBranches'] ? $method['executedBranches'] / $method['executableBranches'] * 100 : 0; + $methodPathCoverage = $method['executablePaths'] ? $method['executedPaths'] / $method['executablePaths'] * 100 : 0; + $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; + $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); + $class['ccn'] += $method['ccn']; + } + unset($method); + $classLineCoverage = $class['executableLines'] ? $class['executedLines'] / $class['executableLines'] * 100 : 100; + $classBranchCoverage = $class['executableBranches'] ? $class['executedBranches'] / $class['executableBranches'] * 100 : 0; + $classPathCoverage = $class['executablePaths'] ? $class['executedPaths'] / $class['executablePaths'] * 100 : 0; + $class['coverage'] = $classBranchCoverage ?: $classLineCoverage; + $class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage ?: $classLineCoverage))->asString(); + if ($class['executableLines'] > 0 && $class['coverage'] === 100) { + $this->numTestedClasses++; + } + } + unset($class); + foreach ($this->functions as &$function) { + $functionLineCoverage = $function['executableLines'] ? $function['executedLines'] / $function['executableLines'] * 100 : 100; + $functionBranchCoverage = $function['executableBranches'] ? $function['executedBranches'] / $function['executableBranches'] * 100 : 0; + $functionPathCoverage = $function['executablePaths'] ? $function['executedPaths'] / $function['executablePaths'] * 100 : 0; + $function['coverage'] = $functionBranchCoverage ?: $functionLineCoverage; + $function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage ?: $functionLineCoverage))->asString(); + if ($function['coverage'] === 100) { + $this->numTestedFunctions++; + } } - return $argument; } -} -uri = $uri; + $link = $this->id() . '.html#'; + foreach ($classes as $className => $class) { + $this->classes[$className] = ['className' => $className, 'namespace' => $class['namespace'], 'methods' => [], 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'link' => $link . $class['startLine']]; + foreach ($class['methods'] as $methodName => $method) { + $methodData = $this->newMethod($className, $methodName, $method, $link); + $this->classes[$className]['methods'][$methodName] = $methodData; + $this->classes[$className]['executableBranches'] += $methodData['executableBranches']; + $this->classes[$className]['executedBranches'] += $methodData['executedBranches']; + $this->classes[$className]['executablePaths'] += $methodData['executablePaths']; + $this->classes[$className]['executedPaths'] += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData['executableBranches']; + $this->numExecutedBranches += $methodData['executedBranches']; + $this->numExecutablePaths += $methodData['executablePaths']; + $this->numExecutedPaths += $methodData['executedPaths']; + foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [&$this->classes[$className], &$this->classes[$className]['methods'][$methodName]]; + } + } + } } - public function __toString() : string + private function processTraits(array $traits) : void { - return $this->uri; + $link = $this->id() . '.html#'; + foreach ($traits as $traitName => $trait) { + $this->traits[$traitName] = ['traitName' => $traitName, 'namespace' => $trait['namespace'], 'methods' => [], 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'link' => $link . $trait['startLine']]; + foreach ($trait['methods'] as $methodName => $method) { + $methodData = $this->newMethod($traitName, $methodName, $method, $link); + $this->traits[$traitName]['methods'][$methodName] = $methodData; + $this->traits[$traitName]['executableBranches'] += $methodData['executableBranches']; + $this->traits[$traitName]['executedBranches'] += $methodData['executedBranches']; + $this->traits[$traitName]['executablePaths'] += $methodData['executablePaths']; + $this->traits[$traitName]['executedPaths'] += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData['executableBranches']; + $this->numExecutedBranches += $methodData['executedBranches']; + $this->numExecutablePaths += $methodData['executablePaths']; + $this->numExecutedPaths += $methodData['executedPaths']; + foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [&$this->traits[$traitName], &$this->traits[$traitName]['methods'][$methodName]]; + } + } + } } -} -fqsen = $fqsen; + $link = $this->id() . '.html#'; + foreach ($functions as $functionName => $function) { + $this->functions[$functionName] = ['functionName' => $functionName, 'namespace' => $function['namespace'], 'signature' => $function['signature'], 'startLine' => $function['startLine'], 'endLine' => $function['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $function['startLine']]; + foreach (range($function['startLine'], $function['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; + } + if (isset($this->functionCoverageData[$functionName]['branches'])) { + $this->functions[$functionName]['executableBranches'] = count($this->functionCoverageData[$functionName]['branches']); + $this->functions[$functionName]['executedBranches'] = count(array_filter($this->functionCoverageData[$functionName]['branches'], static function (array $branch) { + return (bool) $branch['hit']; + })); + } + if (isset($this->functionCoverageData[$functionName]['paths'])) { + $this->functions[$functionName]['executablePaths'] = count($this->functionCoverageData[$functionName]['paths']); + $this->functions[$functionName]['executedPaths'] = count(array_filter($this->functionCoverageData[$functionName]['paths'], static function (array $path) { + return (bool) $path['hit']; + })); + } + $this->numExecutableBranches += $this->functions[$functionName]['executableBranches']; + $this->numExecutedBranches += $this->functions[$functionName]['executedBranches']; + $this->numExecutablePaths += $this->functions[$functionName]['executablePaths']; + $this->numExecutedPaths += $this->functions[$functionName]['executedPaths']; + } } - /** - * @return string string representation of the referenced fqsen - */ - public function __toString() : string + private function newMethod(string $className, string $methodName, array $method, string $link) : array { - return (string) $this->fqsen; + $methodData = ['methodName' => $methodName, 'visibility' => $method['visibility'], 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine']]; + $key = $className . '->' . $methodName; + if (isset($this->functionCoverageData[$key]['branches'])) { + $methodData['executableBranches'] = count($this->functionCoverageData[$key]['branches']); + $methodData['executedBranches'] = count(array_filter($this->functionCoverageData[$key]['branches'], static function (array $branch) { + return (bool) $branch['hit']; + })); + } + if (isset($this->functionCoverageData[$key]['paths'])) { + $methodData['executablePaths'] = count($this->functionCoverageData[$key]['paths']); + $methodData['executedPaths'] = count(array_filter($this->functionCoverageData[$key]['paths'], static function (array $path) { + return (bool) $path['hit']; + })); + } + return $methodData; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; -use PHPUnit\phpDocumentor\Reflection\DocBlock; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use function count; +use RecursiveIterator; /** - * Parses a tag definition for a DocBlock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -abstract class BaseTag implements DocBlock\Tag +final class Iterator implements RecursiveIterator { - /** @var string Name of the tag */ - protected $name = ''; - /** @var Description|null Description of the tag. */ - protected $description; /** - * Gets the name of this tag. - * - * @return string The name of this tag. + * @var int */ - public function getName() : string + private $position; + /** + * @var AbstractNode[] + */ + private $nodes; + public function __construct(Directory $node) { - return $this->name; + $this->nodes = $node->children(); } - public function getDescription() : ?Description + /** + * Rewinds the Iterator to the first element. + */ + public function rewind() : void { - return $this->description; + $this->position = 0; } - public function render(?Formatter $formatter = null) : string + /** + * Checks if there is a current element after calls to rewind() or next(). + */ + public function valid() : bool { - if ($formatter === null) { - $formatter = new Formatter\PassthroughFormatter(); - } - return $formatter->format($this); + return $this->position < count($this->nodes); + } + /** + * Returns the key of the current element. + */ + public function key() : int + { + return $this->position; + } + /** + * Returns the current element. + */ + public function current() : ?AbstractNode + { + return $this->valid() ? $this->nodes[$this->position] : null; + } + /** + * Moves forward to next element. + */ + public function next() : void + { + $this->position++; + } + /** + * Returns the sub iterator for the current element. + */ + public function getChildren() : self + { + return new self($this->nodes[$this->position]); + } + /** + * Checks whether the current element has children. + */ + public function hasChildren() : bool + { + return $this->nodes[$this->position] instanceof Directory; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage; -use InvalidArgumentException; -use function filter_var; -use function preg_match; -use function trim; -use const FILTER_VALIDATE_EMAIL; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function array_unique; +use function count; +use function is_array; +use function ksort; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; /** - * Reflection class for an {@}author tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Author extends BaseTag implements Factory\StaticMethod +final class ProcessedCodeCoverageData { - /** @var string register that this is the author tag. */ - protected $name = 'author'; - /** @var string The name of the author */ - private $authorName; - /** @var string The email of the author */ - private $authorEmail; /** - * Initializes this tag with the author name and e-mail. + * Line coverage data. + * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids. + * + * @var array */ - public function __construct(string $authorName, string $authorEmail) - { - if ($authorEmail && !filter_var($authorEmail, \FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('The author tag does not have a valid e-mail address'); - } - $this->authorName = $authorName; - $this->authorEmail = $authorEmail; - } + private $lineCoverage = []; /** - * Gets the author's name. + * Function coverage data. + * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array + * of testcase ids. * - * @return string The author's name. + * @var array */ - public function getAuthorName() : string + private $functionCoverage = []; + public function initializeUnseenData(RawCodeCoverageData $rawData) : void { - return $this->authorName; + foreach ($rawData->lineCoverage() as $file => $lines) { + if (!isset($this->lineCoverage[$file])) { + $this->lineCoverage[$file] = []; + foreach ($lines as $k => $v) { + $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : []; + } + } + } + foreach ($rawData->functionCoverage() as $file => $functions) { + foreach ($functions as $functionName => $functionData) { + if (isset($this->functionCoverage[$file][$functionName])) { + $this->initPreviouslySeenFunction($file, $functionName, $functionData); + } else { + $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); + } + } + } + } + public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode) : void + { + foreach ($executedCode->lineCoverage() as $file => $lines) { + foreach ($lines as $k => $v) { + if ($v === Driver::LINE_EXECUTED) { + $this->lineCoverage[$file][$k][] = $testCaseId; + } + } + } + foreach ($executedCode->functionCoverage() as $file => $functions) { + foreach ($functions as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branchData) { + if ($branchData['hit'] === Driver::BRANCH_HIT) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId; + } + } + foreach ($functionData['paths'] as $pathId => $pathData) { + if ($pathData['hit'] === Driver::BRANCH_HIT) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId; + } + } + } + } + } + public function setLineCoverage(array $lineCoverage) : void + { + $this->lineCoverage = $lineCoverage; + } + public function lineCoverage() : array + { + ksort($this->lineCoverage); + return $this->lineCoverage; + } + public function setFunctionCoverage(array $functionCoverage) : void + { + $this->functionCoverage = $functionCoverage; + } + public function functionCoverage() : array + { + ksort($this->functionCoverage); + return $this->functionCoverage; + } + public function coveredFiles() : array + { + ksort($this->lineCoverage); + return array_keys($this->lineCoverage); + } + public function renameFile(string $oldFile, string $newFile) : void + { + $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile]; + if (isset($this->functionCoverage[$oldFile])) { + $this->functionCoverage[$newFile] = $this->functionCoverage[$oldFile]; + } + unset($this->lineCoverage[$oldFile], $this->functionCoverage[$oldFile]); + } + public function merge(self $newData) : void + { + foreach ($newData->lineCoverage as $file => $lines) { + if (!isset($this->lineCoverage[$file])) { + $this->lineCoverage[$file] = $lines; + continue; + } + // we should compare the lines if any of two contains data + $compareLineNumbers = array_unique(array_merge(array_keys($this->lineCoverage[$file]), array_keys($newData->lineCoverage[$file]))); + foreach ($compareLineNumbers as $line) { + $thatPriority = $this->priorityForLine($newData->lineCoverage[$file], $line); + $thisPriority = $this->priorityForLine($this->lineCoverage[$file], $line); + if ($thatPriority > $thisPriority) { + $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line]; + } elseif ($thatPriority === $thisPriority && is_array($this->lineCoverage[$file][$line])) { + $this->lineCoverage[$file][$line] = array_unique(array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line])); + } + } + } + foreach ($newData->functionCoverage as $file => $functions) { + if (!isset($this->functionCoverage[$file])) { + $this->functionCoverage[$file] = $functions; + continue; + } + foreach ($functions as $functionName => $functionData) { + if (isset($this->functionCoverage[$file][$functionName])) { + $this->initPreviouslySeenFunction($file, $functionName, $functionData); + } else { + $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); + } + foreach ($functionData['branches'] as $branchId => $branchData) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit'])); + } + foreach ($functionData['paths'] as $pathId => $pathData) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit'])); + } + } + } } /** - * Returns the author's email. + * Determine the priority for a line. * - * @return string The author's email. + * 1 = the line is not set + * 2 = the line has not been tested + * 3 = the line is dead code + * 4 = the line has been tested + * + * During a merge, a higher number is better. */ - public function getEmail() : string + private function priorityForLine(array $data, int $line) : int { - return $this->authorEmail; + if (!array_key_exists($line, $data)) { + return 1; + } + if (is_array($data[$line]) && count($data[$line]) === 0) { + return 2; + } + if ($data[$line] === null) { + return 3; + } + return 4; } /** - * Returns this tag in string form. + * For a function we have never seen before, copy all data over and simply init the 'hit' array. */ - public function __toString() : string + private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData) : void { - if ($this->authorEmail) { - $authorEmail = '<' . $this->authorEmail . '>'; - } else { - $authorEmail = ''; + $this->functionCoverage[$file][$functionName] = $functionData; + foreach (array_keys($functionData['branches']) as $branchId) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; + } + foreach (array_keys($functionData['paths']) as $pathId) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; } - $authorName = (string) $this->authorName; - return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); } /** - * Attempts to create a new Author object based on †he tag body. + * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths. + * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling + * containers) mean that the functions inside a file cannot be relied upon to be static. */ - public static function create(string $body) : ?self + private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData) : void { - $splitTagContent = preg_match('/^([^\\<]*)(?:\\<([^\\>]*)\\>)?$/u', $body, $matches); - if (!$splitTagContent) { - return null; + foreach ($functionData['branches'] as $branchId => $branchData) { + if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData; + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; + } + } + foreach ($functionData['paths'] as $pathId => $pathData) { + if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData; + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; + } } - $authorName = trim($matches[1]); - $email = isset($matches[2]) ? trim($matches[2]) : ''; - return new static($authorName, $email); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\Webmozart\Assert\Assert; -use function preg_match; +use function array_diff; +use function array_diff_key; +use function array_flip; +use function array_intersect; +use function array_intersect_key; +use function count; +use function in_array; +use function range; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; +use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; /** - * Reflection class for a {@}since tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Since extends BaseTag implements Factory\StaticMethod +final class RawCodeCoverageData { - /** @var string */ - protected $name = 'since'; /** - * PCRE regular expression matching a version vector. - * Assumes the "x" modifier. + * @var array> */ - public const REGEX_VECTOR = '(?: - # Normal release vectors. - \\d\\S* - | - # VCS version vectors. Per PHPCS, they are expected to - # follow the form of the VCS name, followed by ":", followed - # by the version vector itself. - # By convention, popular VCSes like CVS, SVN and GIT use "$" - # around the actual version vector. - [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ - )'; - /** @var string|null The version vector. */ - private $version; - public function __construct(?string $version = null, ?Description $description = null) + private static $emptyLineCache = []; + /** + * @var array + * + * @see https://xdebug.org/docs/code_coverage for format + */ + private $lineCoverage; + /** + * @var array + * + * @see https://xdebug.org/docs/code_coverage for format + */ + private $functionCoverage; + public static function fromXdebugWithoutPathCoverage(array $rawCoverage) : self { - Assert::nullOrNotEmpty($version); - $this->version = $version; - $this->description = $description; + return new self($rawCoverage, []); } - public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self + public static function fromXdebugWithPathCoverage(array $rawCoverage) : self { - if (empty($body)) { - return new static(); + $lineCoverage = []; + $functionCoverage = []; + foreach ($rawCoverage as $file => $fileCoverageData) { + $lineCoverage[$file] = $fileCoverageData['lines']; + $functionCoverage[$file] = $fileCoverageData['functions']; } - $matches = []; - if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { - return null; + return new self($lineCoverage, $functionCoverage); + } + public static function fromXdebugWithMixedCoverage(array $rawCoverage) : self + { + $lineCoverage = []; + $functionCoverage = []; + foreach ($rawCoverage as $file => $fileCoverageData) { + if (!isset($fileCoverageData['functions'])) { + // Current file does not have functions, so line coverage + // is stored in $fileCoverageData, not in $fileCoverageData['lines'] + $lineCoverage[$file] = $fileCoverageData; + continue; + } + $lineCoverage[$file] = $fileCoverageData['lines']; + $functionCoverage[$file] = $fileCoverageData['functions']; } - Assert::notNull($descriptionFactory); - return new static($matches[1], $descriptionFactory->create($matches[2] ?? '', $context)); + return new self($lineCoverage, $functionCoverage); + } + public static function fromUncoveredFile(string $filename, FileAnalyser $analyser) : self + { + $lineCoverage = []; + foreach ($analyser->executableLinesIn($filename) as $line) { + $lineCoverage[$line] = Driver::LINE_NOT_EXECUTED; + } + return new self([$filename => $lineCoverage], []); + } + private function __construct(array $lineCoverage, array $functionCoverage) + { + $this->lineCoverage = $lineCoverage; + $this->functionCoverage = $functionCoverage; + $this->skipEmptyLines(); + } + public function clear() : void + { + $this->lineCoverage = $this->functionCoverage = []; + } + public function lineCoverage() : array + { + return $this->lineCoverage; + } + public function functionCoverage() : array + { + return $this->functionCoverage; + } + public function removeCoverageDataForFile(string $filename) : void + { + unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]); } /** - * Gets the version section of the tag. + * @param int[] $lines */ - public function getVersion() : ?string + public function keepLineCoverageDataOnlyForLines(string $filename, array $lines) : void { - return $this->version; + if (!isset($this->lineCoverage[$filename])) { + return; + } + $this->lineCoverage[$filename] = array_intersect_key($this->lineCoverage[$filename], array_flip($lines)); + } + /** + * @param int[] $lines + */ + public function keepFunctionCoverageDataOnlyForLines(string $filename, array $lines) : void + { + if (!isset($this->functionCoverage[$filename])) { + return; + } + foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branch) { + if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) { + unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); + foreach ($functionData['paths'] as $pathId => $path) { + if (in_array($branchId, $path['path'], \true)) { + unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); + } + } + } + } + } + } + /** + * @param int[] $lines + */ + public function removeCoverageDataForLines(string $filename, array $lines) : void + { + if (empty($lines)) { + return; + } + if (!isset($this->lineCoverage[$filename])) { + return; + } + $this->lineCoverage[$filename] = array_diff_key($this->lineCoverage[$filename], array_flip($lines)); + if (isset($this->functionCoverage[$filename])) { + foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branch) { + if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) { + unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); + foreach ($functionData['paths'] as $pathId => $path) { + if (in_array($branchId, $path['path'], \true)) { + unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); + } + } + } + } + } + } } /** - * Returns a string representation for this tag. + * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has + * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine + * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines + * are skipped over for coverage purposes. + * + * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799 */ - public function __toString() : string + private function skipEmptyLines() : void { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + foreach ($this->lineCoverage as $filename => $coverage) { + foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) { + unset($this->lineCoverage[$filename][$emptyLine]); + } } - $version = (string) $this->version; - return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); } -} -getName() . ' ' . $tag); + if (!isset(self::$emptyLineCache[$filename])) { + self::$emptyLineCache[$filename] = []; + if (\is_file($filename)) { + $sourceLines = \explode("\n", \file_get_contents($filename)); + foreach ($sourceLines as $line => $source) { + if (\trim($source) === '') { + self::$emptyLineCache[$filename][] = $line + 1; + } + } + } + } + return self::$emptyLineCache[$filename]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use function count; +use function dirname; +use function file_put_contents; +use function is_string; +use function ksort; use function max; -use function str_repeat; -use function strlen; -class AlignFormatter implements Formatter +use function range; +use function time; +use DOMDocument; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +final class Clover { - /** @var int The maximum tag name length. */ - protected $maxLen = 0; /** - * @param Tag[] $tags All tags that should later be aligned with the formatter. + * @throws WriteOperationFailedException */ - public function __construct(array $tags) + public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null) : string { - foreach ($tags as $tag) { - $this->maxLen = max($this->maxLen, strlen($tag->getName())); + $time = (string) time(); + $xmlDocument = new DOMDocument('1.0', 'UTF-8'); + $xmlDocument->formatOutput = \true; + $xmlCoverage = $xmlDocument->createElement('coverage'); + $xmlCoverage->setAttribute('generated', $time); + $xmlDocument->appendChild($xmlCoverage); + $xmlProject = $xmlDocument->createElement('project'); + $xmlProject->setAttribute('timestamp', $time); + if (is_string($name)) { + $xmlProject->setAttribute('name', $name); } - } - /** - * Formats the given tag to return a simple plain text version. - */ - public function format(Tag $tag) : string - { - return '@' . $tag->getName() . str_repeat(' ', $this->maxLen - strlen($tag->getName()) + 1) . $tag; + $xmlCoverage->appendChild($xmlProject); + $packages = []; + $report = $coverage->getReport(); + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + /* @var File $item */ + $xmlFile = $xmlDocument->createElement('file'); + $xmlFile->setAttribute('name', $item->pathAsString()); + $classes = $item->classesAndTraits(); + $coverageData = $item->lineCoverageData(); + $lines = []; + $namespace = 'global'; + foreach ($classes as $className => $class) { + $classStatements = 0; + $coveredClassStatements = 0; + $coveredMethods = 0; + $classMethods = 0; + foreach ($class['methods'] as $methodName => $method) { + if ($method['executableLines'] == 0) { + continue; + } + $classMethods++; + $classStatements += $method['executableLines']; + $coveredClassStatements += $method['executedLines']; + if ($method['coverage'] == 100) { + $coveredMethods++; + } + $methodCount = 0; + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (isset($coverageData[$line]) && $coverageData[$line] !== null) { + $methodCount = max($methodCount, count($coverageData[$line])); + } + } + $lines[$method['startLine']] = ['ccn' => $method['ccn'], 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'visibility' => $method['visibility'], 'name' => $methodName]; + } + if (!empty($class['package']['namespace'])) { + $namespace = $class['package']['namespace']; + } + $xmlClass = $xmlDocument->createElement('class'); + $xmlClass->setAttribute('name', $className); + $xmlClass->setAttribute('namespace', $namespace); + if (!empty($class['package']['fullPackage'])) { + $xmlClass->setAttribute('fullPackage', $class['package']['fullPackage']); + } + if (!empty($class['package']['category'])) { + $xmlClass->setAttribute('category', $class['package']['category']); + } + if (!empty($class['package']['package'])) { + $xmlClass->setAttribute('package', $class['package']['package']); + } + if (!empty($class['package']['subpackage'])) { + $xmlClass->setAttribute('subpackage', $class['package']['subpackage']); + } + $xmlFile->appendChild($xmlClass); + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); + $xmlMetrics->setAttribute('methods', (string) $classMethods); + $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); + $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('statements', (string) $classStatements); + $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); + $xmlClass->appendChild($xmlMetrics); + } + foreach ($coverageData as $line => $data) { + if ($data === null || isset($lines[$line])) { + continue; + } + $lines[$line] = ['count' => count($data), 'type' => 'stmt']; + } + ksort($lines); + foreach ($lines as $line => $data) { + $xmlLine = $xmlDocument->createElement('line'); + $xmlLine->setAttribute('num', (string) $line); + $xmlLine->setAttribute('type', $data['type']); + if (isset($data['name'])) { + $xmlLine->setAttribute('name', $data['name']); + } + if (isset($data['visibility'])) { + $xmlLine->setAttribute('visibility', $data['visibility']); + } + if (isset($data['ccn'])) { + $xmlLine->setAttribute('complexity', (string) $data['ccn']); + } + if (isset($data['crap'])) { + $xmlLine->setAttribute('crap', (string) $data['crap']); + } + $xmlLine->setAttribute('count', (string) $data['count']); + $xmlFile->appendChild($xmlLine); + } + $linesOfCode = $item->linesOfCode(); + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']); + $xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods()); + $xmlMetrics->setAttribute('conditionals', (string) $item->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $item->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $item->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $item->numberOfExecutedLines()); + $xmlMetrics->setAttribute('elements', (string) ($item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches())); + $xmlFile->appendChild($xmlMetrics); + if ($namespace === 'global') { + $xmlProject->appendChild($xmlFile); + } else { + if (!isset($packages[$namespace])) { + $packages[$namespace] = $xmlDocument->createElement('package'); + $packages[$namespace]->setAttribute('name', $namespace); + $xmlProject->appendChild($packages[$namespace]); + } + $packages[$namespace]->appendChild($xmlFile); + } + } + $linesOfCode = $report->linesOfCode(); + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('files', (string) count($report)); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']); + $xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods()); + $xmlMetrics->setAttribute('conditionals', (string) $report->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $report->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $report->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $report->numberOfExecutedLines()); + $xmlMetrics->setAttribute('elements', (string) ($report->numberOfMethods() + $report->numberOfExecutableLines() + $report->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches())); + $xmlProject->appendChild($xmlMetrics); + $buffer = $xmlDocument->saveXML(); + if ($target !== null) { + Filesystem::createDirectory(dirname($target)); + if (@file_put_contents($target, $buffer) === \false) { + throw new WriteOperationFailedException($target); + } + } + return $buffer; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\TypeResolver; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\phpDocumentor\Reflection\Utils; -use PHPUnit\Webmozart\Assert\Assert; -use function array_shift; -use function array_unshift; -use function implode; -use function strpos; -use function substr; -use const PREG_SPLIT_DELIM_CAPTURE; -/** - * Reflection class for a {@}property-read tag in a Docblock. - */ -final class PropertyRead extends TagWithType implements Factory\StaticMethod +use function count; +use function dirname; +use function file_put_contents; +use function range; +use function time; +use DOMImplementation; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +final class Cobertura { - /** @var string|null */ - protected $variableName; - public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) - { - Assert::string($variableName); - $this->name = 'property-read'; - $this->variableName = $variableName; - $this->type = $type; - $this->description = $description; - } - public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self - { - Assert::stringNotEmpty($body); - Assert::notNull($typeResolver); - Assert::notNull($descriptionFactory); - [$firstPart, $body] = self::extractTypeFromBody($body); - $type = null; - $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, \PREG_SPLIT_DELIM_CAPTURE); - $variableName = ''; - // if the first item that is encountered is not a variable; it is a type - if ($firstPart && $firstPart[0] !== '$') { - $type = $typeResolver->resolve($firstPart, $context); - } else { - // first part is not a type; we should prepend it to the parts array for further processing - array_unshift($parts, $firstPart); - } - // if the next item starts with a $ it must be the variable name - if (isset($parts[0]) && strpos($parts[0], '$') === 0) { - $variableName = array_shift($parts); - if ($type) { - array_shift($parts); - } - Assert::notNull($variableName); - $variableName = substr($variableName, 1); - } - $description = $descriptionFactory->create(implode('', $parts), $context); - return new static($variableName, $type, $description); - } - /** - * Returns the variable's name. - */ - public function getVariableName() : ?string - { - return $this->variableName; - } /** - * Returns a string representation for this tag. + * @throws WriteOperationFailedException */ - public function __toString() : string + public function process(CodeCoverage $coverage, ?string $target = null) : string { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + $time = (string) time(); + $report = $coverage->getReport(); + $implementation = new DOMImplementation(); + $documentType = $implementation->createDocumentType('coverage', '', 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'); + $document = $implementation->createDocument('', '', $documentType); + $document->xmlVersion = '1.0'; + $document->encoding = 'UTF-8'; + $document->formatOutput = \true; + $coverageElement = $document->createElement('coverage'); + $linesValid = $report->numberOfExecutableLines(); + $linesCovered = $report->numberOfExecutedLines(); + $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; + $coverageElement->setAttribute('line-rate', (string) $lineRate); + $branchesValid = $report->numberOfExecutableBranches(); + $branchesCovered = $report->numberOfExecutedBranches(); + $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; + $coverageElement->setAttribute('branch-rate', (string) $branchRate); + $coverageElement->setAttribute('lines-covered', (string) $report->numberOfExecutedLines()); + $coverageElement->setAttribute('lines-valid', (string) $report->numberOfExecutableLines()); + $coverageElement->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches()); + $coverageElement->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches()); + $coverageElement->setAttribute('complexity', ''); + $coverageElement->setAttribute('version', '0.4'); + $coverageElement->setAttribute('timestamp', $time); + $document->appendChild($coverageElement); + $sourcesElement = $document->createElement('sources'); + $coverageElement->appendChild($sourcesElement); + $sourceElement = $document->createElement('source', $report->pathAsString()); + $sourcesElement->appendChild($sourceElement); + $packagesElement = $document->createElement('packages'); + $coverageElement->appendChild($packagesElement); + $complexity = 0; + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + $packageElement = $document->createElement('package'); + $packageComplexity = 0; + $packageElement->setAttribute('name', \str_replace($report->pathAsString() . \DIRECTORY_SEPARATOR, '', $item->pathAsString())); + $linesValid = $item->numberOfExecutableLines(); + $linesCovered = $item->numberOfExecutedLines(); + $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; + $packageElement->setAttribute('line-rate', (string) $lineRate); + $branchesValid = $item->numberOfExecutableBranches(); + $branchesCovered = $item->numberOfExecutedBranches(); + $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; + $packageElement->setAttribute('branch-rate', (string) $branchRate); + $packageElement->setAttribute('complexity', ''); + $packagesElement->appendChild($packageElement); + $classesElement = $document->createElement('classes'); + $packageElement->appendChild($classesElement); + $classes = $item->classesAndTraits(); + $coverageData = $item->lineCoverageData(); + foreach ($classes as $className => $class) { + $complexity += $class['ccn']; + $packageComplexity += $class['ccn']; + if (!empty($class['package']['namespace'])) { + $className = $class['package']['namespace'] . '\\' . $className; + } + $linesValid = $class['executableLines']; + $linesCovered = $class['executedLines']; + $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; + $branchesValid = $class['executableBranches']; + $branchesCovered = $class['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; + $classElement = $document->createElement('class'); + $classElement->setAttribute('name', $className); + $classElement->setAttribute('filename', \str_replace($report->pathAsString() . \DIRECTORY_SEPARATOR, '', $item->pathAsString())); + $classElement->setAttribute('line-rate', (string) $lineRate); + $classElement->setAttribute('branch-rate', (string) $branchRate); + $classElement->setAttribute('complexity', (string) $class['ccn']); + $classesElement->appendChild($classElement); + $methodsElement = $document->createElement('methods'); + $classElement->appendChild($methodsElement); + $classLinesElement = $document->createElement('lines'); + $classElement->appendChild($classLinesElement); + foreach ($class['methods'] as $methodName => $method) { + if ($method['executableLines'] === 0) { + continue; + } + \preg_match("/\\((.*?)\\)/", $method['signature'], $signature); + $linesValid = $method['executableLines']; + $linesCovered = $method['executedLines']; + $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; + $branchesValid = $method['executableBranches']; + $branchesCovered = $method['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; + $methodElement = $document->createElement('method'); + $methodElement->setAttribute('name', $methodName); + $methodElement->setAttribute('signature', $signature[1]); + $methodElement->setAttribute('line-rate', (string) $lineRate); + $methodElement->setAttribute('branch-rate', (string) $branchRate); + $methodElement->setAttribute('complexity', (string) $method['ccn']); + $methodLinesElement = $document->createElement('lines'); + $methodElement->appendChild($methodLinesElement); + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + continue; + } + $methodLineElement = $document->createElement('line'); + $methodLineElement->setAttribute('number', (string) $line); + $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); + $methodLinesElement->appendChild($methodLineElement); + $classLineElement = $methodLineElement->cloneNode(); + $classLinesElement->appendChild($classLineElement); + } + $methodsElement->appendChild($methodElement); + } + } + if ($report->numberOfFunctions() === 0) { + $packageElement->setAttribute('complexity', (string) $packageComplexity); + continue; + } + $functionsComplexity = 0; + $functionsLinesValid = 0; + $functionsLinesCovered = 0; + $functionsBranchesValid = 0; + $functionsBranchesCovered = 0; + $classElement = $document->createElement('class'); + $classElement->setAttribute('name', \basename($item->pathAsString())); + $classElement->setAttribute('filename', \str_replace($report->pathAsString() . \DIRECTORY_SEPARATOR, '', $item->pathAsString())); + $methodsElement = $document->createElement('methods'); + $classElement->appendChild($methodsElement); + $classLinesElement = $document->createElement('lines'); + $classElement->appendChild($classLinesElement); + $functions = $report->functions(); + foreach ($functions as $functionName => $function) { + if ($function['executableLines'] === 0) { + continue; + } + $complexity += $function['ccn']; + $packageComplexity += $function['ccn']; + $functionsComplexity += $function['ccn']; + $linesValid = $function['executableLines']; + $linesCovered = $function['executedLines']; + $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; + $functionsLinesValid += $linesValid; + $functionsLinesCovered += $linesCovered; + $branchesValid = $function['executableBranches']; + $branchesCovered = $function['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; + $functionsBranchesValid += $branchesValid; + $functionsBranchesCovered += $branchesValid; + $methodElement = $document->createElement('method'); + $methodElement->setAttribute('name', $functionName); + $methodElement->setAttribute('signature', $function['signature']); + $methodElement->setAttribute('line-rate', (string) $lineRate); + $methodElement->setAttribute('branch-rate', (string) $branchRate); + $methodElement->setAttribute('complexity', (string) $function['ccn']); + $methodLinesElement = $document->createElement('lines'); + $methodElement->appendChild($methodLinesElement); + foreach (range($function['startLine'], $function['endLine']) as $line) { + if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + continue; + } + $methodLineElement = $document->createElement('line'); + $methodLineElement->setAttribute('number', (string) $line); + $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); + $methodLinesElement->appendChild($methodLineElement); + $classLineElement = $methodLineElement->cloneNode(); + $classLinesElement->appendChild($classLineElement); + } + $methodsElement->appendChild($methodElement); + } + $packageElement->setAttribute('complexity', (string) $packageComplexity); + if ($functionsLinesValid === 0) { + continue; + } + $lineRate = $functionsLinesCovered / $functionsLinesValid; + $branchRate = $functionsBranchesValid === 0 ? 0 : $functionsBranchesCovered / $functionsBranchesValid; + $classElement->setAttribute('line-rate', (string) $lineRate); + $classElement->setAttribute('branch-rate', (string) $branchRate); + $classElement->setAttribute('complexity', (string) $functionsComplexity); + $classesElement->appendChild($classElement); } - if ($this->variableName) { - $variableName = '$' . $this->variableName; - } else { - $variableName = ''; + $coverageElement->setAttribute('complexity', (string) $complexity); + $buffer = $document->saveXML(); + if ($target !== null) { + Filesystem::createDirectory(dirname($target)); + if (@file_put_contents($target, $buffer) === \false) { + throw new WriteOperationFailedException($target); + } } - $type = (string) $this->type; - return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + return $buffer; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; -use PHPUnit\phpDocumentor\Reflection\Type; -use function in_array; -use function strlen; -use function substr; -use function trim; -abstract class TagWithType extends BaseTag +use function date; +use function dirname; +use function file_put_contents; +use function htmlspecialchars; +use function is_string; +use function round; +use DOMDocument; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +final class Crap4j { - /** @var ?Type */ - protected $type; /** - * Returns the type section of the variable. + * @var int */ - public function getType() : ?Type + private $threshold; + public function __construct(int $threshold = 30) { - return $this->type; + $this->threshold = $threshold; } /** - * @return string[] + * @throws WriteOperationFailedException */ - protected static function extractTypeFromBody(string $body) : array + public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null) : string { - $type = ''; - $nestingLevel = 0; - for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) { - $character = $body[$i]; - if ($nestingLevel === 0 && trim($character) === '') { - break; - } - $type .= $character; - if (in_array($character, ['<', '(', '[', '{'])) { - $nestingLevel++; + $document = new DOMDocument('1.0', 'UTF-8'); + $document->formatOutput = \true; + $root = $document->createElement('crap_result'); + $document->appendChild($root); + $project = $document->createElement('project', is_string($name) ? $name : ''); + $root->appendChild($project); + $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s'))); + $stats = $document->createElement('stats'); + $methodsNode = $document->createElement('methods'); + $report = $coverage->getReport(); + unset($coverage); + $fullMethodCount = 0; + $fullCrapMethodCount = 0; + $fullCrapLoad = 0; + $fullCrap = 0; + foreach ($report as $item) { + $namespace = 'global'; + if (!$item instanceof File) { continue; } - if (in_array($character, ['>', ')', ']', '}'])) { - $nestingLevel--; - continue; + $file = $document->createElement('file'); + $file->setAttribute('name', $item->pathAsString()); + $classes = $item->classesAndTraits(); + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']); + $fullCrap += $method['crap']; + $fullCrapLoad += $crapLoad; + $fullMethodCount++; + if ($method['crap'] >= $this->threshold) { + $fullCrapMethodCount++; + } + $methodNode = $document->createElement('method'); + if (!empty($class['namespace'])) { + $namespace = $class['namespace']; + } + $methodNode->appendChild($document->createElement('package', $namespace)); + $methodNode->appendChild($document->createElement('className', $className)); + $methodNode->appendChild($document->createElement('methodName', $methodName)); + $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap']))); + $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); + $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); + $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad))); + $methodsNode->appendChild($methodNode); + } } } - $description = trim(substr($body, strlen($type))); - return [$type, $description]; + $stats->appendChild($document->createElement('name', 'Method Crap Stats')); + $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount)); + $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount)); + $stats->appendChild($document->createElement('crapLoad', (string) round($fullCrapLoad))); + $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap)); + $crapMethodPercent = 0; + if ($fullMethodCount > 0) { + $crapMethodPercent = $this->roundValue(100 * $fullCrapMethodCount / $fullMethodCount); + } + $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent)); + $root->appendChild($stats); + $root->appendChild($methodsNode); + $buffer = $document->saveXML(); + if ($target !== null) { + Filesystem::createDirectory(dirname($target)); + if (@file_put_contents($target, $buffer) === \false) { + throw new WriteOperationFailedException($target); + } + } + return $buffer; + } + private function crapLoad(float $crapValue, int $cyclomaticComplexity, float $coveragePercent) : float + { + $crapLoad = 0; + if ($crapValue >= $this->threshold) { + $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); + $crapLoad += $cyclomaticComplexity / $this->threshold; + } + return $crapLoad; + } + private function roundValue(float $value) : float + { + return round($value, 2); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\TypeResolver; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\phpDocumentor\Reflection\Utils; -use PHPUnit\Webmozart\Assert\Assert; -use function array_shift; -use function array_unshift; -use function implode; -use function strpos; +use const DIRECTORY_SEPARATOR; +use function copy; +use function date; +use function dirname; use function substr; -use const PREG_SPLIT_DELIM_CAPTURE; -/** - * Reflection class for a {@}property-write tag in a Docblock. - */ -final class PropertyWrite extends TagWithType implements Factory\StaticMethod +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\InvalidArgumentException; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +final class Facade { - /** @var string */ - protected $variableName; - public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) + /** + * @var string + */ + private $templatePath; + /** + * @var string + */ + private $generator; + /** + * @var int + */ + private $lowUpperBound; + /** + * @var int + */ + private $highLowerBound; + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, string $generator = '') { - Assert::string($variableName); - $this->name = 'property-write'; - $this->variableName = $variableName; - $this->type = $type; - $this->description = $description; + if ($lowUpperBound > $highLowerBound) { + throw new InvalidArgumentException('$lowUpperBound must not be larger than $highLowerBound'); + } + $this->generator = $generator; + $this->highLowerBound = $highLowerBound; + $this->lowUpperBound = $lowUpperBound; + $this->templatePath = __DIR__ . '/Renderer/Template/'; } - public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self + public function process(CodeCoverage $coverage, string $target) : void { - Assert::stringNotEmpty($body); - Assert::notNull($typeResolver); - Assert::notNull($descriptionFactory); - [$firstPart, $body] = self::extractTypeFromBody($body); - $type = null; - $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, \PREG_SPLIT_DELIM_CAPTURE); - $variableName = ''; - // if the first item that is encountered is not a variable; it is a type - if ($firstPart && $firstPart[0] !== '$') { - $type = $typeResolver->resolve($firstPart, $context); - } else { - // first part is not a type; we should prepend it to the parts array for further processing - array_unshift($parts, $firstPart); - } - // if the next item starts with a $ it must be the variable name - if (isset($parts[0]) && strpos($parts[0], '$') === 0) { - $variableName = array_shift($parts); - if ($type) { - array_shift($parts); + $target = $this->directory($target); + $report = $coverage->getReport(); + $date = date('D M j G:i:s T Y'); + $dashboard = new Dashboard($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); + $directory = new Directory($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); + $file = new File($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); + $directory->render($report, $target . 'index.html'); + $dashboard->render($report, $target . 'dashboard.html'); + foreach ($report as $node) { + $id = $node->id(); + if ($node instanceof DirectoryNode) { + Filesystem::createDirectory($target . $id); + $directory->render($node, $target . $id . '/index.html'); + $dashboard->render($node, $target . $id . '/dashboard.html'); + } else { + $dir = dirname($target . $id); + Filesystem::createDirectory($dir); + $file->render($node, $target . $id); } - Assert::notNull($variableName); - $variableName = substr($variableName, 1); } - $description = $descriptionFactory->create(implode('', $parts), $context); - return new static($variableName, $type, $description); + $this->copyFiles($target); } - /** - * Returns the variable's name. - */ - public function getVariableName() : ?string + private function copyFiles(string $target) : void { - return $this->variableName; + $dir = $this->directory($target . '_css'); + copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); + copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); + copy($this->templatePath . 'css/style.css', $dir . 'style.css'); + copy($this->templatePath . 'css/custom.css', $dir . 'custom.css'); + copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css'); + $dir = $this->directory($target . '_icons'); + copy($this->templatePath . 'icons/file-code.svg', $dir . 'file-code.svg'); + copy($this->templatePath . 'icons/file-directory.svg', $dir . 'file-directory.svg'); + $dir = $this->directory($target . '_js'); + copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); + copy($this->templatePath . 'js/popper.min.js', $dir . 'popper.min.js'); + copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); + copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); + copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); + copy($this->templatePath . 'js/file.js', $dir . 'file.js'); } - /** - * Returns a string representation for this tag. - */ - public function __toString() : string + private function directory(string $directory) : string { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { + $directory .= DIRECTORY_SEPARATOR; } - if ($this->variableName) { - $variableName = '$' . $this->variableName; - } else { - $variableName = ''; - } - $type = (string) $this->type; - return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + Filesystem::createDirectory($directory); + return $directory; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\Webmozart\Assert\Assert; -use function preg_match; +use function array_pop; +use function count; +use function sprintf; +use function str_repeat; +use function substr_count; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Version; +use PHPUnit\SebastianBergmann\Environment\Runtime; +use PHPUnit\SebastianBergmann\Template\Template; /** - * Reflection class for a {@}version tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Version extends BaseTag implements Factory\StaticMethod +abstract class Renderer { - /** @var string */ - protected $name = 'version'; /** - * PCRE regular expression matching a version vector. - * Assumes the "x" modifier. + * @var string */ - public const REGEX_VECTOR = '(?: - # Normal release vectors. - \\d\\S* - | - # VCS version vectors. Per PHPCS, they are expected to - # follow the form of the VCS name, followed by ":", followed - # by the version vector itself. - # By convention, popular VCSes like CVS, SVN and GIT use "$" - # around the actual version vector. - [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ - )'; - /** @var string|null The version vector. */ - private $version; - public function __construct(?string $version = null, ?Description $description = null) + protected $templatePath; + /** + * @var string + */ + protected $generator; + /** + * @var string + */ + protected $date; + /** + * @var int + */ + protected $lowUpperBound; + /** + * @var int + */ + protected $highLowerBound; + /** + * @var bool + */ + protected $hasBranchCoverage; + /** + * @var string + */ + protected $version; + public function __construct(string $templatePath, string $generator, string $date, int $lowUpperBound, int $highLowerBound, bool $hasBranchCoverage) { - Assert::nullOrStringNotEmpty($version); - $this->version = $version; - $this->description = $description; + $this->templatePath = $templatePath; + $this->generator = $generator; + $this->date = $date; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->version = Version::id(); + $this->hasBranchCoverage = $hasBranchCoverage; } - public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self + protected function renderItemTemplate(Template $template, array $data) : string { - if (empty($body)) { - return new static(); + $numSeparator = ' / '; + if (isset($data['numClasses']) && $data['numClasses'] > 0) { + $classesLevel = $this->colorLevel($data['testedClassesPercent']); + $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; + $classesBar = $this->coverageBar($data['testedClassesPercent']); + } else { + $classesLevel = ''; + $classesNumber = '0' . $numSeparator . '0'; + $classesBar = ''; + $data['testedClassesPercentAsString'] = 'n/a'; } - $matches = []; - if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { - return null; + if ($data['numMethods'] > 0) { + $methodsLevel = $this->colorLevel($data['testedMethodsPercent']); + $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; + $methodsBar = $this->coverageBar($data['testedMethodsPercent']); + } else { + $methodsLevel = ''; + $methodsNumber = '0' . $numSeparator . '0'; + $methodsBar = ''; + $data['testedMethodsPercentAsString'] = 'n/a'; } - $description = null; - if ($descriptionFactory !== null) { - $description = $descriptionFactory->create($matches[2] ?? '', $context); + if ($data['numExecutableLines'] > 0) { + $linesLevel = $this->colorLevel($data['linesExecutedPercent']); + $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; + $linesBar = $this->coverageBar($data['linesExecutedPercent']); + } else { + $linesLevel = ''; + $linesNumber = '0' . $numSeparator . '0'; + $linesBar = ''; + $data['linesExecutedPercentAsString'] = 'n/a'; } - return new static($matches[1], $description); + if ($data['numExecutablePaths'] > 0) { + $pathsLevel = $this->colorLevel($data['pathsExecutedPercent']); + $pathsNumber = $data['numExecutedPaths'] . $numSeparator . $data['numExecutablePaths']; + $pathsBar = $this->coverageBar($data['pathsExecutedPercent']); + } else { + $pathsLevel = ''; + $pathsNumber = '0' . $numSeparator . '0'; + $pathsBar = ''; + $data['pathsExecutedPercentAsString'] = 'n/a'; + } + if ($data['numExecutableBranches'] > 0) { + $branchesLevel = $this->colorLevel($data['branchesExecutedPercent']); + $branchesNumber = $data['numExecutedBranches'] . $numSeparator . $data['numExecutableBranches']; + $branchesBar = $this->coverageBar($data['branchesExecutedPercent']); + } else { + $branchesLevel = ''; + $branchesNumber = '0' . $numSeparator . '0'; + $branchesBar = ''; + $data['branchesExecutedPercentAsString'] = 'n/a'; + } + $template->setVar(['icon' => $data['icon'] ?? '', 'crap' => $data['crap'] ?? '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'paths_bar' => $pathsBar, 'paths_executed_percent' => $data['pathsExecutedPercentAsString'], 'paths_level' => $pathsLevel, 'paths_number' => $pathsNumber, 'branches_bar' => $branchesBar, 'branches_executed_percent' => $data['branchesExecutedPercentAsString'], 'branches_level' => $branchesLevel, 'branches_number' => $branchesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber]); + return $template->render(); } - /** - * Gets the version section of the tag. - */ - public function getVersion() : ?string + protected function setCommonTemplateVariables(Template $template, AbstractNode $node) : void { - return $this->version; + $template->setVar(['id' => $node->id(), 'full_path' => $node->pathAsString(), 'path_to_root' => $this->pathToRoot($node), 'breadcrumbs' => $this->breadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime' => $this->runtimeString(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound]); } - /** - * Returns a string representation for this tag. - */ - public function __toString() : string + protected function breadcrumbs(AbstractNode $node) : string + { + $breadcrumbs = ''; + $path = $node->pathAsArray(); + $pathToRoot = []; + $max = count($path); + if ($node instanceof FileNode) { + $max--; + } + for ($i = 0; $i < $max; $i++) { + $pathToRoot[] = str_repeat('../', $i); + } + foreach ($path as $step) { + if ($step !== $node) { + $breadcrumbs .= $this->inactiveBreadcrumb($step, array_pop($pathToRoot)); + } else { + $breadcrumbs .= $this->activeBreadcrumb($step); + } + } + return $breadcrumbs; + } + protected function activeBreadcrumb(AbstractNode $node) : string + { + $buffer = sprintf(' ' . "\n", $node->name()); + if ($node instanceof DirectoryNode) { + $buffer .= ' ' . "\n"; + } + return $buffer; + } + protected function inactiveBreadcrumb(AbstractNode $node, string $pathToRoot) : string + { + return sprintf(' ' . "\n", $pathToRoot, $node->name()); + } + protected function pathToRoot(AbstractNode $node) : string + { + $id = $node->id(); + $depth = substr_count($id, '/'); + if ($id !== 'index' && $node instanceof DirectoryNode) { + $depth++; + } + return str_repeat('../', $depth); + } + protected function coverageBar(float $percent) : string + { + $level = $this->colorLevel($percent); + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'coverage_bar_branch.html' : 'coverage_bar.html'); + $template = new Template($templateName, '{{', '}}'); + $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); + return $template->render(); + } + protected function colorLevel(float $percent) : string { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + if ($percent <= $this->lowUpperBound) { + return 'danger'; } - $version = (string) $this->version; - return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + if ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { + return 'warning'; + } + return 'success'; + } + private function runtimeString() : string + { + $runtime = new Runtime(); + return sprintf('%s %s', $runtime->getVendorUrl(), $runtime->getName(), $runtime->getVersion()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\Webmozart\Assert\Assert; -use function preg_match; +use function array_values; +use function arsort; +use function asort; +use function count; +use function explode; +use function floor; +use function json_encode; +use function sprintf; +use function str_replace; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use PHPUnit\SebastianBergmann\Template\Template; /** - * Reflection class for a {@}deprecated tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Deprecated extends BaseTag implements Factory\StaticMethod +final class Dashboard extends Renderer { - /** @var string */ - protected $name = 'deprecated'; + public function render(DirectoryNode $node, string $file) : void + { + $classes = $node->classesAndTraits(); + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'dashboard_branch.html' : 'dashboard.html'); + $template = new Template($templateName, '{{', '}}'); + $this->setCommonTemplateVariables($template, $node); + $baseLink = $node->id() . '/'; + $complexity = $this->complexity($classes, $baseLink); + $coverageDistribution = $this->coverageDistribution($classes); + $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); + $projectRisks = $this->projectRisks($classes, $baseLink); + $template->setVar(['insufficient_coverage_classes' => $insufficientCoverage['class'], 'insufficient_coverage_methods' => $insufficientCoverage['method'], 'project_risks_classes' => $projectRisks['class'], 'project_risks_methods' => $projectRisks['method'], 'complexity_class' => $complexity['class'], 'complexity_method' => $complexity['method'], 'class_coverage_distribution' => $coverageDistribution['class'], 'method_coverage_distribution' => $coverageDistribution['method']]); + $template->renderTo($file); + } + protected function activeBreadcrumb(AbstractNode $node) : string + { + return sprintf(' ' . "\n" . ' ' . "\n", $node->name()); + } /** - * PCRE regular expression matching a version vector. - * Assumes the "x" modifier. + * Returns the data for the Class/Method Complexity charts. */ - public const REGEX_VECTOR = '(?: - # Normal release vectors. - \\d\\S* - | - # VCS version vectors. Per PHPCS, they are expected to - # follow the form of the VCS name, followed by ":", followed - # by the version vector itself. - # By convention, popular VCSes like CVS, SVN and GIT use "$" - # around the actual version vector. - [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ - )'; - /** @var string|null The version vector. */ - private $version; - public function __construct(?string $version = null, ?Description $description = null) + private function complexity(array $classes, string $baseLink) : array { - Assert::nullOrNotEmpty($version); - $this->version = $version; - $this->description = $description; + $result = ['class' => [], 'method' => []]; + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($className !== '*') { + $methodName = $className . '::' . $methodName; + } + $result['method'][] = [$method['coverage'], $method['ccn'], sprintf('%s', str_replace($baseLink, '', $method['link']), $methodName)]; + } + $result['class'][] = [$class['coverage'], $class['ccn'], sprintf('%s', str_replace($baseLink, '', $class['link']), $className)]; + } + return ['class' => json_encode($result['class']), 'method' => json_encode($result['method'])]; } /** - * @return static + * Returns the data for the Class / Method Coverage Distribution chart. */ - public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self + private function coverageDistribution(array $classes) : array { - if (empty($body)) { - return new static(); - } - $matches = []; - if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { - return new static(null, $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null); + $result = ['class' => ['0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0], 'method' => ['0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0]]; + foreach ($classes as $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] === 0) { + $result['method']['0%']++; + } elseif ($method['coverage'] === 100) { + $result['method']['100%']++; + } else { + $key = floor($method['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['method'][$key]++; + } + } + if ($class['coverage'] === 0) { + $result['class']['0%']++; + } elseif ($class['coverage'] === 100) { + $result['class']['100%']++; + } else { + $key = floor($class['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['class'][$key]++; + } } - Assert::notNull($descriptionFactory); - return new static($matches[1], $descriptionFactory->create($matches[2] ?? '', $context)); + return ['class' => json_encode(array_values($result['class'])), 'method' => json_encode(array_values($result['method']))]; } /** - * Gets the version section of the tag. + * Returns the classes / methods with insufficient coverage. */ - public function getVersion() : ?string + private function insufficientCoverage(array $classes, string $baseLink) : array { - return $this->version; + $leastTestedClasses = []; + $leastTestedMethods = []; + $result = ['class' => '', 'method' => '']; + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound) { + $key = $methodName; + if ($className !== '*') { + $key = $className . '::' . $methodName; + } + $leastTestedMethods[$key] = $method['coverage']; + } + } + if ($class['coverage'] < $this->highLowerBound) { + $leastTestedClasses[$className] = $class['coverage']; + } + } + asort($leastTestedClasses); + asort($leastTestedMethods); + foreach ($leastTestedClasses as $className => $coverage) { + $result['class'] .= sprintf(' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $coverage); + } + foreach ($leastTestedMethods as $methodName => $coverage) { + [$class, $method] = explode('::', $methodName); + $result['method'] .= sprintf(' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $coverage); + } + return $result; } /** - * Returns a string representation for this tag. + * Returns the project risks according to the CRAP index. */ - public function __toString() : string + private function projectRisks(array $classes, string $baseLink) : array { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + $classRisks = []; + $methodRisks = []; + $result = ['class' => '', 'method' => '']; + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { + $key = $methodName; + if ($className !== '*') { + $key = $className . '::' . $methodName; + } + $methodRisks[$key] = $method['crap']; + } + } + if ($class['coverage'] < $this->highLowerBound && $class['ccn'] > count($class['methods'])) { + $classRisks[$className] = $class['crap']; + } } - $version = (string) $this->version; - return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + arsort($classRisks); + arsort($methodRisks); + foreach ($classRisks as $className => $crap) { + $result['class'] .= sprintf(' %s%d' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $crap); + } + foreach ($methodRisks as $methodName => $crap) { + [$class, $method] = explode('::', $methodName); + $result['method'] .= sprintf(' %s%d' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $crap); + } + return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\phpDocumentor\Reflection\Utils; -use PHPUnit\Webmozart\Assert\Assert; +use function count; +use function sprintf; +use function str_repeat; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode as Node; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use PHPUnit\SebastianBergmann\Template\Template; /** - * Reflection class for a {@}link tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Link extends BaseTag implements Factory\StaticMethod +final class Directory extends Renderer { - /** @var string */ - protected $name = 'link'; - /** @var string */ - private $link; - /** - * Initializes a link to a URL. - */ - public function __construct(string $link, ?Description $description = null) - { - $this->link = $link; - $this->description = $description; - } - public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self - { - Assert::notNull($descriptionFactory); - $parts = Utils::pregSplit('/\\s+/Su', $body, 2); - $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; - return new static($parts[0], $description); - } - /** - * Gets the link - */ - public function getLink() : string + public function render(DirectoryNode $node, string $file) : void { - return $this->link; + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_branch.html' : 'directory.html'); + $template = new Template($templateName, '{{', '}}'); + $this->setCommonTemplateVariables($template, $node); + $items = $this->renderItem($node, \true); + foreach ($node->directories() as $item) { + $items .= $this->renderItem($item); + } + foreach ($node->files() as $item) { + $items .= $this->renderItem($item); + } + $template->setVar(['id' => $node->id(), 'items' => $items]); + $template->renderTo($file); } - /** - * Returns a string representation for this tag. - */ - public function __toString() : string + private function renderItem(Node $node, bool $total = \false) : string { - if ($this->description) { - $description = $this->description->render(); + $data = ['numClasses' => $node->numberOfClassesAndTraits(), 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), 'numMethods' => $node->numberOfFunctionsAndMethods(), 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), 'numExecutedLines' => $node->numberOfExecutedLines(), 'numExecutableLines' => $node->numberOfExecutableLines(), 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), 'numExecutedBranches' => $node->numberOfExecutedBranches(), 'numExecutableBranches' => $node->numberOfExecutableBranches(), 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), 'numExecutedPaths' => $node->numberOfExecutedPaths(), 'numExecutablePaths' => $node->numberOfExecutablePaths(), 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString()]; + if ($total) { + $data['name'] = 'Total'; } else { - $description = ''; + $up = str_repeat('../', count($node->pathAsArray()) - 2); + $data['icon'] = sprintf('', $up); + if ($node instanceof DirectoryNode) { + $data['name'] = sprintf('%s', $node->name(), $node->name()); + $data['icon'] = sprintf('', $up); + } elseif ($this->hasBranchCoverage) { + $data['name'] = sprintf('%s [line] [branch] [path]', $node->name(), $node->name(), $node->name(), $node->name()); + } else { + $data['name'] = sprintf('%s', $node->name(), $node->name()); + } } - $link = (string) $this->link; - return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_item_branch.html' : 'directory_item.html'); + return $this->renderItemTemplate(new Template($templateName, '{{', '}}'), $data); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; -use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\TypeResolver; -use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; -use PHPUnit\phpDocumentor\Reflection\Utils; -use PHPUnit\Webmozart\Assert\Assert; -use function array_shift; -use function array_unshift; -use function implode; -use function strpos; +use const ENT_COMPAT; +use const ENT_HTML401; +use const ENT_SUBSTITUTE; +use const T_ABSTRACT; +use const T_ARRAY; +use const T_AS; +use const T_BREAK; +use const T_CALLABLE; +use const T_CASE; +use const T_CATCH; +use const T_CLASS; +use const T_CLONE; +use const T_COMMENT; +use const T_CONST; +use const T_CONTINUE; +use const T_DECLARE; +use const T_DEFAULT; +use const T_DO; +use const T_DOC_COMMENT; +use const T_ECHO; +use const T_ELSE; +use const T_ELSEIF; +use const T_EMPTY; +use const T_ENDDECLARE; +use const T_ENDFOR; +use const T_ENDFOREACH; +use const T_ENDIF; +use const T_ENDSWITCH; +use const T_ENDWHILE; +use const T_EVAL; +use const T_EXIT; +use const T_EXTENDS; +use const T_FINAL; +use const T_FINALLY; +use const T_FOR; +use const T_FOREACH; +use const T_FUNCTION; +use const T_GLOBAL; +use const T_GOTO; +use const T_HALT_COMPILER; +use const T_IF; +use const T_IMPLEMENTS; +use const T_INCLUDE; +use const T_INCLUDE_ONCE; +use const T_INLINE_HTML; +use const T_INSTANCEOF; +use const T_INSTEADOF; +use const T_INTERFACE; +use const T_ISSET; +use const T_LIST; +use const T_NAMESPACE; +use const T_NEW; +use const T_PRINT; +use const T_PRIVATE; +use const T_PROTECTED; +use const T_PUBLIC; +use const T_REQUIRE; +use const T_REQUIRE_ONCE; +use const T_RETURN; +use const T_STATIC; +use const T_SWITCH; +use const T_THROW; +use const T_TRAIT; +use const T_TRY; +use const T_UNSET; +use const T_USE; +use const T_VAR; +use const T_WHILE; +use const T_YIELD; +use const T_YIELD_FROM; +use function array_key_exists; +use function array_pop; +use function array_unique; +use function constant; +use function count; +use function defined; +use function explode; +use function file_get_contents; +use function htmlspecialchars; +use function is_string; +use function sprintf; +use function str_replace; use function substr; -use const PREG_SPLIT_DELIM_CAPTURE; +use function token_get_all; +use function trim; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Percentage; +use PHPUnit\SebastianBergmann\Template\Template; /** - * Reflection class for a {@}var tag in a Docblock. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Var_ extends TagWithType implements Factory\StaticMethod +final class File extends Renderer { - /** @var string|null */ - protected $variableName = ''; - public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) - { - Assert::string($variableName); - $this->name = 'var'; - $this->variableName = $variableName; - $this->type = $type; - $this->description = $description; - } - public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self - { - Assert::stringNotEmpty($body); - Assert::notNull($typeResolver); - Assert::notNull($descriptionFactory); - [$firstPart, $body] = self::extractTypeFromBody($body); - $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, \PREG_SPLIT_DELIM_CAPTURE); - $type = null; - $variableName = ''; - // if the first item that is encountered is not a variable; it is a type - if ($firstPart && $firstPart[0] !== '$') { - $type = $typeResolver->resolve($firstPart, $context); - } else { - // first part is not a type; we should prepend it to the parts array for further processing - array_unshift($parts, $firstPart); - } - // if the next item starts with a $ it must be the variable name - if (isset($parts[0]) && strpos($parts[0], '$') === 0) { - $variableName = array_shift($parts); - if ($type) { - array_shift($parts); - } - Assert::notNull($variableName); - $variableName = substr($variableName, 1); - } - $description = $descriptionFactory->create(implode('', $parts), $context); - return new static($variableName, $type, $description); - } /** - * Returns the variable's name. + * @psalm-var array */ - public function getVariableName() : ?string - { - return $this->variableName; - } + private static $keywordTokens = []; /** - * Returns a string representation for this tag. + * @var array */ - public function __toString() : string + private static $formattedSourceCache = []; + /** + * @var int + */ + private $htmlSpecialCharsFlags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE; + public function render(FileNode $node, string $file) : void { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; - } - if ($this->variableName) { - $variableName = '$' . $this->variableName; - } else { - $variableName = ''; + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_branch.html' : 'file.html'); + $template = new Template($templateName, '{{', '}}'); + $this->setCommonTemplateVariables($template, $node); + $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithLineCoverage($node), 'legend' => '

ExecutedNot ExecutedDead Code

', 'structure' => '']); + $template->renderTo($file . '.html'); + if ($this->hasBranchCoverage) { + $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithBranchCoverage($node), 'legend' => '

Fully coveredPartially coveredNot covered

', 'structure' => $this->renderBranchStructure($node)]); + $template->renderTo($file . '_branch.html'); + $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithPathCoverage($node), 'legend' => '

Fully coveredPartially coveredNot covered

', 'structure' => $this->renderPathStructure($node)]); + $template->renderTo($file . '_path.html'); } - $type = (string) $this->type; - return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } -} -templatePath . ($this->hasBranchCoverage ? 'file_item_branch.html' : 'file_item.html'); + $template = new Template($templateName, '{{', '}}'); + $methodTemplateName = $this->templatePath . ($this->hasBranchCoverage ? 'method_item_branch.html' : 'method_item.html'); + $methodItemTemplate = new Template($methodTemplateName, '{{', '}}'); + $items = $this->renderItemTemplate($template, ['name' => 'Total', 'numClasses' => $node->numberOfClassesAndTraits(), 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), 'numMethods' => $node->numberOfFunctionsAndMethods(), 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), 'numExecutedLines' => $node->numberOfExecutedLines(), 'numExecutableLines' => $node->numberOfExecutableLines(), 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), 'numExecutedBranches' => $node->numberOfExecutedBranches(), 'numExecutableBranches' => $node->numberOfExecutableBranches(), 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), 'numExecutedPaths' => $node->numberOfExecutedPaths(), 'numExecutablePaths' => $node->numberOfExecutablePaths(), 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString(), 'crap' => 'CRAP']); + $items .= $this->renderFunctionItems($node->functions(), $methodItemTemplate); + $items .= $this->renderTraitOrClassItems($node->traits(), $template, $methodItemTemplate); + $items .= $this->renderTraitOrClassItems($node->classes(), $template, $methodItemTemplate); + return $items; + } + private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate) : string { - $this->name = 'return'; - $this->type = $type; - $this->description = $description; + $buffer = ''; + if (empty($items)) { + return $buffer; + } + foreach ($items as $name => $item) { + $numMethods = 0; + $numTestedMethods = 0; + foreach ($item['methods'] as $method) { + if ($method['executableLines'] > 0) { + $numMethods++; + if ($method['executedLines'] === $method['executableLines']) { + $numTestedMethods++; + } + } + } + if ($item['executableLines'] > 0) { + $numClasses = 1; + $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; + $linesExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines'])->asString(); + $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches'])->asString(); + $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths'])->asString(); + } else { + $numClasses = 0; + $numTestedClasses = 0; + $linesExecutedPercentAsString = 'n/a'; + $branchesExecutedPercentAsString = 'n/a'; + $pathsExecutedPercentAsString = 'n/a'; + } + $testedMethodsPercentage = Percentage::fromFractionAndTotal($numTestedMethods, $numMethods); + $testedClassesPercentage = Percentage::fromFractionAndTotal($numTestedMethods === $numMethods ? 1 : 0, 1); + $buffer .= $this->renderItemTemplate($template, ['name' => $this->abbreviateClassName($name), 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines'])->asFloat(), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches'])->asFloat(), 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths'])->asFloat(), 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'testedClassesPercent' => $testedClassesPercentage->asFloat(), 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), 'crap' => $item['crap']]); + foreach ($item['methods'] as $method) { + $buffer .= $this->renderFunctionOrMethodItem($methodItemTemplate, $method, ' '); + } + } + return $buffer; } - public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self + private function renderFunctionItems(array $functions, Template $template) : string { - Assert::notNull($typeResolver); - Assert::notNull($descriptionFactory); - [$type, $description] = self::extractTypeFromBody($body); - $type = $typeResolver->resolve($type, $context); - $description = $descriptionFactory->create($description, $context); - return new static($type, $description); + if (empty($functions)) { + return ''; + } + $buffer = ''; + foreach ($functions as $function) { + $buffer .= $this->renderFunctionOrMethodItem($template, $function); + } + return $buffer; } - public function __toString() : string + private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = '') : string { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + $numMethods = 0; + $numTestedMethods = 0; + if ($item['executableLines'] > 0) { + $numMethods = 1; + if ($item['executedLines'] === $item['executableLines']) { + $numTestedMethods = 1; + } } - $type = $this->type ? '' . $this->type : 'mixed'; - return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + $executedLinesPercentage = Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines']); + $executedBranchesPercentage = Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches']); + $executedPathsPercentage = Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths']); + $testedMethodsPercentage = Percentage::fromFractionAndTotal($numTestedMethods, 1); + return $this->renderItemTemplate($template, ['name' => sprintf('%s%s', $indent, $item['startLine'], htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName']), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'crap' => $item['crap']]); } -} -refers = $refers; - $this->description = $description; + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + $coverageData = $node->lineCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $lines = ''; + $i = 1; + foreach ($codeLines as $line) { + $trClass = ''; + $popoverContent = ''; + $popoverTitle = ''; + if (array_key_exists($i, $coverageData)) { + $numTests = $coverageData[$i] ? count($coverageData[$i]) : 0; + if ($coverageData[$i] === null) { + $trClass = 'warning'; + } elseif ($numTests === 0) { + $trClass = 'danger'; + } else { + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover line ' . $i; + } else { + $popoverTitle = '1 test covers line ' . $i; + } + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
    '; + foreach ($coverageData[$i] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + } + } + $popover = ''; + if (!empty($popoverTitle)) { + $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); + } + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + $i++; + } + $linesTemplate->setVar(['lines' => $lines]); + return $linesTemplate->render(); } - public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?FqsenResolver $resolver = null, ?TypeContext $context = null) : self + private function renderSourceWithBranchCoverage(FileNode $node) : string { - Assert::stringNotEmpty($body); - Assert::notNull($descriptionFactory); - Assert::notNull($resolver); - $parts = Utils::pregSplit('/\\s+/Su', $body, 2); - return new static(self::resolveFqsen($parts[0], $resolver, $context), $descriptionFactory->create($parts[1] ?? '', $context)); + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + $functionCoverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $lineData = []; + /** @var int $line */ + foreach (\array_keys($codeLines) as $line) { + $lineData[$line + 1] = ['includedInBranches' => 0, 'includedInHitBranches' => 0, 'tests' => []]; + } + foreach ($functionCoverageData as $method) { + foreach ($method['branches'] as $branch) { + foreach (\range($branch['line_start'], $branch['line_end']) as $line) { + if (!isset($lineData[$line])) { + // blank line at end of file is sometimes included here + continue; + } + $lineData[$line]['includedInBranches']++; + if ($branch['hit']) { + $lineData[$line]['includedInHitBranches']++; + $lineData[$line]['tests'] = array_unique(\array_merge($lineData[$line]['tests'], $branch['hit'])); + } + } + } + } + $lines = ''; + $i = 1; + /** @var string $line */ + foreach ($codeLines as $line) { + $trClass = ''; + $popover = ''; + if ($lineData[$i]['includedInBranches'] > 0) { + $lineCss = 'success'; + if ($lineData[$i]['includedInHitBranches'] === 0) { + $lineCss = 'danger'; + } elseif ($lineData[$i]['includedInHitBranches'] !== $lineData[$i]['includedInBranches']) { + $lineCss = 'warning'; + } + $popoverContent = '
    '; + if (count($lineData[$i]['tests']) === 1) { + $popoverTitle = '1 test covers line ' . $i; + } else { + $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; + } + $popoverTitle .= '. These are covering ' . $lineData[$i]['includedInHitBranches'] . ' out of the ' . $lineData[$i]['includedInBranches'] . ' code branches.'; + foreach ($lineData[$i]['tests'] as $test) { + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); + } + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + $i++; + } + $linesTemplate->setVar(['lines' => $lines]); + return $linesTemplate->render(); } - private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + private function renderSourceWithPathCoverage(FileNode $node) : string { - Assert::notNull($fqsenResolver); - $fqsenParts = explode('::', $parts); - $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); - if (!array_key_exists(1, $fqsenParts)) { - return $resolved; + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + $functionCoverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $lineData = []; + /** @var int $line */ + foreach (\array_keys($codeLines) as $line) { + $lineData[$line + 1] = ['includedInPaths' => [], 'includedInHitPaths' => [], 'tests' => []]; } - return new Fqsen($resolved . '::' . $fqsenParts[1]); + foreach ($functionCoverageData as $method) { + foreach ($method['paths'] as $pathId => $path) { + foreach ($path['path'] as $branchTaken) { + foreach (\range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) { + if (!isset($lineData[$line])) { + continue; + } + $lineData[$line]['includedInPaths'][] = $pathId; + if ($path['hit']) { + $lineData[$line]['includedInHitPaths'][] = $pathId; + $lineData[$line]['tests'] = array_unique(\array_merge($lineData[$line]['tests'], $path['hit'])); + } + } + } + } + } + $lines = ''; + $i = 1; + /** @var string $line */ + foreach ($codeLines as $line) { + $trClass = ''; + $popover = ''; + $includedInPathsCount = count(array_unique($lineData[$i]['includedInPaths'])); + $includedInHitPathsCount = count(array_unique($lineData[$i]['includedInHitPaths'])); + if ($includedInPathsCount > 0) { + $lineCss = 'success'; + if ($includedInHitPathsCount === 0) { + $lineCss = 'danger'; + } elseif ($includedInHitPathsCount !== $includedInPathsCount) { + $lineCss = 'warning'; + } + $popoverContent = '
    '; + if (count($lineData[$i]['tests']) === 1) { + $popoverTitle = '1 test covers line ' . $i; + } else { + $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; + } + $popoverTitle .= '. These are covering ' . $includedInHitPathsCount . ' out of the ' . $includedInPathsCount . ' code paths.'; + foreach ($lineData[$i]['tests'] as $test) { + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); + } + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + $i++; + } + $linesTemplate->setVar(['lines' => $lines]); + return $linesTemplate->render(); } - /** - * Returns the structural element this tag refers to. - */ - public function getReference() : Fqsen + private function renderBranchStructure(FileNode $node) : string { - return $this->refers; + $branchesTemplate = new Template($this->templatePath . 'branches.html.dist', '{{', '}}'); + $coverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $branches = ''; + \ksort($coverageData); + foreach ($coverageData as $methodName => $methodData) { + if (!$methodData['branches']) { + continue; + } + $branchStructure = ''; + foreach ($methodData['branches'] as $branch) { + $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData); + } + if ($branchStructure !== '') { + // don't show empty branches + $branches .= '
' . $this->abbreviateMethodName($methodName) . '
' . "\n"; + $branches .= $branchStructure; + } + } + $branchesTemplate->setVar(['branches' => $branches]); + return $branchesTemplate->render(); } - /** - * Returns a string representation of this tag. - */ - public function __toString() : string + private function renderBranchLines(array $branch, array $codeLines, array $testData) : string { - if ($this->description) { - $description = $this->description->render(); - } else { - $description = ''; + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + $lines = ''; + $branchLines = \range($branch['line_start'], $branch['line_end']); + \sort($branchLines); + // sometimes end_line < start_line + /** @var int $line */ + foreach ($branchLines as $line) { + if (!isset($codeLines[$line])) { + // blank line at end of file is sometimes included here + continue; + } + $popoverContent = ''; + $popoverTitle = ''; + $numTests = count($branch['hit']); + if ($numTests === 0) { + $trClass = 'danger'; + } else { + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
    '; + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover this branch'; + } else { + $popoverTitle = '1 test covers this branch'; + } + foreach ($branch['hit'] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $trClass = $lineCss . ' popin'; + } + $popover = ''; + if (!empty($popoverTitle)) { + $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); + } + $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); } - $refers = (string) $this->refers; - return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + if ($lines === '') { + return ''; + } + $linesTemplate->setVar(['lines' => $lines]); + return $linesTemplate->render(); } -} -filePath = $filePath; - $this->startingLine = $startingLine; - $this->lineCount = $lineCount; - if ($content !== null) { - $this->content = trim($content); + $pathsTemplate = new Template($this->templatePath . 'paths.html.dist', '{{', '}}'); + $coverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $paths = ''; + \ksort($coverageData); + foreach ($coverageData as $methodName => $methodData) { + if (!$methodData['paths']) { + continue; + } + $pathStructure = ''; + if (count($methodData['paths']) > 100) { + $pathStructure .= '

    ' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

    '; + continue; + } + foreach ($methodData['paths'] as $path) { + $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); + } + if ($pathStructure !== '') { + $paths .= '
    ' . $this->abbreviateMethodName($methodName) . '
    ' . "\n"; + $paths .= $pathStructure; + } } - $this->isURI = $isURI; + $pathsTemplate->setVar(['paths' => $paths]); + return $pathsTemplate->render(); } - public function getContent() : string + private function renderPathLines(array $path, array $branches, array $codeLines, array $testData) : string { - if ($this->content === null || $this->content === '') { - $filePath = $this->filePath; - if ($this->isURI) { - $filePath = $this->isUriRelative($this->filePath) ? str_replace('%2F', '/', rawurlencode($this->filePath)) : $this->filePath; + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + $lines = ''; + $first = \true; + foreach ($path['path'] as $branchId) { + if ($first) { + $first = \false; + } else { + $lines .= '  ' . "\n"; + } + $branchLines = \range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']); + \sort($branchLines); + // sometimes end_line < start_line + /** @var int $line */ + foreach ($branchLines as $line) { + if (!isset($codeLines[$line])) { + // blank line at end of file is sometimes included here + continue; + } + $popoverContent = ''; + $popoverTitle = ''; + $numTests = count($path['hit']); + if ($numTests === 0) { + $trClass = 'danger'; + } else { + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
      '; + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover this path'; + } else { + $popoverTitle = '1 test covers this path'; + } + foreach ($path['hit'] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $trClass = $lineCss . ' popin'; + } + $popover = ''; + if (!empty($popoverTitle)) { + $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); + } + $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); } - return trim($filePath); } - return $this->content; + if ($lines === '') { + return ''; + } + $linesTemplate->setVar(['lines' => $lines]); + return $linesTemplate->render(); } - public function getDescription() : ?string + private function renderLine(Template $template, int $lineNumber, string $lineContent, string $class, string $popover) : string { - return $this->content; + $template->setVar(['lineNumber' => $lineNumber, 'lineContent' => $lineContent, 'class' => $class, 'popover' => $popover]); + return $template->render(); } - public static function create(string $body) : ?Tag + private function loadFile(string $file) : array { - // File component: File path in quotes or File URI / Source information - if (!preg_match('/^\\s*(?:(\\"[^\\"]+\\")|(\\S+))(?:\\s+(.*))?$/sux', $body, $matches)) { - return null; - } - $filePath = null; - $fileUri = null; - if ($matches[1] !== '') { - $filePath = $matches[1]; - } else { - $fileUri = $matches[2]; + if (isset(self::$formattedSourceCache[$file])) { + return self::$formattedSourceCache[$file]; } - $startingLine = 1; - $lineCount = 0; - $description = null; - if (array_key_exists(3, $matches)) { - $description = $matches[3]; - // Starting line / Number of lines / Description - if (preg_match('/^([1-9]\\d*)(?:\\s+((?1))\\s*)?(.*)$/sux', $matches[3], $contentMatches)) { - $startingLine = (int) $contentMatches[1]; - if (isset($contentMatches[2])) { - $lineCount = (int) $contentMatches[2]; + $buffer = file_get_contents($file); + $tokens = token_get_all($buffer); + $result = ['']; + $i = 0; + $stringFlag = \false; + $fileEndsWithNewLine = substr($buffer, -1) === "\n"; + unset($buffer); + foreach ($tokens as $j => $token) { + if (is_string($token)) { + if ($token === '"' && $tokens[$j - 1] !== '\\') { + $result[$i] .= sprintf('%s', htmlspecialchars($token, $this->htmlSpecialCharsFlags)); + $stringFlag = !$stringFlag; + } else { + $result[$i] .= sprintf('%s', htmlspecialchars($token, $this->htmlSpecialCharsFlags)); } - if (array_key_exists(3, $contentMatches)) { - $description = $contentMatches[3]; + continue; + } + [$token, $value] = $token; + $value = str_replace(["\t", ' '], ['    ', ' '], htmlspecialchars($value, $this->htmlSpecialCharsFlags)); + if ($value === "\n") { + $result[++$i] = ''; + } else { + $lines = explode("\n", $value); + foreach ($lines as $jj => $line) { + $line = trim($line); + if ($line !== '') { + if ($stringFlag) { + $colour = 'string'; + } else { + $colour = 'default'; + if ($this->isInlineHtml($token)) { + $colour = 'html'; + } elseif ($this->isComment($token)) { + $colour = 'comment'; + } elseif ($this->isKeyword($token)) { + $colour = 'keyword'; + } + } + $result[$i] .= sprintf('%s', $colour, $line); + } + if (isset($lines[$jj + 1])) { + $result[++$i] = ''; + } } } } - return new static($filePath ?? $fileUri ?? '', $fileUri !== null, $startingLine, $lineCount, $description); + if ($fileEndsWithNewLine) { + unset($result[count($result) - 1]); + } + self::$formattedSourceCache[$file] = $result; + return $result; } - /** - * Returns the file path. - * - * @return string Path to a file to use as an example. - * May also be an absolute URI. - */ - public function getFilePath() : string + private function abbreviateClassName(string $className) : string { - return trim($this->filePath, '"'); + $tmp = explode('\\', $className); + if (count($tmp) > 1) { + $className = sprintf('%s', $className, array_pop($tmp)); + } + return $className; } - /** - * Returns a string representation for this tag. - */ - public function __toString() : string + private function abbreviateMethodName(string $methodName) : string { - $filePath = (string) $this->filePath; - $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; - $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; - $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; - $content = (string) $this->content; - return $filePath . ($startingLine !== '' ? ($filePath !== '' ? ' ' : '') . $startingLine : '') . ($lineCount !== '' ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount : '') . ($content !== '' ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content : ''); + $parts = explode('->', $methodName); + if (count($parts) === 2) { + return $this->abbreviateClassName($parts[0]) . '->' . $parts[1]; + } + return $methodName; } - /** - * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). - */ - private function isUriRelative(string $uri) : bool + private function createPopoverContentForTest(string $test, array $testData) : string { - return strpos($uri, ':') === \false; + $testCSS = ''; + if ($testData['fromTestcase']) { + switch ($testData['status']) { + case BaseTestRunner::STATUS_PASSED: + switch ($testData['size']) { + case 'small': + $testCSS = ' class="covered-by-small-tests"'; + break; + case 'medium': + $testCSS = ' class="covered-by-medium-tests"'; + break; + default: + $testCSS = ' class="covered-by-large-tests"'; + break; + } + break; + case BaseTestRunner::STATUS_SKIPPED: + case BaseTestRunner::STATUS_INCOMPLETE: + case BaseTestRunner::STATUS_RISKY: + case BaseTestRunner::STATUS_WARNING: + $testCSS = ' class="warning"'; + break; + case BaseTestRunner::STATUS_FAILURE: + case BaseTestRunner::STATUS_ERROR: + $testCSS = ' class="danger"'; + break; + } + } + return sprintf('%s', $testCSS, htmlspecialchars($test, $this->htmlSpecialCharsFlags)); } - public function getStartingLine() : int + private function isComment(int $token) : bool { - return $this->startingLine; + return $token === T_COMMENT || $token === T_DOC_COMMENT; } - public function getLineCount() : int + private function isInlineHtml(int $token) : bool { - return $this->lineCount; + return $token === T_INLINE_HTML; } - public function getName() : string + private function isKeyword(int $token) : bool { - return 'example'; + return isset(self::keywordTokens()[$token]); } - public function render(?Formatter $formatter = null) : string + /** + * @psalm-return array + */ + private static function keywordTokens() : array { - if ($formatter === null) { - $formatter = new Formatter\PassthroughFormatter(); + if (self::$keywordTokens !== []) { + return self::$keywordTokens; } - return $formatter->format($this); + self::$keywordTokens = [T_ABSTRACT => \true, T_ARRAY => \true, T_AS => \true, T_BREAK => \true, T_CALLABLE => \true, T_CASE => \true, T_CATCH => \true, T_CLASS => \true, T_CLONE => \true, T_CONST => \true, T_CONTINUE => \true, T_DECLARE => \true, T_DEFAULT => \true, T_DO => \true, T_ECHO => \true, T_ELSE => \true, T_ELSEIF => \true, T_EMPTY => \true, T_ENDDECLARE => \true, T_ENDFOR => \true, T_ENDFOREACH => \true, T_ENDIF => \true, T_ENDSWITCH => \true, T_ENDWHILE => \true, T_EVAL => \true, T_EXIT => \true, T_EXTENDS => \true, T_FINAL => \true, T_FINALLY => \true, T_FOR => \true, T_FOREACH => \true, T_FUNCTION => \true, T_GLOBAL => \true, T_GOTO => \true, T_HALT_COMPILER => \true, T_IF => \true, T_IMPLEMENTS => \true, T_INCLUDE => \true, T_INCLUDE_ONCE => \true, T_INSTANCEOF => \true, T_INSTEADOF => \true, T_INTERFACE => \true, T_ISSET => \true, T_LIST => \true, T_NAMESPACE => \true, T_NEW => \true, T_PRINT => \true, T_PRIVATE => \true, T_PROTECTED => \true, T_PUBLIC => \true, T_REQUIRE => \true, T_REQUIRE_ONCE => \true, T_RETURN => \true, T_STATIC => \true, T_SWITCH => \true, T_THROW => \true, T_TRAIT => \true, T_TRY => \true, T_UNSET => \true, T_USE => \true, T_VAR => \true, T_WHILE => \true, T_YIELD => \true, T_YIELD_FROM => \true]; + if (defined('T_FN')) { + self::$keywordTokens[constant('T_FN')] = \true; + } + if (defined('T_MATCH')) { + self::$keywordTokens[constant('T_MATCH')] = \true; + } + if (defined('T_ENUM')) { + self::$keywordTokens[constant('T_ENUM')] = \true; + } + if (defined('T_READONLY')) { + self::$keywordTokens[constant('T_READONLY')] = \true; + } + return self::$keywordTokens; } } - +

      Branches

      +

      + Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not + necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. + Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement + always has an else as part of its logical flow even if you didn't write one. +

      +{{branches}} +
      +
      + {{percent}}% covered ({{level}}) +
      +
      +
      +
      + {{percent}}% covered ({{level}}) +
      +
      +/*! + * Bootstrap v4.6.1 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}.octicon { + display: inline-block; + vertical-align: text-top; + fill: currentColor; +} +body { + padding-top: 10px; +} + +.popover { + max-width: none; +} + +.octicon { + margin-right:.25em; +} + +.table-bordered>thead>tr>td { + border-bottom-width: 1px; +} + +.table tbody>tr>td, .table thead>tr>td { + padding-top: 3px; + padding-bottom: 3px; +} + +.table-condensed tbody>tr>td { + padding-top: 0; + padding-bottom: 0; +} + +.table .progress { + margin-bottom: inherit; +} + +.table-borderless th, .table-borderless td { + border: 0 !important; +} + +.table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { + background-color: #dff0d8; +} + +.table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { + background-color: #c3e3b5; +} + +.table tbody tr.covered-by-small-tests, li.covered-by-small-tests { + background-color: #99cb84; +} + +.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { + background-color: #f2dede; +} + +.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { + background-color: #fcf8e3; +} + +.table tbody td.info { + background-color: #d9edf7; +} + +td.big { + width: 117px; +} + +td.small { +} + +td.codeLine { + font-family: "Source Code Pro", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + white-space: pre-wrap; +} + +td span.comment { + color: #888a85; +} + +td span.default { + color: #2e3436; +} + +td span.html { + color: #888a85; +} + +td span.keyword { + color: #2e3436; + font-weight: bold; +} + +pre span.string { + color: #2e3436; +} + +span.success, span.warning, span.danger { + margin-right: 2px; + padding-left: 10px; + padding-right: 10px; + text-align: center; +} + +#toplink { + position: fixed; + left: 5px; + bottom: 5px; + outline: 0; +} + +svg text { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + color: #666; + fill: #666; +} + +.scrollbox { + height:245px; + overflow-x:hidden; + overflow-y:scroll; +} + +table + .structure-heading { + border-top: 1px solid lightgrey; + padding-top: 0.5em; +} + + + + + Dashboard for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_classes}} + +
      ClassCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_classes}} + +
      ClassCRAP
      +
      +
      +
      +
      +
      +

      Methods

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_methods}} + +
      MethodCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_methods}} + +
      MethodCRAP
      +
      +
      +
      + +
      + + + + + + + + + + + Dashboard for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_classes}} + +
      ClassCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_classes}} + +
      ClassCRAP
      +
      +
      +
      +
      +
      +

      Methods

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_methods}} + +
      MethodCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_methods}} + +
      MethodCRAP
      +
      +
      +
      + +
      + + + + + + + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Functions and Methods
      Classes and Traits
      +
      +
      +
      +

      Legend

      +

      + Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

      +

      + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

      +
      +
      + + + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Branches
      Paths
      Functions and Methods
      Classes and Traits
      +
      +
      +
      +

      Legend

      +

      + Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

      +

      + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

      +
      +
      + + + + {{icon}}{{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + + + {{icon}}{{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Functions and Methods
      Classes and Traits
      +
      +{{lines}} +{{structure}} + +
      + + + + + + + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Branches
      Paths
      Functions and Methods
      Classes and Traits
      +
      +{{lines}} +{{structure}} + +
      + + + + + + + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + -use PHPUnit\phpDocumentor\Reflection\Exception\PcreException; -use function preg_last_error; -use function preg_split as php_preg_split; -abstract class Utils -{ - /** - * Wrapper function for phps preg_split - * - * This function is inspired by {@link https://github.com/thecodingmachine/safe/blob/master/generated/pcre.php}. But - * since this library is all about performance we decided to strip everything we don't need. Reducing the amount - * of files that have to be loaded, ect. - * - * @param string $pattern The pattern to search for, as a string. - * @param string $subject The input string. - * @param int|null $limit If specified, then only substrings up to limit are returned with the - * rest of the string being placed in the last substring. A limit of -1 or 0 means "no limit". - * @param int $flags flags can be any combination of the following flags (combined with the | bitwise operator): - * *PREG_SPLIT_NO_EMPTY* - * If this flag is set, only non-empty pieces will be returned by preg_split(). - * *PREG_SPLIT_DELIM_CAPTURE* - * If this flag is set, parenthesized expression in the delimiter pattern will be captured - * and returned as well. - * *PREG_SPLIT_OFFSET_CAPTURE* - * If this flag is set, for every occurring match the appendant string offset will also be returned. - * Note that this changes the return value in an array where every element is an array consisting of the - * matched string at offset 0 and its string offset into subject at offset 1. - * - * @return string[] Returns an array containing substrings of subject split along boundaries matched by pattern - * - * @throws PcreException - */ - public static function pregSplit(string $pattern, string $subject, ?int $limit = -1, int $flags = 0) : array - { - $parts = php_preg_split($pattern, $subject, $limit, $flags); - if ($parts === \false) { - throw PcreException::createFromPhpError(preg_last_error()); - } - return $parts; - } -} - + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + -declare (strict_types=1); -namespace PHPUnit\phpDocumentor\Reflection\Exception; +/*! + * Bootstrap v4.6.1 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
      ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); +//# sourceMappingURL=bootstrap.min.js.map!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; +if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); $(function() { + var $window = $(window) + , $top_link = $('#toplink') + , $body = $('body, html') + , offset = $('#code').offset().top + , hidePopover = function ($target) { + $target.data('popover-hover', false); -use InvalidArgumentException; -use const PREG_BACKTRACK_LIMIT_ERROR; -use const PREG_BAD_UTF8_ERROR; -use const PREG_BAD_UTF8_OFFSET_ERROR; -use const PREG_INTERNAL_ERROR; -use const PREG_JIT_STACKLIMIT_ERROR; -use const PREG_NO_ERROR; -use const PREG_RECURSION_LIMIT_ERROR; -final class PcreException extends InvalidArgumentException -{ - public static function createFromPhpError(int $errorCode) : self - { - switch ($errorCode) { - case \PREG_BACKTRACK_LIMIT_ERROR: - return new self('Backtrack limit error'); - case \PREG_RECURSION_LIMIT_ERROR: - return new self('Recursion limit error'); - case \PREG_BAD_UTF8_ERROR: - return new self('Bad UTF8 error'); - case \PREG_BAD_UTF8_OFFSET_ERROR: - return new self('Bad UTF8 offset error'); - case \PREG_JIT_STACKLIMIT_ERROR: - return new self('Jit stacklimit error'); - case \PREG_NO_ERROR: - case \PREG_INTERNAL_ERROR: - default: - } - return new self('Unknown Pcre error'); - } -} -summary = $summary; - $this->description = $description ?: new DocBlock\Description(''); - foreach ($tags as $tag) { - $this->addTag($tag); - } - $this->context = $context; - $this->location = $location; - $this->isTemplateEnd = $isTemplateEnd; - $this->isTemplateStart = $isTemplateStart; - } - public function getSummary() : string - { - return $this->summary; - } - public function getDescription() : DocBlock\Description - { - return $this->description; - } - /** - * Returns the current context. - */ - public function getContext() : ?Types\Context - { - return $this->context; - } - /** - * Returns the current location. - */ - public function getLocation() : ?Location - { - return $this->location; - } - /** - * Returns whether this DocBlock is the start of a Template section. - * - * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker - * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. - * - * An example of such an opening is: - * - * ``` - * /**#@+ - * * My DocBlock - * * / - * ``` - * - * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all - * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). - * - * @see self::isTemplateEnd() for the check whether a closing marker was provided. - */ - public function isTemplateStart() : bool - { - return $this->isTemplateStart; - } - /** - * Returns whether this DocBlock is the end of a Template section. - * - * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. - */ - public function isTemplateEnd() : bool - { - return $this->isTemplateEnd; - } - /** - * Returns the tags for this DocBlock. - * - * @return Tag[] - */ - public function getTags() : array - { - return $this->tags; - } - /** - * Returns an array of tags matching the given name. If no tags are found - * an empty array is returned. - * - * @param string $name String to search by. - * - * @return Tag[] - */ - public function getTagsByName(string $name) : array - { - $result = []; - foreach ($this->getTags() as $tag) { - if ($tag->getName() !== $name) { - continue; - } - $result[] = $tag; - } - return $result; - } - /** - * Checks if a tag of a certain type is present in this DocBlock. - * - * @param string $name Tag name to check for. - */ - public function hasTag(string $name) : bool - { - foreach ($this->getTags() as $tag) { - if ($tag->getName() === $name) { - return \true; - } - } - return \false; - } - /** - * Remove a tag from this DocBlock. - * - * @param Tag $tagToRemove The tag to remove. - */ - public function removeTag(Tag $tagToRemove) : void - { - foreach ($this->tags as $key => $tag) { - if ($tag === $tagToRemove) { - unset($this->tags[$key]); - break; - } - } - } - /** - * Adds a tag to this DocBlock. - * - * @param Tag $tag The tag to add. - */ - private function addTag(Tag $tag) : void - { - $this->tags[] = $tag; + $window.scroll(function() { + if($window.scrollTop() > offset) { + $top_link.fadeIn(); + } else { + $top_link.fadeOut(); } -} -The MIT License (MIT) - -Copyright (c) 2010 Mike van Riel + }).scroll(); -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: + $('.popin') + .popover({trigger: 'manual'}) + .on({ + 'mouseenter.popover': function () { + var $target = $(this); + var $container = $target.children().first(); -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + $target.data('popover-hover', true); -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. -> $additionalTags - */ - public static function createInstance(array $additionalTags = []) : DocBlockFactory; - /** - * @param string|object $docblock - */ - public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock; -} -+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); +x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); +var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] +}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); +var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left +}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) +}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();/* + Copyright (C) Federico Zivolo 2020 + Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge}); +//# sourceMappingURL=popper.min.js.map + {{lineNumber}}{{lineContent}} + + +{{lines}} + +
      + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + + -declare (strict_types=1); -/** - * This file is part of phpDocumentor. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - * @link http://phpdoc.org - */ -namespace PHPUnit\phpDocumentor\Reflection\Types; + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + + -use PHPUnit\phpDocumentor\Reflection\Type; -/** - * Value Object representing the 'resource' Type. - * - * @psalm-immutable - */ -final class Resource_ implements Type -{ - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - return 'resource'; - } -} +
      +

      Paths

      +

      + Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not + necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. + Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement + always has an else as part of its logical flow even if you didn't write one. +

      +{{paths}} * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; -use PHPUnit\phpDocumentor\Reflection\Type; -/** - * Value Object representing a Compound Type. - * - * A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated - * using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type - * may contain a value with any of the given types. - * - * @psalm-immutable - */ -final class Compound extends AggregatedType +use function dirname; +use function file_put_contents; +use function serialize; +use function sprintf; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +final class PHP { - /** - * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface. - * - * @param array $types - */ - public function __construct(array $types) + public function process(CodeCoverage $coverage, ?string $target = null) : string { - parent::__construct($types, '|'); + $buffer = sprintf(" * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; -use PHPUnit\phpDocumentor\Reflection\Type; -/** - * Value object representing Integer type - * - * @psalm-immutable - */ -class Integer implements Type +use const PHP_EOL; +use function array_map; +use function date; +use function ksort; +use function max; +use function sprintf; +use function str_pad; +use function strlen; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Percentage; +final class Text { /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var string */ - public function __toString() : string - { - return 'int'; - } -} -lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + } + public function process(CodeCoverage $coverage, bool $showColors = \false) : string + { + $hasBranchCoverage = !empty($coverage->getData(\true)->functionCoverage()); + $output = PHP_EOL . PHP_EOL; + $report = $coverage->getReport(); + $colors = ['header' => '', 'classes' => '', 'methods' => '', 'lines' => '', 'branches' => '', 'paths' => '', 'reset' => '', 'eol' => '']; + if ($showColors) { + $colors['classes'] = $this->coverageColor($report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits()); + $colors['methods'] = $this->coverageColor($report->numberOfTestedMethods(), $report->numberOfMethods()); + $colors['lines'] = $this->coverageColor($report->numberOfExecutedLines(), $report->numberOfExecutableLines()); + $colors['branches'] = $this->coverageColor($report->numberOfExecutedBranches(), $report->numberOfExecutableBranches()); + $colors['paths'] = $this->coverageColor($report->numberOfExecutedPaths(), $report->numberOfExecutablePaths()); + $colors['reset'] = self::COLOR_RESET; + $colors['header'] = self::COLOR_HEADER; + $colors['eol'] = self::COLOR_EOL; + } + $classes = sprintf(' Classes: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits())->asString(), $report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits()); + $methods = sprintf(' Methods: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfTestedMethods(), $report->numberOfMethods())->asString(), $report->numberOfTestedMethods(), $report->numberOfMethods()); + $paths = ''; + $branches = ''; + if ($hasBranchCoverage) { + $paths = sprintf(' Paths: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedPaths(), $report->numberOfExecutablePaths())->asString(), $report->numberOfExecutedPaths(), $report->numberOfExecutablePaths()); + $branches = sprintf(' Branches: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedBranches(), $report->numberOfExecutableBranches())->asString(), $report->numberOfExecutedBranches(), $report->numberOfExecutableBranches()); + } + $lines = sprintf(' Lines: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedLines(), $report->numberOfExecutableLines())->asString(), $report->numberOfExecutedLines(), $report->numberOfExecutableLines()); + $padding = max(array_map('strlen', [$classes, $methods, $lines])); + if ($this->showOnlySummary) { + $title = 'Code Coverage Report Summary:'; + $padding = max($padding, strlen($title)); + $output .= $this->format($colors['header'], $padding, $title); + } else { + $date = date(' Y-m-d H:i:s'); + $title = 'Code Coverage Report:'; + $output .= $this->format($colors['header'], $padding, $title); + $output .= $this->format($colors['header'], $padding, $date); + $output .= $this->format($colors['header'], $padding, ''); + $output .= $this->format($colors['header'], $padding, ' Summary:'); + } + $output .= $this->format($colors['classes'], $padding, $classes); + $output .= $this->format($colors['methods'], $padding, $methods); + if ($hasBranchCoverage) { + $output .= $this->format($colors['paths'], $padding, $paths); + $output .= $this->format($colors['branches'], $padding, $branches); + } + $output .= $this->format($colors['lines'], $padding, $lines); + if ($this->showOnlySummary) { + return $output . PHP_EOL; + } + $classCoverage = []; + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + $classes = $item->classesAndTraits(); + foreach ($classes as $className => $class) { + $classExecutableLines = 0; + $classExecutedLines = 0; + $classExecutableBranches = 0; + $classExecutedBranches = 0; + $classExecutablePaths = 0; + $classExecutedPaths = 0; + $coveredMethods = 0; + $classMethods = 0; + foreach ($class['methods'] as $method) { + if ($method['executableLines'] == 0) { + continue; + } + $classMethods++; + $classExecutableLines += $method['executableLines']; + $classExecutedLines += $method['executedLines']; + $classExecutableBranches += $method['executableBranches']; + $classExecutedBranches += $method['executedBranches']; + $classExecutablePaths += $method['executablePaths']; + $classExecutedPaths += $method['executedPaths']; + if ($method['coverage'] == 100) { + $coveredMethods++; + } + } + $classCoverage[$className] = ['namespace' => $class['namespace'], 'className' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, 'statementsCovered' => $classExecutedLines, 'statementCount' => $classExecutableLines, 'branchesCovered' => $classExecutedBranches, 'branchesCount' => $classExecutableBranches, 'pathsCovered' => $classExecutedPaths, 'pathsCount' => $classExecutablePaths]; + } + } + ksort($classCoverage); + $methodColor = ''; + $pathsColor = ''; + $branchesColor = ''; + $linesColor = ''; + $resetColor = ''; + foreach ($classCoverage as $fullQualifiedPath => $classInfo) { + if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { + if ($showColors) { + $methodColor = $this->coverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); + $pathsColor = $this->coverageColor($classInfo['pathsCovered'], $classInfo['pathsCount']); + $branchesColor = $this->coverageColor($classInfo['branchesCovered'], $classInfo['branchesCount']); + $linesColor = $this->coverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); + $resetColor = $colors['reset']; + } + $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' '; + if ($hasBranchCoverage) { + $output .= ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathsCount'], 3) . $resetColor . ' ' . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchesCount'], 3) . $resetColor . ' '; + } + $output .= ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor; + } + } + return $output . PHP_EOL; + } + private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfElements) : string + { + $coverage = Percentage::fromFractionAndTotal($numberOfCoveredElements, $totalNumberOfElements); + if ($coverage->asFloat() >= $this->highLowerBound) { + return self::COLOR_GREEN; + } + if ($coverage->asFloat() > $this->lowUpperBound) { + return self::COLOR_YELLOW; + } + return self::COLOR_RED; + } + private function printCoverageCounts(int $numberOfCoveredElements, int $totalNumberOfElements, int $precision) : string + { + $format = '%' . $precision . 's'; + return Percentage::fromFractionAndTotal($numberOfCoveredElements, $totalNumberOfElements)->asFixedWidthString() . ' (' . sprintf($format, $numberOfCoveredElements) . '/' . sprintf($format, $totalNumberOfElements) . ')'; } -} - * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use function constant; +use function phpversion; +use DateTimeImmutable; +use DOMElement; +use PHPUnit\SebastianBergmann\Environment\Runtime; /** - * Value Object representing the return-type 'void'. - * - * Void is generally only used when working with return types as it signifies that the method intentionally does not - * return any value. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Void_ implements Type +final class BuildInformation { /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var DOMElement */ - public function __toString() : string + private $contextNode; + public function __construct(DOMElement $contextNode) { - return 'void'; + $this->contextNode = $contextNode; + } + public function setRuntimeInformation(Runtime $runtime) : void + { + $runtimeNode = $this->nodeByName('runtime'); + $runtimeNode->setAttribute('name', $runtime->getName()); + $runtimeNode->setAttribute('version', $runtime->getVersion()); + $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); + $driverNode = $this->nodeByName('driver'); + if ($runtime->hasPHPDBGCodeCoverage()) { + $driverNode->setAttribute('name', 'phpdbg'); + $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); + } + if ($runtime->hasXdebug()) { + $driverNode->setAttribute('name', 'xdebug'); + $driverNode->setAttribute('version', phpversion('xdebug')); + } + if ($runtime->hasPCOV()) { + $driverNode->setAttribute('name', 'pcov'); + $driverNode->setAttribute('version', phpversion('pcov')); + } + } + public function setBuildTime(DateTimeImmutable $date) : void + { + $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); + } + public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion) : void + { + $this->contextNode->setAttribute('phpunit', $phpUnitVersion); + $this->contextNode->setAttribute('coverage', $coverageVersion); + } + private function nodeByName(string $name) : DOMElement + { + $node = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', $name)->item(0); + if (!$node) { + $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', $name)); + } + return $node; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMElement; +use PHPUnit\SebastianBergmann\CodeCoverage\ReportAlreadyFinalizedException; +use XMLWriter; /** - * Value Object representing a Boolean type. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -class Boolean implements Type +final class Coverage { /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var XMLWriter */ - public function __toString() : string + private $writer; + /** + * @var DOMElement + */ + private $contextNode; + /** + * @var bool + */ + private $finalized = \false; + public function __construct(DOMElement $context, string $line) { - return 'bool'; + $this->contextNode = $context; + $this->writer = new XMLWriter(); + $this->writer->openMemory(); + $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); + $this->writer->writeAttribute('nr', $line); } -} -finalized) { + throw new ReportAlreadyFinalizedException(); + } + $this->writer->startElement('covered'); + $this->writer->writeAttribute('by', $test); + $this->writer->endElement(); } - public function __toString() : string + public function finalize() : void { - return 'array-key'; - } -} -writer->endElement(); + $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); + $fragment->appendXML($this->writer->outputMemory()); + $this->contextNode->parentNode->replaceChild($fragment, $this->contextNode); + $this->finalized = \true; + } } + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; + /** - * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor. + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Directory extends Node +{ +} + * - * @see Context for more information. + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -final class ContextFactory +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use function count; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_dir; +use function is_file; +use function is_writable; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; +use function strlen; +use function substr; +use DateTimeImmutable; +use DOMDocument; +use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException; +use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil; +use PHPUnit\SebastianBergmann\CodeCoverage\Version; +use PHPUnit\SebastianBergmann\CodeCoverage\XmlException; +use PHPUnit\SebastianBergmann\Environment\Runtime; +final class Facade { - /** The literal used at the end of a use statement. */ - private const T_LITERAL_END_OF_USE = ';'; - /** The literal used between sets of use statements */ - private const T_LITERAL_USE_SEPARATOR = ','; /** - * Build a Context given a Class Reflection. - * - * @see Context for more information on Contexts. + * @var string */ - public function createFromReflector(Reflector $reflector) : Context + private $target; + /** + * @var Project + */ + private $project; + /** + * @var string + */ + private $phpUnitVersion; + public function __construct(string $version) { - if ($reflector instanceof ReflectionClass) { - //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable - /** @var ReflectionClass $reflector */ - return $this->createFromReflectionClass($reflector); - } - if ($reflector instanceof ReflectionParameter) { - return $this->createFromReflectionParameter($reflector); - } - if ($reflector instanceof ReflectionMethod) { - return $this->createFromReflectionMethod($reflector); - } - if ($reflector instanceof ReflectionProperty) { - return $this->createFromReflectionProperty($reflector); - } - if ($reflector instanceof ReflectionClassConstant) { - return $this->createFromReflectionClassConstant($reflector); - } - throw new UnexpectedValueException('Unhandled \\Reflector instance given: ' . get_class($reflector)); + $this->phpUnitVersion = $version; } - private function createFromReflectionParameter(ReflectionParameter $parameter) : Context + /** + * @throws XmlException + */ + public function process(CodeCoverage $coverage, string $target) : void { - $class = $parameter->getDeclaringClass(); - if (!$class) { - throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName()); + if (substr($target, -1, 1) !== DIRECTORY_SEPARATOR) { + $target .= DIRECTORY_SEPARATOR; } - return $this->createFromReflectionClass($class); - } - private function createFromReflectionMethod(ReflectionMethod $method) : Context - { - $class = $method->getDeclaringClass(); - return $this->createFromReflectionClass($class); - } - private function createFromReflectionProperty(ReflectionProperty $property) : Context - { - $class = $property->getDeclaringClass(); - return $this->createFromReflectionClass($class); + $this->target = $target; + $this->initTargetDirectory($target); + $report = $coverage->getReport(); + $this->project = new Project($coverage->getReport()->name()); + $this->setBuildInformation(); + $this->processTests($coverage->getTests()); + $this->processDirectory($report, $this->project); + $this->saveDocument($this->project->asDom(), 'index'); } - private function createFromReflectionClassConstant(ReflectionClassConstant $constant) : Context + private function setBuildInformation() : void { - //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable - /** @phpstan-var ReflectionClass $class */ - $class = $constant->getDeclaringClass(); - return $this->createFromReflectionClass($class); + $buildNode = $this->project->buildInformation(); + $buildNode->setRuntimeInformation(new Runtime()); + $buildNode->setBuildTime(new DateTimeImmutable()); + $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); } /** - * @phpstan-param ReflectionClass $class + * @throws PathExistsButIsNotDirectoryException + * @throws WriteOperationFailedException */ - private function createFromReflectionClass(ReflectionClass $class) : Context + private function initTargetDirectory(string $directory) : void { - $fileName = $class->getFileName(); - $namespace = $class->getNamespaceName(); - if (is_string($fileName) && file_exists($fileName)) { - $contents = file_get_contents($fileName); - if ($contents === \false) { - throw new RuntimeException('Unable to read file "' . $fileName . '"'); + if (is_file($directory)) { + if (!is_dir($directory)) { + throw new PathExistsButIsNotDirectoryException($directory); + } + if (!is_writable($directory)) { + throw new WriteOperationFailedException($directory); } - return $this->createForNamespace($namespace, $contents); } - return new Context($namespace, []); + DirectoryUtil::createDirectory($directory); } /** - * Build a Context for a namespace in the provided file contents. - * - * @see Context for more information on Contexts. - * - * @param string $namespace It does not matter if a `\` precedes the namespace name, - * this method first normalizes. - * @param string $fileContents The file's contents to retrieve the aliases from with the given namespace. + * @throws XmlException */ - public function createForNamespace(string $namespace, string $fileContents) : Context + private function processDirectory(DirectoryNode $directory, Node $context) : void { - $namespace = trim($namespace, '\\'); - $useStatements = []; - $currentNamespace = ''; - $tokens = new ArrayIterator(token_get_all($fileContents)); - while ($tokens->valid()) { - $currentToken = $tokens->current(); - switch ($currentToken[0]) { - case \T_NAMESPACE: - $currentNamespace = $this->parseNamespace($tokens); - break; - case \T_CLASS: - // Fast-forward the iterator through the class so that any - // T_USE tokens found within are skipped - these are not - // valid namespace use statements so should be ignored. - $braceLevel = 0; - $firstBraceFound = \false; - while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { - $currentToken = $tokens->current(); - if ($currentToken === '{' || in_array($currentToken[0], [\T_CURLY_OPEN, \T_DOLLAR_OPEN_CURLY_BRACES], \true)) { - if (!$firstBraceFound) { - $firstBraceFound = \true; - } - ++$braceLevel; - } - if ($currentToken === '}') { - --$braceLevel; - } - $tokens->next(); - } - break; - case \T_USE: - if ($currentNamespace === $namespace) { - $useStatements += $this->parseUseStatement($tokens); - } - break; - } - $tokens->next(); + $directoryName = $directory->name(); + if ($this->project->projectSourceDirectory() === $directoryName) { + $directoryName = '/'; + } + $directoryObject = $context->addDirectory($directoryName); + $this->setTotals($directory, $directoryObject->totals()); + foreach ($directory->directories() as $node) { + $this->processDirectory($node, $directoryObject); + } + foreach ($directory->files() as $node) { + $this->processFile($node, $directoryObject); } - return new Context($namespace, $useStatements); } /** - * Deduce the name from tokens when we are at the T_NAMESPACE token. - * - * @param ArrayIterator $tokens + * @throws XmlException */ - private function parseNamespace(ArrayIterator $tokens) : string + private function processFile(FileNode $file, Directory $context) : void { - // skip to the first string or namespace separator - $this->skipToNextStringOrNamespaceSeparator($tokens); - $name = ''; - $acceptedTokens = [\T_STRING, \T_NS_SEPARATOR, \T_NAME_QUALIFIED]; - while ($tokens->valid() && in_array($tokens->current()[0], $acceptedTokens, \true)) { - $name .= $tokens->current()[1]; - $tokens->next(); + $fileObject = $context->addFile($file->name(), $file->id() . '.xml'); + $this->setTotals($file, $fileObject->totals()); + $path = substr($file->pathAsString(), strlen($this->project->projectSourceDirectory())); + $fileReport = new Report($path); + $this->setTotals($file, $fileReport->totals()); + foreach ($file->classesAndTraits() as $unit) { + $this->processUnit($unit, $fileReport); } - return $name; + foreach ($file->functions() as $function) { + $this->processFunction($function, $fileReport); + } + foreach ($file->lineCoverageData() as $line => $tests) { + if (!is_array($tests) || count($tests) === 0) { + continue; + } + $coverage = $fileReport->lineCoverage((string) $line); + foreach ($tests as $test) { + $coverage->addTest($test); + } + $coverage->finalize(); + } + $fileReport->source()->setSourceCode(file_get_contents($file->pathAsString())); + $this->saveDocument($fileReport->asDom(), $file->id()); } - /** - * Deduce the names of all imports when we are at the T_USE token. - * - * @param ArrayIterator $tokens - * - * @return string[] - * @psalm-return array - */ - private function parseUseStatement(ArrayIterator $tokens) : array + private function processUnit(array $unit, Report $report) : void { - $uses = []; - while ($tokens->valid()) { - $this->skipToNextStringOrNamespaceSeparator($tokens); - $uses += $this->extractUseStatements($tokens); - $currentToken = $tokens->current(); - if ($currentToken[0] === self::T_LITERAL_END_OF_USE) { - return $uses; - } + if (isset($unit['className'])) { + $unitObject = $report->classObject($unit['className']); + } else { + $unitObject = $report->traitObject($unit['traitName']); } - return $uses; + $unitObject->setLines($unit['startLine'], $unit['executableLines'], $unit['executedLines']); + $unitObject->setCrap((float) $unit['crap']); + $unitObject->setNamespace($unit['namespace']); + foreach ($unit['methods'] as $method) { + $methodObject = $unitObject->addMethod($method['methodName']); + $methodObject->setSignature($method['signature']); + $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); + $methodObject->setCrap($method['crap']); + $methodObject->setTotals((string) $method['executableLines'], (string) $method['executedLines'], (string) $method['coverage']); + } + } + private function processFunction(array $function, Report $report) : void + { + $functionObject = $report->functionObject($function['functionName']); + $functionObject->setSignature($function['signature']); + $functionObject->setLines((string) $function['startLine']); + $functionObject->setCrap($function['crap']); + $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); + } + private function processTests(array $tests) : void + { + $testsObject = $this->project->tests(); + foreach ($tests as $test => $result) { + $testsObject->addTest($test, $result); + } + } + private function setTotals(AbstractNode $node, Totals $totals) : void + { + $loc = $node->linesOfCode(); + $totals->setNumLines($loc['linesOfCode'], $loc['commentLinesOfCode'], $loc['nonCommentLinesOfCode'], $node->numberOfExecutableLines(), $node->numberOfExecutedLines()); + $totals->setNumClasses($node->numberOfClasses(), $node->numberOfTestedClasses()); + $totals->setNumTraits($node->numberOfTraits(), $node->numberOfTestedTraits()); + $totals->setNumMethods($node->numberOfMethods(), $node->numberOfTestedMethods()); + $totals->setNumFunctions($node->numberOfFunctions(), $node->numberOfTestedFunctions()); + } + private function targetDirectory() : string + { + return $this->target; } /** - * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token. - * - * @param ArrayIterator $tokens + * @throws XmlException */ - private function skipToNextStringOrNamespaceSeparator(ArrayIterator $tokens) : void + private function saveDocument(DOMDocument $document, string $name) : void { - while ($tokens->valid()) { - $currentToken = $tokens->current(); - if (in_array($currentToken[0], [\T_STRING, \T_NS_SEPARATOR], \true)) { - break; - } - if ($currentToken[0] === \T_NAME_QUALIFIED) { - break; - } - if (defined('T_NAME_FULLY_QUALIFIED') && $currentToken[0] === \T_NAME_FULLY_QUALIFIED) { - break; - } - $tokens->next(); - } + $filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name); + $document->formatOutput = \true; + $document->preserveWhiteSpace = \false; + $this->initTargetDirectory(dirname($filename)); + file_put_contents($filename, $this->documentAsString($document)); } /** - * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of - * a USE statement yet. This will return a key/value array of the alias => namespace. - * - * @param ArrayIterator $tokens - * - * @return string[] - * @psalm-return array + * @throws XmlException * - * @psalm-suppress TypeDoesNotContainType + * @see https://bugs.php.net/bug.php?id=79191 */ - private function extractUseStatements(ArrayIterator $tokens) : array + private function documentAsString(DOMDocument $document) : string { - $extractedUseStatements = []; - $groupedNs = ''; - $currentNs = ''; - $currentAlias = ''; - $state = 'start'; - while ($tokens->valid()) { - $currentToken = $tokens->current(); - $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0]; - $tokenValue = is_string($currentToken) ? null : $currentToken[1]; - switch ($state) { - case 'start': - switch ($tokenId) { - case \T_STRING: - case \T_NS_SEPARATOR: - $currentNs .= (string) $tokenValue; - $currentAlias = $tokenValue; - break; - case \T_NAME_QUALIFIED: - case \T_NAME_FULLY_QUALIFIED: - $currentNs .= (string) $tokenValue; - $currentAlias = substr((string) $tokenValue, (int) strrpos((string) $tokenValue, '\\') + 1); - break; - case \T_CURLY_OPEN: - case '{': - $state = 'grouped'; - $groupedNs = $currentNs; - break; - case \T_AS: - $state = 'start-alias'; - break; - case self::T_LITERAL_USE_SEPARATOR: - case self::T_LITERAL_END_OF_USE: - $state = 'end'; - break; - default: - break; - } - break; - case 'start-alias': - switch ($tokenId) { - case \T_STRING: - $currentAlias = $tokenValue; - break; - case self::T_LITERAL_USE_SEPARATOR: - case self::T_LITERAL_END_OF_USE: - $state = 'end'; - break; - default: - break; - } - break; - case 'grouped': - switch ($tokenId) { - case \T_STRING: - case \T_NS_SEPARATOR: - $currentNs .= (string) $tokenValue; - $currentAlias = $tokenValue; - break; - case \T_AS: - $state = 'grouped-alias'; - break; - case self::T_LITERAL_USE_SEPARATOR: - $state = 'grouped'; - $extractedUseStatements[(string) $currentAlias] = $currentNs; - $currentNs = $groupedNs; - $currentAlias = ''; - break; - case self::T_LITERAL_END_OF_USE: - $state = 'end'; - break; - default: - break; - } - break; - case 'grouped-alias': - switch ($tokenId) { - case \T_STRING: - $currentAlias = $tokenValue; - break; - case self::T_LITERAL_USE_SEPARATOR: - $state = 'grouped'; - $extractedUseStatements[(string) $currentAlias] = $currentNs; - $currentNs = $groupedNs; - $currentAlias = ''; - break; - case self::T_LITERAL_END_OF_USE: - $state = 'end'; - break; - default: - break; - } - } - if ($state === 'end') { - break; + $xmlErrorHandling = libxml_use_internal_errors(\true); + $xml = $document->saveXML(); + if ($xml === \false) { + $message = 'Unable to generate the XML'; + foreach (libxml_get_errors() as $error) { + $message .= PHP_EOL . $error->message; } - $tokens->next(); - } - if ($groupedNs !== $currentNs) { - $extractedUseStatements[(string) $currentAlias] = $currentNs; + throw new XmlException($message); } - return $extractedUseStatements; + libxml_clear_errors(); + libxml_use_internal_errors($xmlErrorHandling); + return $xml; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMDocument; +use DOMElement; /** - * Represents a list of values. This is an abstract class for Array_ and Collection. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -abstract class AbstractList implements Type +class File { - /** @var Type */ - protected $valueType; - /** @var Type|null */ - protected $keyType; - /** @var Type */ - protected $defaultKeyType; /** - * Initializes this representation of an array with the given Type. + * @var DOMDocument */ - public function __construct(?Type $valueType = null, ?Type $keyType = null) - { - if ($valueType === null) { - $valueType = new Mixed_(); - } - $this->valueType = $valueType; - $this->defaultKeyType = new Compound([new String_(), new Integer()]); - $this->keyType = $keyType; - } + private $dom; /** - * Returns the type for the keys of this array. + * @var DOMElement */ - public function getKeyType() : Type + private $contextNode; + public function __construct(DOMElement $context) { - return $this->keyType ?? $this->defaultKeyType; + $this->dom = $context->ownerDocument; + $this->contextNode = $context; } - /** - * Returns the value for the keys of this array. - */ - public function getValueType() : Type + public function totals() : Totals { - return $this->valueType; + $totalsContainer = $this->contextNode->firstChild; + if (!$totalsContainer) { + $totalsContainer = $this->contextNode->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'totals')); + } + return new Totals($totalsContainer); } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + public function lineCoverage(string $line) : Coverage { - if ($this->keyType) { - return 'array<' . $this->keyType . ',' . $this->valueType . '>'; - } - if ($this->valueType instanceof Mixed_) { - return 'array'; - } - if ($this->valueType instanceof Compound) { - return '(' . $this->valueType . ')[]'; + $coverage = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'coverage')->item(0); + if (!$coverage) { + $coverage = $this->contextNode->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'coverage')); } - return $this->valueType . '[]'; + $lineNode = $coverage->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'line')); + return new Coverage($lineNode, $line); + } + protected function contextNode() : DOMElement + { + return $this->contextNode; + } + protected function dom() : DOMDocument + { + return $this->dom; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMElement; /** - * Value Object representing the type 'string'. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -class String_ implements Type +final class Method { /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var DOMElement */ - public function __toString() : string + private $contextNode; + public function __construct(DOMElement $context, string $name) { - return 'string'; + $this->contextNode = $context; + $this->setName($name); + } + public function setSignature(string $signature) : void + { + $this->contextNode->setAttribute('signature', $signature); + } + public function setLines(string $start, ?string $end = null) : void + { + $this->contextNode->setAttribute('start', $start); + if ($end !== null) { + $this->contextNode->setAttribute('end', $end); + } + } + public function setTotals(string $executable, string $executed, string $coverage) : void + { + $this->contextNode->setAttribute('executable', $executable); + $this->contextNode->setAttribute('executed', $executed); + $this->contextNode->setAttribute('coverage', $coverage); + } + public function setCrap(string $crap) : void + { + $this->contextNode->setAttribute('crap', $crap); + } + private function setName(string $name) : void + { + $this->contextNode->setAttribute('name', $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Fqsen; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMDocument; +use DOMElement; /** - * Value Object representing the type 'string'. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class ClassString implements Type +abstract class Node { - /** @var Fqsen|null */ - private $fqsen; /** - * Initializes this representation of a class string with the given Fqsen. + * @var DOMDocument */ - public function __construct(?Fqsen $fqsen = null) - { - $this->fqsen = $fqsen; - } + private $dom; /** - * Returns the FQSEN associated with this object. + * @var DOMElement */ - public function getFqsen() : ?Fqsen + private $contextNode; + public function __construct(DOMElement $context) { - return $this->fqsen; + $this->setContextNode($context); } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + public function dom() : DOMDocument { - if ($this->fqsen === null) { - return 'class-string'; + return $this->dom; + } + public function totals() : Totals + { + $totalsContainer = $this->contextNode()->firstChild; + if (!$totalsContainer) { + $totalsContainer = $this->contextNode()->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'totals')); } - return 'class-string<' . (string) $this->fqsen . '>'; + return new Totals($totalsContainer); + } + public function addDirectory(string $name) : Directory + { + $dirNode = $this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'directory'); + $dirNode->setAttribute('name', $name); + $this->contextNode()->appendChild($dirNode); + return new Directory($dirNode); + } + public function addFile(string $name, string $href) : File + { + $fileNode = $this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'file'); + $fileNode->setAttribute('name', $name); + $fileNode->setAttribute('href', $href); + $this->contextNode()->appendChild($fileNode); + return new File($fileNode); + } + protected function setContextNode(DOMElement $context) : void + { + $this->dom = $context->ownerDocument; + $this->contextNode = $context; + } + protected function contextNode() : DOMElement + { + return $this->contextNode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMDocument; /** - * Value Object representing the 'self' type. - * - * Self, as a Type, represents the class in which the associated element was defined. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Self_ implements Type +final class Project extends Node { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + public function __construct(string $directory) { - return 'self'; + $this->init(); + $this->setProjectSourceDirectory($directory); + } + public function projectSourceDirectory() : string + { + return $this->contextNode()->getAttribute('source'); + } + public function buildInformation() : BuildInformation + { + $buildNode = $this->dom()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'build')->item(0); + if (!$buildNode) { + $buildNode = $this->dom()->documentElement->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'build')); + } + return new BuildInformation($buildNode); + } + public function tests() : Tests + { + $testsNode = $this->contextNode()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'tests')->item(0); + if (!$testsNode) { + $testsNode = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'tests')); + } + return new Tests($testsNode); + } + public function asDom() : DOMDocument + { + return $this->dom(); + } + private function init() : void + { + $dom = new DOMDocument(); + $dom->loadXML(''); + $this->setContextNode($dom->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'project')->item(0)); + } + private function setProjectSourceDirectory(string $name) : void + { + $this->contextNode()->setAttribute('source', $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use function basename; +use function dirname; +use DOMDocument; /** - * Value Object representing a Float. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Float_ implements Type +final class Report extends File { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + public function __construct(string $name) { - return 'float'; + $dom = new DOMDocument(); + $dom->loadXML(''); + $contextNode = $dom->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'file')->item(0); + parent::__construct($contextNode); + $this->setName($name); + } + public function asDom() : DOMDocument + { + return $this->dom(); + } + public function functionObject($name) : Method + { + $node = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'function')); + return new Method($node, $name); + } + public function classObject($name) : Unit + { + return $this->unitObject('class', $name); + } + public function traitObject($name) : Unit + { + return $this->unitObject('trait', $name); + } + public function source() : Source + { + $source = $this->contextNode()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'source')->item(0); + if (!$source) { + $source = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'source')); + } + return new Source($source); + } + private function setName(string $name) : void + { + $this->contextNode()->setAttribute('name', basename($name)); + $this->contextNode()->setAttribute('path', dirname($name)); + } + private function unitObject(string $tagName, $name) : Unit + { + $node = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', $tagName)); + return new Unit($node, $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use PHPUnit\phpDocumentor\Reflection\Type; +use DOMElement; +use PHPUnit\TheSeer\Tokenizer\NamespaceUri; +use PHPUnit\TheSeer\Tokenizer\Tokenizer; +use PHPUnit\TheSeer\Tokenizer\XMLSerializer; /** - * Value Object representing the 'static' type. - * - * Self, as a Type, represents the class in which the associated element was called. This differs from self as self does - * not take inheritance into account but static means that the return type is always that of the class of the called - * element. - * - * See the documentation on late static binding in the PHP Documentation for more information on the difference between - * static and self. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Static_ implements Type +final class Source { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + /** @var DOMElement */ + private $context; + public function __construct(DOMElement $context) { - return 'static'; + $this->context = $context; + } + public function setSourceCode(string $source) : void + { + $context = $this->context; + $tokens = (new Tokenizer())->parse($source); + $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); + $context->parentNode->replaceChild($context->ownerDocument->importNode($srcDom->documentElement, \true), $context); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use function strlen; -use function substr; -use function trim; +use DOMElement; /** - * Provides information about the Context in which the DocBlock occurs that receives this context. - * - * A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable - * for the block of code in which it is in. This information is however necessary to resolve Class names in tags since - * you can provide a short form or make use of namespace aliases. - * - * The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your - * own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in - * which an associated class resides for its namespace and imports. - * - * @see ContextFactory::createFromClassReflector() - * @see ContextFactory::createForNamespace() - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Context +final class Tests { - /** @var string The current namespace. */ - private $namespace; - /** - * @var string[] List of namespace aliases => Fully Qualified Namespace. - * @psalm-var array - */ - private $namespaceAliases; - /** - * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN) - * format (without a preceding `\`). - * - * @param string $namespace The namespace where this DocBlock resides in. - * @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace. - * @psalm-param array $namespaceAliases - */ - public function __construct(string $namespace, array $namespaceAliases = []) - { - $this->namespace = $namespace !== 'global' && $namespace !== 'default' ? trim($namespace, '\\') : ''; - foreach ($namespaceAliases as $alias => $fqnn) { - if ($fqnn[0] === '\\') { - $fqnn = substr($fqnn, 1); - } - if ($fqnn[strlen($fqnn) - 1] === '\\') { - $fqnn = substr($fqnn, 0, -1); - } - $namespaceAliases[$alias] = $fqnn; - } - $this->namespaceAliases = $namespaceAliases; - } - /** - * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in. - */ - public function getNamespace() : string + private $contextNode; + private $codeMap = [ + -1 => 'UNKNOWN', + // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN + 0 => 'PASSED', + // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED + 1 => 'SKIPPED', + // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED + 2 => 'INCOMPLETE', + // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE + 3 => 'FAILURE', + // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE + 4 => 'ERROR', + // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR + 5 => 'RISKY', + // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY + 6 => 'WARNING', + ]; + public function __construct(DOMElement $context) { - return $this->namespace; + $this->contextNode = $context; } - /** - * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent - * the alias for the imported Namespace. - * - * @return string[] - * @psalm-return array - */ - public function getNamespaceAliases() : array + public function addTest(string $test, array $result) : void { - return $this->namespaceAliases; + $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'test')); + $node->setAttribute('name', $test); + $node->setAttribute('size', $result['size']); + $node->setAttribute('result', (string) $result['status']); + $node->setAttribute('status', $this->codeMap[(int) $result['status']]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; +use function sprintf; +use DOMElement; +use DOMNode; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Percentage; /** - * Represents an array type as described in the PSR-5, the PHPDoc Standard. - * - * An array can be represented in two forms: - * - * 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'. - * 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a - * type name. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Array_ extends AbstractList +final class Totals { + /** + * @var DOMNode + */ + private $container; + /** + * @var DOMElement + */ + private $linesNode; + /** + * @var DOMElement + */ + private $methodsNode; + /** + * @var DOMElement + */ + private $functionsNode; + /** + * @var DOMElement + */ + private $classesNode; + /** + * @var DOMElement + */ + private $traitsNode; + public function __construct(DOMElement $container) + { + $this->container = $container; + $dom = $container->ownerDocument; + $this->linesNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'lines'); + $this->methodsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'methods'); + $this->functionsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'functions'); + $this->classesNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'classes'); + $this->traitsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'traits'); + $container->appendChild($this->linesNode); + $container->appendChild($this->methodsNode); + $container->appendChild($this->functionsNode); + $container->appendChild($this->classesNode); + $container->appendChild($this->traitsNode); + } + public function container() : DOMNode + { + return $this->container; + } + public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed) : void + { + $this->linesNode->setAttribute('total', (string) $loc); + $this->linesNode->setAttribute('comments', (string) $cloc); + $this->linesNode->setAttribute('code', (string) $ncloc); + $this->linesNode->setAttribute('executable', (string) $executable); + $this->linesNode->setAttribute('executed', (string) $executed); + $this->linesNode->setAttribute('percent', $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat())); + } + public function setNumClasses(int $count, int $tested) : void + { + $this->classesNode->setAttribute('count', (string) $count); + $this->classesNode->setAttribute('tested', (string) $tested); + $this->classesNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + } + public function setNumTraits(int $count, int $tested) : void + { + $this->traitsNode->setAttribute('count', (string) $count); + $this->traitsNode->setAttribute('tested', (string) $tested); + $this->traitsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + } + public function setNumMethods(int $count, int $tested) : void + { + $this->methodsNode->setAttribute('count', (string) $count); + $this->methodsNode->setAttribute('tested', (string) $tested); + $this->methodsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + } + public function setNumFunctions(int $count, int $tested) : void + { + $this->functionsNode->setAttribute('count', (string) $count); + $this->functionsNode->setAttribute('tested', (string) $tested); + $this->functionsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; -use InvalidArgumentException; -use PHPUnit\phpDocumentor\Reflection\Fqsen; -use PHPUnit\phpDocumentor\Reflection\Type; -use function strpos; +use DOMElement; /** - * Value Object representing an object. - * - * An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN, - * pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects - * in general. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Object_ implements Type +final class Unit { - /** @var Fqsen|null */ - private $fqsen; /** - * Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'. - * - * @throws InvalidArgumentException When provided $fqsen is not a valid type. + * @var DOMElement */ - public function __construct(?Fqsen $fqsen = null) + private $contextNode; + public function __construct(DOMElement $context, string $name) { - if (strpos((string) $fqsen, '::') !== \false || strpos((string) $fqsen, '()') !== \false) { - throw new InvalidArgumentException('Object types can only refer to a class, interface or trait but a method, function, constant or ' . 'property was received: ' . (string) $fqsen); - } - $this->fqsen = $fqsen; + $this->contextNode = $context; + $this->setName($name); } - /** - * Returns the FQSEN associated with this object. - */ - public function getFqsen() : ?Fqsen + public function setLines(int $start, int $executable, int $executed) : void { - return $this->fqsen; + $this->contextNode->setAttribute('start', (string) $start); + $this->contextNode->setAttribute('executable', (string) $executable); + $this->contextNode->setAttribute('executed', (string) $executed); } - public function __toString() : string + public function setCrap(float $crap) : void { - if ($this->fqsen) { - return (string) $this->fqsen; + $this->contextNode->setAttribute('crap', (string) $crap); + } + public function setNamespace(string $namespace) : void + { + $node = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'namespace')->item(0); + if (!$node) { + $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'namespace')); } - return 'object'; + $node->setAttribute('name', $namespace); + } + public function addMethod(string $name) : Method + { + $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'method')); + return new Method($node, $name); + } + private function setName(string $name) : void + { + $this->contextNode->setAttribute('name', $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use PHPUnit\phpDocumentor\Reflection\Type; -/** - * Value Object representing a Callable type. - * - * @psalm-immutable - */ -final class Callable_ implements Type +use PHPUnit\SebastianBergmann\CodeCoverage\Filter; +final class CacheWarmer { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string + public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter) : void { - return 'callable'; + $analyser = new CachingFileAnalyser($cacheDirectory, new ParsingFileAnalyser($useAnnotationsForIgnoringCode, $ignoreDeprecatedCode)); + foreach ($filter->files() as $file) { + $analyser->process($file); + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use PHPUnit\phpDocumentor\Reflection\Fqsen; -use PHPUnit\phpDocumentor\Reflection\Type; +use function file_get_contents; +use function file_put_contents; +use function implode; +use function is_file; +use function md5; +use function serialize; +use PHPUnit\SebastianBergmann\CodeCoverage\Util\Filesystem; +use PHPUnit\SebastianBergmann\FileIterator\Facade as FileIteratorFacade; /** - * Represents a collection type as described in the PSR-5, the PHPDoc Standard. - * - * A collection can be represented in two forms: - * - * 1. `ACollectionObject` - * 2. `ACollectionObject` - * - * - ACollectionObject can be 'array' or an object that can act as an array - * - aValueType and aKeyType can be any type expression - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Collection extends AbstractList +final class CachingFileAnalyser implements FileAnalyser { - /** @var Fqsen|null */ - private $fqsen; /** - * Initializes this representation of an array with the given Type or Fqsen. + * @var ?string */ - public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null) + private static $cacheVersion; + /** + * @var FileAnalyser + */ + private $analyser; + /** + * @var array + */ + private $cache = []; + /** + * @var string + */ + private $directory; + public function __construct(string $directory, FileAnalyser $analyser) { - parent::__construct($valueType, $keyType); - $this->fqsen = $fqsen; + Filesystem::createDirectory($directory); + $this->analyser = $analyser; + $this->directory = $directory; + } + public function classesIn(string $filename) : array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['classesIn']; + } + public function traitsIn(string $filename) : array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['traitsIn']; + } + public function functionsIn(string $filename) : array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['functionsIn']; } /** - * Returns the FQSEN associated with this object. + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} */ - public function getFqsen() : ?Fqsen + public function linesOfCodeFor(string $filename) : array { - return $this->fqsen; + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['linesOfCodeFor']; + } + public function executableLinesIn(string $filename) : array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['executableLinesIn']; + } + public function ignoredLinesFor(string $filename) : array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + return $this->cache[$filename]['ignoredLinesFor']; + } + public function process(string $filename) : void + { + $cache = $this->read($filename); + if ($cache !== \false) { + $this->cache[$filename] = $cache; + return; + } + $this->cache[$filename] = ['classesIn' => $this->analyser->classesIn($filename), 'traitsIn' => $this->analyser->traitsIn($filename), 'functionsIn' => $this->analyser->functionsIn($filename), 'linesOfCodeFor' => $this->analyser->linesOfCodeFor($filename), 'ignoredLinesFor' => $this->analyser->ignoredLinesFor($filename), 'executableLinesIn' => $this->analyser->executableLinesIn($filename)]; + $this->write($filename, $this->cache[$filename]); } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @return mixed */ - public function __toString() : string + private function read(string $filename) { - $objectType = (string) ($this->fqsen ?? 'object'); - if ($this->keyType === null) { - return $objectType . '<' . $this->valueType . '>'; + $cacheFile = $this->cacheFile($filename); + if (!is_file($cacheFile)) { + return \false; } - return $objectType . '<' . $this->keyType . ',' . $this->valueType . '>'; + return \unserialize(file_get_contents($cacheFile), ['allowed_classes' => \false]); } -} -cacheFile($filename), serialize($data)); + } + private function cacheFile(string $filename) : string + { + return $this->directory . \DIRECTORY_SEPARATOR . md5($filename . "\x00" . file_get_contents($filename) . "\x00" . self::cacheVersion()); + } + private static function cacheVersion() : string + { + if (self::$cacheVersion !== null) { + return self::$cacheVersion; + } + $buffer = []; + foreach ((new FileIteratorFacade())->getFilesAsArray(__DIR__, '.php') as $file) { + $buffer[] = $file; + $buffer[] = file_get_contents($file); + } + self::$cacheVersion = md5(implode("\x00", $buffer)); + return self::$cacheVersion; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use PHPUnit\phpDocumentor\Reflection\Type; +use function implode; +use function rtrim; +use function trim; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\ComplexType; +use PHPUnit\PhpParser\Node\Identifier; +use PHPUnit\PhpParser\Node\IntersectionType; +use PHPUnit\PhpParser\Node\Name; +use PHPUnit\PhpParser\Node\NullableType; +use PHPUnit\PhpParser\Node\Stmt\Class_; +use PHPUnit\PhpParser\Node\Stmt\ClassMethod; +use PHPUnit\PhpParser\Node\Stmt\Enum_; +use PHPUnit\PhpParser\Node\Stmt\Function_; +use PHPUnit\PhpParser\Node\Stmt\Interface_; +use PHPUnit\PhpParser\Node\Stmt\Trait_; +use PHPUnit\PhpParser\Node\UnionType; +use PHPUnit\PhpParser\NodeTraverser; +use PHPUnit\PhpParser\NodeVisitorAbstract; +use PHPUnit\SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; /** - * Represents an expression type as described in the PSR-5, the PHPDoc Standard. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Expression implements Type +final class CodeUnitFindingVisitor extends NodeVisitorAbstract { - /** @var Type */ - protected $valueType; /** - * Initializes this representation of an array with the given Type. + * @psalm-var array}> */ - public function __construct(Type $valueType) + private $classes = []; + /** + * @psalm-var array}> + */ + private $traits = []; + /** + * @psalm-var array + */ + private $functions = []; + public function enterNode(Node $node) : void { - $this->valueType = $valueType; + if ($node instanceof Class_) { + if ($node->isAnonymous()) { + return; + } + $this->processClass($node); + } + if ($node instanceof Trait_) { + $this->processTrait($node); + } + if (!$node instanceof ClassMethod && !$node instanceof Function_) { + return; + } + if ($node instanceof ClassMethod) { + $parentNode = $node->getAttribute('parent'); + if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) { + return; + } + $this->processMethod($node); + return; + } + $this->processFunction($node); } /** - * Returns the value for the keys of this array. + * @psalm-return array}> */ - public function getValueType() : Type + public function classes() : array { - return $this->valueType; + return $this->classes; } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @psalm-return array}> */ - public function __toString() : string + public function traits() : array { - return '(' . $this->valueType . ')'; + return $this->traits; } -} - */ - public function __toString() : string + public function functions() : array { - return 'mixed'; + return $this->functions; + } + /** + * @psalm-param ClassMethod|Function_ $node + */ + private function cyclomaticComplexity(Node $node) : int + { + \assert($node instanceof ClassMethod || $node instanceof Function_); + $nodes = $node->getStmts(); + if ($nodes === null) { + return 0; + } + $traverser = new NodeTraverser(); + $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor(); + $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + } + /** + * @psalm-param ClassMethod|Function_ $node + */ + private function signature(Node $node) : string + { + \assert($node instanceof ClassMethod || $node instanceof Function_); + $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '('; + $parameters = []; + foreach ($node->getParams() as $parameter) { + \assert(isset($parameter->var->name)); + $parameterAsString = ''; + if ($parameter->type !== null) { + $parameterAsString = $this->type($parameter->type) . ' '; + } + $parameterAsString .= '$' . $parameter->var->name; + /* @todo Handle default values */ + $parameters[] = $parameterAsString; + } + $signature .= implode(', ', $parameters) . ')'; + $returnType = $node->getReturnType(); + if ($returnType !== null) { + $signature .= ': ' . $this->type($returnType); + } + return $signature; + } + /** + * @psalm-param Identifier|Name|ComplexType $type + */ + private function type(Node $type) : string + { + \assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType); + if ($type instanceof NullableType) { + return '?' . $type->type; + } + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $this->unionOrIntersectionAsString($type); + } + return $type->toString(); + } + private function visibility(ClassMethod $node) : string + { + if ($node->isPrivate()) { + return 'private'; + } + if ($node->isProtected()) { + return 'protected'; + } + return 'public'; + } + private function processClass(Class_ $node) : void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + $this->classes[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'methods' => []]; + } + private function processTrait(Trait_ $node) : void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + $this->traits[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'methods' => []]; + } + private function processMethod(ClassMethod $node) : void + { + $parentNode = $node->getAttribute('parent'); + if ($parentNode instanceof Interface_) { + return; + } + \assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_); + \assert(isset($parentNode->name)); + \assert(isset($parentNode->namespacedName)); + \assert($parentNode->namespacedName instanceof Name); + $parentName = $parentNode->name->toString(); + $parentNamespacedName = $parentNode->namespacedName->toString(); + if ($parentNode instanceof Class_) { + $storage =& $this->classes; + } else { + $storage =& $this->traits; + } + if (!isset($storage[$parentNamespacedName])) { + $storage[$parentNamespacedName] = ['name' => $parentName, 'namespacedName' => $parentNamespacedName, 'namespace' => $this->namespace($parentNamespacedName, $parentName), 'startLine' => $parentNode->getStartLine(), 'endLine' => $parentNode->getEndLine(), 'methods' => []]; + } + $storage[$parentNamespacedName]['methods'][$node->name->toString()] = ['methodName' => $node->name->toString(), 'signature' => $this->signature($node), 'visibility' => $this->visibility($node), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'ccn' => $this->cyclomaticComplexity($node)]; + } + private function processFunction(Function_ $node) : void + { + \assert(isset($node->name)); + \assert(isset($node->namespacedName)); + \assert($node->namespacedName instanceof Name); + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + $this->functions[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'signature' => $this->signature($node), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'ccn' => $this->cyclomaticComplexity($node)]; + } + private function namespace(string $namespacedName, string $name) : string + { + return trim(rtrim($namespacedName, $name), '\\'); + } + /** + * @psalm-param UnionType|IntersectionType $type + */ + private function unionOrIntersectionAsString(ComplexType $type) : string + { + if ($type instanceof UnionType) { + $separator = '|'; + } else { + $separator = '&'; + } + $types = []; + foreach ($type->types as $_type) { + if ($_type instanceof Name) { + $types[] = $_type->toCodeString(); + } else { + $types[] = $_type->toString(); + } + } + return implode($separator, $types); } } * - * @link http://phpdoc.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -declare (strict_types=1); -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use ArrayIterator; -use IteratorAggregate; -use PHPUnit\phpDocumentor\Reflection\Type; -use function array_key_exists; -use function implode; -/** - * Base class for aggregated types like Compound and Intersection - * - * A Aggregated Type is not so much a special keyword or object reference but is a series of Types that are separated - * using separator. - * - * @psalm-immutable - * @template-implements IteratorAggregate +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\Expr\Array_; +use PHPUnit\PhpParser\Node\Expr\ArrayDimFetch; +use PHPUnit\PhpParser\Node\Expr\ArrayItem; +use PHPUnit\PhpParser\Node\Expr\Assign; +use PHPUnit\PhpParser\Node\Expr\BinaryOp; +use PHPUnit\PhpParser\Node\Expr\CallLike; +use PHPUnit\PhpParser\Node\Expr\Cast; +use PHPUnit\PhpParser\Node\Expr\Closure; +use PHPUnit\PhpParser\Node\Expr\Match_; +use PHPUnit\PhpParser\Node\Expr\MethodCall; +use PHPUnit\PhpParser\Node\Expr\NullsafePropertyFetch; +use PHPUnit\PhpParser\Node\Expr\PropertyFetch; +use PHPUnit\PhpParser\Node\Expr\StaticPropertyFetch; +use PHPUnit\PhpParser\Node\Expr\Ternary; +use PHPUnit\PhpParser\Node\MatchArm; +use PHPUnit\PhpParser\Node\Scalar\Encapsed; +use PHPUnit\PhpParser\Node\Stmt\Break_; +use PHPUnit\PhpParser\Node\Stmt\Case_; +use PHPUnit\PhpParser\Node\Stmt\Catch_; +use PHPUnit\PhpParser\Node\Stmt\Class_; +use PHPUnit\PhpParser\Node\Stmt\ClassMethod; +use PHPUnit\PhpParser\Node\Stmt\Continue_; +use PHPUnit\PhpParser\Node\Stmt\Do_; +use PHPUnit\PhpParser\Node\Stmt\Echo_; +use PHPUnit\PhpParser\Node\Stmt\Else_; +use PHPUnit\PhpParser\Node\Stmt\ElseIf_; +use PHPUnit\PhpParser\Node\Stmt\Expression; +use PHPUnit\PhpParser\Node\Stmt\Finally_; +use PHPUnit\PhpParser\Node\Stmt\For_; +use PHPUnit\PhpParser\Node\Stmt\Foreach_; +use PHPUnit\PhpParser\Node\Stmt\Goto_; +use PHPUnit\PhpParser\Node\Stmt\If_; +use PHPUnit\PhpParser\Node\Stmt\Property; +use PHPUnit\PhpParser\Node\Stmt\Return_; +use PHPUnit\PhpParser\Node\Stmt\Switch_; +use PHPUnit\PhpParser\Node\Stmt\Throw_; +use PHPUnit\PhpParser\Node\Stmt\TryCatch; +use PHPUnit\PhpParser\Node\Stmt\Unset_; +use PHPUnit\PhpParser\Node\Stmt\While_; +use PHPUnit\PhpParser\NodeVisitorAbstract; +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -abstract class AggregatedType implements Type, IteratorAggregate +final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract { /** - * @psalm-allow-private-mutation - * @var array + * @psalm-var array */ - private $types = []; - /** @var string */ - private $token; + private $executableLines = []; /** - * @param array $types + * @psalm-var array */ - public function __construct(array $types, string $token) - { - foreach ($types as $type) { - $this->add($type); - } - $this->token = $token; - } + private $propertyLines = []; /** - * Returns the type at the given index. + * @psalm-var array */ - public function get(int $index) : ?Type + private $returns = []; + public function enterNode(Node $node) : void { - if (!$this->has($index)) { - return null; + $this->savePropertyLines($node); + if (!$this->isExecutable($node)) { + return; + } + foreach ($this->getLines($node) as $line) { + if (isset($this->propertyLines[$line])) { + return; + } + $this->executableLines[$line] = $line; } - return $this->types[$index]; } /** - * Tests if this compound type has a type with the given index. + * @psalm-return array */ - public function has(int $index) : bool + public function executableLines() : array { - return array_key_exists($index, $this->types); + $this->computeReturns(); + \sort($this->executableLines); + return $this->executableLines; } - /** - * Tests if this compound type contains the given type. - */ - public function contains(Type $type) : bool + private function savePropertyLines(Node $node) : void { - foreach ($this->types as $typePart) { - // if the type is duplicate; do not add it - if ((string) $typePart === (string) $type) { - return \true; - } + if (!$node instanceof Property && !$node instanceof Node\Stmt\ClassConst) { + return; + } + foreach (\range($node->getStartLine(), $node->getEndLine()) as $index) { + $this->propertyLines[$index] = $index; } - return \false; - } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - return implode($this->token, $this->types); } - /** - * @return ArrayIterator - */ - public function getIterator() : ArrayIterator + private function computeReturns() : void { - return new ArrayIterator($this->types); + foreach ($this->returns as $return) { + foreach (\range($return->getStartLine(), $return->getEndLine()) as $loc) { + if (isset($this->executableLines[$loc])) { + continue 2; + } + } + $line = $return->getEndLine(); + if ($return->expr !== null) { + $line = $return->expr->getStartLine(); + } + $this->executableLines[$line] = $line; + } } /** - * @psalm-suppress ImpureMethodCall + * @return int[] */ - private function add(Type $type) : void + private function getLines(Node $node) : array { - if ($type instanceof self) { - foreach ($type->getIterator() as $subType) { - $this->add($subType); + if ($node instanceof BinaryOp) { + if (($node->left instanceof Node\Scalar || $node->left instanceof Node\Expr\ConstFetch) && ($node->right instanceof Node\Scalar || $node->right instanceof Node\Expr\ConstFetch)) { + return [$node->right->getStartLine()]; } - return; + return []; } - // if the type is duplicate; do not add it - if ($this->contains($type)) { - return; + if ($node instanceof Cast || $node instanceof PropertyFetch || $node instanceof NullsafePropertyFetch || $node instanceof StaticPropertyFetch) { + return [$node->getEndLine()]; } - $this->types[] = $type; + if ($node instanceof ArrayDimFetch) { + if (null === $node->dim) { + return []; + } + return [$node->dim->getStartLine()]; + } + if ($node instanceof Array_) { + $startLine = $node->getStartLine(); + if (isset($this->executableLines[$startLine])) { + return []; + } + if ([] === $node->items) { + return [$node->getEndLine()]; + } + if ($node->items[0] instanceof ArrayItem) { + return [$node->items[0]->getStartLine()]; + } + } + if ($node instanceof ClassMethod) { + if ($node->name->name !== '__construct') { + return []; + } + $existsAPromotedProperty = \false; + foreach ($node->getParams() as $param) { + if (0 !== ($param->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + $existsAPromotedProperty = \true; + break; + } + } + if ($existsAPromotedProperty) { + // Only the line with `function` keyword should be listed here + // but `nikic/php-parser` doesn't provide a way to fetch it + return \range($node->getStartLine(), $node->name->getEndLine()); + } + return []; + } + if ($node instanceof MethodCall) { + return [$node->name->getStartLine()]; + } + if ($node instanceof Ternary) { + $lines = [$node->cond->getStartLine()]; + if (null !== $node->if) { + $lines[] = $node->if->getStartLine(); + } + $lines[] = $node->else->getStartLine(); + return $lines; + } + if ($node instanceof Match_) { + return [$node->cond->getStartLine()]; + } + if ($node instanceof MatchArm) { + return [$node->body->getStartLine()]; + } + if ($node instanceof Expression && ($node->expr instanceof Cast || $node->expr instanceof Match_ || $node->expr instanceof MethodCall)) { + return []; + } + if ($node instanceof Return_) { + $this->returns[] = $node; + return []; + } + return [$node->getStartLine()]; + } + private function isExecutable(Node $node) : bool + { + return $node instanceof Assign || $node instanceof ArrayDimFetch || $node instanceof Array_ || $node instanceof BinaryOp || $node instanceof Break_ || $node instanceof CallLike || $node instanceof Case_ || $node instanceof Cast || $node instanceof Catch_ || $node instanceof ClassMethod || $node instanceof Closure || $node instanceof Continue_ || $node instanceof Do_ || $node instanceof Echo_ || $node instanceof ElseIf_ || $node instanceof Else_ || $node instanceof Encapsed || $node instanceof Expression || $node instanceof Finally_ || $node instanceof For_ || $node instanceof Foreach_ || $node instanceof Goto_ || $node instanceof If_ || $node instanceof Match_ || $node instanceof MatchArm || $node instanceof MethodCall || $node instanceof NullsafePropertyFetch || $node instanceof PropertyFetch || $node instanceof Return_ || $node instanceof StaticPropertyFetch || $node instanceof Switch_ || $node instanceof Ternary || $node instanceof Throw_ || $node instanceof TryCatch || $node instanceof Unset_ || $node instanceof While_; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; /** - * Value Object representing iterable type - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Iterable_ extends AbstractList +interface FileAnalyser { + public function classesIn(string $filename) : array; + public function traitsIn(string $filename) : array; + public function functionsIn(string $filename) : array; /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} */ - public function __toString() : string - { - if ($this->keyType) { - return 'iterable<' . $this->keyType . ',' . $this->valueType . '>'; - } - if ($this->valueType instanceof Mixed_) { - return 'iterable'; - } - return 'iterable<' . $this->valueType . '>'; - } + public function linesOfCodeFor(string $filename) : array; + public function executableLinesIn(string $filename) : array; + public function ignoredLinesFor(string $filename) : array; } * - * @link http://phpdoc.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -declare (strict_types=1); -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use PHPUnit\phpDocumentor\Reflection\Type; +use function array_merge; +use function assert; +use function range; +use function strpos; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\Attribute; +use PHPUnit\PhpParser\Node\Stmt\Class_; +use PHPUnit\PhpParser\Node\Stmt\ClassMethod; +use PHPUnit\PhpParser\Node\Stmt\Function_; +use PHPUnit\PhpParser\Node\Stmt\Interface_; +use PHPUnit\PhpParser\Node\Stmt\Trait_; +use PHPUnit\PhpParser\NodeVisitorAbstract; /** - * Value Object representing a Compound Type. - * - * A Intersection Type is not so much a special keyword or object reference but is a series of Types that are separated - * using an AND operator (`&`). This combination of types signifies that whatever is associated with this Intersection - * type may contain a value with any of the given types. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Intersection extends AggregatedType +final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract { /** - * Initializes a intersection type (i.e. `\A&\B`) and tests if the provided types all implement the Type interface. - * - * @param array $types + * @psalm-var list */ - public function __construct(array $types) + private $ignoredLines = []; + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode; + /** + * @var bool + */ + private $ignoreDeprecated; + public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) { - parent::__construct($types, '&'); + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecated = $ignoreDeprecated; + } + public function enterNode(Node $node) : void + { + if (!$node instanceof Class_ && !$node instanceof Trait_ && !$node instanceof Interface_ && !$node instanceof ClassMethod && !$node instanceof Function_ && !$node instanceof Attribute) { + return; + } + if ($node instanceof Class_ && $node->isAnonymous()) { + return; + } + if ($node instanceof Class_ || $node instanceof Trait_ || $node instanceof Interface_ || $node instanceof Attribute) { + $this->ignoredLines[] = $node->getStartLine(); + assert($node->name !== null); + // Workaround for https://github.com/nikic/PHP-Parser/issues/886 + $this->ignoredLines[] = $node->name->getStartLine(); + } + if (!$this->useAnnotationsForIgnoringCode) { + return; + } + if ($node instanceof Interface_) { + return; + } + $this->processDocComment($node); } -} - */ - public function __toString() : string + public function ignoredLines() : array { - return '$this'; + return $this->ignoredLines; + } + private function processDocComment(Node $node) : void + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return; + } + if (strpos($docComment->getText(), '@codeCoverageIgnore') !== \false) { + $this->ignoredLines = array_merge($this->ignoredLines, range($node->getStartLine(), $node->getEndLine())); + } + if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== \false) { + $this->ignoredLines = array_merge($this->ignoredLines, range($node->getStartLine(), $node->getEndLine())); + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; -use PHPUnit\phpDocumentor\Reflection\Type; +use function array_unique; +use function assert; +use function file_get_contents; +use function is_array; +use function max; +use function sprintf; +use function substr_count; +use function token_get_all; +use function trim; +use PHPUnit\PhpParser\Error; +use PHPUnit\PhpParser\Lexer; +use PHPUnit\PhpParser\NodeTraverser; +use PHPUnit\PhpParser\NodeVisitor\NameResolver; +use PHPUnit\PhpParser\NodeVisitor\ParentConnectingVisitor; +use PHPUnit\PhpParser\ParserFactory; +use PHPUnit\SebastianBergmann\CodeCoverage\ParserException; +use PHPUnit\SebastianBergmann\LinesOfCode\LineCountingVisitor; /** - * Value Object representing a nullable type. The real type is wrapped. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Nullable implements Type +final class ParsingFileAnalyser implements FileAnalyser { - /** @var Type The actual type that is wrapped */ - private $realType; /** - * Initialises this nullable type using the real type embedded + * @var array */ - public function __construct(Type $realType) + private $classes = []; + /** + * @var array + */ + private $traits = []; + /** + * @var array + */ + private $functions = []; + /** + * @var array + */ + private $linesOfCode = []; + /** + * @var array + */ + private $ignoredLines = []; + /** + * @var array + */ + private $executableLines = []; + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode; + /** + * @var bool + */ + private $ignoreDeprecatedCode; + public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) { - $this->realType = $realType; + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; + } + public function classesIn(string $filename) : array + { + $this->analyse($filename); + return $this->classes[$filename]; + } + public function traitsIn(string $filename) : array + { + $this->analyse($filename); + return $this->traits[$filename]; + } + public function functionsIn(string $filename) : array + { + $this->analyse($filename); + return $this->functions[$filename]; } /** - * Provide access to the actual type directly, if needed. + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} */ - public function getActualType() : Type + public function linesOfCodeFor(string $filename) : array { - return $this->realType; + $this->analyse($filename); + return $this->linesOfCode[$filename]; + } + public function executableLinesIn(string $filename) : array + { + $this->analyse($filename); + return $this->executableLines[$filename]; + } + public function ignoredLinesFor(string $filename) : array + { + $this->analyse($filename); + return $this->ignoredLines[$filename]; } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @throws ParserException */ - public function __toString() : string + private function analyse(string $filename) : void { - return '?' . $this->realType->__toString(); + if (isset($this->classes[$filename])) { + return; + } + $source = file_get_contents($filename); + $linesOfCode = max(substr_count($source, "\n") + 1, substr_count($source, "\r") + 1); + if ($linesOfCode === 0 && !empty($source)) { + $linesOfCode = 1; + } + $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); + try { + $nodes = $parser->parse($source); + assert($nodes !== null); + $traverser = new NodeTraverser(); + $codeUnitFindingVisitor = new CodeUnitFindingVisitor(); + $lineCountingVisitor = new LineCountingVisitor($linesOfCode); + $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); + $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor(); + $traverser->addVisitor(new NameResolver()); + $traverser->addVisitor(new ParentConnectingVisitor()); + $traverser->addVisitor($codeUnitFindingVisitor); + $traverser->addVisitor($lineCountingVisitor); + $traverser->addVisitor($ignoredLinesFindingVisitor); + $traverser->addVisitor($executableLinesFindingVisitor); + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new ParserException(sprintf('Cannot parse %s: %s', $filename, $error->getMessage()), (int) $error->getCode(), $error); + } + // @codeCoverageIgnoreEnd + $this->classes[$filename] = $codeUnitFindingVisitor->classes(); + $this->traits[$filename] = $codeUnitFindingVisitor->traits(); + $this->functions[$filename] = $codeUnitFindingVisitor->functions(); + $this->executableLines[$filename] = $executableLinesFindingVisitor->executableLines(); + $this->ignoredLines[$filename] = []; + $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode); + $this->ignoredLines[$filename] = array_unique(\array_merge($this->ignoredLines[$filename], $ignoredLinesFindingVisitor->ignoredLines())); + \sort($this->ignoredLines[$filename]); + $result = $lineCountingVisitor->result(); + $this->linesOfCode[$filename] = ['linesOfCode' => $result->linesOfCode(), 'commentLinesOfCode' => $result->commentLinesOfCode(), 'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode()]; + } + private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode) : void + { + $ignore = \false; + $stop = \false; + foreach (token_get_all($source) as $token) { + if (!is_array($token)) { + continue; + } + switch ($token[0]) { + case \T_COMMENT: + case \T_DOC_COMMENT: + if (!$useAnnotationsForIgnoringCode) { + break; + } + $comment = trim($token[1]); + if ($comment === '// @codeCoverageIgnore' || $comment === '//@codeCoverageIgnore') { + $ignore = \true; + $stop = \true; + } elseif ($comment === '// @codeCoverageIgnoreStart' || $comment === '//@codeCoverageIgnoreStart') { + $ignore = \true; + } elseif ($comment === '// @codeCoverageIgnoreEnd' || $comment === '//@codeCoverageIgnoreEnd') { + $stop = \true; + } + break; + } + if ($ignore) { + $this->ignoredLines[$filename][] = $token[2]; + if ($stop) { + $ignore = \false; + $stop = \false; + } + } + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\Types; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Util; -use PHPUnit\phpDocumentor\Reflection\Fqsen; -use PHPUnit\phpDocumentor\Reflection\Type; +use function is_dir; +use function mkdir; +use function sprintf; /** - * Value Object representing the type 'string'. - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class InterfaceString implements Type +final class Filesystem { - /** @var Fqsen|null */ - private $fqsen; - /** - * Initializes this representation of a class string with the given Fqsen. - */ - public function __construct(?Fqsen $fqsen = null) - { - $this->fqsen = $fqsen; - } - /** - * Returns the FQSEN associated with this object. - */ - public function getFqsen() : ?Fqsen - { - return $this->fqsen; - } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @throws DirectoryCouldNotBeCreatedException */ - public function __toString() : string + public static function createDirectory(string $directory) : void { - if ($this->fqsen === null) { - return 'interface-string'; + $success = !(!is_dir($directory) && !@mkdir($directory, 0777, \true) && !is_dir($directory)); + if (!$success) { + throw new DirectoryCouldNotBeCreatedException(sprintf('Directory "%s" could not be created', $directory)); } - return 'interface-string<' . (string) $this->fqsen . '>'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection; +namespace PHPUnit\SebastianBergmann\CodeCoverage\Util; -use InvalidArgumentException; -use PHPUnit\phpDocumentor\Reflection\Types\Context; -use function explode; -use function implode; -use function strpos; +use function sprintf; /** - * Resolver for Fqsen using Context information - * - * @psalm-immutable + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -class FqsenResolver +final class Percentage { - /** @var string Definition of the NAMESPACE operator in PHP */ - private const OPERATOR_NAMESPACE = '\\'; - public function resolve(string $fqsen, ?Context $context = null) : Fqsen + /** + * @var float + */ + private $fraction; + /** + * @var float + */ + private $total; + public static function fromFractionAndTotal(float $fraction, float $total) : self { - if ($context === null) { - $context = new Context(''); - } - if ($this->isFqsen($fqsen)) { - return new Fqsen($fqsen); + return new self($fraction, $total); + } + private function __construct(float $fraction, float $total) + { + $this->fraction = $fraction; + $this->total = $total; + } + public function asFloat() : float + { + if ($this->total > 0) { + return $this->fraction / $this->total * 100; } - return $this->resolvePartialStructuralElementName($fqsen, $context); + return 100.0; } - /** - * Tests whether the given type is a Fully Qualified Structural Element Name. - */ - private function isFqsen(string $type) : bool + public function asString() : string { - return strpos($type, self::OPERATOR_NAMESPACE) === 0; + if ($this->total > 0) { + return sprintf('%01.2F%%', $this->asFloat()); + } + return ''; } - /** - * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation - * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context. - * - * @throws InvalidArgumentException When type is not a valid FQSEN. - */ - private function resolvePartialStructuralElementName(string $type, Context $context) : Fqsen + public function asFixedWidthString() : string { - $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2); - $namespaceAliases = $context->getNamespaceAliases(); - // if the first segment is not an alias; prepend namespace name and return - if (!isset($namespaceAliases[$typeParts[0]])) { - $namespace = $context->getNamespace(); - if ($namespace !== '') { - $namespace .= self::OPERATOR_NAMESPACE; - } - return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); + if ($this->total > 0) { + return sprintf('%6.2F%%', $this->asFloat()); } - $typeParts[0] = $namespaceAliases[$typeParts[0]]; - return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); + return ''; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\CodeCoverage; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class HtmlEscapedString extends String_ implements PseudoType +use function dirname; +use PHPUnit\SebastianBergmann\Version as VersionId; +final class Version { - public function underlyingType() : Type - { - return new String_(); - } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var string */ - public function __toString() : string + private static $version; + public static function id() : string { - return 'html-escaped-string'; + if (self::$version === null) { + self::$version = (new VersionId('9.2.18', dirname(__DIR__)))->getVersion(); + } + return self::$version; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link https://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\FileIterator; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\Boolean; -use function class_alias; -/** - * Value Object representing the PseudoType 'False', which is a Boolean type. - * - * @psalm-immutable - */ -final class False_ extends Boolean implements PseudoType +use const DIRECTORY_SEPARATOR; +use function array_unique; +use function count; +use function dirname; +use function explode; +use function is_file; +use function is_string; +use function realpath; +use function sort; +class Facade { - public function underlyingType() : Type + /** + * @param array|string $paths + * @param array|string $suffixes + * @param array|string $prefixes + */ + public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = [], bool $commonPath = \false) : array { - return new Boolean(); + if (is_string($paths)) { + $paths = [$paths]; + } + $iterator = (new Factory())->getFileIterator($paths, $suffixes, $prefixes, $exclude); + $files = []; + foreach ($iterator as $file) { + $file = $file->getRealPath(); + if ($file) { + $files[] = $file; + } + } + foreach ($paths as $path) { + if (is_file($path)) { + $files[] = realpath($path); + } + } + $files = array_unique($files); + sort($files); + if ($commonPath) { + return ['commonPath' => $this->getCommonPath($files), 'files' => $files]; + } + return $files; } - public function __toString() : string + protected function getCommonPath(array $files) : string { - return 'false'; + $count = count($files); + if ($count === 0) { + return ''; + } + if ($count === 1) { + return dirname($files[0]) . DIRECTORY_SEPARATOR; + } + $_files = []; + foreach ($files as $file) { + $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file); + if (empty($_fileParts[0])) { + $_fileParts[0] = DIRECTORY_SEPARATOR; + } + } + $common = ''; + $done = \false; + $j = 0; + $count--; + while (!$done) { + for ($i = 0; $i < $count; $i++) { + if ($_files[$i][$j] != $_files[$i + 1][$j]) { + $done = \true; + break; + } + } + if (!$done) { + $common .= $_files[0][$j]; + if ($j > 0) { + $common .= DIRECTORY_SEPARATOR; + } + } + $j++; + } + return DIRECTORY_SEPARATOR . $common; } } -class_alias('PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\False_', 'PHPUnit\\phpDocumentor\\Reflection\\Types\\False_', \false); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\FileIterator; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class NonEmptyString extends String_ implements PseudoType +use const GLOB_ONLYDIR; +use function array_filter; +use function array_map; +use function array_merge; +use function glob; +use function is_dir; +use function is_string; +use function realpath; +use AppendIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +class Factory { - public function underlyingType() : Type - { - return new String_(); - } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @param array|string $paths + * @param array|string $suffixes + * @param array|string $prefixes */ - public function __toString() : string + public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = []) : AppendIterator { - return 'non-empty-string'; + if (is_string($paths)) { + $paths = [$paths]; + } + $paths = $this->getPathsAfterResolvingWildcards($paths); + $exclude = $this->getPathsAfterResolvingWildcards($exclude); + if (is_string($prefixes)) { + if ($prefixes !== '') { + $prefixes = [$prefixes]; + } else { + $prefixes = []; + } + } + if (is_string($suffixes)) { + if ($suffixes !== '') { + $suffixes = [$suffixes]; + } else { + $suffixes = []; + } + } + $iterator = new AppendIterator(); + foreach ($paths as $path) { + if (is_dir($path)) { + $iterator->append(new Iterator($path, new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::SKIP_DOTS)), $suffixes, $prefixes, $exclude)); + } + } + return $iterator; + } + protected function getPathsAfterResolvingWildcards(array $paths) : array + { + $_paths = [[]]; + foreach ($paths as $path) { + if ($locals = glob($path, GLOB_ONLYDIR)) { + $_paths[] = array_map('\\realpath', $locals); + } else { + $_paths[] = [realpath($path)]; + } + } + return array_filter(array_merge(...$_paths)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\FileIterator; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class TraitString extends String_ implements PseudoType +use function array_filter; +use function array_map; +use function preg_match; +use function realpath; +use function str_replace; +use function strlen; +use function strpos; +use function substr; +use FilterIterator; +class Iterator extends FilterIterator { - public function underlyingType() : Type - { - return new String_(); - } + public const PREFIX = 0; + public const SUFFIX = 1; /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @var string */ - public function __toString() : string + private $basePath; + /** + * @var array + */ + private $suffixes = []; + /** + * @var array + */ + private $prefixes = []; + /** + * @var array + */ + private $exclude = []; + public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = [], array $exclude = []) { - return 'trait-string'; + $this->basePath = realpath($basePath); + $this->prefixes = $prefixes; + $this->suffixes = $suffixes; + $this->exclude = array_filter(array_map('realpath', $exclude)); + parent::__construct($iterator); + } + public function accept() : bool + { + $current = $this->getInnerIterator()->current(); + $filename = $current->getFilename(); + $realPath = $current->getRealPath(); + if ($realPath === \false) { + return \false; + } + return $this->acceptPath($realPath) && $this->acceptPrefix($filename) && $this->acceptSuffix($filename); + } + private function acceptPath(string $path) : bool + { + // Filter files in hidden directories by checking path that is relative to the base path. + if (preg_match('=/\\.[^/]*/=', str_replace($this->basePath, '', $path))) { + return \false; + } + foreach ($this->exclude as $exclude) { + if (strpos($path, $exclude) === 0) { + return \false; + } + } + return \true; + } + private function acceptPrefix(string $filename) : bool + { + return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); + } + private function acceptSuffix(string $filename) : bool + { + return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); + } + private function acceptSubString(string $filename, array $subStrings, int $type) : bool + { + if (empty($subStrings)) { + return \true; + } + $matched = \false; + foreach ($subStrings as $string) { + if ($type === self::PREFIX && strpos($filename, $string) === 0 || $type === self::SUFFIX && substr($filename, -1 * strlen($string)) === $string) { + $matched = \true; + break; + } + } + return $matched; } } +php-file-iterator + +Copyright (c) 2009-2021, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link https://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\Invoker; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\Boolean; -use function class_alias; -/** - * Value Object representing the PseudoType 'False', which is a Boolean type. - * - * @psalm-immutable - */ -final class True_ extends Boolean implements PseudoType +use const SIGALRM; +use function call_user_func_array; +use function function_exists; +use function pcntl_alarm; +use function pcntl_async_signals; +use function pcntl_signal; +use function sprintf; +use Throwable; +final class Invoker { - public function underlyingType() : Type + /** + * @var int + */ + private $timeout; + /** + * @throws Throwable + */ + public function invoke(callable $callable, array $arguments, int $timeout) { - return new Boolean(); + if (!$this->canInvokeWithTimeout()) { + throw new ProcessControlExtensionNotLoadedException('The pcntl (process control) extension for PHP is required'); + } + pcntl_signal(SIGALRM, function () : void { + throw new TimeoutException(sprintf('Execution aborted after %d second%s', $this->timeout, $this->timeout === 1 ? '' : 's')); + }, \true); + $this->timeout = $timeout; + pcntl_async_signals(\true); + pcntl_alarm($timeout); + try { + return call_user_func_array($callable, $arguments); + } finally { + pcntl_alarm(0); + } } - public function __toString() : string + public function canInvokeWithTimeout() : bool { - return 'true'; + return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); } } -class_alias('PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\True_', 'PHPUnit\\phpDocumentor\\Reflection\\Types\\True_', \false); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\Invoker; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class CallableString extends String_ implements PseudoType +use Throwable; +interface Exception extends Throwable { - public function underlyingType() : Type - { - return new String_(); - } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - return 'callable-string'; - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\Invoker; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. +use RuntimeException; +final class ProcessControlExtensionNotLoadedException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -final class NonEmptyLowercaseString extends String_ implements PseudoType +namespace PHPUnit\SebastianBergmann\Invoker; + +use RuntimeException; +final class TimeoutException extends RuntimeException implements Exception { - public function underlyingType() : Type - { - return new String_(); - } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - return 'non-empty-lowercase-string'; - } } +phpunit/php-text-template + +Copyright (c) 2009-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\Template; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class NumericString extends String_ implements PseudoType +use function array_merge; +use function file_exists; +use function file_get_contents; +use function file_put_contents; +use function sprintf; +use function str_replace; +final class Template { - public function underlyingType() : Type + /** + * @var string + */ + private $template = ''; + /** + * @var string + */ + private $openDelimiter; + /** + * @var string + */ + private $closeDelimiter; + /** + * @var array + */ + private $values = []; + /** + * @throws InvalidArgumentException + */ + public function __construct(string $file = '', string $openDelimiter = '{', string $closeDelimiter = '}') { - return new String_(); + $this->setFile($file); + $this->openDelimiter = $openDelimiter; + $this->closeDelimiter = $closeDelimiter; } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @throws InvalidArgumentException */ - public function __toString() : string + public function setFile(string $file) : void { - return 'numeric-string'; + $distFile = $file . '.dist'; + if (file_exists($file)) { + $this->template = file_get_contents($file); + } elseif (file_exists($distFile)) { + $this->template = file_get_contents($distFile); + } else { + throw new InvalidArgumentException(sprintf('Failed to load template "%s"', $file)); + } } -} -values)) { + $this->values = $values; + } else { + $this->values = array_merge($this->values, $values); + } + } + public function render() : string + { + $keys = []; + foreach ($this->values as $key => $value) { + $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; + } + return str_replace($keys, $this->values, $this->template); } /** - * Returns a rendered output of the Type as it would be used in a DocBlock. + * @codeCoverageIgnore */ - public function __toString() : string + public function renderTo(string $target) : void { - return 'lowercase-string'; + if (!file_put_contents($target, $this->render())) { + throw new RuntimeException(sprintf('Writing rendered result to "%s" failed', $target)); + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; +namespace PHPUnit\SebastianBergmann\Template; -use PHPUnit\phpDocumentor\Reflection\PseudoType; -use PHPUnit\phpDocumentor\Reflection\Type; -use PHPUnit\phpDocumentor\Reflection\Types\Integer; -/** - * Value Object representing the type 'string'. - * - * @psalm-immutable - */ -final class PositiveInteger extends Integer implements PseudoType +use Throwable; +interface Exception extends Throwable { - public function underlyingType() : Type - { - return new Integer(); - } - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string - { - return 'positive-int'; - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection; +namespace PHPUnit\SebastianBergmann\Template; -/** - * @psalm-immutable - */ -interface Type +final class InvalidArgumentException extends \InvalidArgumentException implements Exception { - /** - * Returns a rendered output of the Type as it would be used in a DocBlock. - */ - public function __toString() : string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection; +namespace PHPUnit\SebastianBergmann\Template; -interface PseudoType extends Type +use InvalidArgumentException; +final class RuntimeException extends InvalidArgumentException implements Exception { - public function underlyingType() : Type; } -The MIT License (MIT) - -Copyright (c) 2010 Mike van Riel - -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. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * @link http://phpdoc.org */ -namespace PHPUnit\phpDocumentor\Reflection; +namespace PHPUnit\SebastianBergmann\Timer; -use ArrayIterator; -use InvalidArgumentException; -use PHPUnit\phpDocumentor\Reflection\Types\Array_; -use PHPUnit\phpDocumentor\Reflection\Types\ClassString; -use PHPUnit\phpDocumentor\Reflection\Types\Collection; -use PHPUnit\phpDocumentor\Reflection\Types\Compound; -use PHPUnit\phpDocumentor\Reflection\Types\Context; -use PHPUnit\phpDocumentor\Reflection\Types\Expression; -use PHPUnit\phpDocumentor\Reflection\Types\Integer; -use PHPUnit\phpDocumentor\Reflection\Types\InterfaceString; -use PHPUnit\phpDocumentor\Reflection\Types\Intersection; -use PHPUnit\phpDocumentor\Reflection\Types\Iterable_; -use PHPUnit\phpDocumentor\Reflection\Types\Nullable; -use PHPUnit\phpDocumentor\Reflection\Types\Object_; -use PHPUnit\phpDocumentor\Reflection\Types\String_; -use RuntimeException; -use function array_key_exists; -use function array_pop; -use function array_values; -use function class_exists; -use function class_implements; -use function count; -use function end; -use function in_array; -use function key; -use function preg_split; -use function strpos; -use function strtolower; -use function trim; -use const PREG_SPLIT_DELIM_CAPTURE; -use const PREG_SPLIT_NO_EMPTY; -final class TypeResolver +use function floor; +use function sprintf; +/** + * @psalm-immutable + */ +final class Duration { - /** @var string Definition of the ARRAY operator for types */ - private const OPERATOR_ARRAY = '[]'; - /** @var string Definition of the NAMESPACE operator in PHP */ - private const OPERATOR_NAMESPACE = '\\'; - /** @var int the iterator parser is inside a compound context */ - private const PARSER_IN_COMPOUND = 0; - /** @var int the iterator parser is inside a nullable expression context */ - private const PARSER_IN_NULLABLE = 1; - /** @var int the iterator parser is inside an array expression context */ - private const PARSER_IN_ARRAY_EXPRESSION = 2; - /** @var int the iterator parser is inside a collection expression context */ - private const PARSER_IN_COLLECTION_EXPRESSION = 3; /** - * @var array List of recognized keywords and unto which Value Object they map - * @psalm-var array> + * @var float */ - private $keywords = ['string' => Types\String_::class, 'class-string' => Types\ClassString::class, 'interface-string' => Types\InterfaceString::class, 'html-escaped-string' => PseudoTypes\HtmlEscapedString::class, 'lowercase-string' => PseudoTypes\LowercaseString::class, 'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class, 'non-empty-string' => PseudoTypes\NonEmptyString::class, 'numeric-string' => PseudoTypes\NumericString::class, 'trait-string' => PseudoTypes\TraitString::class, 'int' => Types\Integer::class, 'integer' => Types\Integer::class, 'positive-int' => PseudoTypes\PositiveInteger::class, 'bool' => Types\Boolean::class, 'boolean' => Types\Boolean::class, 'real' => Types\Float_::class, 'float' => Types\Float_::class, 'double' => Types\Float_::class, 'object' => Types\Object_::class, 'mixed' => Types\Mixed_::class, 'array' => Types\Array_::class, 'array-key' => Types\ArrayKey::class, 'resource' => Types\Resource_::class, 'void' => Types\Void_::class, 'null' => Types\Null_::class, 'scalar' => Types\Scalar::class, 'callback' => Types\Callable_::class, 'callable' => Types\Callable_::class, 'callable-string' => PseudoTypes\CallableString::class, 'false' => PseudoTypes\False_::class, 'true' => PseudoTypes\True_::class, 'self' => Types\Self_::class, '$this' => Types\This::class, 'static' => Types\Static_::class, 'parent' => Types\Parent_::class, 'iterable' => Types\Iterable_::class, 'never' => Types\Never_::class]; + private $nanoseconds; /** - * @var FqsenResolver - * @psalm-readonly + * @var int */ - private $fqsenResolver; + private $hours; /** - * Initializes this TypeResolver with the means to create and resolve Fqsen objects. + * @var int */ - public function __construct(?FqsenResolver $fqsenResolver = null) - { - $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); - } + private $minutes; /** - * Analyzes the given type and returns the FQCN variant. - * - * When a type is provided this method checks whether it is not a keyword or - * Fully Qualified Class Name. If so it will use the given namespace and - * aliases to expand the type to a FQCN representation. - * - * This method only works as expected if the namespace and aliases are set; - * no dynamic reflection is being performed here. - * - * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be - * replaced with another namespace. - * @uses Context::getNamespace() to determine with what to prefix the type name. - * - * @param string $type The relative or absolute type. + * @var int */ - public function resolve(string $type, ?Context $context = null) : Type - { - $type = trim($type); - if (!$type) { - throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty'); - } - if ($context === null) { - $context = new Context(''); - } - // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names - $tokens = preg_split('/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/', $type, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); - if ($tokens === \false) { - throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens'); - } - /** @var ArrayIterator $tokenIterator */ - $tokenIterator = new ArrayIterator($tokens); - return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND); - } + private $seconds; /** - * Analyse each tokens and creates types - * - * @param ArrayIterator $tokens the iterator on tokens - * @param int $parserContext on of self::PARSER_* constants, indicating - * the context where we are in the parsing + * @var int */ - private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext) : Type + private $milliseconds; + public static function fromMicroseconds(float $microseconds) : self { - $types = []; - $token = ''; - $compoundToken = '|'; - while ($tokens->valid()) { - $token = $tokens->current(); - if ($token === null) { - throw new RuntimeException('Unexpected nullable character'); - } - if ($token === '|' || $token === '&') { - if (count($types) === 0) { - throw new RuntimeException('A type is missing before a type separator'); - } - if (!in_array($parserContext, [self::PARSER_IN_COMPOUND, self::PARSER_IN_ARRAY_EXPRESSION, self::PARSER_IN_COLLECTION_EXPRESSION], \true)) { - throw new RuntimeException('Unexpected type separator'); - } - $compoundToken = $token; - $tokens->next(); - } elseif ($token === '?') { - if (!in_array($parserContext, [self::PARSER_IN_COMPOUND, self::PARSER_IN_ARRAY_EXPRESSION, self::PARSER_IN_COLLECTION_EXPRESSION], \true)) { - throw new RuntimeException('Unexpected nullable character'); - } - $tokens->next(); - $type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE); - $types[] = new Nullable($type); - } elseif ($token === '(') { - $tokens->next(); - $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION); - $token = $tokens->current(); - if ($token === null) { - // Someone did not properly close their array expression .. - break; - } - $tokens->next(); - $resolvedType = new Expression($type); - $types[] = $resolvedType; - } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && isset($token[0]) && $token[0] === ')') { - break; - } elseif ($token === '<') { - if (count($types) === 0) { - throw new RuntimeException('Unexpected collection operator "<", class name is missing'); - } - $classType = array_pop($types); - if ($classType !== null) { - if ((string) $classType === 'class-string') { - $types[] = $this->resolveClassString($tokens, $context); - } elseif ((string) $classType === 'interface-string') { - $types[] = $this->resolveInterfaceString($tokens, $context); - } else { - $types[] = $this->resolveCollection($tokens, $classType, $context); - } - } - $tokens->next(); - } elseif ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION && ($token === '>' || trim($token) === ',')) { - break; - } elseif ($token === self::OPERATOR_ARRAY) { - end($types); - $last = key($types); - $lastItem = $types[$last]; - if ($lastItem instanceof Expression) { - $lastItem = $lastItem->getValueType(); - } - $types[$last] = new Array_($lastItem); - $tokens->next(); - } else { - $type = $this->resolveSingleType($token, $context); - $tokens->next(); - if ($parserContext === self::PARSER_IN_NULLABLE) { - return $type; - } - $types[] = $type; - } - } - if ($token === '|' || $token === '&') { - throw new RuntimeException('A type is missing after a type separator'); - } - if (count($types) === 0) { - if ($parserContext === self::PARSER_IN_NULLABLE) { - throw new RuntimeException('A type is missing after a nullable character'); - } - if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) { - throw new RuntimeException('A type is missing in an array expression'); - } - if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) { - throw new RuntimeException('A type is missing in a collection expression'); - } - } elseif (count($types) === 1) { - return $types[0]; - } - if ($compoundToken === '|') { - return new Compound(array_values($types)); - } - return new Intersection(array_values($types)); + return new self($microseconds * 1000); } - /** - * resolve the given type into a type object - * - * @param string $type the type string, representing a single type - * - * @return Type|Array_|Object_ - * - * @psalm-mutation-free - */ - private function resolveSingleType(string $type, Context $context) : object + public static function fromNanoseconds(float $nanoseconds) : self { - switch (\true) { - case $this->isKeyword($type): - return $this->resolveKeyword($type); - case $this->isFqsen($type): - return $this->resolveTypedObject($type); - case $this->isPartialStructuralElementName($type): - return $this->resolveTypedObject($type, $context); - // @codeCoverageIgnoreStart - default: - // I haven't got the foggiest how the logic would come here but added this as a defense. - throw new RuntimeException('Unable to resolve type "' . $type . '", there is no known method to resolve it'); - } - // @codeCoverageIgnoreEnd + return new self($nanoseconds); } - /** - * Adds a keyword to the list of Keywords and associates it with a specific Value Object. - * - * @psalm-param class-string $typeClassName - */ - public function addKeyword(string $keyword, string $typeClassName) : void + private function __construct(float $nanoseconds) { - if (!class_exists($typeClassName)) { - throw new InvalidArgumentException('The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' . ' but we could not find the class ' . $typeClassName); - } - $interfaces = class_implements($typeClassName); - if ($interfaces === \false) { - throw new InvalidArgumentException('The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' . ' but we could not find the class ' . $typeClassName); - } - if (!in_array(Type::class, $interfaces, \true)) { - throw new InvalidArgumentException('The class "' . $typeClassName . '" must implement the interface "phpDocumentor\\Reflection\\Type"'); - } - $this->keywords[$keyword] = $typeClassName; + $this->nanoseconds = $nanoseconds; + $timeInMilliseconds = $nanoseconds / 1000000; + $hours = floor($timeInMilliseconds / 60 / 60 / 1000); + $hoursInMilliseconds = $hours * 60 * 60 * 1000; + $minutes = floor($timeInMilliseconds / 60 / 1000) % 60; + $minutesInMilliseconds = $minutes * 60 * 1000; + $seconds = floor(($timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds) / 1000); + $secondsInMilliseconds = $seconds * 1000; + $milliseconds = $timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds - $secondsInMilliseconds; + $this->hours = (int) $hours; + $this->minutes = $minutes; + $this->seconds = (int) $seconds; + $this->milliseconds = (int) $milliseconds; } - /** - * Detects whether the given type represents a PHPDoc keyword. - * - * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. - * - * @psalm-mutation-free - */ - private function isKeyword(string $type) : bool + public function asNanoseconds() : float { - return array_key_exists(strtolower($type), $this->keywords); + return $this->nanoseconds; } - /** - * Detects whether the given type represents a relative structural element name. - * - * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. - * - * @psalm-mutation-free - */ - private function isPartialStructuralElementName(string $type) : bool + public function asMicroseconds() : float { - return isset($type[0]) && $type[0] !== self::OPERATOR_NAMESPACE && !$this->isKeyword($type); + return $this->nanoseconds / 1000; } - /** - * Tests whether the given type is a Fully Qualified Structural Element Name. - * - * @psalm-mutation-free - */ - private function isFqsen(string $type) : bool + public function asMilliseconds() : float { - return strpos($type, self::OPERATOR_NAMESPACE) === 0; + return $this->nanoseconds / 1000000; } - /** - * Resolves the given keyword (such as `string`) into a Type object representing that keyword. - * - * @psalm-mutation-free - */ - private function resolveKeyword(string $type) : Type + public function asSeconds() : float { - $className = $this->keywords[strtolower($type)]; - return new $className(); + return $this->nanoseconds / 1000000000; + } + public function asString() : string + { + $result = ''; + if ($this->hours > 0) { + $result = sprintf('%02d', $this->hours) . ':'; + } + $result .= sprintf('%02d', $this->minutes) . ':'; + $result .= sprintf('%02d', $this->seconds); + if ($this->milliseconds > 0) { + $result .= '.' . sprintf('%03d', $this->milliseconds); + } + return $result; } +} +phpunit/php-timer + +Copyright (c) 2010-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Timer; + +use function is_float; +use function memory_get_peak_usage; +use function microtime; +use function sprintf; +final class ResourceUsageFormatter +{ /** - * Resolves the given FQSEN string into an FQSEN object. - * - * @psalm-mutation-free + * @psalm-var array */ - private function resolveTypedObject(string $type, ?Context $context = null) : Object_ + private const SIZES = ['GB' => 1073741824, 'MB' => 1048576, 'KB' => 1024]; + public function resourceUsage(Duration $duration) : string { - return new Object_($this->fqsenResolver->resolve($type, $context)); + return sprintf('Time: %s, Memory: %s', $duration->asString(), $this->bytesToString(memory_get_peak_usage(\true))); } /** - * Resolves class string - * - * @param ArrayIterator $tokens + * @throws TimeSinceStartOfRequestNotAvailableException */ - private function resolveClassString(ArrayIterator $tokens, Context $context) : Type + public function resourceUsageSinceStartOfRequest() : string { - $tokens->next(); - $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - if (!$classType instanceof Object_ || $classType->getFqsen() === null) { - throw new RuntimeException($classType . ' is not a class string'); + if (!isset($_SERVER['REQUEST_TIME_FLOAT'])) { + throw new TimeSinceStartOfRequestNotAvailableException('Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available'); } - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException('class-string: ">" is missing'); - } - throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); + if (!is_float($_SERVER['REQUEST_TIME_FLOAT'])) { + throw new TimeSinceStartOfRequestNotAvailableException('Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float'); } - return new ClassString($classType->getFqsen()); + return $this->resourceUsage(Duration::fromMicroseconds(1000000 * (microtime(\true) - $_SERVER['REQUEST_TIME_FLOAT']))); } - /** - * Resolves class string - * - * @param ArrayIterator $tokens - */ - private function resolveInterfaceString(ArrayIterator $tokens, Context $context) : Type + private function bytesToString(int $bytes) : string { - $tokens->next(); - $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - if (!$classType instanceof Object_ || $classType->getFqsen() === null) { - throw new RuntimeException($classType . ' is not a interface string'); - } - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException('interface-string: ">" is missing'); + foreach (self::SIZES as $unit => $value) { + if ($bytes >= $value) { + return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); } - throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); } - return new InterfaceString($classType->getFqsen()); + // @codeCoverageIgnoreStart + return $bytes . ' byte' . ($bytes !== 1 ? 's' : ''); + // @codeCoverageIgnoreEnd } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Timer; + +use function array_pop; +use function hrtime; +final class Timer +{ /** - * Resolves the collection values and keys - * - * @param ArrayIterator $tokens - * - * @return Array_|Iterable_|Collection + * @psalm-var list */ - private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context) : Type + private $startTimes = []; + public function start() : void { - $isArray = (string) $classType === 'array'; - $isIterable = (string) $classType === 'iterable'; - // allow only "array", "iterable" or class name before "<" - if (!$isArray && !$isIterable && (!$classType instanceof Object_ || $classType->getFqsen() === null)) { - throw new RuntimeException($classType . ' is not a collection'); - } - $tokens->next(); - $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - $keyType = null; - $token = $tokens->current(); - if ($token !== null && trim($token) === ',') { - // if we have a comma, then we just parsed the key type, not the value type - $keyType = $valueType; - if ($isArray) { - // check the key type for an "array" collection. We allow only - // strings or integers. - if (!$keyType instanceof String_ && !$keyType instanceof Integer && !$keyType instanceof Compound) { - throw new RuntimeException('An array can have only integers or strings as keys'); - } - if ($keyType instanceof Compound) { - foreach ($keyType->getIterator() as $item) { - if (!$item instanceof String_ && !$item instanceof Integer) { - throw new RuntimeException('An array can have only integers or strings as keys'); - } - } - } - } - $tokens->next(); - // now let's parse the value type - $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - } - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException('Collection: ">" is missing'); - } - throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); - } - if ($isArray) { - return new Array_($valueType, $keyType); - } - if ($isIterable) { - return new Iterable_($valueType, $keyType); - } - if ($classType instanceof Object_) { - return $this->makeCollectionFromObject($classType, $valueType, $keyType); - } - throw new RuntimeException('Invalid $classType provided'); + $this->startTimes[] = (float) hrtime(\true); } /** - * @psalm-pure + * @throws NoActiveTimerException */ - private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null) : Collection + public function stop() : Duration { - return new Collection($object->getFqsen(), $valueType, $keyType); + if (empty($this->startTimes)) { + throw new NoActiveTimerException('Timer::start() has to be called before Timer::stop()'); + } + return Duration::fromNanoseconds((float) hrtime(\true) - array_pop($this->startTimes)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\Environment; +namespace PHPUnit\SebastianBergmann\Timer; -use const DIRECTORY_SEPARATOR; -use const STDIN; -use const STDOUT; -use function defined; -use function fclose; -use function fstat; -use function function_exists; -use function getenv; -use function is_resource; -use function is_string; -use function posix_isatty; -use function preg_match; -use function proc_close; -use function proc_open; -use function sapi_windows_vt100_support; -use function shell_exec; -use function stream_get_contents; -use function stream_isatty; -use function trim; -final class Console +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Timer; + +use LogicException; +final class NoActiveTimerException extends LogicException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Timer; + +use RuntimeException; +final class TimeSinceStartOfRequestNotAvailableException extends RuntimeException implements Exception +{ +} +isWindows()) { - // @codeCoverageIgnoreStart - return defined('STDOUT') && function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(\STDOUT) || \false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); - // @codeCoverageIgnoreEnd - } - if (!defined('STDOUT')) { - // @codeCoverageIgnoreStart - return \false; - // @codeCoverageIgnoreEnd - } - return $this->isInteractive(\STDOUT); - } + public function md5() : string; /** - * Returns the number of columns of the terminal. + * Returns an relative path to the file. + */ + public function path() : string; +} +isInteractive(defined('STDIN') ? \STDIN : self::STDIN)) { - return 80; + $matches = []; + $result = preg_match( + //phpcs:ignore Generic.Files.LineLength.TooLong + '/^\\\\([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff\\\\]*)?(?:[:]{2}\\$?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*))?(?:\\(\\))?$/', + $fqsen, + $matches + ); + if ($result === 0) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid Fqsen.', $fqsen)); } - if ($this->isWindows()) { - return $this->getNumberOfColumnsWindows(); + $this->fqsen = $fqsen; + if (isset($matches[2])) { + $this->name = $matches[2]; + } else { + $matches = explode('\\', $fqsen); + $name = end($matches); + assert(is_string($name)); + $this->name = trim($name, '()'); } - return $this->getNumberOfColumnsInteractive(); } /** - * Returns if the file descriptor is an interactive terminal or not. - * - * Normally, we want to use a resource as a parameter, yet sadly it's not always awailable, - * eg when running code in interactive console (`php -a`), STDIN/STDOUT/STDERR constants are not defined. - * - * @param int|resource $fileDescriptor + * converts this class to string. */ - public function isInteractive($fileDescriptor = self::STDOUT) : bool + public function __toString() : string { - if (is_resource($fileDescriptor)) { - // These functions require a descriptor that is a real resource, not a numeric ID of it - if (function_exists('stream_isatty') && @stream_isatty($fileDescriptor)) { - return \true; - } - // Check if formatted mode is S_IFCHR - if (function_exists('fstat') && @stream_isatty($fileDescriptor)) { - $stat = @fstat(\STDOUT); - return $stat ? 020000 === ($stat['mode'] & 0170000) : \false; - } - return \false; - } - return function_exists('posix_isatty') && @posix_isatty($fileDescriptor); + return $this->fqsen; } - private function isWindows() : bool + /** + * Returns the name of the element without path. + */ + public function getName() : string { - return \DIRECTORY_SEPARATOR === '\\'; + return $this->name; } +} +The MIT License (MIT) + +Copyright (c) 2015 phpDocumentor + +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. + + 0) { - return (int) $match[1]; - } - } - if (function_exists('shell_exec') && preg_match('#columns = (\\d+);#', shell_exec('stty') ?: '', $match) === 1) { - if ((int) $match[1] > 0) { - return (int) $match[1]; - } - } - return 80; + $this->lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; } /** - * @codeCoverageIgnore + * Returns the line number that is covered by this location. */ - private function getNumberOfColumnsWindows() : int + public function getLineNumber() : int { - $ansicon = getenv('ANSICON'); - $columns = 80; - if (is_string($ansicon) && preg_match('/^(\\d+)x\\d+ \\(\\d+x(\\d+)\\)$/', trim($ansicon), $matches)) { - $columns = (int) $matches[1]; - } elseif (function_exists('proc_open')) { - $process = proc_open('mode CON', [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, null, ['suppress_errors' => \true]); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - if (preg_match('/--------+\\r?\\n.+?(\\d+)\\r?\\n.+?(\\d+)\\r?\\n/', $info, $matches)) { - $columns = (int) $matches[2]; - } - } - } - return $columns - 1; + return $this->lineNumber; + } + /** + * Returns the column number (character position on a line) for this location object. + */ + public function getColumnNumber() : int + { + return $this->columnNumber; } } +/** + * phpDocumentor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Environment; +namespace PHPUnit\phpDocumentor\Reflection; -use const DIRECTORY_SEPARATOR; -use const PHP_OS; -use const PHP_OS_FAMILY; -use function defined; -final class OperatingSystem +/** + * Interface for project. Since the definition of a project can be different per factory this interface will be small. + */ +interface Project { /** - * Returns PHP_OS_FAMILY (if defined (which it is on PHP >= 7.2)). - * Returns a string (compatible with PHP_OS_FAMILY) derived from PHP_OS otherwise. + * Returns the name of the project. */ - public function getFamily() : string - { - if (defined('PHP_OS_FAMILY')) { - return \PHP_OS_FAMILY; - } - if (\DIRECTORY_SEPARATOR === '\\') { - return 'Windows'; - } - switch (\PHP_OS) { - case 'Darwin': - return 'Darwin'; - case 'DragonFly': - case 'FreeBSD': - case 'NetBSD': - case 'OpenBSD': - return 'BSD'; - case 'Linux': - return 'Linux'; - case 'SunOS': - return 'Solaris'; - default: - return 'Unknown'; - } - } + public function getName() : string; } +/** + * phpDocumentor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Environment; +namespace PHPUnit\phpDocumentor\Reflection; -use const PHP_BINARY; -use const PHP_BINDIR; -use const PHP_MAJOR_VERSION; -use const PHP_SAPI; -use const PHP_VERSION; -use function array_map; -use function array_merge; -use function defined; -use function escapeshellarg; -use function explode; -use function extension_loaded; -use function getenv; -use function ini_get; -use function is_readable; -use function parse_ini_file; -use function php_ini_loaded_file; -use function php_ini_scanned_files; -use function phpversion; -use function sprintf; -use function strpos; /** - * Utility class for HHVM/PHP environment handling. + * Interface for project factories. A project factory shall convert a set of files + * into an object implementing the Project interface. */ -final class Runtime +interface ProjectFactory { /** - * @var string - */ - private static $binary; - /** - * Returns true when Xdebug or PCOV is available or - * the runtime used is PHPDBG. - */ - public function canCollectCodeCoverage() : bool - { - return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage(); - } - /** - * Returns true when Zend OPcache is loaded, enabled, - * and is configured to discard comments. - */ - public function discardsComments() : bool - { - if (!$this->isOpcacheActive()) { - return \false; - } - if (ini_get('opcache.save_comments') !== '0') { - return \false; - } - return \true; - } - /** - * Returns true when Zend OPcache is loaded, enabled, - * and is configured to perform just-in-time compilation. + * Creates a project from the set of files. + * + * @param File[] $files */ - public function performsJustInTimeCompilation() : bool - { - if (\PHP_MAJOR_VERSION < 8) { - return \false; - } - if (!$this->isOpcacheActive()) { - return \false; - } - if (strpos(ini_get('opcache.jit'), '0') === 0) { - return \false; - } - return \true; - } + public function create(string $name, array $files) : Project; +} +isHHVM()) { - // @codeCoverageIgnoreStart - if ((self::$binary = getenv('PHP_BINARY')) === \false) { - self::$binary = \PHP_BINARY; - } - self::$binary = escapeshellarg(self::$binary) . ' --php' . ' -d hhvm.php7.all=1'; - // @codeCoverageIgnoreEnd - } - if (self::$binary === null && \PHP_BINARY !== '') { - self::$binary = escapeshellarg(\PHP_BINARY); - } - if (self::$binary === null) { - // @codeCoverageIgnoreStart - $possibleBinaryLocations = [\PHP_BINDIR . '/php', \PHP_BINDIR . '/php-cli.exe', \PHP_BINDIR . '/php.exe']; - foreach ($possibleBinaryLocations as $binary) { - if (is_readable($binary)) { - self::$binary = escapeshellarg($binary); - break; - } - } - // @codeCoverageIgnoreEnd - } - if (self::$binary === null) { - // @codeCoverageIgnoreStart - self::$binary = 'php'; - // @codeCoverageIgnoreEnd - } - return self::$binary; - } - public function getNameWithVersion() : string - { - return $this->getName() . ' ' . $this->getVersion(); - } - public function getNameWithVersionAndCodeCoverageDriver() : string - { - if (!$this->canCollectCodeCoverage() || $this->hasPHPDBGCodeCoverage()) { - return $this->getNameWithVersion(); - } - if ($this->hasPCOV()) { - return sprintf('%s with PCOV %s', $this->getNameWithVersion(), phpversion('pcov')); - } - if ($this->hasXdebug()) { - return sprintf('%s with Xdebug %s', $this->getNameWithVersion(), phpversion('xdebug')); - } - } - public function getName() : string - { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return 'HHVM'; - // @codeCoverageIgnoreEnd - } - if ($this->isPHPDBG()) { - // @codeCoverageIgnoreStart - return 'PHPDBG'; - // @codeCoverageIgnoreEnd - } - return 'PHP'; - } - public function getVendorUrl() : string + public function __construct(string $summary = '', ?DocBlock\Description $description = null, array $tags = [], ?Types\Context $context = null, ?Location $location = null, bool $isTemplateStart = \false, bool $isTemplateEnd = \false) { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return 'http://hhvm.com/'; - // @codeCoverageIgnoreEnd + Assert::allIsInstanceOf($tags, Tag::class); + $this->summary = $summary; + $this->description = $description ?: new DocBlock\Description(''); + foreach ($tags as $tag) { + $this->addTag($tag); } - return 'https://secure.php.net/'; + $this->context = $context; + $this->location = $location; + $this->isTemplateEnd = $isTemplateEnd; + $this->isTemplateStart = $isTemplateStart; } - public function getVersion() : string + public function getSummary() : string { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return HHVM_VERSION; - // @codeCoverageIgnoreEnd - } - return \PHP_VERSION; + return $this->summary; } - /** - * Returns true when the runtime used is PHP and Xdebug is loaded. - */ - public function hasXdebug() : bool + public function getDescription() : DocBlock\Description { - return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug'); + return $this->description; } /** - * Returns true when the runtime used is HHVM. + * Returns the current context. */ - public function isHHVM() : bool + public function getContext() : ?Types\Context { - return defined('HHVM_VERSION'); + return $this->context; } /** - * Returns true when the runtime used is PHP without the PHPDBG SAPI. + * Returns the current location. */ - public function isPHP() : bool + public function getLocation() : ?Location { - return !$this->isHHVM() && !$this->isPHPDBG(); + return $this->location; } /** - * Returns true when the runtime used is PHP with the PHPDBG SAPI. + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. */ - public function isPHPDBG() : bool + public function isTemplateStart() : bool { - return \PHP_SAPI === 'phpdbg' && !$this->isHHVM(); + return $this->isTemplateStart; } /** - * Returns true when the runtime used is PHP with the PHPDBG SAPI - * and the phpdbg_*_oplog() functions are available (PHP >= 7.0). + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. */ - public function hasPHPDBGCodeCoverage() : bool + public function isTemplateEnd() : bool { - return $this->isPHPDBG(); + return $this->isTemplateEnd; } /** - * Returns true when the runtime used is PHP with PCOV loaded and enabled. + * Returns the tags for this DocBlock. + * + * @return Tag[] */ - public function hasPCOV() : bool + public function getTags() : array { - return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled'); + return $this->tags; } /** - * Parses the loaded php.ini file (if any) as well as all - * additional php.ini files from the additional ini dir for - * a list of all configuration settings loaded from files - * at startup. Then checks for each php.ini setting passed - * via the `$values` parameter whether this setting has - * been changed at runtime. Returns an array of strings - * where each string has the format `key=value` denoting - * the name of a changed php.ini setting with its new value. + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. * - * @return string[] + * @param string $name String to search by. + * + * @return Tag[] */ - public function getCurrentSettings(array $values) : array + public function getTagsByName(string $name) : array { - $diff = []; - $files = []; - if ($file = php_ini_loaded_file()) { - $files[] = $file; - } - if ($scanned = php_ini_scanned_files()) { - $files = array_merge($files, array_map('trim', explode(",\n", $scanned))); - } - foreach ($files as $ini) { - $config = parse_ini_file($ini, \true); - foreach ($values as $value) { - $set = ini_get($value); - if (isset($config[$value]) && $set != $config[$value]) { - $diff[] = sprintf('%s=%s', $value, $set); - } + $result = []; + foreach ($this->getTags() as $tag) { + if ($tag->getName() !== $name) { + continue; } + $result[] = $tag; } - return $diff; - } - private function isOpcacheActive() : bool - { - if (!extension_loaded('Zend OPcache')) { - return \false; - } - if ((\PHP_SAPI === 'cli' || \PHP_SAPI === 'phpdbg') && ini_get('opcache.enable_cli') === '1') { - return \true; - } - if (\PHP_SAPI !== 'cli' && \PHP_SAPI !== 'phpdbg' && ini_get('opcache.enable') === '1') { - return \true; - } - return \false; + return $result; } -} -sebastian/environment - -Copyright (c) 2014-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use RuntimeException; -use PHPUnit\SebastianBergmann\Diff\Differ; -use PHPUnit\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; -/** - * Thrown when an assertion for string equality failed. - */ -class ComparisonFailure extends RuntimeException -{ - /** - * Expected value of the retrieval which does not match $actual. - * - * @var mixed - */ - protected $expected; - /** - * Actually retrieved value which does not match $expected. - * - * @var mixed - */ - protected $actual; - /** - * The string representation of the expected value. - * - * @var string - */ - protected $expectedAsString; - /** - * The string representation of the actual value. - * - * @var string - */ - protected $actualAsString; - /** - * @var bool - */ - protected $identical; /** - * Optional message which is placed in front of the first line - * returned by toString(). + * Returns an array of tags with type matching the given name. If no tags are found + * an empty array is returned. * - * @var string - */ - protected $message; - /** - * Initialises with the expected value and the actual value. + * @param string $name String to search by. * - * @param mixed $expected expected value retrieved - * @param mixed $actual actual value retrieved - * @param string $expectedAsString - * @param string $actualAsString - * @param bool $identical - * @param string $message a string which is prefixed on all returned lines - * in the difference output - */ - public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = \false, $message = '') - { - $this->expected = $expected; - $this->actual = $actual; - $this->expectedAsString = $expectedAsString; - $this->actualAsString = $actualAsString; - $this->message = $message; - } - public function getActual() - { - return $this->actual; - } - public function getExpected() - { - return $this->expected; - } - /** - * @return string + * @return TagWithType[] */ - public function getActualAsString() + public function getTagsWithTypeByName(string $name) : array { - return $this->actualAsString; + $result = []; + foreach ($this->getTagsByName($name) as $tag) { + if (!$tag instanceof TagWithType) { + continue; + } + $result[] = $tag; + } + return $result; } /** - * @return string + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. */ - public function getExpectedAsString() + public function hasTag(string $name) : bool { - return $this->expectedAsString; + foreach ($this->getTags() as $tag) { + if ($tag->getName() === $name) { + return \true; + } + } + return \false; } /** - * @return string + * Remove a tag from this DocBlock. + * + * @param Tag $tagToRemove The tag to remove. */ - public function getDiff() + public function removeTag(Tag $tagToRemove) : void { - if (!$this->actualAsString && !$this->expectedAsString) { - return ''; + foreach ($this->tags as $key => $tag) { + if ($tag === $tagToRemove) { + unset($this->tags[$key]); + break; + } } - $differ = new Differ(new UnifiedDiffOutputBuilder("\n--- Expected\n+++ Actual\n")); - return $differ->diff($this->expectedAsString, $this->actualAsString); } /** - * @return string + * Adds a tag to this DocBlock. + * + * @param Tag $tag The tag to add. */ - public function toString() + private function addTag(Tag $tag) : void { - return $this->message . $this->getDiff(); + $this->tags[] = $tag; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use function is_resource; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter; +use function vsprintf; /** - * Compares resources for equality. + * Object representing to description for a DocBlock. + * + * A Description object can consist of plain text but can also include tags. A Description Formatter can then combine + * a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete + * description text using the format that you would prefer. + * + * Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is + * thus recommended to use that to create a Description object, like this: + * + * $description = $descriptionFactory->create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Description\Formatter for the formatting of the body and tags. */ -class ResourceComparator extends Comparator +class Description { + /** @var string */ + private $bodyTemplate; + /** @var Tag[] */ + private $tags; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * Initializes a Description with its body (template) and a listing of the tags used in the body template. * - * @return bool + * @param Tag[] $tags */ - public function accepts($expected, $actual) + public function __construct(string $bodyTemplate, array $tags = []) { - return is_resource($expected) && is_resource($actual); + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure + * Returns the body template. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + public function getBodyTemplate() : string { - if ($actual != $expected) { - throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual)); - } + return $this->bodyTemplate; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use function is_float; -use function is_numeric; -/** - * Compares doubles for equality. - */ -class DoubleComparator extends NumericComparator -{ /** - * Smallest value available in PHP. + * Returns the tags for this DocBlock. * - * @var float + * @return Tag[] */ - public const EPSILON = 1.0E-10; + public function getTags() : array + { + return $this->tags; + } /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. */ - public function accepts($expected, $actual) + public function render(?Formatter $formatter = null) : string { - return (is_float($expected) || is_float($actual)) && is_numeric($expected) && is_numeric($actual); + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + return vsprintf($this->bodyTemplate, $tags); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure + * Returns a plain string representation of this description. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + public function __toString() : string { - if ($delta == 0) { - $delta = self::EPSILON; - } - parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase); + return $this->render(); } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use function abs; -use function is_float; -use function is_infinite; -use function is_nan; -use function is_numeric; -use function is_string; -use function sprintf; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use function count; +use function implode; +use function ltrim; +use function min; +use function str_replace; +use function strlen; +use function strpos; +use function substr; +use function trim; +use const PREG_SPLIT_DELIM_CAPTURE; /** - * Compares numerical values for equality. + * Creates a new Description object given a body of text. + * + * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their + * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the + * Description object's `render` method. + * + * In addition to the above does a Description support two types of escape sequences: + * + * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}` + * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description + * of an inline tag. + * + * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning + * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping + * over unexpected spaces as can be observed with tag descriptions. */ -class NumericComparator extends ScalarComparator +class DescriptionFactory { + /** @var TagFactory */ + private $tagFactory; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * Initializes this factory with the means to construct (inline) tags. */ - public function accepts($expected, $actual) + public function __construct(TagFactory $tagFactory) { - // all numerical values, but not if one of them is a double - // or both of them are strings - return is_numeric($expected) && is_numeric($actual) && !(is_float($expected) || is_float($actual)) && !(is_string($expected) && is_string($actual)); + $this->tagFactory = $tagFactory; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure + * Returns the parsed text of this description. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + public function create(string $contents, ?TypeContext $context = null) : Description { - if ($this->isInfinite($actual) && $this->isInfinite($expected)) { - return; + $tokens = $this->lex($contents); + $count = count($tokens); + $tagCount = 0; + $tags = []; + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; } - if (($this->isInfinite($actual) xor $this->isInfinite($expected)) || ($this->isNan($actual) || $this->isNan($expected)) || abs($actual - $expected) > $delta) { - throw new ComparisonFailure($expected, $actual, '', '', \false, sprintf('Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected))); + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); } + return new Description(implode('', $tokens), $tags); } - private function isInfinite($value) : bool + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex(string $contents) : array { - return is_float($value) && is_infinite($value); + $contents = $this->removeSuperfluousStartingWhitespace($contents); + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === \false) { + return [$contents]; + } + return Utils::pregSplit('/\\{ + # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally. + (?!@\\}) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \\{(?1)?\\} + | + # Make sure we match hanging "{". + \\{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \\}/Sux', $contents, 0, PREG_SPLIT_DELIM_CAPTURE); } - private function isNan($value) : bool + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + */ + private function removeSuperfluousStartingWhitespace(string $contents) : string { - return is_float($value) && is_nan($value); + $lines = Utils::pregSplit("/\r\n?|\n/", $contents); + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + // lines with a no length do not count as they are not indented at all + if (trim($lines[$i]) === '') { + continue; + } + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + return implode("\n", $lines); } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use function array_unshift; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Example; +use function array_slice; +use function file; +use function getcwd; +use function implode; +use function is_readable; +use function rtrim; +use function sprintf; +use function trim; +use const DIRECTORY_SEPARATOR; /** - * Factory for comparators which compare values for equality. + * Class used to find an example file's location based on a given ExampleDescriptor. */ -class Factory +class ExampleFinder { + /** @var string */ + private $sourceDirectory = ''; + /** @var string[] */ + private $exampleDirectories = []; /** - * @var Factory - */ - private static $instance; - /** - * @var Comparator[] - */ - private $customComparators = []; - /** - * @var Comparator[] + * Attempts to find the example contents for the given descriptor. */ - private $defaultComparators = []; + public function find(Example $example) : string + { + $filename = $example->getFilePath(); + $file = $this->getExampleFileContents($filename); + if (!$file) { + return sprintf('** File not found : %s **', $filename); + } + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } /** - * @return Factory + * Registers the project's root directory where an 'examples' folder can be expected. */ - public static function getInstance() + public function setSourceDirectory(string $directory = '') : void { - if (self::$instance === null) { - self::$instance = new self(); - // @codeCoverageIgnore - } - return self::$instance; + $this->sourceDirectory = $directory; } /** - * Constructs a new factory. + * Returns the project's root directory where an 'examples' folder can be expected. */ - public function __construct() + public function getSourceDirectory() : string { - $this->registerDefaultComparators(); + return $this->sourceDirectory; } /** - * Returns the correct comparator for comparing two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * Registers a series of directories that may contain examples. * - * @return Comparator + * @param string[] $directories */ - public function getComparatorFor($expected, $actual) + public function setExampleDirectories(array $directories) : void { - foreach ($this->customComparators as $comparator) { - if ($comparator->accepts($expected, $actual)) { - return $comparator; - } - } - foreach ($this->defaultComparators as $comparator) { - if ($comparator->accepts($expected, $actual)) { - return $comparator; - } - } - throw new RuntimeException('No suitable Comparator implementation found'); + $this->exampleDirectories = $directories; } /** - * Registers a new comparator. - * - * This comparator will be returned by getComparatorFor() if its accept() method - * returns TRUE for the compared values. It has higher priority than the - * existing comparators, meaning that its accept() method will be invoked - * before those of the other comparators. + * Returns a series of directories that may contain examples. * - * @param Comparator $comparator The comparator to be registered + * @return string[] */ - public function register(Comparator $comparator) + public function getExampleDirectories() : array { - array_unshift($this->customComparators, $comparator); - $comparator->setFactory($this); + return $this->exampleDirectories; } /** - * Unregisters a comparator. + * Attempts to find the requested example file and returns its contents or null if no file was found. * - * This comparator will no longer be considered by getComparatorFor(). + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: * - * @param Comparator $comparator The comparator to be unregistered + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @return string[] all lines of the example file */ - public function unregister(Comparator $comparator) + private function getExampleFileContents(string $filename) : ?array { - foreach ($this->customComparators as $key => $_comparator) { - if ($comparator === $_comparator) { - unset($this->customComparators[$key]); + $normalizedPath = null; + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; } } - } - /** - * Unregisters all non-default comparators. - */ - public function reset() - { - $this->customComparators = []; - } - private function registerDefaultComparators() : void - { - $this->registerDefaultComparator(new MockObjectComparator()); - $this->registerDefaultComparator(new DateTimeComparator()); - $this->registerDefaultComparator(new DOMNodeComparator()); - $this->registerDefaultComparator(new SplObjectStorageComparator()); - $this->registerDefaultComparator(new ExceptionComparator()); - $this->registerDefaultComparator(new ObjectComparator()); - $this->registerDefaultComparator(new ResourceComparator()); - $this->registerDefaultComparator(new ArrayComparator()); - $this->registerDefaultComparator(new DoubleComparator()); - $this->registerDefaultComparator(new NumericComparator()); - $this->registerDefaultComparator(new ScalarComparator()); - $this->registerDefaultComparator(new TypeComparator()); - } - private function registerDefaultComparator(Comparator $comparator) : void - { - $this->defaultComparators[] = $comparator; - $comparator->setFactory($this); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use function sprintf; -use function strtolower; -use DOMDocument; -use DOMNode; -use ValueError; -/** - * Compares DOMNode instances for equality. - */ -class DOMNodeComparator extends ObjectComparator -{ + if (!$normalizedPath) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + $lines = $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : \false; + return $lines !== \false ? $lines : null; + } /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * Get example filepath based on the example directory inside your project. */ - public function accepts($expected, $actual) + private function getExamplePathFromExampleDirectory(string $file) : string { - return $expected instanceof DOMNode && $actual instanceof DOMNode; + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) - * - * @throws ComparisonFailure + * Returns a path to the example file in the given directory.. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) + private function constructExamplePath(string $directory, string $file) : string { - $expectedAsString = $this->nodeToText($expected, \true, $ignoreCase); - $actualAsString = $this->nodeToText($actual, \true, $ignoreCase); - if ($expectedAsString !== $actualAsString) { - $type = $expected instanceof DOMDocument ? 'documents' : 'nodes'; - throw new ComparisonFailure($expected, $actual, $expectedAsString, $actualAsString, \false, sprintf("Failed asserting that two DOM %s are equal.\n", $type)); - } + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; } /** - * Returns the normalized, whitespace-cleaned, and indented textual - * representation of a DOMNode. + * Get example filepath based on sourcecode. */ - private function nodeToText(DOMNode $node, bool $canonicalize, bool $ignoreCase) : string + private function getExamplePathFromSource(string $file) : string { - if ($canonicalize) { - $document = new DOMDocument(); - try { - @$document->loadXML($node->C14N()); - } catch (ValueError $e) { - } - $node = $document; - } - $document = $node instanceof DOMDocument ? $node : $node->ownerDocument; - $document->formatOutput = \true; - $document->normalizeDocument(); - $text = $node instanceof DOMDocument ? $node->saveXML() : $document->saveXML($node); - return $ignoreCase ? strtolower($text) : $text; + return sprintf('%s%s%s', trim($this->getSourceDirectory(), '\\/'), DIRECTORY_SEPARATOR, trim($file, '"')); } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use function get_class; -use function in_array; -use function is_object; +use PHPUnit\phpDocumentor\Reflection\DocBlock; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter; use function sprintf; -use function substr_replace; +use function str_repeat; +use function str_replace; +use function strlen; +use function wordwrap; /** - * Compares objects for equality. + * Converts a DocBlock back from an object to a complete DocComment including Asterisks. */ -class ObjectComparator extends ArrayComparator +class Serializer { + /** @var string The string to indent the comment with. */ + protected $indentString = ' '; + /** @var int The number of times the indent string is repeated. */ + protected $indent = 0; + /** @var bool Whether to indent the first line with the given indent amount and string. */ + protected $isFirstLineIndented = \true; + /** @var int|null The max length of a line. */ + protected $lineLength; + /** @var Formatter A custom tag formatter. */ + protected $tagFormatter; + /** @var string */ + private $lineEnding; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * Create a Serializer instance. * - * @return bool + * @param int $indent The number of times the indent string is repeated. + * @param string $indentString The string to indent the comment with. + * @param bool $indentFirstLine Whether to indent the first line. + * @param int|null $lineLength The max length of a line or NULL to disable line wrapping. + * @param Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter. + * @param string $lineEnding Line ending used in the output, by default \n is used. */ - public function accepts($expected, $actual) + public function __construct(int $indent = 0, string $indentString = ' ', bool $indentFirstLine = \true, ?int $lineLength = null, ?Formatter $tagFormatter = null, string $lineEnding = "\n") { - return is_object($expected) && is_object($actual); + $this->indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + $this->lineEnding = $lineEnding; } /** - * Asserts that two values are equal. + * Generate a DocBlock comment. * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) + * @param DocBlock $docblock The DocBlock to serialize. * - * @throws ComparisonFailure + * @return string The serialized doc block. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) + public function getDocComment(DocBlock $docblock) : string { - if (get_class($actual) !== get_class($expected)) { - throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, sprintf('%s is not instance of expected class "%s".', $this->exporter->export($actual), get_class($expected))); - } - // don't compare twice to allow for cyclic dependencies - if (in_array([$actual, $expected], $processed, \true) || in_array([$expected, $actual], $processed, \true)) { - return; + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; + $text = $this->removeTrailingSpaces($indent, $this->addAsterisksForEachLine($indent, $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength))); + $comment = $firstIndent . "/**\n"; + if ($text) { + $comment .= $indent . ' * ' . $text . "\n"; + $comment .= $indent . " *\n"; } - $processed[] = [$actual, $expected]; - // don't compare objects if they are identical - // this helps to avoid the error "maximum function nesting level reached" - // CAUTION: this conditional clause is not tested - if ($actual !== $expected) { - try { - parent::assertEquals($this->toArray($expected), $this->toArray($actual), $delta, $canonicalize, $ignoreCase, $processed); - } catch (ComparisonFailure $e) { - throw new ComparisonFailure( - $expected, - $actual, - // replace "Array" with "MyClass object" - substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), - substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), - \false, - 'Failed asserting that two objects are equal.' - ); - } + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + return str_replace("\n", $this->lineEnding, $comment . $indent . ' */'); + } + private function removeTrailingSpaces(string $indent, string $text) : string + { + return str_replace(sprintf("\n%s * \n", $indent), sprintf("\n%s *\n", $indent), $text); + } + private function addAsterisksForEachLine(string $indent, string $text) : string + { + return str_replace("\n", sprintf("\n%s * ", $indent), $text); + } + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength) : string + { + $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + return $text; } + return $text; } - /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object - * - * @return array - */ - protected function toArray($object) + private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment) : string { - return $this->exporter->toArray($object); + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + $tagText = str_replace("\n", sprintf("\n%s * ", $indent), $tagText); + $comment .= sprintf("%s * %s\n", $indent, $tagText); + } + return $comment; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use function is_object; -use function is_scalar; -use function is_string; -use function method_exists; -use function sprintf; -use function strtolower; +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Author; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Covers; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Deprecated; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Generic; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Param; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Property; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Return_; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\See as SeeTag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Since; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Source; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Throws; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Uses; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Var_; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Version; +use PHPUnit\phpDocumentor\Reflection\FqsenResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; +use PHPUnit\Webmozart\Assert\Assert; +use function array_merge; +use function array_slice; +use function call_user_func_array; +use function count; +use function get_class; +use function preg_match; +use function strpos; +use function trim; /** - * Compares scalar or NULL values for equality. + * Creates a Tag object given the contents of a tag. + * + * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create` + * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can + * pass the dependencies that you need to construct a tag object. + * + * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. */ -class ScalarComparator extends Comparator +final class StandardTagFactory implements TagFactory { + /** PCRE regular expression matching a tag name. */ + public const REGEX_TAGNAME = '[\\w\\-\\_\\\\:]+'; /** - * Returns whether the comparator can compare two values. + * @var array> An array with a tag as a key, and an + * FQCN to a class that handles it as an array value. + */ + private $tagHandlerMappings = [ + 'author' => Author::class, + 'covers' => Covers::class, + 'deprecated' => Deprecated::class, + // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => LinkTag::class, + 'method' => Method::class, + 'param' => Param::class, + 'property-read' => PropertyRead::class, + 'property' => Property::class, + 'property-write' => PropertyWrite::class, + 'return' => Return_::class, + 'see' => SeeTag::class, + 'since' => Since::class, + 'source' => Source::class, + 'throw' => Throws::class, + 'throws' => Throws::class, + 'uses' => Uses::class, + 'var' => Var_::class, + 'version' => Version::class, + ]; + /** + * @var array> An array with a anotation s a key, and an + * FQCN to a class that handles it as an array value. + */ + private $annotationMappings = []; + /** + * @var ReflectionParameter[][] a lazy-loading cache containing parameters + * for each tagHandler that has been used. + */ + private $tagHandlerParameterCache = []; + /** @var FqsenResolver */ + private $fqsenResolver; + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private $serviceLocator = []; + /** + * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property + * is used. * - * @return bool + * @see self::registerTagHandler() to add a new tag handler to the existing default list. * - * @since Method available since Release 3.6.0 + * @param array> $tagHandlers */ - public function accepts($expected, $actual) + public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null) { - return (is_scalar($expected) xor null === $expected) && (is_scalar($actual) xor null === $actual) || is_string($expected) && is_object($actual) && method_exists($actual, '__toString') || is_object($expected) && method_exists($expected, '__toString') && is_string($actual); + $this->fqsenResolver = $fqsenResolver; + if ($tagHandlers !== null) { + $this->tagHandlerMappings = $tagHandlers; + } + $this->addService($fqsenResolver, FqsenResolver::class); + } + public function create(string $tagLine, ?TypeContext $context = null) : Tag + { + if (!$context) { + $context = new TypeContext(''); + } + [$tagName, $tagBody] = $this->extractTagParts($tagLine); + return $this->createTag(trim($tagBody), $tagName, $context); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure + * @param mixed $value */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + public function addParameter(string $name, $value) : void { - $expectedToCompare = $expected; - $actualToCompare = $actual; - // always compare as strings to avoid strange behaviour - // otherwise 0 == 'Foobar' - if (is_string($expected) || is_string($actual)) { - $expectedToCompare = (string) $expectedToCompare; - $actualToCompare = (string) $actualToCompare; - if ($ignoreCase) { - $expectedToCompare = strtolower($expectedToCompare); - $actualToCompare = strtolower($actualToCompare); - } - } - if ($expectedToCompare !== $actualToCompare && is_string($expected) && is_string($actual)) { - throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two strings are equal.'); - } - if ($expectedToCompare != $actualToCompare) { - throw new ComparisonFailure( - $expected, - $actual, - // no diff is required - '', - '', - \false, - sprintf('Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected)) - ); + $this->serviceLocator[$name] = $value; + } + public function addService(object $service, ?string $alias = null) : void + { + $this->serviceLocator[$alias ?: get_class($service)] = $service; + } + public function registerTagHandler(string $tagName, string $handler) : void + { + Assert::stringNotEmpty($tagName); + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); + if (strpos($tagName, '\\') && $tagName[0] !== '\\') { + throw new InvalidArgumentException('A namespaced tag must have a leading backslash as it must be fully qualified'); } + $this->tagHandlerMappings[$tagName] = $handler; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use function abs; -use function floor; -use function sprintf; -use DateInterval; -use DateTime; -use DateTimeInterface; -use DateTimeZone; -use Exception; -/** - * Compares DateTimeInterface instances for equality. - */ -class DateTimeComparator extends ObjectComparator -{ /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * Extracts all components for a tag. * - * @return bool + * @return string[] */ - public function accepts($expected, $actual) + private function extractTagParts(string $tagLine) : array { - return ($expected instanceof DateTime || $expected instanceof DateTimeInterface) && ($actual instanceof DateTime || $actual instanceof DateTimeInterface); + $matches = []; + if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\\s\\(\\{])\\s*([^\\s].*)|$)/us', $tagLine, $matches)) { + throw new InvalidArgumentException('The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'); + } + if (count($matches) < 3) { + $matches[] = ''; + } + return array_slice($matches, 1); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) - * - * @throws Exception - * @throws ComparisonFailure + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) + private function createTag(string $body, string $name, TypeContext $context) : Tag { - /** @var DateTimeInterface $expected */ - /** @var DateTimeInterface $actual */ - $absDelta = abs($delta); - $delta = new DateInterval(sprintf('PT%dS', $absDelta)); - $delta->f = $absDelta - floor($absDelta); - $actualClone = (clone $actual)->setTimezone(new DateTimeZone('UTC')); - $expectedLower = (clone $expected)->setTimezone(new DateTimeZone('UTC'))->sub($delta); - $expectedUpper = (clone $expected)->setTimezone(new DateTimeZone('UTC'))->add($delta); - if ($actualClone < $expectedLower || $actualClone > $expectedUpper) { - throw new ComparisonFailure($expected, $actual, $this->dateTimeToString($expected), $this->dateTimeToString($actual), \false, 'Failed asserting that two DateTime objects are equal.'); + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring($this->fetchParametersForHandlerFactoryMethod($handlerClassName), $this->getServiceLocatorWithDynamicParameters($context, $name, $body)); + try { + $callable = [$handlerClassName, 'create']; + Assert::isCallable($callable); + /** @phpstan-var callable(string): ?Tag $callable */ + $tag = call_user_func_array($callable, $arguments); + return $tag ?? InvalidTag::create($body, $name); + } catch (InvalidArgumentException $e) { + return InvalidTag::create($body, $name)->withError($e); } } /** - * Returns an ISO 8601 formatted string representation of a datetime or - * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly - * initialized. + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @return class-string */ - private function dateTimeToString(DateTimeInterface $datetime) : string + private function findHandlerClassName(string $tagName, TypeContext $context) : string { - $string = $datetime->format('Y-m-d\\TH:i:s.uO'); - return $string ?: 'Invalid DateTimeInterface object'; + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); + if (isset($this->annotationMappings[$tagName])) { + $handlerClassName = $this->annotationMappings[$tagName]; + } + } + return $handlerClassName; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use function gettype; -use function sprintf; -/** - * Compares values for type equality. - */ -class TypeComparator extends Comparator -{ /** - * Returns whether the comparator can compare two values. + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * @param ReflectionParameter[] $parameters + * @param mixed[] $locator * - * @return bool + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. */ - public function accepts($expected, $actual) + private function getArgumentsForParametersFromWiring(array $parameters, array $locator) : array { - return \true; + $arguments = []; + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $typeHint = null; + if ($type instanceof ReflectionNamedType) { + $typeHint = $type->getName(); + if ($typeHint === 'self') { + $declaringClass = $parameter->getDeclaringClass(); + if ($declaringClass !== null) { + $typeHint = $declaringClass->getName(); + } + } + } + if (isset($locator[$typeHint])) { + $arguments[] = $locator[$typeHint]; + continue; + } + $parameterName = $parameter->getName(); + if (isset($locator[$parameterName])) { + $arguments[] = $locator[$parameterName]; + continue; + } + $arguments[] = null; + } + return $arguments; } /** - * Asserts that two values are equal. + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true + * @param class-string $handlerClassName * - * @throws ComparisonFailure + * @return ReflectionParameter[] */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + private function fetchParametersForHandlerFactoryMethod(string $handlerClassName) : array { - if (gettype($expected) != gettype($actual)) { - throw new ComparisonFailure( - $expected, - $actual, - // we don't need a diff - '', - '', - \false, - sprintf('%s does not match expected type "%s".', $this->exporter->shortenedExport($actual), gettype($expected)) - ); + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); } + return $this->tagHandlerParameterCache[$handlerClassName]; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Comparator; - -use Exception; -/** - * Compares Exception instances for equality. - */ -class ExceptionComparator extends ObjectComparator -{ /** - * Returns whether the comparator can compare two values. + * Returns a copy of this class' Service Locator with added dynamic parameters, + * such as the tag's name, body and Context. * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare + * @param TypeContext $context The Context (namespace and aliasses) that may be + * passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be + * passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be + * passed onto the factory method of the Tag class. * - * @return bool + * @return mixed[] */ - public function accepts($expected, $actual) + private function getServiceLocatorWithDynamicParameters(TypeContext $context, string $tagName, string $tagBody) : array { - return $expected instanceof Exception && $actual instanceof Exception; + return array_merge($this->serviceLocator, ['name' => $tagName, 'body' => $tagBody, TypeContext::class => $context]); } /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object + * Returns whether the given tag belongs to an annotation. * - * @return array + * @todo this method should be populated once we implement Annotation notation support. */ - protected function toArray($object) + private function isAnnotation(string $tagContent) : bool { - $array = parent::toArray($object); - unset($array['file'], $array['line'], $array['trace'], $array['string'], $array['xdebug_message']); - return $array; + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + return \false; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -final class RuntimeException extends \RuntimeException implements Exception +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +interface Tag { + public function getName() : string; + /** + * @return Tag|mixed Class that implements Tag + * @phpstan-return ?Tag + */ + public static function create(string $body); + public function render(?Formatter $formatter = null) : string; + public function __toString() : string; } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock; -use Throwable; -interface Exception extends Throwable +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +interface TagFactory { + /** + * Adds a parameter to the service locator that can be injected in a tag's factory method. + * + * When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to + * typehint a parameter in the signature so that we can use that interface or class name to inject a dependency + * (see {@see addService()} for more information on that). + * + * Another way is to check the name of the argument against the names in the Service Locator. With this method + * you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching + * name. + * + * Be aware that there are two reserved names: + * + * - name, representing the name of the tag. + * - body, representing the complete body of the tag. + * + * These parameters are injected at the last moment and will override any existing parameter with those names. + * + * @param mixed $value + */ + public function addParameter(string $name, $value) : void; + /** + * Factory method responsible for instantiating the correct sub type. + * + * @param string $tagLine The text for this tag, including description. + * + * @return Tag A new tag object. + * + * @throws InvalidArgumentException If an invalid tag line was presented. + */ + public function create(string $tagLine, ?TypeContext $context = null) : Tag; + /** + * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided. + * + * When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter + * has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint. + * + * Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the + * interface is passed as alias then every time that interface is requested the provided service will be returned. + */ + public function addService(object $service) : void; + /** + * Registers a handler for tags. + * + * If you want to use your own tags then you can use this method to instruct the TagFactory + * to register the name of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement + * the {@see Tag} interface (and thus the create method). + * + * @param string $tagName Name of tag to register a handler for. When registering a namespaced + * tag, the full name, along with a prefixing slash MUST be provided. + * @param class-string $handler FQCN of handler. + * + * @throws InvalidArgumentException If the tag name is not a string. + * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but + * does not start with a backslash. + * @throws InvalidArgumentException If the handler is not a string. + * @throws InvalidArgumentException If the handler is not an existing class. + * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + */ + public function registerTagHandler(string $tagName, string $handler) : void; } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use SplObjectStorage; +use InvalidArgumentException; +use function filter_var; +use function preg_match; +use function trim; +use const FILTER_VALIDATE_EMAIL; /** - * Compares \SplObjectStorage instances for equality. + * Reflection class for an {@}author tag in a Docblock. */ -class SplObjectStorageComparator extends Comparator +final class Author extends BaseTag implements Factory\StaticMethod { + /** @var string register that this is the author tag. */ + protected $name = 'author'; + /** @var string The name of the author */ + private $authorName; + /** @var string The email of the author */ + private $authorEmail; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * Initializes this tag with the author name and e-mail. */ - public function accepts($expected, $actual) + public function __construct(string $authorName, string $authorEmail) { - return $expected instanceof SplObjectStorage && $actual instanceof SplObjectStorage; + if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) { + throw new InvalidArgumentException('The author tag does not have a valid e-mail address'); + } + $this->authorName = $authorName; + $this->authorEmail = $authorEmail; } /** - * Asserts that two values are equal. + * Gets the author's name. * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true + * @return string The author's name. + */ + public function getAuthorName() : string + { + return $this->authorName; + } + /** + * Returns the author's email. * - * @throws ComparisonFailure + * @return string The author's email. */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) + public function getEmail() : string { - foreach ($actual as $object) { - if (!$expected->contains($object)) { - throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two objects are equal.'); - } + return $this->authorEmail; + } + /** + * Returns this tag in string form. + */ + public function __toString() : string + { + if ($this->authorEmail) { + $authorEmail = '<' . $this->authorEmail . '>'; + } else { + $authorEmail = ''; } - foreach ($expected as $object) { - if (!$actual->contains($object)) { - throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two objects are equal.'); - } + $authorName = $this->authorName; + return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); + } + /** + * Attempts to create a new Author object based on the tag body. + */ + public static function create(string $body) : ?self + { + $splitTagContent = preg_match('/^([^\\<]*)(?:\\<([^\\>]*)\\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; } + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + return new static($authorName, $email); } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use PHPUnit\SebastianBergmann\Exporter\Exporter; +use PHPUnit\phpDocumentor\Reflection\DocBlock; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; /** - * Abstract base class for comparators which compare values for equality. + * Parses a tag definition for a DocBlock. */ -abstract class Comparator +abstract class BaseTag implements DocBlock\Tag { + /** @var string Name of the tag */ + protected $name = ''; + /** @var Description|null Description of the tag. */ + protected $description; /** - * @var Factory - */ - protected $factory; - /** - * @var Exporter + * Gets the name of this tag. + * + * @return string The name of this tag. */ - protected $exporter; - public function __construct() + public function getName() : string { - $this->exporter = new Exporter(); + return $this->name; } - public function setFactory(Factory $factory) + public function getDescription() : ?Description { - $this->factory = $factory; + return $this->description; + } + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + return $formatter->format($this); } - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public abstract function accepts($expected, $actual); - /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure - */ - public abstract function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false); } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\FqsenResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; use function array_key_exists; -use function is_array; -use function sort; -use function sprintf; -use function str_replace; -use function trim; +use function explode; /** - * Compares arrays for equality. - * - * Arrays are equal if they contain the same key-value pairs. - * The order of the keys does not matter. - * The types of key-value pairs do not matter. + * Reflection class for a @covers tag in a Docblock. */ -class ArrayComparator extends Comparator +final class Covers extends BaseTag implements Factory\StaticMethod { + /** @var string */ + protected $name = 'covers'; + /** @var Fqsen */ + private $refers; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * Initializes this tag. */ - public function accepts($expected, $actual) + public function __construct(Fqsen $refers, ?Description $description = null) { - return is_array($expected) && is_array($actual); + $this->refers = $refers; + $this->description = $description; } - /** - * Asserts that two arrays are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) - * - * @throws ComparisonFailure - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) + public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?FqsenResolver $resolver = null, ?TypeContext $context = null) : self { - if ($canonicalize) { - sort($expected); - sort($actual); - } - $remaining = $actual; - $actualAsString = "Array (\n"; - $expectedAsString = "Array (\n"; - $equal = \true; - foreach ($expected as $key => $value) { - unset($remaining[$key]); - if (!array_key_exists($key, $actual)) { - $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); - $equal = \false; - continue; - } - try { - $comparator = $this->factory->getComparatorFor($value, $actual[$key]); - $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); - $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); - $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($actual[$key])); - } catch (ComparisonFailure $e) { - $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected())); - $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual())); - $equal = \false; - } - } - foreach ($remaining as $key => $value) { - $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); - $equal = \false; - } - $expectedAsString .= ')'; - $actualAsString .= ')'; - if (!$equal) { - throw new ComparisonFailure($expected, $actual, $expectedAsString, $actualAsString, \false, 'Failed asserting that two arrays are equal.'); + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + Assert::notNull($resolver); + $parts = Utils::pregSplit('/\\s+/Su', $body, 2); + return new static(self::resolveFqsen($parts[0], $resolver, $context), $descriptionFactory->create($parts[1] ?? '', $context)); + } + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; } + return new Fqsen($resolved . '::' . $fqsenParts[1]); } - protected function indent($lines) + /** + * Returns the structural element this tag refers to. + */ + public function getReference() : Fqsen { - return trim(str_replace("\n", "\n ", $lines)); + return $this->refers; } -} -Comparator - -Copyright (c) 2002-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. + /** + * Returns a string representation of this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $refers = (string) $this->refers; + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Comparator; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +use function preg_match; /** - * Compares PHPUnit\Framework\MockObject\MockObject instances for equality. + * Reflection class for a {@}deprecated tag in a Docblock. */ -class MockObjectComparator extends ObjectComparator +final class Deprecated extends BaseTag implements Factory\StaticMethod { + /** @var string */ + protected $name = 'deprecated'; /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. */ - public function accepts($expected, $actual) + public const REGEX_VECTOR = '(?: + # Normal release vectors. + \\d\\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ + )'; + /** @var string|null The version vector. */ + private $version; + public function __construct(?string $version = null, ?Description $description = null) { - return $expected instanceof MockObject && $actual instanceof MockObject; + Assert::nullOrNotEmpty($version); + $this->version = $version; + $this->description = $description; } /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object - * - * @return array + * @return static */ - protected function toArray($object) + public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - $array = parent::toArray($object); - unset($array['__phpunit_invocationMocker']); - return $array; + if (empty($body)) { + return new static(); + } + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { + return new static(null, $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null); + } + Assert::notNull($descriptionFactory); + return new static($matches[1], $descriptionFactory->create($matches[2] ?? '', $context)); + } + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string + { + return $this->version; + } + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $version = (string) $this->version; + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use PHPUnit\PharIo\Version\Exception as VersionException; -use PHPUnit\PharIo\Version\Version; -use PHPUnit\PharIo\Version\VersionConstraintParser; -class ManifestDocumentMapper +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; +use PHPUnit\Webmozart\Assert\Assert; +use function array_key_exists; +use function preg_match; +use function rawurlencode; +use function str_replace; +use function strpos; +use function trim; +/** + * Reflection class for a {@}example tag in a Docblock. + */ +final class Example implements Tag, Factory\StaticMethod { - public function map(ManifestDocument $document) : Manifest + /** @var string Path to a file to use as an example. May also be an absolute URI. */ + private $filePath; + /** + * @var bool Whether the file path component represents an URI. This determines how the file portion + * appears at {@link getContent()}. + */ + private $isURI; + /** @var int */ + private $startingLine; + /** @var int */ + private $lineCount; + /** @var string|null */ + private $content; + public function __construct(string $filePath, bool $isURI, int $startingLine, int $lineCount, ?string $content) { - try { - $contains = $document->getContainsElement(); - $type = $this->mapType($contains); - $copyright = $this->mapCopyright($document->getCopyrightElement()); - $requirements = $this->mapRequirements($document->getRequiresElement()); - $bundledComponents = $this->mapBundledComponents($document); - return new Manifest(new ApplicationName($contains->getName()), new Version($contains->getVersion()), $type, $copyright, $requirements, $bundledComponents); - } catch (VersionException $e) { - throw new ManifestDocumentMapperException($e->getMessage(), (int) $e->getCode(), $e); - } catch (Exception $e) { - throw new ManifestDocumentMapperException($e->getMessage(), (int) $e->getCode(), $e); + Assert::stringNotEmpty($filePath); + Assert::greaterThanEq($startingLine, 1); + Assert::greaterThanEq($lineCount, 0); + $this->filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + if ($content !== null) { + $this->content = trim($content); } + $this->isURI = $isURI; } - private function mapType(ContainsElement $contains) : Type + public function getContent() : string { - switch ($contains->getType()) { - case 'application': - return Type::application(); - case 'library': - return Type::library(); - case 'extension': - return $this->mapExtension($contains->getExtensionElement()); + if ($this->content === null || $this->content === '') { + $filePath = $this->filePath; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) ? str_replace('%2F', '/', rawurlencode($this->filePath)) : $this->filePath; + } + return trim($filePath); } - throw new ManifestDocumentMapperException(\sprintf('Unsupported type %s', $contains->getType())); + return $this->content; } - private function mapCopyright(CopyrightElement $copyright) : CopyrightInformation + public function getDescription() : ?string { - $authors = new AuthorCollection(); - foreach ($copyright->getAuthorElements() as $authorElement) { - $authors->add(new Author($authorElement->getName(), new Email($authorElement->getEmail()))); - } - $licenseElement = $copyright->getLicenseElement(); - $license = new License($licenseElement->getType(), new Url($licenseElement->getUrl())); - return new CopyrightInformation($authors, $license); + return $this->content; } - private function mapRequirements(RequiresElement $requires) : RequirementCollection + public static function create(string $body) : ?Tag { - $collection = new RequirementCollection(); - $phpElement = $requires->getPHPElement(); - $parser = new VersionConstraintParser(); - try { - $versionConstraint = $parser->parse($phpElement->getVersion()); - } catch (VersionException $e) { - throw new ManifestDocumentMapperException(\sprintf('Unsupported version constraint - %s', $e->getMessage()), (int) $e->getCode(), $e); + // File component: File path in quotes or File URI / Source information + if (!preg_match('/^\\s*(?:(\\"[^\\"]+\\")|(\\S+))(?:\\s+(.*))?$/sux', $body, $matches)) { + return null; } - $collection->add(new PhpVersionRequirement($versionConstraint)); - if (!$phpElement->hasExtElements()) { - return $collection; + $filePath = null; + $fileUri = null; + if ($matches[1] !== '') { + $filePath = $matches[1]; + } else { + $fileUri = $matches[2]; } - foreach ($phpElement->getExtElements() as $extElement) { - $collection->add(new PhpExtensionRequirement($extElement->getName())); + $startingLine = 1; + $lineCount = 0; + $description = null; + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\\d*)(?:\\s+((?1))\\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int) $contentMatches[1]; + if (isset($contentMatches[2])) { + $lineCount = (int) $contentMatches[2]; + } + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } } - return $collection; + return new static($filePath ?? $fileUri ?? '', $fileUri !== null, $startingLine, $lineCount, $description); } - private function mapBundledComponents(ManifestDocument $document) : BundledComponentCollection + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath() : string { - $collection = new BundledComponentCollection(); - if (!$document->hasBundlesElement()) { - return $collection; - } - foreach ($document->getBundlesElement()->getComponentElements() as $componentElement) { - $collection->add(new BundledComponent($componentElement->getName(), new Version($componentElement->getVersion()))); - } - return $collection; + return trim($this->filePath, '"'); } - private function mapExtension(ExtensionElement $extension) : Extension + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - try { - $versionConstraint = (new VersionConstraintParser())->parse($extension->getCompatible()); - return Type::extension(new ApplicationName($extension->getFor()), $versionConstraint); - } catch (VersionException $e) { - throw new ManifestDocumentMapperException(\sprintf('Unsupported version constraint - %s', $e->getMessage()), (int) $e->getCode(), $e); + $filePath = $this->filePath; + $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; + $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; + $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; + $content = (string) $this->content; + return $filePath . ($startingLine !== '' ? ($filePath !== '' ? ' ' : '') . $startingLine : '') . ($lineCount !== '' ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount : '') . ($content !== '' ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content : ''); + } + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + */ + private function isUriRelative(string $uri) : bool + { + return strpos($uri, ':') === \false; + } + public function getStartingLine() : int + { + return $this->startingLine; + } + public function getLineCount() : int + { + return $this->lineCount; + } + public function getName() : string + { + return 'example'; + } + public function render(?Formatter $formatter = null) : string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); } + return $formatter->format($this); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Factory; -class InvalidUrlException extends \InvalidArgumentException implements Exception +/** + * @deprecated This contract is totally covered by Tag contract. Every class using StaticMethod also use Tag + */ +interface StaticMethod { + /** + * @return mixed + */ + public static function create(string $body); } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; - -class InvalidApplicationNameException extends \InvalidArgumentException implements Exception -{ - public const InvalidFormat = 2; -} -, Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; -class InvalidEmailException extends \InvalidArgumentException implements Exception +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use function max; +use function str_repeat; +use function strlen; +class AlignFormatter implements Formatter { + /** @var int The maximum tag name length. */ + protected $maxLen = 0; + /** + * @param Tag[] $tags All tags that should later be aligned with the formatter. + */ + public function __construct(array $tags) + { + foreach ($tags as $tag) { + $this->maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag) : string + { + return '@' . $tag->getName() . str_repeat(' ', $this->maxLen - strlen($tag->getName()) + 1) . $tag; + } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; -interface Exception extends \Throwable +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use function trim; +class PassthroughFormatter implements Formatter { + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag) : string + { + return trim('@' . $tag->getName() . ' ' . $tag); + } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use LibXMLError; -class ManifestDocumentLoadingException extends \Exception implements Exception +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\DocBlock\StandardTagFactory; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +use function preg_match; +/** + * Parses a tag definition for a DocBlock. + */ +final class Generic extends BaseTag implements Factory\StaticMethod { - /** @var LibXMLError[] */ - private $libxmlErrors; /** - * ManifestDocumentLoadingException constructor. + * Parses a tag and populates the member variables. * - * @param LibXMLError[] $libxmlErrors + * @param string $name Name of the tag. + * @param Description $description The contents of the given tag. */ - public function __construct(array $libxmlErrors) + public function __construct(string $name, ?Description $description = null) { - $this->libxmlErrors = $libxmlErrors; - $first = $this->libxmlErrors[0]; - parent::__construct(\sprintf('%s (Line: %d / Column: %d / File: %s)', $first->message, $first->line, $first->column, $first->file), $first->code); + $this->validateTagName($name); + $this->name = $name; + $this->description = $description; } /** - * @return LibXMLError[] + * Creates a new tag that represents any unknown tag type. + * + * @return static */ - public function getLibxmlErrors() : array + public static function create(string $body, string $name = '', ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->libxmlErrors; + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; + return new static($name, $description); + } + /** + * Returns the tag as a serialized string + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + return $description; + } + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + */ + private function validateTagName(string $name) : void + { + if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new InvalidArgumentException('The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' . 'hyphens and backslashes.'); + } } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class ElementCollectionException extends \InvalidArgumentException implements Exception -{ -} -, Sebastian Heuer , Sebastian Bergmann + * Since the internals of the library are relaying on the correct syntax of a docblock + * we cannot simply throw exceptions at all time because the exceptions will break the creation of a + * docklock. Just silently ignore the exceptions is not an option because the user as an issue to fix. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * This tag holds that error information until a using application is able to display it. The object wil just behave + * like any normal tag. So the normal application flow will not break. */ -namespace PHPUnit\PharIo\Manifest; - -use PHPUnit\PharIo\Version\AnyVersionConstraint; -use PHPUnit\PharIo\Version\Version; -use PHPUnit\PharIo\Version\VersionConstraint; -use XMLWriter; -/** @psalm-suppress MissingConstructor */ -class ManifestSerializer +final class InvalidTag implements Tag { - /** @var XMLWriter */ - private $xmlWriter; - public function serializeToFile(Manifest $manifest, string $filename) : void + /** @var string */ + private $name; + /** @var string */ + private $body; + /** @var Throwable|null */ + private $throwable; + private function __construct(string $name, string $body) { - \file_put_contents($filename, $this->serializeToString($manifest)); + $this->name = $name; + $this->body = $body; } - public function serializeToString(Manifest $manifest) : string + public function getException() : ?Throwable { - $this->startDocument(); - $this->addContains($manifest->getName(), $manifest->getVersion(), $manifest->getType()); - $this->addCopyright($manifest->getCopyrightInformation()); - $this->addRequirements($manifest->getRequirements()); - $this->addBundles($manifest->getBundledComponents()); - return $this->finishDocument(); + return $this->throwable; } - private function startDocument() : void + public function getName() : string { - $xmlWriter = new XMLWriter(); - $xmlWriter->openMemory(); - $xmlWriter->setIndent(\true); - $xmlWriter->setIndentString(\str_repeat(' ', 4)); - $xmlWriter->startDocument('1.0', 'UTF-8'); - $xmlWriter->startElement('phar'); - $xmlWriter->writeAttribute('xmlns', 'https://phar.io/xml/manifest/1.0'); - $this->xmlWriter = $xmlWriter; + return $this->name; } - private function finishDocument() : string + public static function create(string $body, string $name = '') : self { - $this->xmlWriter->endElement(); - $this->xmlWriter->endDocument(); - return $this->xmlWriter->outputMemory(); + return new self($name, $body); } - private function addContains(ApplicationName $name, Version $version, Type $type) : void + public function withError(Throwable $exception) : self { - $this->xmlWriter->startElement('contains'); - $this->xmlWriter->writeAttribute('name', $name->asString()); - $this->xmlWriter->writeAttribute('version', $version->getVersionString()); - switch (\true) { - case $type->isApplication(): - $this->xmlWriter->writeAttribute('type', 'application'); - break; - case $type->isLibrary(): - $this->xmlWriter->writeAttribute('type', 'library'); - break; - case $type->isExtension(): - $this->xmlWriter->writeAttribute('type', 'extension'); - /* @var $type Extension */ - $this->addExtension($type->getApplicationName(), $type->getVersionConstraint()); - break; - default: - $this->xmlWriter->writeAttribute('type', 'custom'); - } - $this->xmlWriter->endElement(); + $this->flattenExceptionBacktrace($exception); + $tag = new self($this->name, $this->body); + $tag->throwable = $exception; + return $tag; } - private function addCopyright(CopyrightInformation $copyrightInformation) : void + /** + * Removes all complex types from backtrace + * + * Not all objects are serializable. So we need to remove them from the + * stored exception to be sure that we do not break existing library usage. + */ + private function flattenExceptionBacktrace(Throwable $exception) : void { - $this->xmlWriter->startElement('copyright'); - foreach ($copyrightInformation->getAuthors() as $author) { - $this->xmlWriter->startElement('author'); - $this->xmlWriter->writeAttribute('name', $author->getName()); - $this->xmlWriter->writeAttribute('email', $author->getEmail()->asString()); - $this->xmlWriter->endElement(); - } - $license = $copyrightInformation->getLicense(); - $this->xmlWriter->startElement('license'); - $this->xmlWriter->writeAttribute('type', $license->getName()); - $this->xmlWriter->writeAttribute('url', $license->getUrl()->asString()); - $this->xmlWriter->endElement(); - $this->xmlWriter->endElement(); + $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); + $traceProperty->setAccessible(\true); + do { + $trace = $exception->getTrace(); + if (isset($trace[0]['args'])) { + $trace = array_map(function (array $call) : array { + $call['args'] = array_map([$this, 'flattenArguments'], $call['args'] ?? []); + return $call; + }, $trace); + } + $traceProperty->setValue($exception, $trace); + $exception = $exception->getPrevious(); + } while ($exception !== null); + $traceProperty->setAccessible(\false); } - private function addRequirements(RequirementCollection $requirementCollection) : void + /** + * @param mixed $value + * + * @return mixed + * + * @throws ReflectionException + */ + private function flattenArguments($value) { - $phpRequirement = new AnyVersionConstraint(); - $extensions = []; - foreach ($requirementCollection as $requirement) { - if ($requirement instanceof PhpVersionRequirement) { - $phpRequirement = $requirement->getVersionConstraint(); - continue; - } - if ($requirement instanceof PhpExtensionRequirement) { - $extensions[] = $requirement->asString(); - } - } - $this->xmlWriter->startElement('requires'); - $this->xmlWriter->startElement('php'); - $this->xmlWriter->writeAttribute('version', $phpRequirement->asString()); - foreach ($extensions as $extension) { - $this->xmlWriter->startElement('ext'); - $this->xmlWriter->writeAttribute('name', $extension); - $this->xmlWriter->endElement(); + if ($value instanceof Closure) { + $closureReflection = new ReflectionFunction($value); + $value = sprintf('(Closure at %s:%s)', $closureReflection->getFileName(), $closureReflection->getStartLine()); + } elseif (is_object($value)) { + $value = sprintf('object(%s)', get_class($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (is_array($value)) { + $value = array_map([$this, 'flattenArguments'], $value); } - $this->xmlWriter->endElement(); - $this->xmlWriter->endElement(); + return $value; } - private function addBundles(BundledComponentCollection $bundledComponentCollection) : void + public function render(?Formatter $formatter = null) : string { - if (\count($bundledComponentCollection) === 0) { - return; - } - $this->xmlWriter->startElement('bundles'); - foreach ($bundledComponentCollection as $bundledComponent) { - $this->xmlWriter->startElement('component'); - $this->xmlWriter->writeAttribute('name', $bundledComponent->getName()); - $this->xmlWriter->writeAttribute('version', $bundledComponent->getVersion()->getVersionString()); - $this->xmlWriter->endElement(); + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); } - $this->xmlWriter->endElement(); + return $formatter->format($this); } - private function addExtension(ApplicationName $applicationName, VersionConstraint $versionConstraint) : void + public function __toString() : string { - $this->xmlWriter->startElement('extension'); - $this->xmlWriter->writeAttribute('for', $applicationName->asString()); - $this->xmlWriter->writeAttribute('compatible', $versionConstraint->asString()); - $this->xmlWriter->endElement(); + return $this->body; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ExtElement extends ManifestElement +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +/** + * Reflection class for a {@}link tag in a Docblock. + */ +final class Link extends BaseTag implements Factory\StaticMethod { - public function getName() : string + /** @var string */ + protected $name = 'link'; + /** @var string */ + private $link; + /** + * Initializes a link to a URL. + */ + public function __construct(string $link, ?Description $description = null) { - return $this->getAttributeValue('name'); + $this->link = $link; + $this->description = $description; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class CopyrightElement extends ManifestElement -{ - public function getAuthorElements() : AuthorElementCollection + public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return new AuthorElementCollection($this->getChildrenByName('author')); + Assert::notNull($descriptionFactory); + $parts = Utils::pregSplit('/\\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + return new static($parts[0], $description); } - public function getLicenseElement() : LicenseElement + /** + * Gets the link + */ + public function getLink() : string { - return new LicenseElement($this->getChildByName('license')); + return $this->link; + } + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $link = $this->link; + return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class PhpElement extends ManifestElement +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Types\Mixed_; +use PHPUnit\phpDocumentor\Reflection\Types\Void_; +use PHPUnit\Webmozart\Assert\Assert; +use function array_keys; +use function explode; +use function implode; +use function is_string; +use function preg_match; +use function sort; +use function strpos; +use function substr; +use function trim; +use function var_export; +/** + * Reflection class for an {@}method in a Docblock. + */ +final class Method extends BaseTag implements Factory\StaticMethod { - public function getVersion() : string + /** @var string */ + protected $name = 'method'; + /** @var string */ + private $methodName; + /** + * @phpstan-var array + * @var array> + */ + private $arguments; + /** @var bool */ + private $isStatic; + /** @var Type */ + private $returnType; + /** + * @param array> $arguments + * @phpstan-param array $arguments + */ + public function __construct(string $methodName, array $arguments = [], ?Type $returnType = null, bool $static = \false, ?Description $description = null) { - return $this->getAttributeValue('version'); + Assert::stringNotEmpty($methodName); + if ($returnType === null) { + $returnType = new Void_(); + } + $this->methodName = $methodName; + $this->arguments = $this->filterArguments($arguments); + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; } - public function hasExtElements() : bool + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self { - return $this->hasChild('ext'); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 5. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 6. any remaining text : as description + if (!preg_match('/^ + # Static keyword + # Declares a static method ONLY if type is also present + (?: + (static) + \\s+ + )? + # Return type + (?: + ( + (?:[\\w\\|_\\\\]*\\$this[\\w\\|_\\\\]*) + | + (?: + (?:[\\w\\|_\\\\]+) + # array notation + (?:\\[\\])* + )*+ + ) + \\s+ + )? + # Method name + ([\\w_]+) + # Arguments + (?: + \\(([^\\)]*)\\) + )? + \\s* + # Description + (.*) + $/sux', $body, $matches)) { + return null; + } + [, $static, $returnType, $methodName, $argumentLines, $description] = $matches; + $static = $static === 'static'; + if ($returnType === '') { + $returnType = 'void'; + } + $returnType = $typeResolver->resolve($returnType, $context); + $description = $descriptionFactory->create($description, $context); + /** @phpstan-var array $arguments */ + $arguments = []; + if ($argumentLines !== '') { + $argumentsExploded = explode(',', $argumentLines); + foreach ($argumentsExploded as $argument) { + $argument = explode(' ', self::stripRestArg(trim($argument)), 2); + if (strpos($argument[0], '$') === 0) { + $argumentName = substr($argument[0], 1); + $argumentType = new Mixed_(); + } else { + $argumentType = $typeResolver->resolve($argument[0], $context); + $argumentName = ''; + if (isset($argument[1])) { + $argument[1] = self::stripRestArg($argument[1]); + $argumentName = substr($argument[1], 1); + } + } + $arguments[] = ['name' => $argumentName, 'type' => $argumentType]; + } + } + return new static($methodName, $arguments, $returnType, $static, $description); } - public function getExtElements() : ExtElementCollection + /** + * Retrieves the method name. + */ + public function getMethodName() : string { - return new ExtElementCollection($this->getChildrenByName('ext')); + return $this->methodName; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class AuthorElementCollection extends ElementCollection -{ - public function current() : AuthorElement + /** + * @return array> + * @phpstan-return array + */ + public function getArguments() : array { - return new AuthorElement($this->getCurrentElement()); + return $this->arguments; + } + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic() : bool + { + return $this->isStatic; + } + public function getReturnType() : Type + { + return $this->returnType; + } + public function __toString() : string + { + $arguments = []; + foreach ($this->arguments as $argument) { + $arguments[] = $argument['type'] . ' $' . $argument['name']; + } + $argumentStr = '(' . implode(', ', $arguments) . ')'; + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $static = $this->isStatic ? 'static' : ''; + $returnType = (string) $this->returnType; + $methodName = $this->methodName; + return $static . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '') . $argumentStr . ($description !== '' ? ' ' . $description : ''); + } + /** + * @param mixed[][]|string[] $arguments + * @phpstan-param array $arguments + * + * @return mixed[][] + * @phpstan-return array + */ + private function filterArguments(array $arguments = []) : array + { + $result = []; + foreach ($arguments as $argument) { + if (is_string($argument)) { + $argument = ['name' => $argument]; + } + if (!isset($argument['type'])) { + $argument['type'] = new Mixed_(); + } + $keys = array_keys($argument); + sort($keys); + if ($keys !== ['name', 'type']) { + throw new InvalidArgumentException('Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, \true)); + } + $result[] = $argument; + } + return $result; + } + private static function stripRestArg(string $argument) : string + { + if (strpos($argument, '...') === 0) { + $argument = trim(substr($argument, 3)); + } + return $argument; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use DOMElement; -use DOMNodeList; -abstract class ElementCollection implements \Iterator +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function array_unshift; +use function implode; +use function strpos; +use function substr; +use const PREG_SPLIT_DELIM_CAPTURE; +/** + * Reflection class for the {@}param tag in a Docblock. + */ +final class Param extends TagWithType implements Factory\StaticMethod { - /** @var DOMElement[] */ - private $nodes = []; - /** @var int */ - private $position; - public function __construct(DOMNodeList $nodeList) + /** @var string|null */ + private $variableName; + /** @var bool determines whether this is a variadic argument */ + private $isVariadic; + /** @var bool determines whether this is passed by reference */ + private $isReference; + public function __construct(?string $variableName, ?Type $type = null, bool $isVariadic = \false, ?Description $description = null, bool $isReference = \false) { - $this->position = 0; - $this->importNodes($nodeList); + $this->name = 'param'; + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + $this->isReference = $isReference; } - #[\ReturnTypeWillChange] - public abstract function current(); - public function next() : void + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - $this->position++; + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + $isVariadic = \false; + $isReference = \false; + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && !self::strStartsWithVariable($firstPart)) { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + // if the next item starts with a $ or ...$ or &$ or &...$ it must be the variable name + if (isset($parts[0]) && self::strStartsWithVariable($parts[0])) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + Assert::notNull($variableName); + if (strpos($variableName, '$') === 0) { + $variableName = substr($variableName, 1); + } elseif (strpos($variableName, '&$') === 0) { + $isReference = \true; + $variableName = substr($variableName, 2); + } elseif (strpos($variableName, '...$') === 0) { + $isVariadic = \true; + $variableName = substr($variableName, 4); + } elseif (strpos($variableName, '&...$') === 0) { + $isVariadic = \true; + $isReference = \true; + $variableName = substr($variableName, 5); + } + } + $description = $descriptionFactory->create(implode('', $parts), $context); + return new static($variableName, $type, $isVariadic, $description, $isReference); } - public function key() : int + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string { - return $this->position; + return $this->variableName; } - public function valid() : bool + /** + * Returns whether this tag is variadic. + */ + public function isVariadic() : bool { - return $this->position < \count($this->nodes); + return $this->isVariadic; } - public function rewind() : void + /** + * Returns whether this tag is passed by reference. + */ + public function isReference() : bool { - $this->position = 0; + return $this->isReference; } - protected function getCurrentElement() : DOMElement + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - return $this->nodes[$this->position]; + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $variableName = ''; + if ($this->variableName) { + $variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : ''); + $variableName .= '$' . $this->variableName; + } + $type = (string) $this->type; + return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } - private function importNodes(DOMNodeList $nodeList) : void + private static function strStartsWithVariable(string $str) : bool { - foreach ($nodeList as $node) { - if (!$node instanceof DOMElement) { - throw new ElementCollectionException(\sprintf('\\DOMElement expected, got \\%s', \get_class($node))); - } - $this->nodes[] = $node; - } + return strpos($str, '$') === 0 || strpos($str, '...$') === 0 || strpos($str, '&$') === 0 || strpos($str, '&...$') === 0; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ExtensionElement extends ManifestElement +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function array_unshift; +use function implode; +use function strpos; +use function substr; +use const PREG_SPLIT_DELIM_CAPTURE; +/** + * Reflection class for a {@}property tag in a Docblock. + */ +final class Property extends TagWithType implements Factory\StaticMethod { - public function getFor() : string + /** @var string|null */ + protected $variableName; + public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) { - return $this->getAttributeValue('for'); + Assert::string($variableName); + $this->name = 'property'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; } - public function getCompatible() : string + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->getAttributeValue('compatible'); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + Assert::notNull($variableName); + $variableName = substr($variableName, 1); + } + $description = $descriptionFactory->create(implode('', $parts), $context); + return new static($variableName, $type, $description); } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class LicenseElement extends ManifestElement -{ - public function getType() : string + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string { - return $this->getAttributeValue('type'); + return $this->variableName; } - public function getUrl() : string + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - return $this->getAttributeValue('url'); + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + $type = (string) $this->type; + return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ExtElementCollection extends ElementCollection +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function array_unshift; +use function implode; +use function strpos; +use function substr; +use const PREG_SPLIT_DELIM_CAPTURE; +/** + * Reflection class for a {@}property-read tag in a Docblock. + */ +final class PropertyRead extends TagWithType implements Factory\StaticMethod { - public function current() : ExtElement + /** @var string|null */ + protected $variableName; + public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) { - return new ExtElement($this->getCurrentElement()); + Assert::string($variableName); + $this->name = 'property-read'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class RequiresElement extends ManifestElement -{ - public function getPHPElement() : PhpElement + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return new PhpElement($this->getChildByName('php')); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + Assert::notNull($variableName); + $variableName = substr($variableName, 1); + } + $description = $descriptionFactory->create(implode('', $parts), $context); + return new static($variableName, $type, $description); } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class BundlesElement extends ManifestElement -{ - public function getComponentElements() : ComponentElementCollection + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string { - return new ComponentElementCollection($this->getChildrenByName('component')); + return $this->variableName; + } + /** + * Returns a string representation for this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + $type = (string) $this->type; + return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ContainsElement extends ManifestElement +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function array_unshift; +use function implode; +use function strpos; +use function substr; +use const PREG_SPLIT_DELIM_CAPTURE; +/** + * Reflection class for a {@}property-write tag in a Docblock. + */ +final class PropertyWrite extends TagWithType implements Factory\StaticMethod { - public function getName() : string + /** @var string */ + protected $variableName; + public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) { - return $this->getAttributeValue('name'); + Assert::string($variableName); + $this->name = 'property-write'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; } - public function getVersion() : string + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->getAttributeValue('version'); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + Assert::notNull($variableName); + $variableName = substr($variableName, 1); + } + $description = $descriptionFactory->create(implode('', $parts), $context); + return new static($variableName, $type, $description); } - public function getType() : string + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string { - return $this->getAttributeValue('type'); + return $this->variableName; } - public function getExtensionElement() : ExtensionElement + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - return new ExtensionElement($this->getChildByName('extension')); + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + $type = (string) $this->type; + return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference; -use DOMDocument; -use DOMElement; -class ManifestDocument +use PHPUnit\phpDocumentor\Reflection\Fqsen as RealFqsen; +/** + * Fqsen reference used by {@see \phpDocumentor\Reflection\DocBlock\Tags\See} + */ +final class Fqsen implements Reference { - public const XMLNS = 'https://phar.io/xml/manifest/1.0'; - /** @var DOMDocument */ - private $dom; - public static function fromFile(string $filename) : ManifestDocument - { - if (!\file_exists($filename)) { - throw new ManifestDocumentException(\sprintf('File "%s" not found', $filename)); - } - return self::fromString(\file_get_contents($filename)); - } - public static function fromString(string $xmlString) : ManifestDocument - { - $prev = \libxml_use_internal_errors(\true); - \libxml_clear_errors(); - $dom = new DOMDocument(); - $dom->loadXML($xmlString); - $errors = \libxml_get_errors(); - \libxml_use_internal_errors($prev); - if (\count($errors) !== 0) { - throw new ManifestDocumentLoadingException($errors); - } - return new self($dom); - } - private function __construct(DOMDocument $dom) - { - $this->ensureCorrectDocumentType($dom); - $this->dom = $dom; - } - public function getContainsElement() : ContainsElement - { - return new ContainsElement($this->fetchElementByName('contains')); - } - public function getCopyrightElement() : CopyrightElement - { - return new CopyrightElement($this->fetchElementByName('copyright')); - } - public function getRequiresElement() : RequiresElement - { - return new RequiresElement($this->fetchElementByName('requires')); - } - public function hasBundlesElement() : bool - { - return $this->dom->getElementsByTagNameNS(self::XMLNS, 'bundles')->length === 1; - } - public function getBundlesElement() : BundlesElement - { - return new BundlesElement($this->fetchElementByName('bundles')); - } - private function ensureCorrectDocumentType(DOMDocument $dom) : void + /** @var RealFqsen */ + private $fqsen; + public function __construct(RealFqsen $fqsen) { - $root = $dom->documentElement; - if ($root->localName !== 'phar' || $root->namespaceURI !== self::XMLNS) { - throw new ManifestDocumentException('Not a phar.io manifest document'); - } + $this->fqsen = $fqsen; } - private function fetchElementByName(string $elementName) : DOMElement + /** + * @return string string representation of the referenced fqsen + */ + public function __toString() : string { - $element = $this->dom->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); - if (!$element instanceof DOMElement) { - throw new ManifestDocumentException(\sprintf('Element %s missing', $elementName)); - } - return $element; + return (string) $this->fqsen; } } , Sebastian Heuer , Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference; -class ComponentElement extends ManifestElement +/** + * Interface for references in {@see \phpDocumentor\Reflection\DocBlock\Tags\See} + */ +interface Reference { - public function getName() : string - { - return $this->getAttributeValue('name'); - } - public function getVersion() : string - { - return $this->getAttributeValue('version'); - } + public function __toString() : string; } , Sebastian Heuer , Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference; -use DOMElement; -use DOMNodeList; -class ManifestElement +use PHPUnit\Webmozart\Assert\Assert; +/** + * Url reference used by {@see \phpDocumentor\Reflection\DocBlock\Tags\See} + */ +final class Url implements Reference { - public const XMLNS = 'https://phar.io/xml/manifest/1.0'; - /** @var DOMElement */ - private $element; - public function __construct(DOMElement $element) - { - $this->element = $element; - } - protected function getAttributeValue(string $name) : string - { - if (!$this->element->hasAttribute($name)) { - throw new ManifestElementException(\sprintf('Attribute %s not set on element %s', $name, $this->element->localName)); - } - return $this->element->getAttribute($name); - } - protected function getChildByName(string $elementName) : DOMElement - { - $element = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); - if (!$element instanceof DOMElement) { - throw new ManifestElementException(\sprintf('Element %s missing', $elementName)); - } - return $element; - } - protected function getChildrenByName(string $elementName) : DOMNodeList + /** @var string */ + private $uri; + public function __construct(string $uri) { - $elementList = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName); - if ($elementList->length === 0) { - throw new ManifestElementException(\sprintf('Element(s) %s missing', $elementName)); - } - return $elementList; + Assert::stringNotEmpty($uri); + $this->uri = $uri; } - protected function hasChild(string $elementName) : bool + public function __toString() : string { - return $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->length !== 0; + return $this->uri; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class AuthorElement extends ManifestElement +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +/** + * Reflection class for a {@}return tag in a Docblock. + */ +final class Return_ extends TagWithType implements Factory\StaticMethod { - public function getName() : string + public function __construct(Type $type, ?Description $description = null) { - return $this->getAttributeValue('name'); + $this->name = 'return'; + $this->type = $type; + $this->description = $description; } - public function getEmail() : string + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->getAttributeValue('email'); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$type, $description] = self::extractTypeFromBody($body); + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + return new static($type, $description); + } + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $type = $this->type ? '' . $this->type : 'mixed'; + return $type . ($description !== '' ? ' ' . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ComponentElementCollection extends ElementCollection +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\FqsenResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_key_exists; +use function explode; +use function preg_match; +/** + * Reflection class for an {@}see tag in a Docblock. + */ +final class See extends BaseTag implements Factory\StaticMethod { - public function current() : ComponentElement + /** @var string */ + protected $name = 'see'; + /** @var Reference */ + protected $refers; + /** + * Initializes this tag. + */ + public function __construct(Reference $refers, ?Description $description = null) { - return new ComponentElement($this->getCurrentElement()); + $this->refers = $refers; + $this->description = $description; + } + public static function create(string $body, ?FqsenResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self + { + Assert::notNull($descriptionFactory); + $parts = Utils::pregSplit('/\\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('#\\w://\\w#', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); + } + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + /** + * Returns the ref of this tag. + */ + public function getReference() : Reference + { + return $this->refers; + } + /** + * Returns a string representation of this tag. + */ + public function __toString() : string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $refers = (string) $this->refers; + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); } } -Phar.io - Manifest - -Copyright (c) 2016-2019 Arne Blankerts , Sebastian Heuer , Sebastian Bergmann , and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Arne Blankerts nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -use PHPUnit\PharIo\Version\Version; -class Manifest +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +use function preg_match; +/** + * Reflection class for a {@}since tag in a Docblock. + */ +final class Since extends BaseTag implements Factory\StaticMethod { - /** @var ApplicationName */ - private $name; - /** @var Version */ + /** @var string */ + protected $name = 'since'; + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + public const REGEX_VECTOR = '(?: + # Normal release vectors. + \\d\\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ + )'; + /** @var string|null The version vector. */ private $version; - /** @var Type */ - private $type; - /** @var CopyrightInformation */ - private $copyrightInformation; - /** @var RequirementCollection */ - private $requirements; - /** @var BundledComponentCollection */ - private $bundledComponents; - public function __construct(ApplicationName $name, Version $version, Type $type, CopyrightInformation $copyrightInformation, RequirementCollection $requirements, BundledComponentCollection $bundledComponents) + public function __construct(?string $version = null, ?Description $description = null) { - $this->name = $name; + Assert::nullOrNotEmpty($version); $this->version = $version; - $this->type = $type; - $this->copyrightInformation = $copyrightInformation; - $this->requirements = $requirements; - $this->bundledComponents = $bundledComponents; + $this->description = $description; } - public function getName() : ApplicationName + public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self { - return $this->name; + if (empty($body)) { + return new static(); + } + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { + return null; + } + Assert::notNull($descriptionFactory); + return new static($matches[1], $descriptionFactory->create($matches[2] ?? '', $context)); } - public function getVersion() : Version + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string { return $this->version; } - public function getType() : Type + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - return $this->type; + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $version = (string) $this->version; + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); } - public function getCopyrightInformation() : CopyrightInformation +} +copyrightInformation; + Assert::integerish($startingLine); + Assert::nullOrIntegerish($lineCount); + $this->startingLine = (int) $startingLine; + $this->lineCount = $lineCount !== null ? (int) $lineCount : null; + $this->description = $description; } - public function getRequirements() : RequirementCollection + public static function create(string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->requirements; + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + $startingLine = 1; + $lineCount = null; + $description = null; + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\\d*)\\s*(?:((?1))\\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int) $matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int) $matches[2]; + } + $description = $matches[3]; + } + return new static($startingLine, $lineCount, $descriptionFactory->create($description ?? '', $context)); } - public function getBundledComponents() : BundledComponentCollection + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine() : int { - return $this->bundledComponents; + return $this->startingLine; } - public function isApplication() : bool + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount() : ?int { - return $this->type->isApplication(); + return $this->lineCount; } - public function isLibrary() : bool + public function __toString() : string { - return $this->type->isLibrary(); + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $startingLine = (string) $this->startingLine; + $lineCount = $this->lineCount !== null ? ' ' . $this->lineCount : ''; + return $startingLine . $lineCount . ($description !== '' ? ' ' . $description : ''); } - public function isExtension() : bool +} +type->isExtension(); + return $this->type; } - public function isExtensionFor(ApplicationName $application, Version $version = null) : bool + /** + * @return string[] + */ + protected static function extractTypeFromBody(string $body) : array { - if (!$this->isExtension()) { - return \false; - } - /** @var Extension $type */ - $type = $this->type; - if ($version !== null) { - return $type->isCompatibleWith($application, $version); + $type = ''; + $nestingLevel = 0; + for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) { + $character = $body[$i]; + if ($nestingLevel === 0 && trim($character) === '') { + break; + } + $type .= $character; + if (in_array($character, ['<', '(', '[', '{'])) { + $nestingLevel++; + continue; + } + if (in_array($character, ['>', ')', ']', '}'])) { + $nestingLevel--; + continue; + } } - return $type->isExtensionFor($application); + $description = trim(substr($body, strlen($type))); + return [$type, $description]; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class Url +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +/** + * Reflection class for a {@}throws tag in a Docblock. + */ +final class Throws extends TagWithType implements Factory\StaticMethod { - /** @var string */ - private $url; - public function __construct(string $url) + public function __construct(Type $type, ?Description $description = null) { - $this->ensureUrlIsValid($url); - $this->url = $url; + $this->name = 'throws'; + $this->type = $type; + $this->description = $description; } - public function asString() : string + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->url; + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$type, $description] = self::extractTypeFromBody($body); + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + return new static($type, $description); } - /** - * @param string $url - * - * @throws InvalidUrlException - */ - private function ensureUrlIsValid($url) : void + public function __toString() : string { - if (\filter_var($url, \FILTER_VALIDATE_URL) === \false) { - throw new InvalidUrlException(); + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; } + $type = (string) $this->type; + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class AuthorCollectionIterator implements \Iterator +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\FqsenResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_key_exists; +use function explode; +/** + * Reflection class for a {@}uses tag in a Docblock. + */ +final class Uses extends BaseTag implements Factory\StaticMethod { - /** @var Author[] */ - private $authors; - /** @var int */ - private $position = 0; - public function __construct(AuthorCollection $authors) - { - $this->authors = $authors->getAuthors(); - } - public function rewind() : void + /** @var string */ + protected $name = 'uses'; + /** @var Fqsen */ + protected $refers; + /** + * Initializes this tag. + */ + public function __construct(Fqsen $refers, ?Description $description = null) { - $this->position = 0; + $this->refers = $refers; + $this->description = $description; } - public function valid() : bool + public static function create(string $body, ?FqsenResolver $resolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return $this->position < \count($this->authors); + Assert::notNull($resolver); + Assert::notNull($descriptionFactory); + $parts = Utils::pregSplit('/\\s+/Su', $body, 2); + return new static(self::resolveFqsen($parts[0], $resolver, $context), $descriptionFactory->create($parts[1] ?? '', $context)); } - public function key() : int + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen { - return $this->position; + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + return new Fqsen($resolved . '::' . $fqsenParts[1]); } - public function current() : Author + /** + * Returns the structural element this tag refers to. + */ + public function getReference() : Fqsen { - return $this->authors[$this->position]; + return $this->refers; } - public function next() : void + /** + * Returns a string representation of this tag. + */ + public function __toString() : string { - $this->position++; + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + $refers = (string) $this->refers; + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class Author +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\TypeResolver; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\phpDocumentor\Reflection\Utils; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function array_unshift; +use function implode; +use function strpos; +use function substr; +use const PREG_SPLIT_DELIM_CAPTURE; +/** + * Reflection class for a {@}var tag in a Docblock. + */ +final class Var_ extends TagWithType implements Factory\StaticMethod { - /** @var string */ - private $name; - /** @var Email */ - private $email; - public function __construct(string $name, Email $email) + /** @var string|null */ + protected $variableName = ''; + public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null) { - $this->name = $name; - $this->email = $email; + Assert::string($variableName); + $this->name = 'var'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; } - public function asString() : string + public static function create(string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : self { - return \sprintf('%s <%s>', $this->name, $this->email->asString()); + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + [$firstPart, $body] = self::extractTypeFromBody($body); + $parts = Utils::pregSplit('/(\\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + Assert::notNull($variableName); + $variableName = substr($variableName, 1); + } + $description = $descriptionFactory->create(implode('', $parts), $context); + return new static($variableName, $type, $description); } - public function getName() : string + /** + * Returns the variable's name. + */ + public function getVariableName() : ?string { - return $this->name; + return $this->variableName; } - public function getEmail() : Email + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - return $this->email; + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + $type = (string) $this->type; + return $type . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\DocBlock\Tags; -class ApplicationName +use PHPUnit\phpDocumentor\Reflection\DocBlock\Description; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\Types\Context as TypeContext; +use PHPUnit\Webmozart\Assert\Assert; +use function preg_match; +/** + * Reflection class for a {@}version tag in a Docblock. + */ +final class Version extends BaseTag implements Factory\StaticMethod { /** @var string */ - private $name; - public function __construct(string $name) + protected $name = 'version'; + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + public const REGEX_VECTOR = '(?: + # Normal release vectors. + \\d\\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\\s\\:]+\\:\\s*\\$[^\\$]+\\$ + )'; + /** @var string|null The version vector. */ + private $version; + public function __construct(?string $version = null, ?Description $description = null) { - $this->ensureValidFormat($name); - $this->name = $name; + Assert::nullOrStringNotEmpty($version); + $this->version = $version; + $this->description = $description; } - public function asString() : string + public static function create(?string $body, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null) : ?self { - return $this->name; + if (empty($body)) { + return new static(); + } + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\\s*(.+)?$/sux', $body, $matches)) { + return null; + } + $description = null; + if ($descriptionFactory !== null) { + $description = $descriptionFactory->create($matches[2] ?? '', $context); + } + return new static($matches[1], $description); } - public function isEqual(ApplicationName $name) : bool + /** + * Gets the version section of the tag. + */ + public function getVersion() : ?string { - return $this->name === $name->name; + return $this->version; } - private function ensureValidFormat(string $name) : void + /** + * Returns a string representation for this tag. + */ + public function __toString() : string { - if (!\preg_match('#\\w/\\w#', $name)) { - throw new InvalidApplicationNameException(\sprintf('Format of name "%s" is not valid - expected: vendor/packagename', $name), InvalidApplicationNameException::InvalidFormat); + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; } + $version = (string) $this->version; + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class Application extends Type -{ - public function isApplication() : bool - { - return \true; - } -} -, Sebastian Heuer , Sebastian Bergmann * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection; -use PHPUnit\PharIo\Version\VersionConstraint; -class PhpVersionRequirement implements Requirement +use InvalidArgumentException; +use LogicException; +use PHPUnit\phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use PHPUnit\phpDocumentor\Reflection\DocBlock\StandardTagFactory; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\TagFactory; +use PHPUnit\Webmozart\Assert\Assert; +use function array_shift; +use function count; +use function explode; +use function is_object; +use function method_exists; +use function preg_match; +use function preg_replace; +use function str_replace; +use function strpos; +use function substr; +use function trim; +final class DocBlockFactory implements DocBlockFactoryInterface { - /** @var VersionConstraint */ - private $versionConstraint; - public function __construct(VersionConstraint $versionConstraint) + /** @var DocBlock\DescriptionFactory */ + private $descriptionFactory; + /** @var DocBlock\TagFactory */ + private $tagFactory; + /** + * Initializes this factory with the required subcontractors. + */ + public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) { - $this->versionConstraint = $versionConstraint; + $this->descriptionFactory = $descriptionFactory; + $this->tagFactory = $tagFactory; } - public function getVersionConstraint() : VersionConstraint + /** + * Factory method for easy instantiation. + * + * @param array> $additionalTags + */ + public static function createInstance(array $additionalTags = []) : self { - return $this->versionConstraint; + $fqsenResolver = new FqsenResolver(); + $tagFactory = new StandardTagFactory($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + $tagFactory->addService($descriptionFactory); + $tagFactory->addService(new TypeResolver($fqsenResolver)); + $docBlockFactory = new self($descriptionFactory, $tagFactory); + foreach ($additionalTags as $tagName => $tagHandler) { + $docBlockFactory->registerTagHandler($tagName, $tagHandler); + } + return $docBlockFactory; } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class License -{ - /** @var string */ - private $name; - /** @var Url */ - private $url; - public function __construct(string $name, Url $url) + /** + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock { - $this->name = $name; - $this->url = $url; + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + throw new InvalidArgumentException($exceptionMessage); + } + $docblock = $docblock->getDocComment(); + Assert::string($docblock); + } + Assert::stringNotEmpty($docblock); + if ($context === null) { + $context = new Types\Context(''); + } + $parts = $this->splitDocBlock($this->stripDocComment($docblock)); + [$templateMarker, $summary, $description, $tags] = $parts; + return new DocBlock($summary, $description ? $this->descriptionFactory->create($description, $context) : null, $this->parseTagBlock($tags, $context), $context, $location, $templateMarker === '#@+', $templateMarker === '#@-'); } - public function getName() : string + /** + * @param class-string $handler + */ + public function registerTagHandler(string $tagName, string $handler) : void { - return $this->name; + $this->tagFactory->registerTagHandler($tagName, $handler); } - public function getUrl() : Url + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + */ + private function stripDocComment(string $comment) : string { - return $this->url; + $comment = preg_replace('#[ \\t]*(?:\\/\\*\\*|\\*\\/|\\*)?[ \\t]?(.*)?#u', '$1', $comment); + Assert::string($comment); + $comment = trim($comment); + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) === '*/') { + $comment = trim(substr($comment, 0, -2)); + } + return str_replace(["\r\n", "\r"], "\n", $comment); } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; - -class Library extends Type -{ - public function isLibrary() : bool + // phpcs:disable + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + * + * @author Mike van Riel for extending the regex with template marker support. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + */ + private function splitDocBlock(string $comment) : array { - return \true; - } -} -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; + # 2. Extract the summary + (?: + (?! @\\pL ) # The summary may not start with an @ + ( + [^\\n.]+ + (?: + (?! \\. \\n | \\n{2} ) # End summary upon a dot followed by newline or two newlines + [\\n.]* (?! [ \\t]* @\\pL ) # End summary when an @ is found as first character on a new line + [^\\n.]+ # Include anything else + )* + \\.? + )? + ) -class AuthorCollection implements \Countable, \IteratorAggregate -{ - /** @var Author[] */ - private $authors = []; - public function add(Author $author) : void - { - $this->authors[] = $author; + # 3. Extract the description + (?: + \\s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\\pL ) # The description may not start with an @ + ( + [^\\n]+ + (?: \\n+ + (?! [ \\t]* @\\pL ) # End description when an @ is found as first character on a new line + [^\\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\\s+ [\\s\\S]*)? # everything that follows + /ux', $comment, $matches); + array_shift($matches); + while (count($matches) < 4) { + $matches[] = ''; + } + return $matches; } /** - * @return Author[] + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * @param Types\Context $context Context of the parsed Tag + * + * @return DocBlock\Tag[] */ - public function getAuthors() : array + private function parseTagBlock(string $tags, Types\Context $context) : array { - return $this->authors; + $tags = $this->filterTagBlock($tags); + if ($tags === null) { + return []; + } + $result = []; + $lines = $this->splitTagBlockIntoTagLines($tags); + foreach ($lines as $key => $tagLine) { + $result[$key] = $this->tagFactory->create(trim($tagLine), $context); + } + return $result; } - public function count() : int + /** + * @return string[] + */ + private function splitTagBlockIntoTagLines(string $tags) : array { - return \count($this->authors); + $result = []; + foreach (explode("\n", $tags) as $tagLine) { + if ($tagLine !== '' && strpos($tagLine, '@') === 0) { + $result[] = $tagLine; + } else { + $result[count($result) - 1] .= "\n" . $tagLine; + } + } + return $result; } - public function getIterator() : AuthorCollectionIterator + private function filterTagBlock(string $tags) : ?string { - return new AuthorCollectionIterator($this); + $tags = trim($tags); + if (!$tags) { + return null; + } + if ($tags[0] !== '@') { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. + throw new LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + // @codeCoverageIgnoreEnd + } + return $tags; } } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection; -interface Requirement +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag; +// phpcs:ignore SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix +interface DocBlockFactoryInterface { + /** + * Factory method for easy instantiation. + * + * @param array> $additionalTags + */ + public static function createInstance(array $additionalTags = []) : DocBlockFactory; + /** + * @param string|object $docblock + */ + public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock; } , Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\Exception; -use PHPUnit\PharIo\Version\Version; -use PHPUnit\PharIo\Version\VersionConstraint; -class Extension extends Type +use InvalidArgumentException; +use const PREG_BACKTRACK_LIMIT_ERROR; +use const PREG_BAD_UTF8_ERROR; +use const PREG_BAD_UTF8_OFFSET_ERROR; +use const PREG_INTERNAL_ERROR; +use const PREG_JIT_STACKLIMIT_ERROR; +use const PREG_NO_ERROR; +use const PREG_RECURSION_LIMIT_ERROR; +final class PcreException extends InvalidArgumentException { - /** @var ApplicationName */ - private $application; - /** @var VersionConstraint */ - private $versionConstraint; - public function __construct(ApplicationName $application, VersionConstraint $versionConstraint) - { - $this->application = $application; - $this->versionConstraint = $versionConstraint; - } - public function getApplicationName() : ApplicationName - { - return $this->application; - } - public function getVersionConstraint() : VersionConstraint - { - return $this->versionConstraint; - } - public function isExtension() : bool - { - return \true; - } - public function isExtensionFor(ApplicationName $name) : bool - { - return $this->application->isEqual($name); - } - public function isCompatibleWith(ApplicationName $name, Version $version) : bool + public static function createFromPhpError(int $errorCode) : self { - return $this->isExtensionFor($name) && $this->versionConstraint->complies($version); + switch ($errorCode) { + case PREG_BACKTRACK_LIMIT_ERROR: + return new self('Backtrack limit error'); + case PREG_RECURSION_LIMIT_ERROR: + return new self('Recursion limit error'); + case PREG_BAD_UTF8_ERROR: + return new self('Bad UTF8 error'); + case PREG_BAD_UTF8_OFFSET_ERROR: + return new self('Bad UTF8 offset error'); + case PREG_JIT_STACKLIMIT_ERROR: + return new self('Jit stacklimit error'); + case PREG_NO_ERROR: + case PREG_INTERNAL_ERROR: + default: + } + return new self('Unknown Pcre error'); } } -, Sebastian Heuer , Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\PharIo\Manifest; +Copyright (c) 2010 Mike van Riel -class BundledComponentCollectionIterator implements \Iterator -{ - /** @var BundledComponent[] */ - private $bundledComponents; - /** @var int */ - private $position = 0; - public function __construct(BundledComponentCollection $bundledComponents) - { - $this->bundledComponents = $bundledComponents->getBundledComponents(); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return $this->position < \count($this->bundledComponents); - } - public function key() : int - { - return $this->position; - } - public function current() : BundledComponent - { - return $this->bundledComponents[$this->position]; - } - public function next() : void - { - $this->position++; - } -} +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. , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection; -class RequirementCollection implements \Countable, \IteratorAggregate +use PHPUnit\phpDocumentor\Reflection\Exception\PcreException; +use PHPUnit\Webmozart\Assert\Assert; +use function preg_last_error; +use function preg_split as php_preg_split; +abstract class Utils { - /** @var Requirement[] */ - private $requirements = []; - public function add(Requirement $requirement) : void - { - $this->requirements[] = $requirement; - } /** - * @return Requirement[] + * Wrapper function for phps preg_split + * + * This function is inspired by {@link https://github.com/thecodingmachine/safe/blob/master/generated/pcre.php}. But + * since this library is all about performance we decided to strip everything we don't need. Reducing the amount + * of files that have to be loaded, ect. + * + * @param string $pattern The pattern to search for, as a string. + * @param string $subject The input string. + * @param int $limit If specified, then only substrings up to limit are returned with the + * rest of the string being placed in the last substring. A limit of -1 or 0 means "no limit". + * @param int $flags flags can be any combination of the following flags (combined with the | bitwise operator): + * *PREG_SPLIT_NO_EMPTY* + * If this flag is set, only non-empty pieces will be returned by preg_split(). + * *PREG_SPLIT_DELIM_CAPTURE* + * If this flag is set, parenthesized expression in the delimiter pattern will be captured + * and returned as well. + * *PREG_SPLIT_OFFSET_CAPTURE* + * If this flag is set, for every occurring match the appendant string offset will also be returned. + * Note that this changes the return value in an array where every element is an array consisting of the + * matched string at offset 0 and its string offset into subject at offset 1. + * + * @return string[] Returns an array containing substrings of subject + * split along boundaries matched by pattern + * + * @throws PcreException */ - public function getRequirements() : array + public static function pregSplit(string $pattern, string $subject, int $limit = -1, int $flags = 0) : array { - return $this->requirements; - } - public function count() : int - { - return \count($this->requirements); - } - public function getIterator() : RequirementCollectionIterator - { - return new RequirementCollectionIterator($this); + $parts = php_preg_split($pattern, $subject, $limit, $flags); + if ($parts === \false) { + throw PcreException::createFromPhpError(preg_last_error()); + } + Assert::allString($parts); + return $parts; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; - -class RequirementCollectionIterator implements \Iterator -{ - /** @var Requirement[] */ - private $requirements; - /** @var int */ - private $position = 0; - public function __construct(RequirementCollection $requirements) - { - $this->requirements = $requirements->getRequirements(); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return $this->position < \count($this->requirements); - } - public function key() : int - { - return $this->position; - } - public function current() : Requirement - { - return $this->requirements[$this->position]; - } - public function next() : void - { - $this->position++; - } -} -, Sebastian Heuer , Sebastian Bergmann +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\Types\Context; +use function explode; +use function implode; +use function strpos; +/** + * Resolver for Fqsen using Context information * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @psalm-immutable */ -namespace PHPUnit\PharIo\Manifest; - -class Email +class FqsenResolver { - /** @var string */ - private $email; - public function __construct(string $email) + /** @var string Definition of the NAMESPACE operator in PHP */ + private const OPERATOR_NAMESPACE = '\\'; + public function resolve(string $fqsen, ?Context $context = null) : Fqsen { - $this->ensureEmailIsValid($email); - $this->email = $email; + if ($context === null) { + $context = new Context(''); + } + if ($this->isFqsen($fqsen)) { + return new Fqsen($fqsen); + } + return $this->resolvePartialStructuralElementName($fqsen, $context); } - public function asString() : string + /** + * Tests whether the given type is a Fully Qualified Structural Element Name. + */ + private function isFqsen(string $type) : bool { - return $this->email; + return strpos($type, self::OPERATOR_NAMESPACE) === 0; } - private function ensureEmailIsValid(string $url) : void + /** + * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation + * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context. + * + * @throws InvalidArgumentException When type is not a valid FQSEN. + */ + private function resolvePartialStructuralElementName(string $type, Context $context) : Fqsen { - if (\filter_var($url, \FILTER_VALIDATE_EMAIL) === \false) { - throw new InvalidEmailException(); + $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2); + $namespaceAliases = $context->getNamespaceAliases(); + // if the first segment is not an alias; prepend namespace name and return + if (!isset($namespaceAliases[$typeParts[0]])) { + $namespace = $context->getNamespace(); + if ($namespace !== '') { + $namespace .= self::OPERATOR_NAMESPACE; + } + return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); } + $typeParts[0] = $namespaceAliases[$typeParts[0]]; + return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); } } +The MIT License (MIT) + +Copyright (c) 2010 Mike van Riel + +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. , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection; -use PHPUnit\PharIo\Version\VersionConstraint; -abstract class Type +interface PseudoType extends Type { - public static function application() : Application - { - return new Application(); - } - public static function library() : Library - { - return new Library(); - } - public static function extension(ApplicationName $application, VersionConstraint $versionConstraint) : Extension - { - return new Extension($application, $versionConstraint); - } - /** @psalm-assert-if-true Application $this */ - public function isApplication() : bool - { - return \false; - } - /** @psalm-assert-if-true Library $this */ - public function isLibrary() : bool - { - return \false; - } - /** @psalm-assert-if-true Extension $this */ - public function isExtension() : bool - { - return \false; - } + public function underlyingType() : Type; } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -class BundledComponentCollection implements \Countable, \IteratorAggregate +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class CallableString extends String_ implements PseudoType { - /** @var BundledComponent[] */ - private $bundledComponents = []; - public function add(BundledComponent $bundledComponent) : void + public function underlyingType() : Type { - $this->bundledComponents[] = $bundledComponent; + return new String_(); } /** - * @return BundledComponent[] + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - public function getBundledComponents() : array - { - return $this->bundledComponents; - } - public function count() : int - { - return \count($this->bundledComponents); - } - public function getIterator() : BundledComponentCollectionIterator + public function __toString() : string { - return new BundledComponentCollectionIterator($this); + return 'callable-string'; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link https://phpdoc.org + */ +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; + +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\Boolean; +use function class_alias; +/** + * Value Object representing the PseudoType 'False', which is a Boolean type. + * + * @psalm-immutable */ -namespace PHPUnit\PharIo\Manifest; - -class PhpExtensionRequirement implements Requirement +final class False_ extends Boolean implements PseudoType { - /** @var string */ - private $extension; - public function __construct(string $extension) + public function underlyingType() : Type { - $this->extension = $extension; + return new Boolean(); } - public function asString() : string + public function __toString() : string { - return $this->extension; + return 'false'; } } +class_alias('PHPUnit\\phpDocumentor\\Reflection\\PseudoTypes\\False_', 'PHPUnit\\phpDocumentor\\Reflection\\Types\\False_', \false); , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -use PHPUnit\PharIo\Version\Version; -class BundledComponent +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class HtmlEscapedString extends String_ implements PseudoType { - /** @var string */ - private $name; - /** @var Version */ - private $version; - public function __construct(string $name, Version $version) - { - $this->name = $name; - $this->version = $version; - } - public function getName() : string + public function underlyingType() : Type { - return $this->name; + return new String_(); } - public function getVersion() : Version + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - return $this->version; + return 'html-escaped-string'; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -class CopyrightInformation +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\Integer; +/** + * Value Object representing the type 'int'. + * + * @psalm-immutable + */ +final class IntegerRange extends Integer implements PseudoType { - /** @var AuthorCollection */ - private $authors; - /** @var License */ - private $license; - public function __construct(AuthorCollection $authors, License $license) + /** @var string */ + private $minValue; + /** @var string */ + private $maxValue; + public function __construct(string $minValue, string $maxValue) { - $this->authors = $authors; - $this->license = $license; + $this->minValue = $minValue; + $this->maxValue = $maxValue; } - public function getAuthors() : AuthorCollection + public function underlyingType() : Type { - return $this->authors; + return new Integer(); } - public function getLicense() : License + public function getMinValue() : string { - return $this->license; + return $this->minValue; + } + public function getMaxValue() : string + { + return $this->maxValue; + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'int<' . $this->minValue . ', ' . $this->maxValue . '>'; } } , Sebastian Heuer , Sebastian Bergmann +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\PharIo\Manifest; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -class ManifestLoader +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\Array_; +use PHPUnit\phpDocumentor\Reflection\Types\Integer; +use PHPUnit\phpDocumentor\Reflection\Types\Mixed_; +/** + * Value Object representing the type 'list'. + * + * @psalm-immutable + */ +final class List_ extends Array_ implements PseudoType { - public static function fromFile(string $filename) : Manifest + public function underlyingType() : Type { - try { - return (new ManifestDocumentMapper())->map(ManifestDocument::fromFile($filename)); - } catch (Exception $e) { - throw new ManifestLoaderException(\sprintf('Loading %s failed.', $filename), (int) $e->getCode(), $e); - } + return new Array_(); } - public static function fromPhar(string $filename) : Manifest + public function __construct(?Type $valueType = null) { - return self::fromFile('phar://' . $filename . '/manifest.xml'); + parent::__construct($valueType, new Integer()); } - public static function fromString(string $manifest) : Manifest + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - try { - return (new ManifestDocumentMapper())->map(ManifestDocument::fromString($manifest)); - } catch (Exception $e) { - throw new ManifestLoaderException('Processing string failed', (int) $e->getCode(), $e); + if ($this->valueType instanceof Mixed_) { + return 'list'; } + return 'list<' . $this->valueType . '>'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CliParser; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -use function sprintf; -use RuntimeException; -final class AmbiguousOptionException extends RuntimeException implements Exception +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class LiteralString extends String_ implements PseudoType { - public function __construct(string $option) + public function underlyingType() : Type { - parent::__construct(sprintf('Option "%s" is ambiguous', $option)); + return new String_(); + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'literal-string'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CliParser; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -use function sprintf; -use RuntimeException; -final class UnknownOptionException extends RuntimeException implements Exception +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class LowercaseString extends String_ implements PseudoType { - public function __construct(string $option) + public function underlyingType() : Type { - parent::__construct(sprintf('Unknown option "%s"', $option)); + return new String_(); + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'lowercase-string'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CliParser; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -use function sprintf; -use RuntimeException; -final class OptionDoesNotAllowArgumentException extends RuntimeException implements Exception +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\Integer; +/** + * Value Object representing the type 'int'. + * + * @psalm-immutable + */ +final class NegativeInteger extends Integer implements PseudoType { - public function __construct(string $option) + public function underlyingType() : Type { - parent::__construct(sprintf('Option "%s" does not allow an argument', $option)); + return new Integer(); + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'negative-int'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CliParser; - -use Throwable; -interface Exception extends Throwable -{ -} - +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @psalm-immutable */ -namespace PHPUnit\SebastianBergmann\CliParser; - -use function sprintf; -use RuntimeException; -final class RequiredOptionArgumentMissingException extends RuntimeException implements Exception +final class NonEmptyLowercaseString extends String_ implements PseudoType { - public function __construct(string $option) + public function underlyingType() : Type { - parent::__construct(sprintf('Required argument for option "%s" is missing', $option)); + return new String_(); + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'non-empty-lowercase-string'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CliParser; +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; -use function array_map; -use function array_merge; -use function array_shift; -use function array_slice; -use function assert; -use function count; -use function current; -use function explode; -use function is_array; -use function is_int; -use function is_string; -use function key; -use function next; -use function preg_replace; -use function reset; -use function sort; -use function strlen; -use function strpos; -use function strstr; -use function substr; -final class Parser +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class NonEmptyString extends String_ implements PseudoType { - /** - * @psalm-param list $argv - * @psalm-param list $longOptions - * - * @throws AmbiguousOptionException - * @throws RequiredOptionArgumentMissingException - * @throws OptionDoesNotAllowArgumentException - * @throws UnknownOptionException - */ - public function parse(array $argv, string $shortOptions, array $longOptions = null) : array - { - if (empty($argv)) { - return [[], []]; - } - $options = []; - $nonOptions = []; - if ($longOptions) { - sort($longOptions); - } - if (isset($argv[0][0]) && $argv[0][0] !== '-') { - array_shift($argv); - } - reset($argv); - $argv = array_map('trim', $argv); - while (\false !== ($arg = current($argv))) { - $i = key($argv); - assert(is_int($i)); - next($argv); - if ($arg === '') { - continue; - } - if ($arg === '--') { - $nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1)); - break; - } - if ($arg[0] !== '-' || strlen($arg) > 1 && $arg[1] === '-' && !$longOptions) { - $nonOptions[] = $arg; - continue; - } - if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) { - $this->parseLongOption(substr($arg, 2), $longOptions, $options, $argv); - } else { - $this->parseShortOption(substr($arg, 1), $shortOptions, $options, $argv); - } - } - return [$options, $nonOptions]; - } - /** - * @throws RequiredOptionArgumentMissingException - */ - private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args) : void + public function underlyingType() : Type { - $argLength = strlen($arg); - for ($i = 0; $i < $argLength; $i++) { - $option = $arg[$i]; - $optionArgument = null; - if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === \false) { - throw new UnknownOptionException('-' . $option); - } - assert(is_string($spec)); - if (strlen($spec) > 1 && $spec[1] === ':') { - if ($i + 1 < $argLength) { - $opts[] = [$option, substr($arg, $i + 1)]; - break; - } - if (!(strlen($spec) > 2 && $spec[2] === ':')) { - $optionArgument = current($args); - if (!$optionArgument) { - throw new RequiredOptionArgumentMissingException('-' . $option); - } - assert(is_string($optionArgument)); - next($args); - } - } - $opts[] = [$option, $optionArgument]; - } + return new String_(); } /** - * @psalm-param list $longOptions - * - * @throws AmbiguousOptionException - * @throws RequiredOptionArgumentMissingException - * @throws OptionDoesNotAllowArgumentException - * @throws UnknownOptionException + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args) : void + public function __toString() : string { - $count = count($longOptions); - $list = explode('=', $arg); - $option = $list[0]; - $optionArgument = null; - if (count($list) > 1) { - $optionArgument = $list[1]; - } - $optionLength = strlen($option); - foreach ($longOptions as $i => $longOption) { - $opt_start = substr($longOption, 0, $optionLength); - if ($opt_start !== $option) { - continue; - } - $opt_rest = substr($longOption, $optionLength); - if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) { - throw new AmbiguousOptionException('--' . $option); - } - if (substr($longOption, -1) === '=') { - /* @noinspection StrlenInEmptyStringCheckContextInspection */ - if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) { - if (\false === ($optionArgument = current($args))) { - throw new RequiredOptionArgumentMissingException('--' . $option); - } - next($args); - } - } elseif ($optionArgument) { - throw new OptionDoesNotAllowArgumentException('--' . $option); - } - $fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption); - $opts[] = [$fullOption, $optionArgument]; - return; - } - throw new UnknownOptionException('--' . $option); + return 'non-empty-string'; } } -sebastian/cli-parser - -Copyright (c) 2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +declare (strict_types=1); +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -use PHPUnit\Symfony\Polyfill\Ctype as p; -if (\PHP_VERSION_ID >= 80000) { - return require __DIR__ . '/bootstrap80.php'; -} -if (!\function_exists('ctype_alnum')) { - function ctype_alnum($text) - { - return p\Ctype::ctype_alnum($text); - } -} -if (!\function_exists('ctype_alpha')) { - function ctype_alpha($text) - { - return p\Ctype::ctype_alpha($text); - } -} -if (!\function_exists('ctype_cntrl')) { - function ctype_cntrl($text) - { - return p\Ctype::ctype_cntrl($text); - } -} -if (!\function_exists('ctype_digit')) { - function ctype_digit($text) - { - return p\Ctype::ctype_digit($text); - } -} -if (!\function_exists('ctype_graph')) { - function ctype_graph($text) - { - return p\Ctype::ctype_graph($text); - } -} -if (!\function_exists('ctype_lower')) { - function ctype_lower($text) - { - return p\Ctype::ctype_lower($text); - } -} -if (!\function_exists('ctype_print')) { - function ctype_print($text) - { - return p\Ctype::ctype_print($text); - } -} -if (!\function_exists('ctype_punct')) { - function ctype_punct($text) - { - return p\Ctype::ctype_punct($text); - } -} -if (!\function_exists('ctype_space')) { - function ctype_space($text) - { - return p\Ctype::ctype_space($text); - } -} -if (!\function_exists('ctype_upper')) { - function ctype_upper($text) +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; + +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class NumericString extends String_ implements PseudoType +{ + public function underlyingType() : Type { - return p\Ctype::ctype_upper($text); + return new String_(); } -} -if (!\function_exists('ctype_xdigit')) { - function ctype_xdigit($text) + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - return p\Ctype::ctype_xdigit($text); + return 'numeric-string'; } } +declare (strict_types=1); +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -use PHPUnit\Symfony\Polyfill\Ctype as p; -if (!\function_exists('ctype_alnum')) { - function ctype_alnum(mixed $text) : bool - { - return p\Ctype::ctype_alnum($text); - } -} -if (!\function_exists('ctype_alpha')) { - function ctype_alpha(mixed $text) : bool - { - return p\Ctype::ctype_alpha($text); - } -} -if (!\function_exists('ctype_cntrl')) { - function ctype_cntrl(mixed $text) : bool +namespace PHPUnit\phpDocumentor\Reflection\PseudoTypes; + +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +use PHPUnit\phpDocumentor\Reflection\Types\AggregatedType; +use PHPUnit\phpDocumentor\Reflection\Types\Compound; +use PHPUnit\phpDocumentor\Reflection\Types\Float_; +use PHPUnit\phpDocumentor\Reflection\Types\Integer; +/** + * Value Object representing the 'numeric' pseudo-type, which is either a numeric-string, integer or float. + * + * @psalm-immutable + */ +final class Numeric_ extends AggregatedType implements PseudoType +{ + public function __construct() { - return p\Ctype::ctype_cntrl($text); + AggregatedType::__construct([new NumericString(), new Integer(), new Float_()], '|'); } -} -if (!\function_exists('ctype_digit')) { - function ctype_digit(mixed $text) : bool + public function underlyingType() : Type { - return p\Ctype::ctype_digit($text); + return new Compound([new NumericString(), new Integer(), new Float_()]); } -} -if (!\function_exists('ctype_graph')) { - function ctype_graph(mixed $text) : bool + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - return p\Ctype::ctype_graph($text); + return 'numeric'; } } -if (!\function_exists('ctype_lower')) { - function ctype_lower(mixed $text) : bool + +declare (strict_types=1); +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org + */ +namespace PHPUnit\phpDocumentor\Reflection; + +/** + * @psalm-immutable */ -namespace PHPUnit\Symfony\Polyfill\Ctype; +interface Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string; +} + + * @link http://phpdoc.org */ -final class Ctype +namespace PHPUnit\phpDocumentor\Reflection; + +use ArrayIterator; +use InvalidArgumentException; +use PHPUnit\phpDocumentor\Reflection\PseudoTypes\IntegerRange; +use PHPUnit\phpDocumentor\Reflection\PseudoTypes\List_; +use PHPUnit\phpDocumentor\Reflection\Types\Array_; +use PHPUnit\phpDocumentor\Reflection\Types\ArrayKey; +use PHPUnit\phpDocumentor\Reflection\Types\ClassString; +use PHPUnit\phpDocumentor\Reflection\Types\Collection; +use PHPUnit\phpDocumentor\Reflection\Types\Compound; +use PHPUnit\phpDocumentor\Reflection\Types\Context; +use PHPUnit\phpDocumentor\Reflection\Types\Expression; +use PHPUnit\phpDocumentor\Reflection\Types\Integer; +use PHPUnit\phpDocumentor\Reflection\Types\InterfaceString; +use PHPUnit\phpDocumentor\Reflection\Types\Intersection; +use PHPUnit\phpDocumentor\Reflection\Types\Iterable_; +use PHPUnit\phpDocumentor\Reflection\Types\Nullable; +use PHPUnit\phpDocumentor\Reflection\Types\Object_; +use PHPUnit\phpDocumentor\Reflection\Types\String_; +use RuntimeException; +use function array_key_exists; +use function array_pop; +use function array_values; +use function class_exists; +use function class_implements; +use function count; +use function current; +use function end; +use function in_array; +use function is_numeric; +use function key; +use function preg_split; +use function strpos; +use function strtolower; +use function trim; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; +final class TypeResolver { + /** @var string Definition of the ARRAY operator for types */ + private const OPERATOR_ARRAY = '[]'; + /** @var string Definition of the NAMESPACE operator in PHP */ + private const OPERATOR_NAMESPACE = '\\'; + /** @var int the iterator parser is inside a compound context */ + private const PARSER_IN_COMPOUND = 0; + /** @var int the iterator parser is inside a nullable expression context */ + private const PARSER_IN_NULLABLE = 1; + /** @var int the iterator parser is inside an array expression context */ + private const PARSER_IN_ARRAY_EXPRESSION = 2; + /** @var int the iterator parser is inside a collection expression context */ + private const PARSER_IN_COLLECTION_EXPRESSION = 3; /** - * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. - * - * @see https://php.net/ctype-alnum - * - * @param string|int $text - * - * @return bool + * @var array List of recognized keywords and unto which Value Object they map + * @psalm-var array> */ - public static function ctype_alnum($text) - { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^A-Za-z0-9]/', $text); - } + private $keywords = ['string' => Types\String_::class, 'class-string' => Types\ClassString::class, 'interface-string' => Types\InterfaceString::class, 'html-escaped-string' => PseudoTypes\HtmlEscapedString::class, 'lowercase-string' => PseudoTypes\LowercaseString::class, 'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class, 'non-empty-string' => PseudoTypes\NonEmptyString::class, 'numeric-string' => PseudoTypes\NumericString::class, 'numeric' => PseudoTypes\Numeric_::class, 'trait-string' => PseudoTypes\TraitString::class, 'int' => Types\Integer::class, 'integer' => Types\Integer::class, 'positive-int' => PseudoTypes\PositiveInteger::class, 'negative-int' => PseudoTypes\NegativeInteger::class, 'bool' => Types\Boolean::class, 'boolean' => Types\Boolean::class, 'real' => Types\Float_::class, 'float' => Types\Float_::class, 'double' => Types\Float_::class, 'object' => Types\Object_::class, 'mixed' => Types\Mixed_::class, 'array' => Types\Array_::class, 'array-key' => Types\ArrayKey::class, 'resource' => Types\Resource_::class, 'void' => Types\Void_::class, 'null' => Types\Null_::class, 'scalar' => Types\Scalar::class, 'callback' => Types\Callable_::class, 'callable' => Types\Callable_::class, 'callable-string' => PseudoTypes\CallableString::class, 'false' => PseudoTypes\False_::class, 'true' => PseudoTypes\True_::class, 'literal-string' => PseudoTypes\LiteralString::class, 'self' => Types\Self_::class, '$this' => Types\This::class, 'static' => Types\Static_::class, 'parent' => Types\Parent_::class, 'iterable' => Types\Iterable_::class, 'never' => Types\Never_::class, 'list' => PseudoTypes\List_::class]; /** - * Returns TRUE if every character in text is a letter, FALSE otherwise. - * - * @see https://php.net/ctype-alpha - * - * @param string|int $text - * - * @return bool + * @var FqsenResolver + * @psalm-readonly */ - public static function ctype_alpha($text) - { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^A-Za-z]/', $text); - } + private $fqsenResolver; /** - * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. - * - * @see https://php.net/ctype-cntrl - * - * @param string|int $text - * - * @return bool + * Initializes this TypeResolver with the means to create and resolve Fqsen objects. */ - public static function ctype_cntrl($text) + public function __construct(?FqsenResolver $fqsenResolver = null) { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^\\x00-\\x1f\\x7f]/', $text); + $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); } /** - * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. - * - * @see https://php.net/ctype-digit - * - * @param string|int $text + * Analyzes the given type and returns the FQCN variant. * - * @return bool - */ - public static function ctype_digit($text) - { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^0-9]/', $text); - } - /** - * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * When a type is provided this method checks whether it is not a keyword or + * Fully Qualified Class Name. If so it will use the given namespace and + * aliases to expand the type to a FQCN representation. * - * @see https://php.net/ctype-graph + * This method only works as expected if the namespace and aliases are set; + * no dynamic reflection is being performed here. * - * @param string|int $text + * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be + * replaced with another namespace. + * @uses Context::getNamespace() to determine with what to prefix the type name. * - * @return bool + * @param string $type The relative or absolute type. */ - public static function ctype_graph($text) + public function resolve(string $type, ?Context $context = null) : Type { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^!-~]/', $text); + $type = trim($type); + if (!$type) { + throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty'); + } + if ($context === null) { + $context = new Context(''); + } + // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names + $tokens = preg_split('/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/', $type, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + if ($tokens === \false) { + throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens'); + } + /** @var ArrayIterator $tokenIterator */ + $tokenIterator = new ArrayIterator($tokens); + return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND); } /** - * Returns TRUE if every character in text is a lowercase letter. - * - * @see https://php.net/ctype-lower - * - * @param string|int $text + * Analyse each tokens and creates types * - * @return bool + * @param ArrayIterator $tokens the iterator on tokens + * @param int $parserContext on of self::PARSER_* constants, indicating + * the context where we are in the parsing */ - public static function ctype_lower($text) + private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext) : Type { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^a-z]/', $text); + $types = []; + $token = ''; + $compoundToken = '|'; + while ($tokens->valid()) { + $token = $tokens->current(); + if ($token === null) { + throw new RuntimeException('Unexpected nullable character'); + } + if ($token === '|' || $token === '&') { + if (count($types) === 0) { + throw new RuntimeException('A type is missing before a type separator'); + } + if (!in_array($parserContext, [self::PARSER_IN_COMPOUND, self::PARSER_IN_ARRAY_EXPRESSION, self::PARSER_IN_COLLECTION_EXPRESSION], \true)) { + throw new RuntimeException('Unexpected type separator'); + } + $compoundToken = $token; + $tokens->next(); + } elseif ($token === '?') { + if (!in_array($parserContext, [self::PARSER_IN_COMPOUND, self::PARSER_IN_ARRAY_EXPRESSION, self::PARSER_IN_COLLECTION_EXPRESSION], \true)) { + throw new RuntimeException('Unexpected nullable character'); + } + $tokens->next(); + $type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE); + $types[] = new Nullable($type); + } elseif ($token === '(') { + $tokens->next(); + $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION); + $token = $tokens->current(); + if ($token === null) { + // Someone did not properly close their array expression .. + break; + } + $tokens->next(); + $resolvedType = new Expression($type); + $types[] = $resolvedType; + } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && isset($token[0]) && $token[0] === ')') { + break; + } elseif ($token === '<') { + if (count($types) === 0) { + throw new RuntimeException('Unexpected collection operator "<", class name is missing'); + } + $classType = array_pop($types); + if ($classType !== null) { + if ((string) $classType === 'class-string') { + $types[] = $this->resolveClassString($tokens, $context); + } elseif ((string) $classType === 'int') { + $types[] = $this->resolveIntRange($tokens); + } elseif ((string) $classType === 'interface-string') { + $types[] = $this->resolveInterfaceString($tokens, $context); + } else { + $types[] = $this->resolveCollection($tokens, $classType, $context); + } + } + $tokens->next(); + } elseif ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION && ($token === '>' || trim($token) === ',')) { + break; + } elseif ($token === self::OPERATOR_ARRAY) { + end($types); + $last = key($types); + if ($last === null) { + throw new InvalidArgumentException('Unexpected array operator'); + } + $lastItem = $types[$last]; + if ($lastItem instanceof Expression) { + $lastItem = $lastItem->getValueType(); + } + $types[$last] = new Array_($lastItem); + $tokens->next(); + } else { + $type = $this->resolveSingleType($token, $context); + $tokens->next(); + if ($parserContext === self::PARSER_IN_NULLABLE) { + return $type; + } + $types[] = $type; + } + } + if ($token === '|' || $token === '&') { + throw new RuntimeException('A type is missing after a type separator'); + } + if (count($types) === 0) { + if ($parserContext === self::PARSER_IN_NULLABLE) { + throw new RuntimeException('A type is missing after a nullable character'); + } + if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) { + throw new RuntimeException('A type is missing in an array expression'); + } + if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) { + throw new RuntimeException('A type is missing in a collection expression'); + } + } elseif (count($types) === 1) { + return current($types); + } + if ($compoundToken === '|') { + return new Compound(array_values($types)); + } + return new Intersection(array_values($types)); } /** - * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * resolve the given type into a type object * - * @see https://php.net/ctype-print + * @param string $type the type string, representing a single type * - * @param string|int $text + * @return Type|Array_|Object_ * - * @return bool + * @psalm-mutation-free */ - public static function ctype_print($text) + private function resolveSingleType(string $type, Context $context) : object { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^ -~]/', $text); + switch (\true) { + case $this->isKeyword($type): + return $this->resolveKeyword($type); + case $this->isFqsen($type): + return $this->resolveTypedObject($type); + case $this->isPartialStructuralElementName($type): + return $this->resolveTypedObject($type, $context); + // @codeCoverageIgnoreStart + default: + // I haven't got the foggiest how the logic would come here but added this as a defense. + throw new RuntimeException('Unable to resolve type "' . $type . '", there is no known method to resolve it'); + } + // @codeCoverageIgnoreEnd } /** - * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. - * - * @see https://php.net/ctype-punct - * - * @param string|int $text + * Adds a keyword to the list of Keywords and associates it with a specific Value Object. * - * @return bool + * @psalm-param class-string $typeClassName */ - public static function ctype_punct($text) + public function addKeyword(string $keyword, string $typeClassName) : void { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^!-\\/\\:-@\\[-`\\{-~]/', $text); + if (!class_exists($typeClassName)) { + throw new InvalidArgumentException('The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' . ' but we could not find the class ' . $typeClassName); + } + $interfaces = class_implements($typeClassName); + if ($interfaces === \false) { + throw new InvalidArgumentException('The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' . ' but we could not find the class ' . $typeClassName); + } + if (!in_array(Type::class, $interfaces, \true)) { + throw new InvalidArgumentException('The class "' . $typeClassName . '" must implement the interface "phpDocumentor\\Reflection\\Type"'); + } + $this->keywords[$keyword] = $typeClassName; } /** - * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. - * - * @see https://php.net/ctype-space + * Detects whether the given type represents a PHPDoc keyword. * - * @param string|int $text + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. * - * @return bool + * @psalm-mutation-free */ - public static function ctype_space($text) + private function isKeyword(string $type) : bool { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^\\s]/', $text); + return array_key_exists(strtolower($type), $this->keywords); } /** - * Returns TRUE if every character in text is an uppercase letter. - * - * @see https://php.net/ctype-upper + * Detects whether the given type represents a relative structural element name. * - * @param string|int $text + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. * - * @return bool + * @psalm-mutation-free */ - public static function ctype_upper($text) + private function isPartialStructuralElementName(string $type) : bool { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^A-Z]/', $text); + return isset($type[0]) && $type[0] !== self::OPERATOR_NAMESPACE && !$this->isKeyword($type); } /** - * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. - * - * @see https://php.net/ctype-xdigit - * - * @param string|int $text + * Tests whether the given type is a Fully Qualified Structural Element Name. * - * @return bool + * @psalm-mutation-free */ - public static function ctype_xdigit($text) + private function isFqsen(string $type) : bool { - $text = self::convert_int_to_char_for_ctype($text); - return \is_string($text) && '' !== $text && !\preg_match('/[^A-Fa-f0-9]/', $text); + return strpos($type, self::OPERATOR_NAMESPACE) === 0; } /** - * Converts integers to their char versions according to normal ctype behaviour, if needed. - * - * If an integer between -128 and 255 inclusive is provided, - * it is interpreted as the ASCII value of a single character - * (negative values have 256 added in order to allow characters in the Extended ASCII range). - * Any other integer is interpreted as a string containing the decimal digits of the integer. - * - * @param string|int $int + * Resolves the given keyword (such as `string`) into a Type object representing that keyword. * - * @return mixed + * @psalm-mutation-free */ - private static function convert_int_to_char_for_ctype($int) + private function resolveKeyword(string $type) : Type { - if (!\is_int($int)) { - return $int; - } - if ($int < -128 || $int > 255) { - return (string) $int; - } - if ($int < 0) { - $int += 256; - } - return \chr($int); - } -} -Copyright (c) 2018-2019 Fabien Potencier - -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. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Complexity; - -final class RuntimeException extends \RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use Throwable; -interface Exception extends Throwable -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use PHPUnit\PhpParser\Error; -use PHPUnit\PhpParser\Lexer; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\NodeVisitor\NameResolver; -use PHPUnit\PhpParser\NodeVisitor\ParentConnectingVisitor; -use PHPUnit\PhpParser\Parser; -use PHPUnit\PhpParser\ParserFactory; -final class Calculator -{ + $className = $this->keywords[strtolower($type)]; + return new $className(); + } /** - * @throws RuntimeException + * Resolves the given FQSEN string into an FQSEN object. + * + * @psalm-mutation-free */ - public function calculateForSourceFile(string $sourceFile) : ComplexityCollection + private function resolveTypedObject(string $type, ?Context $context = null) : Object_ { - return $this->calculateForSourceString(\file_get_contents($sourceFile)); + return new Object_($this->fqsenResolver->resolve($type, $context)); } /** - * @throws RuntimeException + * Resolves class string + * + * @param ArrayIterator $tokens */ - public function calculateForSourceString(string $source) : ComplexityCollection + private function resolveClassString(ArrayIterator $tokens, Context $context) : Type { - try { - $nodes = $this->parser()->parse($source); - \assert($nodes !== null); - return $this->calculateForAbstractSyntaxTree($nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + $tokens->next(); + $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + if (!$classType instanceof Object_ || $classType->getFqsen() === null) { + throw new RuntimeException($classType . ' is not a class string'); } - // @codeCoverageIgnoreEnd + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException('class-string: ">" is missing'); + } + throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); + } + return new ClassString($classType->getFqsen()); } /** - * @param Node[] $nodes + * Resolves integer ranges * - * @throws RuntimeException + * @param ArrayIterator $tokens */ - public function calculateForAbstractSyntaxTree(array $nodes) : ComplexityCollection + private function resolveIntRange(ArrayIterator $tokens) : Type { - $traverser = new NodeTraverser(); - $complexityCalculatingVisitor = new ComplexityCalculatingVisitor(\true); - $traverser->addVisitor(new NameResolver()); - $traverser->addVisitor(new ParentConnectingVisitor()); - $traverser->addVisitor($complexityCalculatingVisitor); - try { - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + $tokens->next(); + $token = ''; + $minValue = null; + $maxValue = null; + $commaFound = \false; + $tokenCounter = 0; + while ($tokens->valid()) { + $tokenCounter++; + $token = $tokens->current(); + if ($token === null) { + throw new RuntimeException('Unexpected nullable character'); + } + $token = trim($token); + if ($token === '>') { + break; + } + if ($token === ',') { + $commaFound = \true; + } + if ($commaFound === \false && $minValue === null) { + if (is_numeric($token) || $token === 'max' || $token === 'min') { + $minValue = $token; + } + } + if ($commaFound === \true && $maxValue === null) { + if (is_numeric($token) || $token === 'max' || $token === 'min') { + $maxValue = $token; + } + } + $tokens->next(); } - // @codeCoverageIgnoreEnd - return $complexityCalculatingVisitor->result(); - } - private function parser() : Parser - { - return (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException('interface-string: ">" is missing'); + } + throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); + } + if ($minValue === null || $maxValue === null || $tokenCounter > 4) { + throw new RuntimeException('int has not the correct format'); + } + return new IntegerRange($minValue, $maxValue); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use function count; -use Countable; -use IteratorAggregate; -/** - * @psalm-immutable - */ -final class ComplexityCollection implements Countable, IteratorAggregate -{ /** - * @psalm-var list + * Resolves class string + * + * @param ArrayIterator $tokens */ - private $items = []; - public static function fromList(Complexity ...$items) : self + private function resolveInterfaceString(ArrayIterator $tokens, Context $context) : Type { - return new self($items); + $tokens->next(); + $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + if (!$classType instanceof Object_ || $classType->getFqsen() === null) { + throw new RuntimeException($classType . ' is not a interface string'); + } + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException('interface-string: ">" is missing'); + } + throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); + } + return new InterfaceString($classType->getFqsen()); } /** - * @psalm-param list $items + * Resolves the collection values and keys + * + * @param ArrayIterator $tokens + * + * @return Array_|Iterable_|Collection */ - private function __construct(array $items) + private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context) : Type { - $this->items = $items; + $isArray = (string) $classType === 'array'; + $isIterable = (string) $classType === 'iterable'; + $isList = (string) $classType === 'list'; + // allow only "array", "iterable" or class name before "<" + if (!$isArray && !$isIterable && !$isList && (!$classType instanceof Object_ || $classType->getFqsen() === null)) { + throw new RuntimeException($classType . ' is not a collection'); + } + $tokens->next(); + $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + $keyType = null; + $token = $tokens->current(); + if ($token !== null && trim($token) === ',' && !$isList) { + // if we have a comma, then we just parsed the key type, not the value type + $keyType = $valueType; + if ($isArray) { + // check the key type for an "array" collection. We allow only + // strings or integers. + if (!$keyType instanceof ArrayKey && !$keyType instanceof String_ && !$keyType instanceof Integer && !$keyType instanceof Compound) { + throw new RuntimeException('An array can have only integers or strings as keys'); + } + if ($keyType instanceof Compound) { + foreach ($keyType->getIterator() as $item) { + if (!$item instanceof ArrayKey && !$item instanceof String_ && !$item instanceof Integer) { + throw new RuntimeException('An array can have only integers or strings as keys'); + } + } + } + } + $tokens->next(); + // now let's parse the value type + $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + } + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException('Collection: ">" is missing'); + } + throw new RuntimeException('Unexpected character "' . $token . '", ">" is missing'); + } + if ($isArray) { + return new Array_($valueType, $keyType); + } + if ($isIterable) { + return new Iterable_($valueType, $keyType); + } + if ($isList) { + return new List_($valueType); + } + if ($classType instanceof Object_) { + return $this->makeCollectionFromObject($classType, $valueType, $keyType); + } + throw new RuntimeException('Invalid $classType provided'); } /** - * @psalm-return list + * @psalm-pure */ - public function asArray() : array - { - return $this->items; - } - public function getIterator() : ComplexityCollectionIterator - { - return new ComplexityCollectionIterator($this); - } - public function count() : int - { - return count($this->items); - } - public function isEmpty() : bool - { - return empty($this->items); - } - public function cyclomaticComplexity() : int + private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null) : Collection { - $cyclomaticComplexity = 0; - foreach ($this as $item) { - $cyclomaticComplexity += $item->cyclomaticComplexity(); - } - return $cyclomaticComplexity; + return new Collection($object->getFqsen(), $valueType, $keyType); } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Complexity; +namespace PHPUnit\phpDocumentor\Reflection\Types; +use PHPUnit\phpDocumentor\Reflection\Type; /** + * Represents a list of values. This is an abstract class for Array_ and Collection. + * * @psalm-immutable */ -final class Complexity +abstract class AbstractList implements Type { + /** @var Type */ + protected $valueType; + /** @var Type|null */ + protected $keyType; + /** @var Type */ + protected $defaultKeyType; /** - * @var string + * Initializes this representation of an array with the given Type. */ - private $name; + public function __construct(?Type $valueType = null, ?Type $keyType = null) + { + if ($valueType === null) { + $valueType = new Mixed_(); + } + $this->valueType = $valueType; + $this->defaultKeyType = new Compound([new String_(), new Integer()]); + $this->keyType = $keyType; + } /** - * @var int + * Returns the type for the keys of this array. */ - private $cyclomaticComplexity; - public function __construct(string $name, int $cyclomaticComplexity) + public function getKeyType() : Type { - $this->name = $name; - $this->cyclomaticComplexity = $cyclomaticComplexity; + return $this->keyType ?? $this->defaultKeyType; } - public function name() : string + /** + * Returns the value for the keys of this array. + */ + public function getValueType() : Type { - return $this->name; + return $this->valueType; } - public function cyclomaticComplexity() : int + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - return $this->cyclomaticComplexity; + if ($this->keyType) { + return 'array<' . $this->keyType . ',' . $this->valueType . '>'; + } + if ($this->valueType instanceof Mixed_) { + return 'array'; + } + if ($this->valueType instanceof Compound) { + return '(' . $this->valueType . ')[]'; + } + return $this->valueType . '[]'; } } + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use Iterator; -final class ComplexityCollectionIterator implements Iterator -{ - /** - * @psalm-var list - */ - private $items; - /** - * @var int - */ - private $position = 0; - public function __construct(ComplexityCollection $items) - { - $this->items = $items->asArray(); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return isset($this->items[$this->position]); - } - public function key() : int - { - return $this->position; - } - public function current() : Complexity - { - return $this->items[$this->position]; - } - public function next() : void - { - $this->position++; - } -} -sebastian/complexity - -Copyright (c) 2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - + * A Aggregated Type is not so much a special keyword or object reference but is a series of Types that are separated + * using separator. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @psalm-immutable + * @template-implements IteratorAggregate */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use function assert; -use function is_array; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Name; -use PHPUnit\PhpParser\Node\Stmt; -use PHPUnit\PhpParser\Node\Stmt\Class_; -use PHPUnit\PhpParser\Node\Stmt\ClassMethod; -use PHPUnit\PhpParser\Node\Stmt\Function_; -use PHPUnit\PhpParser\Node\Stmt\Trait_; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\NodeVisitorAbstract; -final class ComplexityCalculatingVisitor extends NodeVisitorAbstract +abstract class AggregatedType implements Type, IteratorAggregate { /** - * @psalm-var list + * @psalm-allow-private-mutation + * @var array */ - private $result = []; + private $types = []; + /** @var string */ + private $token; /** - * @var bool + * @param array $types */ - private $shortCircuitTraversal; - public function __construct(bool $shortCircuitTraversal) + public function __construct(array $types, string $token) { - $this->shortCircuitTraversal = $shortCircuitTraversal; + foreach ($types as $type) { + $this->add($type); + } + $this->token = $token; } - public function enterNode(Node $node) : ?int + /** + * Returns the type at the given index. + */ + public function get(int $index) : ?Type { - if (!$node instanceof ClassMethod && !$node instanceof Function_) { + if (!$this->has($index)) { return null; } - if ($node instanceof ClassMethod) { - $name = $this->classMethodName($node); - } else { - $name = $this->functionName($node); - } - $statements = $node->getStmts(); - assert(is_array($statements)); - $this->result[] = new Complexity($name, $this->cyclomaticComplexity($statements)); - if ($this->shortCircuitTraversal) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - return null; - } - public function result() : ComplexityCollection - { - return ComplexityCollection::fromList(...$this->result); + return $this->types[$index]; } /** - * @param Stmt[] $statements + * Tests if this compound type has a type with the given index. */ - private function cyclomaticComplexity(array $statements) : int + public function has(int $index) : bool { - $traverser = new NodeTraverser(); - $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor(); - $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($statements); - return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + return array_key_exists($index, $this->types); } - private function classMethodName(ClassMethod $node) : string + /** + * Tests if this compound type contains the given type. + */ + public function contains(Type $type) : bool { - $parent = $node->getAttribute('parent'); - assert($parent instanceof Class_ || $parent instanceof Trait_); - assert(isset($parent->namespacedName)); - assert($parent->namespacedName instanceof Name); - return $parent->namespacedName->toString() . '::' . $node->name->toString(); + foreach ($this->types as $typePart) { + // if the type is duplicate; do not add it + if ((string) $typePart === (string) $type) { + return \true; + } + } + return \false; } - private function functionName(Function_ $node) : string + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string { - assert(isset($node->namespacedName)); - assert($node->namespacedName instanceof Name); - return $node->namespacedName->toString(); + return implode($this->token, $this->types); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Complexity; - -use function get_class; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Expr\BinaryOp\BooleanAnd; -use PHPUnit\PhpParser\Node\Expr\BinaryOp\BooleanOr; -use PHPUnit\PhpParser\Node\Expr\BinaryOp\LogicalAnd; -use PHPUnit\PhpParser\Node\Expr\BinaryOp\LogicalOr; -use PHPUnit\PhpParser\Node\Expr\Ternary; -use PHPUnit\PhpParser\Node\Stmt\Case_; -use PHPUnit\PhpParser\Node\Stmt\Catch_; -use PHPUnit\PhpParser\Node\Stmt\ElseIf_; -use PHPUnit\PhpParser\Node\Stmt\For_; -use PHPUnit\PhpParser\Node\Stmt\Foreach_; -use PHPUnit\PhpParser\Node\Stmt\If_; -use PHPUnit\PhpParser\Node\Stmt\While_; -use PHPUnit\PhpParser\NodeVisitorAbstract; -final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract -{ /** - * @var int + * @return ArrayIterator */ - private $cyclomaticComplexity = 1; - public function enterNode(Node $node) : void + public function getIterator() : ArrayIterator { - /* @noinspection GetClassMissUseInspection */ - switch (get_class($node)) { - case BooleanAnd::class: - case BooleanOr::class: - case Case_::class: - case Catch_::class: - case ElseIf_::class: - case For_::class: - case Foreach_::class: - case If_::class: - case LogicalAnd::class: - case LogicalOr::class: - case Ternary::class: - case While_::class: - $this->cyclomaticComplexity++; - } + return new ArrayIterator($this->types); } - public function cyclomaticComplexity() : int + /** + * @psalm-suppress ImpureMethodCall + */ + private function add(Type $type) : void { - return $this->cyclomaticComplexity; + if ($type instanceof self) { + foreach ($type->getIterator() as $subType) { + $this->add($subType); + } + return; + } + // if the type is duplicate; do not add it + if ($this->contains($type)) { + return; + } + $this->types[] = $type; } } -Object Reflector - -Copyright (c) 2017-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use InvalidArgumentException; -final class NegativeValueException extends InvalidArgumentException implements Exception +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing a array-key Type. + * + * A array-key Type is the supertype (but not a union) of int and string. + * + * @psalm-immutable + */ +final class ArrayKey extends AggregatedType implements PseudoType { + public function __construct() + { + parent::__construct([new String_(), new Integer()], '|'); + } + public function underlyingType() : Type + { + return new Compound([new String_(), new Integer()]); + } + public function __toString() : string + { + return 'array-key'; + } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use LogicException; -final class IllogicalValuesException extends LogicException implements Exception +/** + * Represents an array type as described in the PSR-5, the PHPDoc Standard. + * + * An array can be represented in two forms: + * + * 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'. + * 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a + * type name. + * + * @psalm-immutable + */ +class Array_ extends AbstractList { } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -final class RuntimeException extends \RuntimeException implements Exception +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing a Boolean type. + * + * @psalm-immutable + */ +class Boolean implements Type { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'bool'; + } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use Throwable; -interface Exception extends Throwable +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing a Callable type. + * + * @psalm-immutable + */ +final class Callable_ implements Type { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return 'callable'; + } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use function substr_count; -use PHPUnit\PhpParser\Error; -use PHPUnit\PhpParser\Lexer; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\Parser; -use PHPUnit\PhpParser\ParserFactory; -final class Counter +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\PseudoType; +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class ClassString extends String_ implements PseudoType { + /** @var Fqsen|null */ + private $fqsen; /** - * @throws RuntimeException + * Initializes this representation of a class string with the given Fqsen. */ - public function countInSourceFile(string $sourceFile) : LinesOfCode + public function __construct(?Fqsen $fqsen = null) { - return $this->countInSourceString(\file_get_contents($sourceFile)); + $this->fqsen = $fqsen; + } + public function underlyingType() : Type + { + return new String_(); } /** - * @throws RuntimeException + * Returns the FQSEN associated with this object. */ - public function countInSourceString(string $source) : LinesOfCode + public function getFqsen() : ?Fqsen { - $linesOfCode = substr_count($source, "\n"); - if ($linesOfCode === 0 && !empty($source)) { - $linesOfCode = 1; - } - try { - $nodes = $this->parser()->parse($source); - \assert($nodes !== null); - return $this->countInAbstractSyntaxTree($linesOfCode, $nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); - } - // @codeCoverageIgnoreEnd + return $this->fqsen; } /** - * @param Node[] $nodes - * - * @throws RuntimeException + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes) : LinesOfCode + public function __toString() : string { - $traverser = new NodeTraverser(); - $visitor = new LineCountingVisitor($linesOfCode); - $traverser->addVisitor($visitor); - try { - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + if ($this->fqsen === null) { + return 'class-string'; } - // @codeCoverageIgnoreEnd - return $visitor->result(); - } - private function parser() : Parser - { - return (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); + return 'class-string<' . (string) $this->fqsen . '>'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\Type; /** + * Represents a collection type as described in the PSR-5, the PHPDoc Standard. + * + * A collection can be represented in two forms: + * + * 1. `ACollectionObject` + * 2. `ACollectionObject` + * + * - ACollectionObject can be 'array' or an object that can act as an array + * - aValueType and aKeyType can be any type expression + * * @psalm-immutable */ -final class LinesOfCode +final class Collection extends AbstractList { + /** @var Fqsen|null */ + private $fqsen; /** - * @var int - */ - private $linesOfCode; - /** - * @var int - */ - private $commentLinesOfCode; - /** - * @var int + * Initializes this representation of an array with the given Type or Fqsen. */ - private $nonCommentLinesOfCode; + public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null) + { + parent::__construct($valueType, $keyType); + $this->fqsen = $fqsen; + } /** - * @var int + * Returns the FQSEN associated with this object. */ - private $logicalLinesOfCode; + public function getFqsen() : ?Fqsen + { + return $this->fqsen; + } /** - * @throws IllogicalValuesException - * @throws NegativeValueException + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode, int $logicalLinesOfCode) + public function __toString() : string { - if ($linesOfCode < 0) { - throw new NegativeValueException('$linesOfCode must not be negative'); - } - if ($commentLinesOfCode < 0) { - throw new NegativeValueException('$commentLinesOfCode must not be negative'); - } - if ($nonCommentLinesOfCode < 0) { - throw new NegativeValueException('$nonCommentLinesOfCode must not be negative'); - } - if ($logicalLinesOfCode < 0) { - throw new NegativeValueException('$logicalLinesOfCode must not be negative'); - } - if ($linesOfCode - $commentLinesOfCode !== $nonCommentLinesOfCode) { - throw new IllogicalValuesException('$linesOfCode !== $commentLinesOfCode + $nonCommentLinesOfCode'); + $objectType = (string) ($this->fqsen ?? 'object'); + if ($this->keyType === null) { + return $objectType . '<' . $this->valueType . '>'; } - $this->linesOfCode = $linesOfCode; - $this->commentLinesOfCode = $commentLinesOfCode; - $this->nonCommentLinesOfCode = $nonCommentLinesOfCode; - $this->logicalLinesOfCode = $logicalLinesOfCode; - } - public function linesOfCode() : int - { - return $this->linesOfCode; - } - public function commentLinesOfCode() : int - { - return $this->commentLinesOfCode; - } - public function nonCommentLinesOfCode() : int - { - return $this->nonCommentLinesOfCode; - } - public function logicalLinesOfCode() : int - { - return $this->logicalLinesOfCode; - } - public function plus(self $other) : self - { - return new self($this->linesOfCode() + $other->linesOfCode(), $this->commentLinesOfCode() + $other->commentLinesOfCode(), $this->nonCommentLinesOfCode() + $other->nonCommentLinesOfCode(), $this->logicalLinesOfCode() + $other->logicalLinesOfCode()); + return $objectType . '<' . $this->keyType . ',' . $this->valueType . '>'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\LinesOfCode; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use function array_merge; -use function array_unique; -use function count; -use PHPUnit\PhpParser\Comment; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Expr; -use PHPUnit\PhpParser\NodeVisitorAbstract; -final class LineCountingVisitor extends NodeVisitorAbstract +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing a Compound Type. + * + * A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated + * using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type + * may contain a value with any of the given types. + * + * @psalm-immutable + */ +final class Compound extends AggregatedType { /** - * @var int - */ - private $linesOfCode; - /** - * @var Comment[] - */ - private $comments = []; - /** - * @var int[] - */ - private $linesWithStatements = []; - public function __construct(int $linesOfCode) - { - $this->linesOfCode = $linesOfCode; - } - public function enterNode(Node $node) : void - { - $this->comments = array_merge($this->comments, $node->getComments()); - if (!$node instanceof Expr) { - return; - } - $this->linesWithStatements[] = $node->getStartLine(); - } - public function result() : LinesOfCode - { - $commentLinesOfCode = 0; - foreach ($this->comments() as $comment) { - $commentLinesOfCode += $comment->getEndLine() - $comment->getStartLine() + 1; - } - return new LinesOfCode($this->linesOfCode, $commentLinesOfCode, $this->linesOfCode - $commentLinesOfCode, count(array_unique($this->linesWithStatements))); - } - /** - * @return Comment[] + * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface. + * + * @param array $types */ - private function comments() : array + public function __construct(array $types) { - $comments = []; - foreach ($this->comments as $comment) { - $comments[$comment->getStartLine() . '_' . $comment->getStartTokenPos() . '_' . $comment->getEndLine() . '_' . $comment->getEndTokenPos()] = $comment; - } - return $comments; + parent::__construct($types, '|'); } } -sebastian/lines-of-code - -Copyright (c) 2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -phpunit/phpunit: 9.5.10 -doctrine/instantiator: 1.4.0 -myclabs/deep-copy: 1.10.2 -nikic/php-parser: v4.13.0 -phar-io/manifest: 2.0.3 -phar-io/version: 3.1.0 -phpdocumentor/reflection-common: 2.2.0 -phpdocumentor/reflection-docblock: 5.2.2 -phpdocumentor/type-resolver: 1.5.0 -phpspec/prophecy: 1.14.0 -phpunit/php-code-coverage: 9.2.7 -phpunit/php-file-iterator: 3.0.5 -phpunit/php-invoker: 3.1.1 -phpunit/php-text-template: 2.0.4 -phpunit/php-timer: 5.0.3 -sebastian/cli-parser: 1.0.1 -sebastian/code-unit: 1.0.8 -sebastian/code-unit-reverse-lookup: 2.0.3 -sebastian/comparator: 4.0.6 -sebastian/complexity: 2.0.2 -sebastian/diff: 4.0.4 -sebastian/environment: 5.1.3 -sebastian/exporter: 4.0.3 -sebastian/global-state: 5.0.3 -sebastian/lines-of-code: 1.0.3 -sebastian/object-enumerator: 4.0.4 -sebastian/object-reflector: 2.0.4 -sebastian/recursion-context: 4.0.4 -sebastian/resource-operations: 3.0.3 -sebastian/type: 2.3.4 -sebastian/version: 3.0.2 -symfony/polyfill-ctype: v1.23.0 -theseer/tokenizer: 1.2.1 -webmozart/assert: 1.10.0 Fully Qualified Namespace. + * @psalm-var array + */ + private $namespaceAliases; + /** + * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN) + * format (without a preceding `\`). + * + * @param string $namespace The namespace where this DocBlock resides in. + * @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace. + * @psalm-param array $namespaceAliases + */ + public function __construct(string $namespace, array $namespaceAliases = []) + { + $this->namespace = $namespace !== 'global' && $namespace !== 'default' ? trim($namespace, '\\') : ''; + foreach ($namespaceAliases as $alias => $fqnn) { + if ($fqnn[0] === '\\') { + $fqnn = substr($fqnn, 1); + } + if ($fqnn[strlen($fqnn) - 1] === '\\') { + $fqnn = substr($fqnn, 0, -1); + } + $namespaceAliases[$alias] = $fqnn; + } + $this->namespaceAliases = $namespaceAliases; + } /** - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in. */ - public static function fromSerializationTriggeredException(ReflectionClass $reflectionClass, Exception $exception) : self + public function getNamespace() : string { - return new self(sprintf('An exception was raised while trying to instantiate an instance of "%s" via un-serialization', $reflectionClass->getName()), 0, $exception); + return $this->namespace; } /** - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent + * the alias for the imported Namespace. + * + * @return string[] + * @psalm-return array */ - public static function fromUncleanUnSerialization(ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine) : self + public function getNamespaceAliases() : array { - return new self(sprintf('Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine), 0, new Exception($errorString, $errorCode)); + return $this->namespaceAliases; } } $reflectionClass - */ - public static function fromAbstractClass(ReflectionClass $reflectionClass) : self - { - return new self(sprintf('The provided class "%s" is abstract, and can not be instantiated', $reflectionClass->getName())); - } -} - $reflector */ + return $this->createFromReflectionClass($reflector); } - if (isset(self::$cachedInstantiators[$className])) { - $factory = self::$cachedInstantiators[$className]; - return $factory(); + if ($reflector instanceof ReflectionParameter) { + return $this->createFromReflectionParameter($reflector); } - return $this->buildAndCacheFromFactory($className); + if ($reflector instanceof ReflectionMethod) { + return $this->createFromReflectionMethod($reflector); + } + if ($reflector instanceof ReflectionProperty) { + return $this->createFromReflectionProperty($reflector); + } + if ($reflector instanceof ReflectionClassConstant) { + return $this->createFromReflectionClassConstant($reflector); + } + throw new UnexpectedValueException('Unhandled \\Reflector instance given: ' . get_class($reflector)); } - /** - * Builds the requested object and caches it in static properties for performance - * - * @return object - * - * @template T of object - * @phpstan-param class-string $className - * - * @phpstan-return T - */ - private function buildAndCacheFromFactory(string $className) + private function createFromReflectionParameter(ReflectionParameter $parameter) : Context { - $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); - $instance = $factory(); - if ($this->isSafeToClone(new ReflectionClass($instance))) { - self::$cachedCloneables[$className] = clone $instance; + $class = $parameter->getDeclaringClass(); + if (!$class) { + throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName()); } - return $instance; + return $this->createFromReflectionClass($class); } - /** - * Builds a callable capable of instantiating the given $className without - * invoking its constructor. - * - * @throws InvalidArgumentException - * @throws UnexpectedValueException - * @throws ReflectionException - * - * @template T of object - * @phpstan-param class-string $className - * - * @phpstan-return callable(): T - */ - private function buildFactory(string $className) : callable + private function createFromReflectionMethod(ReflectionMethod $method) : Context { - $reflectionClass = $this->getReflectionClass($className); - if ($this->isInstantiableViaReflection($reflectionClass)) { - return [$reflectionClass, 'newInstanceWithoutConstructor']; - } - $serializedString = sprintf('%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className); - $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); - return static function () use($serializedString) { - return unserialize($serializedString); - }; + $class = $method->getDeclaringClass(); + return $this->createFromReflectionClass($class); } - /** - * @throws InvalidArgumentException - * @throws ReflectionException - * - * @template T of object - * @phpstan-param class-string $className - * - * @phpstan-return ReflectionClass - */ - private function getReflectionClass(string $className) : ReflectionClass + private function createFromReflectionProperty(ReflectionProperty $property) : Context { - if (!class_exists($className)) { - throw InvalidArgumentException::fromNonExistingClass($className); - } - $reflection = new ReflectionClass($className); - if ($reflection->isAbstract()) { - throw InvalidArgumentException::fromAbstractClass($reflection); - } - return $reflection; + $class = $property->getDeclaringClass(); + return $this->createFromReflectionClass($class); + } + private function createFromReflectionClassConstant(ReflectionClassConstant $constant) : Context + { + //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable + /** @phpstan-var ReflectionClass $class */ + $class = $constant->getDeclaringClass(); + return $this->createFromReflectionClass($class); } /** - * @throws UnexpectedValueException - * - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * @phpstan-param ReflectionClass $class */ - private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString) : void + private function createFromReflectionClass(ReflectionClass $class) : Context { - set_error_handler(static function (int $code, string $message, string $file, int $line) use($reflectionClass, &$error) : bool { - $error = UnexpectedValueException::fromUncleanUnSerialization($reflectionClass, $message, $code, $file, $line); - return \true; - }); - try { - $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); - } finally { - restore_error_handler(); - } - if ($error) { - throw $error; + $fileName = $class->getFileName(); + $namespace = $class->getNamespaceName(); + if (is_string($fileName) && file_exists($fileName)) { + $contents = file_get_contents($fileName); + if ($contents === \false) { + throw new RuntimeException('Unable to read file "' . $fileName . '"'); + } + return $this->createForNamespace($namespace, $contents); } + return new Context($namespace, []); } /** - * @throws UnexpectedValueException + * Build a Context for a namespace in the provided file contents. * - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * @see Context for more information on Contexts. + * + * @param string $namespace It does not matter if a `\` precedes the namespace name, + * this method first normalizes. + * @param string $fileContents The file's contents to retrieve the aliases from with the given namespace. */ - private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString) : void + public function createForNamespace(string $namespace, string $fileContents) : Context { - try { - unserialize($serializedString); - } catch (Exception $exception) { - throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); + $namespace = trim($namespace, '\\'); + $useStatements = []; + $currentNamespace = ''; + $tokens = new ArrayIterator(token_get_all($fileContents)); + while ($tokens->valid()) { + $currentToken = $tokens->current(); + switch ($currentToken[0]) { + case T_NAMESPACE: + $currentNamespace = $this->parseNamespace($tokens); + break; + case T_CLASS: + // Fast-forward the iterator through the class so that any + // T_USE tokens found within are skipped - these are not + // valid namespace use statements so should be ignored. + $braceLevel = 0; + $firstBraceFound = \false; + while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { + $currentToken = $tokens->current(); + if ($currentToken === '{' || in_array($currentToken[0], [T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES], \true)) { + if (!$firstBraceFound) { + $firstBraceFound = \true; + } + ++$braceLevel; + } + if ($currentToken === '}') { + --$braceLevel; + } + $tokens->next(); + } + break; + case T_USE: + if ($currentNamespace === $namespace) { + $useStatements += $this->parseUseStatement($tokens); + } + break; + } + $tokens->next(); } + return new Context($namespace, $useStatements); } /** - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * Deduce the name from tokens when we are at the T_NAMESPACE token. + * + * @param ArrayIterator $tokens */ - private function isInstantiableViaReflection(ReflectionClass $reflectionClass) : bool + private function parseNamespace(ArrayIterator $tokens) : string { - return !($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); + // skip to the first string or namespace separator + $this->skipToNextStringOrNamespaceSeparator($tokens); + $name = ''; + $acceptedTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED]; + while ($tokens->valid() && in_array($tokens->current()[0], $acceptedTokens, \true)) { + $name .= $tokens->current()[1]; + $tokens->next(); + } + return $name; } /** - * Verifies whether the given class is to be considered internal + * Deduce the names of all imports when we are at the T_USE token. * - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * @param ArrayIterator $tokens + * + * @return string[] + * @psalm-return array */ - private function hasInternalAncestors(ReflectionClass $reflectionClass) : bool + private function parseUseStatement(ArrayIterator $tokens) : array { - do { - if ($reflectionClass->isInternal()) { - return \true; + $uses = []; + while ($tokens->valid()) { + $this->skipToNextStringOrNamespaceSeparator($tokens); + $uses += $this->extractUseStatements($tokens); + $currentToken = $tokens->current(); + if ($currentToken[0] === self::T_LITERAL_END_OF_USE) { + return $uses; } - $reflectionClass = $reflectionClass->getParentClass(); - } while ($reflectionClass); - return \false; + } + return $uses; } /** - * Checks if a class is cloneable - * - * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. + * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token. * - * @template T of object - * @phpstan-param ReflectionClass $reflectionClass + * @param ArrayIterator $tokens */ - private function isSafeToClone(ReflectionClass $reflectionClass) : bool + private function skipToNextStringOrNamespaceSeparator(ArrayIterator $tokens) : void { - return $reflectionClass->isCloneable() && !$reflectionClass->hasMethod('__clone') && !$reflectionClass->isSubclassOf(ArrayIterator::class); + while ($tokens->valid()) { + $currentToken = $tokens->current(); + if (in_array($currentToken[0], [T_STRING, T_NS_SEPARATOR], \true)) { + break; + } + if ($currentToken[0] === T_NAME_QUALIFIED) { + break; + } + if (defined('T_NAME_FULLY_QUALIFIED') && $currentToken[0] === T_NAME_FULLY_QUALIFIED) { + break; + } + $tokens->next(); + } } -} - namespace. * - * @return object + * @param ArrayIterator $tokens * - * @throws ExceptionInterface + * @return string[] + * @psalm-return array * - * @template T of object - * @phpstan-param class-string $className + * @psalm-suppress TypeDoesNotContainType */ - public function instantiate($className); -} -Copyright (c) 2014 Doctrine Project - -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. + private function extractUseStatements(ArrayIterator $tokens) : array + { + $extractedUseStatements = []; + $groupedNs = ''; + $currentNs = ''; + $currentAlias = ''; + $state = 'start'; + while ($tokens->valid()) { + $currentToken = $tokens->current(); + $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0]; + $tokenValue = is_string($currentToken) ? null : $currentToken[1]; + switch ($state) { + case 'start': + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= (string) $tokenValue; + $currentAlias = $tokenValue; + break; + case T_NAME_QUALIFIED: + case T_NAME_FULLY_QUALIFIED: + $currentNs .= (string) $tokenValue; + $currentAlias = substr((string) $tokenValue, (int) strrpos((string) $tokenValue, '\\') + 1); + break; + case T_CURLY_OPEN: + case '{': + $state = 'grouped'; + $groupedNs = $currentNs; + break; + case T_AS: + $state = 'start-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case 'start-alias': + switch ($tokenId) { + case T_STRING: + $currentAlias = $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case 'grouped': + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= (string) $tokenValue; + $currentAlias = $tokenValue; + break; + case T_AS: + $state = 'grouped-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[(string) $currentAlias] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = ''; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case 'grouped-alias': + switch ($tokenId) { + case T_STRING: + $currentAlias = $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[(string) $currentAlias] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = ''; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + } + if ($state === 'end') { + break; + } + $tokens->next(); + } + if ($groupedNs !== $currentNs) { + $extractedUseStatements[(string) $currentAlias] = $currentNs; + } + return $extractedUseStatements; + } +} +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\ResourceOperations; +namespace PHPUnit\phpDocumentor\Reflection\Types; -final class ResourceOperations +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Represents an expression type as described in the PSR-5, the PHPDoc Standard. + * + * @psalm-immutable + */ +final class Expression implements Type { + /** @var Type */ + protected $valueType; /** - * @return string[] + * Initializes this representation of an array with the given Type. */ - public static function getFunctions() : array + public function __construct(Type $valueType) { - return ['Directory::close', 'Directory::read', 'Directory::rewind', 'DirectoryIterator::openFile', 'FilesystemIterator::openFile', 'Gmagick::readimagefile', 'HttpResponse::getRequestBodyStream', 'HttpResponse::getStream', 'HttpResponse::setStream', 'Imagick::pingImageFile', 'Imagick::readImageFile', 'Imagick::writeImageFile', 'Imagick::writeImagesFile', 'MongoGridFSCursor::__construct', 'MongoGridFSFile::getResource', 'MysqlndUhConnection::stmtInit', 'MysqlndUhConnection::storeResult', 'MysqlndUhConnection::useResult', 'PDF_activate_item', 'PDF_add_launchlink', 'PDF_add_locallink', 'PDF_add_nameddest', 'PDF_add_note', 'PDF_add_pdflink', 'PDF_add_table_cell', 'PDF_add_textflow', 'PDF_add_thumbnail', 'PDF_add_weblink', 'PDF_arc', 'PDF_arcn', 'PDF_attach_file', 'PDF_begin_document', 'PDF_begin_font', 'PDF_begin_glyph', 'PDF_begin_item', 'PDF_begin_layer', 'PDF_begin_page', 'PDF_begin_page_ext', 'PDF_begin_pattern', 'PDF_begin_template', 'PDF_begin_template_ext', 'PDF_circle', 'PDF_clip', 'PDF_close', 'PDF_close_image', 'PDF_close_pdi', 'PDF_close_pdi_page', 'PDF_closepath', 'PDF_closepath_fill_stroke', 'PDF_closepath_stroke', 'PDF_concat', 'PDF_continue_text', 'PDF_create_3dview', 'PDF_create_action', 'PDF_create_annotation', 'PDF_create_bookmark', 'PDF_create_field', 'PDF_create_fieldgroup', 'PDF_create_gstate', 'PDF_create_pvf', 'PDF_create_textflow', 'PDF_curveto', 'PDF_define_layer', 'PDF_delete', 'PDF_delete_pvf', 'PDF_delete_table', 'PDF_delete_textflow', 'PDF_encoding_set_char', 'PDF_end_document', 'PDF_end_font', 'PDF_end_glyph', 'PDF_end_item', 'PDF_end_layer', 'PDF_end_page', 'PDF_end_page_ext', 'PDF_end_pattern', 'PDF_end_template', 'PDF_endpath', 'PDF_fill', 'PDF_fill_imageblock', 'PDF_fill_pdfblock', 'PDF_fill_stroke', 'PDF_fill_textblock', 'PDF_findfont', 'PDF_fit_image', 'PDF_fit_pdi_page', 'PDF_fit_table', 'PDF_fit_textflow', 'PDF_fit_textline', 'PDF_get_apiname', 'PDF_get_buffer', 'PDF_get_errmsg', 'PDF_get_errnum', 'PDF_get_parameter', 'PDF_get_pdi_parameter', 'PDF_get_pdi_value', 'PDF_get_value', 'PDF_info_font', 'PDF_info_matchbox', 'PDF_info_table', 'PDF_info_textflow', 'PDF_info_textline', 'PDF_initgraphics', 'PDF_lineto', 'PDF_load_3ddata', 'PDF_load_font', 'PDF_load_iccprofile', 'PDF_load_image', 'PDF_makespotcolor', 'PDF_moveto', 'PDF_new', 'PDF_open_ccitt', 'PDF_open_file', 'PDF_open_image', 'PDF_open_image_file', 'PDF_open_memory_image', 'PDF_open_pdi', 'PDF_open_pdi_document', 'PDF_open_pdi_page', 'PDF_pcos_get_number', 'PDF_pcos_get_stream', 'PDF_pcos_get_string', 'PDF_place_image', 'PDF_place_pdi_page', 'PDF_process_pdi', 'PDF_rect', 'PDF_restore', 'PDF_resume_page', 'PDF_rotate', 'PDF_save', 'PDF_scale', 'PDF_set_border_color', 'PDF_set_border_dash', 'PDF_set_border_style', 'PDF_set_gstate', 'PDF_set_info', 'PDF_set_layer_dependency', 'PDF_set_parameter', 'PDF_set_text_pos', 'PDF_set_value', 'PDF_setcolor', 'PDF_setdash', 'PDF_setdashpattern', 'PDF_setflat', 'PDF_setfont', 'PDF_setgray', 'PDF_setgray_fill', 'PDF_setgray_stroke', 'PDF_setlinecap', 'PDF_setlinejoin', 'PDF_setlinewidth', 'PDF_setmatrix', 'PDF_setmiterlimit', 'PDF_setrgbcolor', 'PDF_setrgbcolor_fill', 'PDF_setrgbcolor_stroke', 'PDF_shading', 'PDF_shading_pattern', 'PDF_shfill', 'PDF_show', 'PDF_show_boxed', 'PDF_show_xy', 'PDF_skew', 'PDF_stringwidth', 'PDF_stroke', 'PDF_suspend_page', 'PDF_translate', 'PDF_utf16_to_utf8', 'PDF_utf32_to_utf16', 'PDF_utf8_to_utf16', 'PDO::pgsqlLOBOpen', 'RarEntry::getStream', 'SQLite3::openBlob', 'SWFMovie::saveToFile', 'SplFileInfo::openFile', 'SplFileObject::openFile', 'SplTempFileObject::openFile', 'V8Js::compileString', 'V8Js::executeScript', 'Vtiful\\Kernel\\Excel::setColumn', 'Vtiful\\Kernel\\Excel::setRow', 'Vtiful\\Kernel\\Format::align', 'Vtiful\\Kernel\\Format::bold', 'Vtiful\\Kernel\\Format::italic', 'Vtiful\\Kernel\\Format::underline', 'XMLWriter::openMemory', 'XMLWriter::openURI', 'ZipArchive::getStream', 'Zookeeper::setLogStream', 'apc_bin_dumpfile', 'apc_bin_loadfile', 'bbcode_add_element', 'bbcode_add_smiley', 'bbcode_create', 'bbcode_destroy', 'bbcode_parse', 'bbcode_set_arg_parser', 'bbcode_set_flags', 'bcompiler_read', 'bcompiler_write_class', 'bcompiler_write_constant', 'bcompiler_write_exe_footer', 'bcompiler_write_file', 'bcompiler_write_footer', 'bcompiler_write_function', 'bcompiler_write_functions_from_file', 'bcompiler_write_header', 'bcompiler_write_included_filename', 'bzclose', 'bzerrno', 'bzerror', 'bzerrstr', 'bzflush', 'bzopen', 'bzread', 'bzwrite', 'cairo_surface_write_to_png', 'closedir', 'copy', 'crack_closedict', 'crack_opendict', 'cubrid_bind', 'cubrid_close_prepare', 'cubrid_close_request', 'cubrid_col_get', 'cubrid_col_size', 'cubrid_column_names', 'cubrid_column_types', 'cubrid_commit', 'cubrid_connect', 'cubrid_connect_with_url', 'cubrid_current_oid', 'cubrid_db_parameter', 'cubrid_disconnect', 'cubrid_drop', 'cubrid_fetch', 'cubrid_free_result', 'cubrid_get', 'cubrid_get_autocommit', 'cubrid_get_charset', 'cubrid_get_class_name', 'cubrid_get_db_parameter', 'cubrid_get_query_timeout', 'cubrid_get_server_info', 'cubrid_insert_id', 'cubrid_is_instance', 'cubrid_lob2_bind', 'cubrid_lob2_close', 'cubrid_lob2_export', 'cubrid_lob2_import', 'cubrid_lob2_new', 'cubrid_lob2_read', 'cubrid_lob2_seek', 'cubrid_lob2_seek64', 'cubrid_lob2_size', 'cubrid_lob2_size64', 'cubrid_lob2_tell', 'cubrid_lob2_tell64', 'cubrid_lob2_write', 'cubrid_lob_export', 'cubrid_lob_get', 'cubrid_lob_send', 'cubrid_lob_size', 'cubrid_lock_read', 'cubrid_lock_write', 'cubrid_move_cursor', 'cubrid_next_result', 'cubrid_num_cols', 'cubrid_num_rows', 'cubrid_pconnect', 'cubrid_pconnect_with_url', 'cubrid_prepare', 'cubrid_put', 'cubrid_query', 'cubrid_rollback', 'cubrid_schema', 'cubrid_seq_add', 'cubrid_seq_drop', 'cubrid_seq_insert', 'cubrid_seq_put', 'cubrid_set_add', 'cubrid_set_autocommit', 'cubrid_set_db_parameter', 'cubrid_set_drop', 'cubrid_set_query_timeout', 'cubrid_unbuffered_query', 'curl_close', 'curl_copy_handle', 'curl_errno', 'curl_error', 'curl_escape', 'curl_exec', 'curl_getinfo', 'curl_multi_add_handle', 'curl_multi_close', 'curl_multi_errno', 'curl_multi_exec', 'curl_multi_getcontent', 'curl_multi_info_read', 'curl_multi_remove_handle', 'curl_multi_select', 'curl_multi_setopt', 'curl_pause', 'curl_reset', 'curl_setopt', 'curl_setopt_array', 'curl_share_close', 'curl_share_errno', 'curl_share_init', 'curl_share_setopt', 'curl_unescape', 'cyrus_authenticate', 'cyrus_bind', 'cyrus_close', 'cyrus_connect', 'cyrus_query', 'cyrus_unbind', 'db2_autocommit', 'db2_bind_param', 'db2_client_info', 'db2_close', 'db2_column_privileges', 'db2_columns', 'db2_commit', 'db2_conn_error', 'db2_conn_errormsg', 'db2_connect', 'db2_cursor_type', 'db2_exec', 'db2_execute', 'db2_fetch_array', 'db2_fetch_assoc', 'db2_fetch_both', 'db2_fetch_object', 'db2_fetch_row', 'db2_field_display_size', 'db2_field_name', 'db2_field_num', 'db2_field_precision', 'db2_field_scale', 'db2_field_type', 'db2_field_width', 'db2_foreign_keys', 'db2_free_result', 'db2_free_stmt', 'db2_get_option', 'db2_last_insert_id', 'db2_lob_read', 'db2_next_result', 'db2_num_fields', 'db2_num_rows', 'db2_pclose', 'db2_pconnect', 'db2_prepare', 'db2_primary_keys', 'db2_procedure_columns', 'db2_procedures', 'db2_result', 'db2_rollback', 'db2_server_info', 'db2_set_option', 'db2_special_columns', 'db2_statistics', 'db2_stmt_error', 'db2_stmt_errormsg', 'db2_table_privileges', 'db2_tables', 'dba_close', 'dba_delete', 'dba_exists', 'dba_fetch', 'dba_firstkey', 'dba_insert', 'dba_nextkey', 'dba_open', 'dba_optimize', 'dba_popen', 'dba_replace', 'dba_sync', 'dbplus_add', 'dbplus_aql', 'dbplus_close', 'dbplus_curr', 'dbplus_find', 'dbplus_first', 'dbplus_flush', 'dbplus_freelock', 'dbplus_freerlocks', 'dbplus_getlock', 'dbplus_getunique', 'dbplus_info', 'dbplus_last', 'dbplus_lockrel', 'dbplus_next', 'dbplus_open', 'dbplus_prev', 'dbplus_rchperm', 'dbplus_rcreate', 'dbplus_rcrtexact', 'dbplus_rcrtlike', 'dbplus_restorepos', 'dbplus_rkeys', 'dbplus_ropen', 'dbplus_rquery', 'dbplus_rrename', 'dbplus_rsecindex', 'dbplus_runlink', 'dbplus_rzap', 'dbplus_savepos', 'dbplus_setindex', 'dbplus_setindexbynumber', 'dbplus_sql', 'dbplus_tremove', 'dbplus_undo', 'dbplus_undoprepare', 'dbplus_unlockrel', 'dbplus_unselect', 'dbplus_update', 'dbplus_xlockrel', 'dbplus_xunlockrel', 'deflate_add', 'dio_close', 'dio_fcntl', 'dio_open', 'dio_read', 'dio_seek', 'dio_stat', 'dio_tcsetattr', 'dio_truncate', 'dio_write', 'dir', 'eio_busy', 'eio_cancel', 'eio_chmod', 'eio_chown', 'eio_close', 'eio_custom', 'eio_dup2', 'eio_fallocate', 'eio_fchmod', 'eio_fchown', 'eio_fdatasync', 'eio_fstat', 'eio_fstatvfs', 'eio_fsync', 'eio_ftruncate', 'eio_futime', 'eio_get_last_error', 'eio_grp', 'eio_grp_add', 'eio_grp_cancel', 'eio_grp_limit', 'eio_link', 'eio_lstat', 'eio_mkdir', 'eio_mknod', 'eio_nop', 'eio_open', 'eio_read', 'eio_readahead', 'eio_readdir', 'eio_readlink', 'eio_realpath', 'eio_rename', 'eio_rmdir', 'eio_seek', 'eio_sendfile', 'eio_stat', 'eio_statvfs', 'eio_symlink', 'eio_sync', 'eio_sync_file_range', 'eio_syncfs', 'eio_truncate', 'eio_unlink', 'eio_utime', 'eio_write', 'enchant_broker_describe', 'enchant_broker_dict_exists', 'enchant_broker_free', 'enchant_broker_free_dict', 'enchant_broker_get_dict_path', 'enchant_broker_get_error', 'enchant_broker_init', 'enchant_broker_list_dicts', 'enchant_broker_request_dict', 'enchant_broker_request_pwl_dict', 'enchant_broker_set_dict_path', 'enchant_broker_set_ordering', 'enchant_dict_add_to_personal', 'enchant_dict_add_to_session', 'enchant_dict_check', 'enchant_dict_describe', 'enchant_dict_get_error', 'enchant_dict_is_in_session', 'enchant_dict_quick_check', 'enchant_dict_store_replacement', 'enchant_dict_suggest', 'event_add', 'event_base_free', 'event_base_loop', 'event_base_loopbreak', 'event_base_loopexit', 'event_base_new', 'event_base_priority_init', 'event_base_reinit', 'event_base_set', 'event_buffer_base_set', 'event_buffer_disable', 'event_buffer_enable', 'event_buffer_fd_set', 'event_buffer_free', 'event_buffer_new', 'event_buffer_priority_set', 'event_buffer_read', 'event_buffer_set_callback', 'event_buffer_timeout_set', 'event_buffer_watermark_set', 'event_buffer_write', 'event_del', 'event_free', 'event_new', 'event_priority_set', 'event_set', 'event_timer_add', 'event_timer_del', 'event_timer_pending', 'event_timer_set', 'expect_expectl', 'expect_popen', 'fam_cancel_monitor', 'fam_close', 'fam_monitor_collection', 'fam_monitor_directory', 'fam_monitor_file', 'fam_next_event', 'fam_open', 'fam_pending', 'fam_resume_monitor', 'fam_suspend_monitor', 'fann_cascadetrain_on_data', 'fann_cascadetrain_on_file', 'fann_clear_scaling_params', 'fann_copy', 'fann_create_from_file', 'fann_create_shortcut_array', 'fann_create_standard', 'fann_create_standard_array', 'fann_create_train', 'fann_create_train_from_callback', 'fann_descale_input', 'fann_descale_output', 'fann_descale_train', 'fann_destroy', 'fann_destroy_train', 'fann_duplicate_train_data', 'fann_get_MSE', 'fann_get_activation_function', 'fann_get_activation_steepness', 'fann_get_bias_array', 'fann_get_bit_fail', 'fann_get_bit_fail_limit', 'fann_get_cascade_activation_functions', 'fann_get_cascade_activation_functions_count', 'fann_get_cascade_activation_steepnesses', 'fann_get_cascade_activation_steepnesses_count', 'fann_get_cascade_candidate_change_fraction', 'fann_get_cascade_candidate_limit', 'fann_get_cascade_candidate_stagnation_epochs', 'fann_get_cascade_max_cand_epochs', 'fann_get_cascade_max_out_epochs', 'fann_get_cascade_min_cand_epochs', 'fann_get_cascade_min_out_epochs', 'fann_get_cascade_num_candidate_groups', 'fann_get_cascade_num_candidates', 'fann_get_cascade_output_change_fraction', 'fann_get_cascade_output_stagnation_epochs', 'fann_get_cascade_weight_multiplier', 'fann_get_connection_array', 'fann_get_connection_rate', 'fann_get_errno', 'fann_get_errstr', 'fann_get_layer_array', 'fann_get_learning_momentum', 'fann_get_learning_rate', 'fann_get_network_type', 'fann_get_num_input', 'fann_get_num_layers', 'fann_get_num_output', 'fann_get_quickprop_decay', 'fann_get_quickprop_mu', 'fann_get_rprop_decrease_factor', 'fann_get_rprop_delta_max', 'fann_get_rprop_delta_min', 'fann_get_rprop_delta_zero', 'fann_get_rprop_increase_factor', 'fann_get_sarprop_step_error_shift', 'fann_get_sarprop_step_error_threshold_factor', 'fann_get_sarprop_temperature', 'fann_get_sarprop_weight_decay_shift', 'fann_get_total_connections', 'fann_get_total_neurons', 'fann_get_train_error_function', 'fann_get_train_stop_function', 'fann_get_training_algorithm', 'fann_init_weights', 'fann_length_train_data', 'fann_merge_train_data', 'fann_num_input_train_data', 'fann_num_output_train_data', 'fann_randomize_weights', 'fann_read_train_from_file', 'fann_reset_errno', 'fann_reset_errstr', 'fann_run', 'fann_save', 'fann_save_train', 'fann_scale_input', 'fann_scale_input_train_data', 'fann_scale_output', 'fann_scale_output_train_data', 'fann_scale_train', 'fann_scale_train_data', 'fann_set_activation_function', 'fann_set_activation_function_hidden', 'fann_set_activation_function_layer', 'fann_set_activation_function_output', 'fann_set_activation_steepness', 'fann_set_activation_steepness_hidden', 'fann_set_activation_steepness_layer', 'fann_set_activation_steepness_output', 'fann_set_bit_fail_limit', 'fann_set_callback', 'fann_set_cascade_activation_functions', 'fann_set_cascade_activation_steepnesses', 'fann_set_cascade_candidate_change_fraction', 'fann_set_cascade_candidate_limit', 'fann_set_cascade_candidate_stagnation_epochs', 'fann_set_cascade_max_cand_epochs', 'fann_set_cascade_max_out_epochs', 'fann_set_cascade_min_cand_epochs', 'fann_set_cascade_min_out_epochs', 'fann_set_cascade_num_candidate_groups', 'fann_set_cascade_output_change_fraction', 'fann_set_cascade_output_stagnation_epochs', 'fann_set_cascade_weight_multiplier', 'fann_set_error_log', 'fann_set_input_scaling_params', 'fann_set_learning_momentum', 'fann_set_learning_rate', 'fann_set_output_scaling_params', 'fann_set_quickprop_decay', 'fann_set_quickprop_mu', 'fann_set_rprop_decrease_factor', 'fann_set_rprop_delta_max', 'fann_set_rprop_delta_min', 'fann_set_rprop_delta_zero', 'fann_set_rprop_increase_factor', 'fann_set_sarprop_step_error_shift', 'fann_set_sarprop_step_error_threshold_factor', 'fann_set_sarprop_temperature', 'fann_set_sarprop_weight_decay_shift', 'fann_set_scaling_params', 'fann_set_train_error_function', 'fann_set_train_stop_function', 'fann_set_training_algorithm', 'fann_set_weight', 'fann_set_weight_array', 'fann_shuffle_train_data', 'fann_subset_train_data', 'fann_test', 'fann_test_data', 'fann_train', 'fann_train_epoch', 'fann_train_on_data', 'fann_train_on_file', 'fbsql_affected_rows', 'fbsql_autocommit', 'fbsql_blob_size', 'fbsql_change_user', 'fbsql_clob_size', 'fbsql_close', 'fbsql_commit', 'fbsql_connect', 'fbsql_create_blob', 'fbsql_create_clob', 'fbsql_create_db', 'fbsql_data_seek', 'fbsql_database', 'fbsql_database_password', 'fbsql_db_query', 'fbsql_db_status', 'fbsql_drop_db', 'fbsql_errno', 'fbsql_error', 'fbsql_fetch_array', 'fbsql_fetch_assoc', 'fbsql_fetch_field', 'fbsql_fetch_lengths', 'fbsql_fetch_object', 'fbsql_fetch_row', 'fbsql_field_flags', 'fbsql_field_len', 'fbsql_field_name', 'fbsql_field_seek', 'fbsql_field_table', 'fbsql_field_type', 'fbsql_free_result', 'fbsql_get_autostart_info', 'fbsql_hostname', 'fbsql_insert_id', 'fbsql_list_dbs', 'fbsql_list_fields', 'fbsql_list_tables', 'fbsql_next_result', 'fbsql_num_fields', 'fbsql_num_rows', 'fbsql_password', 'fbsql_pconnect', 'fbsql_query', 'fbsql_read_blob', 'fbsql_read_clob', 'fbsql_result', 'fbsql_rollback', 'fbsql_rows_fetched', 'fbsql_select_db', 'fbsql_set_characterset', 'fbsql_set_lob_mode', 'fbsql_set_password', 'fbsql_set_transaction', 'fbsql_start_db', 'fbsql_stop_db', 'fbsql_table_name', 'fbsql_username', 'fclose', 'fdf_add_doc_javascript', 'fdf_add_template', 'fdf_close', 'fdf_create', 'fdf_enum_values', 'fdf_get_ap', 'fdf_get_attachment', 'fdf_get_encoding', 'fdf_get_file', 'fdf_get_flags', 'fdf_get_opt', 'fdf_get_status', 'fdf_get_value', 'fdf_get_version', 'fdf_next_field_name', 'fdf_open', 'fdf_open_string', 'fdf_remove_item', 'fdf_save', 'fdf_save_string', 'fdf_set_ap', 'fdf_set_encoding', 'fdf_set_file', 'fdf_set_flags', 'fdf_set_javascript_action', 'fdf_set_on_import_javascript', 'fdf_set_opt', 'fdf_set_status', 'fdf_set_submit_form_action', 'fdf_set_target_frame', 'fdf_set_value', 'fdf_set_version', 'feof', 'fflush', 'ffmpeg_frame::__construct', 'ffmpeg_frame::toGDImage', 'fgetc', 'fgetcsv', 'fgets', 'fgetss', 'file', 'file_get_contents', 'file_put_contents', 'finfo::buffer', 'finfo::file', 'finfo_buffer', 'finfo_close', 'finfo_file', 'finfo_open', 'finfo_set_flags', 'flock', 'fopen', 'fpassthru', 'fprintf', 'fputcsv', 'fputs', 'fread', 'fscanf', 'fseek', 'fstat', 'ftell', 'ftp_alloc', 'ftp_append', 'ftp_cdup', 'ftp_chdir', 'ftp_chmod', 'ftp_close', 'ftp_delete', 'ftp_exec', 'ftp_fget', 'ftp_fput', 'ftp_get', 'ftp_get_option', 'ftp_login', 'ftp_mdtm', 'ftp_mkdir', 'ftp_mlsd', 'ftp_nb_continue', 'ftp_nb_fget', 'ftp_nb_fput', 'ftp_nb_get', 'ftp_nb_put', 'ftp_nlist', 'ftp_pasv', 'ftp_put', 'ftp_pwd', 'ftp_quit', 'ftp_raw', 'ftp_rawlist', 'ftp_rename', 'ftp_rmdir', 'ftp_set_option', 'ftp_site', 'ftp_size', 'ftp_systype', 'ftruncate', 'fwrite', 'get_resource_type', 'gmp_div', 'gnupg::init', 'gnupg_adddecryptkey', 'gnupg_addencryptkey', 'gnupg_addsignkey', 'gnupg_cleardecryptkeys', 'gnupg_clearencryptkeys', 'gnupg_clearsignkeys', 'gnupg_decrypt', 'gnupg_decryptverify', 'gnupg_encrypt', 'gnupg_encryptsign', 'gnupg_export', 'gnupg_geterror', 'gnupg_getprotocol', 'gnupg_import', 'gnupg_init', 'gnupg_keyinfo', 'gnupg_setarmor', 'gnupg_seterrormode', 'gnupg_setsignmode', 'gnupg_sign', 'gnupg_verify', 'gupnp_context_get_host_ip', 'gupnp_context_get_port', 'gupnp_context_get_subscription_timeout', 'gupnp_context_host_path', 'gupnp_context_new', 'gupnp_context_set_subscription_timeout', 'gupnp_context_timeout_add', 'gupnp_context_unhost_path', 'gupnp_control_point_browse_start', 'gupnp_control_point_browse_stop', 'gupnp_control_point_callback_set', 'gupnp_control_point_new', 'gupnp_device_action_callback_set', 'gupnp_device_info_get', 'gupnp_device_info_get_service', 'gupnp_root_device_get_available', 'gupnp_root_device_get_relative_location', 'gupnp_root_device_new', 'gupnp_root_device_set_available', 'gupnp_root_device_start', 'gupnp_root_device_stop', 'gupnp_service_action_get', 'gupnp_service_action_return', 'gupnp_service_action_return_error', 'gupnp_service_action_set', 'gupnp_service_freeze_notify', 'gupnp_service_info_get', 'gupnp_service_info_get_introspection', 'gupnp_service_introspection_get_state_variable', 'gupnp_service_notify', 'gupnp_service_proxy_action_get', 'gupnp_service_proxy_action_set', 'gupnp_service_proxy_add_notify', 'gupnp_service_proxy_callback_set', 'gupnp_service_proxy_get_subscribed', 'gupnp_service_proxy_remove_notify', 'gupnp_service_proxy_send_action', 'gupnp_service_proxy_set_subscribed', 'gupnp_service_thaw_notify', 'gzclose', 'gzeof', 'gzgetc', 'gzgets', 'gzgetss', 'gzpassthru', 'gzputs', 'gzread', 'gzrewind', 'gzseek', 'gztell', 'gzwrite', 'hash_update_stream', 'http\\Env\\Response::send', 'http_get_request_body_stream', 'ibase_add_user', 'ibase_affected_rows', 'ibase_backup', 'ibase_blob_add', 'ibase_blob_cancel', 'ibase_blob_close', 'ibase_blob_create', 'ibase_blob_get', 'ibase_blob_open', 'ibase_close', 'ibase_commit', 'ibase_commit_ret', 'ibase_connect', 'ibase_db_info', 'ibase_delete_user', 'ibase_drop_db', 'ibase_execute', 'ibase_fetch_assoc', 'ibase_fetch_object', 'ibase_fetch_row', 'ibase_field_info', 'ibase_free_event_handler', 'ibase_free_query', 'ibase_free_result', 'ibase_gen_id', 'ibase_maintain_db', 'ibase_modify_user', 'ibase_name_result', 'ibase_num_fields', 'ibase_num_params', 'ibase_param_info', 'ibase_pconnect', 'ibase_prepare', 'ibase_query', 'ibase_restore', 'ibase_rollback', 'ibase_rollback_ret', 'ibase_server_info', 'ibase_service_attach', 'ibase_service_detach', 'ibase_set_event_handler', 'ibase_trans', 'ifx_affected_rows', 'ifx_close', 'ifx_connect', 'ifx_do', 'ifx_error', 'ifx_fetch_row', 'ifx_fieldproperties', 'ifx_fieldtypes', 'ifx_free_result', 'ifx_getsqlca', 'ifx_htmltbl_result', 'ifx_num_fields', 'ifx_num_rows', 'ifx_pconnect', 'ifx_prepare', 'ifx_query', 'image2wbmp', 'imageaffine', 'imagealphablending', 'imageantialias', 'imagearc', 'imagebmp', 'imagechar', 'imagecharup', 'imagecolorallocate', 'imagecolorallocatealpha', 'imagecolorat', 'imagecolorclosest', 'imagecolorclosestalpha', 'imagecolorclosesthwb', 'imagecolordeallocate', 'imagecolorexact', 'imagecolorexactalpha', 'imagecolormatch', 'imagecolorresolve', 'imagecolorresolvealpha', 'imagecolorset', 'imagecolorsforindex', 'imagecolorstotal', 'imagecolortransparent', 'imageconvolution', 'imagecopy', 'imagecopymerge', 'imagecopymergegray', 'imagecopyresampled', 'imagecopyresized', 'imagecrop', 'imagecropauto', 'imagedashedline', 'imagedestroy', 'imageellipse', 'imagefill', 'imagefilledarc', 'imagefilledellipse', 'imagefilledpolygon', 'imagefilledrectangle', 'imagefilltoborder', 'imagefilter', 'imageflip', 'imagefttext', 'imagegammacorrect', 'imagegd', 'imagegd2', 'imagegetclip', 'imagegif', 'imagegrabscreen', 'imagegrabwindow', 'imageinterlace', 'imageistruecolor', 'imagejpeg', 'imagelayereffect', 'imageline', 'imageopenpolygon', 'imagepalettecopy', 'imagepalettetotruecolor', 'imagepng', 'imagepolygon', 'imagepsencodefont', 'imagepsextendfont', 'imagepsfreefont', 'imagepsloadfont', 'imagepsslantfont', 'imagepstext', 'imagerectangle', 'imageresolution', 'imagerotate', 'imagesavealpha', 'imagescale', 'imagesetbrush', 'imagesetclip', 'imagesetinterpolation', 'imagesetpixel', 'imagesetstyle', 'imagesetthickness', 'imagesettile', 'imagestring', 'imagestringup', 'imagesx', 'imagesy', 'imagetruecolortopalette', 'imagettftext', 'imagewbmp', 'imagewebp', 'imagexbm', 'imap_append', 'imap_body', 'imap_bodystruct', 'imap_check', 'imap_clearflag_full', 'imap_close', 'imap_create', 'imap_createmailbox', 'imap_delete', 'imap_deletemailbox', 'imap_expunge', 'imap_fetch_overview', 'imap_fetchbody', 'imap_fetchheader', 'imap_fetchmime', 'imap_fetchstructure', 'imap_fetchtext', 'imap_gc', 'imap_get_quota', 'imap_get_quotaroot', 'imap_getacl', 'imap_getmailboxes', 'imap_getsubscribed', 'imap_header', 'imap_headerinfo', 'imap_headers', 'imap_list', 'imap_listmailbox', 'imap_listscan', 'imap_listsubscribed', 'imap_lsub', 'imap_mail_copy', 'imap_mail_move', 'imap_mailboxmsginfo', 'imap_msgno', 'imap_num_msg', 'imap_num_recent', 'imap_ping', 'imap_rename', 'imap_renamemailbox', 'imap_reopen', 'imap_savebody', 'imap_scan', 'imap_scanmailbox', 'imap_search', 'imap_set_quota', 'imap_setacl', 'imap_setflag_full', 'imap_sort', 'imap_status', 'imap_subscribe', 'imap_thread', 'imap_uid', 'imap_undelete', 'imap_unsubscribe', 'inflate_add', 'inflate_get_read_len', 'inflate_get_status', 'ingres_autocommit', 'ingres_autocommit_state', 'ingres_charset', 'ingres_close', 'ingres_commit', 'ingres_connect', 'ingres_cursor', 'ingres_errno', 'ingres_error', 'ingres_errsqlstate', 'ingres_escape_string', 'ingres_execute', 'ingres_fetch_array', 'ingres_fetch_assoc', 'ingres_fetch_object', 'ingres_fetch_proc_return', 'ingres_fetch_row', 'ingres_field_length', 'ingres_field_name', 'ingres_field_nullable', 'ingres_field_precision', 'ingres_field_scale', 'ingres_field_type', 'ingres_free_result', 'ingres_next_error', 'ingres_num_fields', 'ingres_num_rows', 'ingres_pconnect', 'ingres_prepare', 'ingres_query', 'ingres_result_seek', 'ingres_rollback', 'ingres_set_environment', 'ingres_unbuffered_query', 'inotify_add_watch', 'inotify_init', 'inotify_queue_len', 'inotify_read', 'inotify_rm_watch', 'kadm5_chpass_principal', 'kadm5_create_principal', 'kadm5_delete_principal', 'kadm5_destroy', 'kadm5_flush', 'kadm5_get_policies', 'kadm5_get_principal', 'kadm5_get_principals', 'kadm5_init_with_password', 'kadm5_modify_principal', 'ldap_add', 'ldap_bind', 'ldap_close', 'ldap_compare', 'ldap_control_paged_result', 'ldap_control_paged_result_response', 'ldap_count_entries', 'ldap_delete', 'ldap_errno', 'ldap_error', 'ldap_exop', 'ldap_exop_passwd', 'ldap_exop_refresh', 'ldap_exop_whoami', 'ldap_first_attribute', 'ldap_first_entry', 'ldap_first_reference', 'ldap_free_result', 'ldap_get_attributes', 'ldap_get_dn', 'ldap_get_entries', 'ldap_get_option', 'ldap_get_values', 'ldap_get_values_len', 'ldap_mod_add', 'ldap_mod_del', 'ldap_mod_replace', 'ldap_modify', 'ldap_modify_batch', 'ldap_next_attribute', 'ldap_next_entry', 'ldap_next_reference', 'ldap_parse_exop', 'ldap_parse_reference', 'ldap_parse_result', 'ldap_rename', 'ldap_sasl_bind', 'ldap_set_option', 'ldap_set_rebind_proc', 'ldap_sort', 'ldap_start_tls', 'ldap_unbind', 'libxml_set_streams_context', 'm_checkstatus', 'm_completeauthorizations', 'm_connect', 'm_connectionerror', 'm_deletetrans', 'm_destroyconn', 'm_getcell', 'm_getcellbynum', 'm_getcommadelimited', 'm_getheader', 'm_initconn', 'm_iscommadelimited', 'm_maxconntimeout', 'm_monitor', 'm_numcolumns', 'm_numrows', 'm_parsecommadelimited', 'm_responsekeys', 'm_responseparam', 'm_returnstatus', 'm_setblocking', 'm_setdropfile', 'm_setip', 'm_setssl', 'm_setssl_cafile', 'm_setssl_files', 'm_settimeout', 'm_transactionssent', 'm_transinqueue', 'm_transkeyval', 'm_transnew', 'm_transsend', 'm_validateidentifier', 'm_verifyconnection', 'm_verifysslcert', 'mailparse_determine_best_xfer_encoding', 'mailparse_msg_create', 'mailparse_msg_extract_part', 'mailparse_msg_extract_part_file', 'mailparse_msg_extract_whole_part_file', 'mailparse_msg_free', 'mailparse_msg_get_part', 'mailparse_msg_get_part_data', 'mailparse_msg_get_structure', 'mailparse_msg_parse', 'mailparse_msg_parse_file', 'mailparse_stream_encode', 'mailparse_uudecode_all', 'maxdb::use_result', 'maxdb_affected_rows', 'maxdb_connect', 'maxdb_disable_rpl_parse', 'maxdb_dump_debug_info', 'maxdb_embedded_connect', 'maxdb_enable_reads_from_master', 'maxdb_enable_rpl_parse', 'maxdb_errno', 'maxdb_error', 'maxdb_fetch_lengths', 'maxdb_field_tell', 'maxdb_get_host_info', 'maxdb_get_proto_info', 'maxdb_get_server_info', 'maxdb_get_server_version', 'maxdb_info', 'maxdb_init', 'maxdb_insert_id', 'maxdb_master_query', 'maxdb_more_results', 'maxdb_next_result', 'maxdb_num_fields', 'maxdb_num_rows', 'maxdb_rpl_parse_enabled', 'maxdb_rpl_probe', 'maxdb_select_db', 'maxdb_sqlstate', 'maxdb_stmt::result_metadata', 'maxdb_stmt_affected_rows', 'maxdb_stmt_errno', 'maxdb_stmt_error', 'maxdb_stmt_num_rows', 'maxdb_stmt_param_count', 'maxdb_stmt_result_metadata', 'maxdb_stmt_sqlstate', 'maxdb_thread_id', 'maxdb_use_result', 'maxdb_warning_count', 'mcrypt_enc_get_algorithms_name', 'mcrypt_enc_get_block_size', 'mcrypt_enc_get_iv_size', 'mcrypt_enc_get_key_size', 'mcrypt_enc_get_modes_name', 'mcrypt_enc_get_supported_key_sizes', 'mcrypt_enc_is_block_algorithm', 'mcrypt_enc_is_block_algorithm_mode', 'mcrypt_enc_is_block_mode', 'mcrypt_enc_self_test', 'mcrypt_generic', 'mcrypt_generic_deinit', 'mcrypt_generic_end', 'mcrypt_generic_init', 'mcrypt_module_close', 'mcrypt_module_open', 'mdecrypt_generic', 'mkdir', 'mqseries_back', 'mqseries_begin', 'mqseries_close', 'mqseries_cmit', 'mqseries_conn', 'mqseries_connx', 'mqseries_disc', 'mqseries_get', 'mqseries_inq', 'mqseries_open', 'mqseries_put', 'mqseries_put1', 'mqseries_set', 'msg_get_queue', 'msg_receive', 'msg_remove_queue', 'msg_send', 'msg_set_queue', 'msg_stat_queue', 'msql_affected_rows', 'msql_close', 'msql_connect', 'msql_create_db', 'msql_data_seek', 'msql_db_query', 'msql_drop_db', 'msql_fetch_array', 'msql_fetch_field', 'msql_fetch_object', 'msql_fetch_row', 'msql_field_flags', 'msql_field_len', 'msql_field_name', 'msql_field_seek', 'msql_field_table', 'msql_field_type', 'msql_free_result', 'msql_list_dbs', 'msql_list_fields', 'msql_list_tables', 'msql_num_fields', 'msql_num_rows', 'msql_pconnect', 'msql_query', 'msql_result', 'msql_select_db', 'mssql_bind', 'mssql_close', 'mssql_connect', 'mssql_data_seek', 'mssql_execute', 'mssql_fetch_array', 'mssql_fetch_assoc', 'mssql_fetch_batch', 'mssql_fetch_field', 'mssql_fetch_object', 'mssql_fetch_row', 'mssql_field_length', 'mssql_field_name', 'mssql_field_seek', 'mssql_field_type', 'mssql_free_result', 'mssql_free_statement', 'mssql_init', 'mssql_next_result', 'mssql_num_fields', 'mssql_num_rows', 'mssql_pconnect', 'mssql_query', 'mssql_result', 'mssql_rows_affected', 'mssql_select_db', 'mysql_affected_rows', 'mysql_client_encoding', 'mysql_close', 'mysql_connect', 'mysql_create_db', 'mysql_data_seek', 'mysql_db_name', 'mysql_db_query', 'mysql_drop_db', 'mysql_errno', 'mysql_error', 'mysql_fetch_array', 'mysql_fetch_assoc', 'mysql_fetch_field', 'mysql_fetch_lengths', 'mysql_fetch_object', 'mysql_fetch_row', 'mysql_field_flags', 'mysql_field_len', 'mysql_field_name', 'mysql_field_seek', 'mysql_field_table', 'mysql_field_type', 'mysql_free_result', 'mysql_get_host_info', 'mysql_get_proto_info', 'mysql_get_server_info', 'mysql_info', 'mysql_insert_id', 'mysql_list_dbs', 'mysql_list_fields', 'mysql_list_processes', 'mysql_list_tables', 'mysql_num_fields', 'mysql_num_rows', 'mysql_pconnect', 'mysql_ping', 'mysql_query', 'mysql_real_escape_string', 'mysql_result', 'mysql_select_db', 'mysql_set_charset', 'mysql_stat', 'mysql_tablename', 'mysql_thread_id', 'mysql_unbuffered_query', 'mysqlnd_uh_convert_to_mysqlnd', 'ncurses_bottom_panel', 'ncurses_del_panel', 'ncurses_delwin', 'ncurses_getmaxyx', 'ncurses_getyx', 'ncurses_hide_panel', 'ncurses_keypad', 'ncurses_meta', 'ncurses_move_panel', 'ncurses_mvwaddstr', 'ncurses_new_panel', 'ncurses_newpad', 'ncurses_newwin', 'ncurses_panel_above', 'ncurses_panel_below', 'ncurses_panel_window', 'ncurses_pnoutrefresh', 'ncurses_prefresh', 'ncurses_replace_panel', 'ncurses_show_panel', 'ncurses_top_panel', 'ncurses_waddch', 'ncurses_waddstr', 'ncurses_wattroff', 'ncurses_wattron', 'ncurses_wattrset', 'ncurses_wborder', 'ncurses_wclear', 'ncurses_wcolor_set', 'ncurses_werase', 'ncurses_wgetch', 'ncurses_whline', 'ncurses_wmouse_trafo', 'ncurses_wmove', 'ncurses_wnoutrefresh', 'ncurses_wrefresh', 'ncurses_wstandend', 'ncurses_wstandout', 'ncurses_wvline', 'newt_button', 'newt_button_bar', 'newt_checkbox', 'newt_checkbox_get_value', 'newt_checkbox_set_flags', 'newt_checkbox_set_value', 'newt_checkbox_tree', 'newt_checkbox_tree_add_item', 'newt_checkbox_tree_find_item', 'newt_checkbox_tree_get_current', 'newt_checkbox_tree_get_entry_value', 'newt_checkbox_tree_get_multi_selection', 'newt_checkbox_tree_get_selection', 'newt_checkbox_tree_multi', 'newt_checkbox_tree_set_current', 'newt_checkbox_tree_set_entry', 'newt_checkbox_tree_set_entry_value', 'newt_checkbox_tree_set_width', 'newt_compact_button', 'newt_component_add_callback', 'newt_component_takes_focus', 'newt_create_grid', 'newt_draw_form', 'newt_entry', 'newt_entry_get_value', 'newt_entry_set', 'newt_entry_set_filter', 'newt_entry_set_flags', 'newt_form', 'newt_form_add_component', 'newt_form_add_components', 'newt_form_add_hot_key', 'newt_form_destroy', 'newt_form_get_current', 'newt_form_run', 'newt_form_set_background', 'newt_form_set_height', 'newt_form_set_size', 'newt_form_set_timer', 'newt_form_set_width', 'newt_form_watch_fd', 'newt_grid_add_components_to_form', 'newt_grid_basic_window', 'newt_grid_free', 'newt_grid_get_size', 'newt_grid_h_close_stacked', 'newt_grid_h_stacked', 'newt_grid_place', 'newt_grid_set_field', 'newt_grid_simple_window', 'newt_grid_v_close_stacked', 'newt_grid_v_stacked', 'newt_grid_wrapped_window', 'newt_grid_wrapped_window_at', 'newt_label', 'newt_label_set_text', 'newt_listbox', 'newt_listbox_append_entry', 'newt_listbox_clear', 'newt_listbox_clear_selection', 'newt_listbox_delete_entry', 'newt_listbox_get_current', 'newt_listbox_get_selection', 'newt_listbox_insert_entry', 'newt_listbox_item_count', 'newt_listbox_select_item', 'newt_listbox_set_current', 'newt_listbox_set_current_by_key', 'newt_listbox_set_data', 'newt_listbox_set_entry', 'newt_listbox_set_width', 'newt_listitem', 'newt_listitem_get_data', 'newt_listitem_set', 'newt_radio_get_current', 'newt_radiobutton', 'newt_run_form', 'newt_scale', 'newt_scale_set', 'newt_scrollbar_set', 'newt_textbox', 'newt_textbox_get_num_lines', 'newt_textbox_reflowed', 'newt_textbox_set_height', 'newt_textbox_set_text', 'newt_vertical_scrollbar', 'oci_bind_array_by_name', 'oci_bind_by_name', 'oci_cancel', 'oci_close', 'oci_commit', 'oci_connect', 'oci_define_by_name', 'oci_error', 'oci_execute', 'oci_fetch', 'oci_fetch_all', 'oci_fetch_array', 'oci_fetch_assoc', 'oci_fetch_object', 'oci_fetch_row', 'oci_field_is_null', 'oci_field_name', 'oci_field_precision', 'oci_field_scale', 'oci_field_size', 'oci_field_type', 'oci_field_type_raw', 'oci_free_cursor', 'oci_free_statement', 'oci_get_implicit_resultset', 'oci_new_collection', 'oci_new_connect', 'oci_new_cursor', 'oci_new_descriptor', 'oci_num_fields', 'oci_num_rows', 'oci_parse', 'oci_pconnect', 'oci_register_taf_callback', 'oci_result', 'oci_rollback', 'oci_server_version', 'oci_set_action', 'oci_set_client_identifier', 'oci_set_client_info', 'oci_set_module_name', 'oci_set_prefetch', 'oci_statement_type', 'oci_unregister_taf_callback', 'odbc_autocommit', 'odbc_close', 'odbc_columnprivileges', 'odbc_columns', 'odbc_commit', 'odbc_connect', 'odbc_cursor', 'odbc_data_source', 'odbc_do', 'odbc_error', 'odbc_errormsg', 'odbc_exec', 'odbc_execute', 'odbc_fetch_array', 'odbc_fetch_into', 'odbc_fetch_row', 'odbc_field_len', 'odbc_field_name', 'odbc_field_num', 'odbc_field_precision', 'odbc_field_scale', 'odbc_field_type', 'odbc_foreignkeys', 'odbc_free_result', 'odbc_gettypeinfo', 'odbc_next_result', 'odbc_num_fields', 'odbc_num_rows', 'odbc_pconnect', 'odbc_prepare', 'odbc_primarykeys', 'odbc_procedurecolumns', 'odbc_procedures', 'odbc_result', 'odbc_result_all', 'odbc_rollback', 'odbc_setoption', 'odbc_specialcolumns', 'odbc_statistics', 'odbc_tableprivileges', 'odbc_tables', 'openal_buffer_create', 'openal_buffer_data', 'openal_buffer_destroy', 'openal_buffer_get', 'openal_buffer_loadwav', 'openal_context_create', 'openal_context_current', 'openal_context_destroy', 'openal_context_process', 'openal_context_suspend', 'openal_device_close', 'openal_device_open', 'openal_source_create', 'openal_source_destroy', 'openal_source_get', 'openal_source_pause', 'openal_source_play', 'openal_source_rewind', 'openal_source_set', 'openal_source_stop', 'openal_stream', 'opendir', 'openssl_csr_new', 'openssl_dh_compute_key', 'openssl_free_key', 'openssl_pkey_export', 'openssl_pkey_free', 'openssl_pkey_get_details', 'openssl_spki_new', 'openssl_x509_free', 'pclose', 'pfsockopen', 'pg_affected_rows', 'pg_cancel_query', 'pg_client_encoding', 'pg_close', 'pg_connect_poll', 'pg_connection_busy', 'pg_connection_reset', 'pg_connection_status', 'pg_consume_input', 'pg_convert', 'pg_copy_from', 'pg_copy_to', 'pg_dbname', 'pg_delete', 'pg_end_copy', 'pg_escape_bytea', 'pg_escape_identifier', 'pg_escape_literal', 'pg_escape_string', 'pg_execute', 'pg_fetch_all', 'pg_fetch_all_columns', 'pg_fetch_array', 'pg_fetch_assoc', 'pg_fetch_row', 'pg_field_name', 'pg_field_num', 'pg_field_size', 'pg_field_table', 'pg_field_type', 'pg_field_type_oid', 'pg_flush', 'pg_free_result', 'pg_get_notify', 'pg_get_pid', 'pg_get_result', 'pg_host', 'pg_insert', 'pg_last_error', 'pg_last_notice', 'pg_last_oid', 'pg_lo_close', 'pg_lo_create', 'pg_lo_export', 'pg_lo_import', 'pg_lo_open', 'pg_lo_read', 'pg_lo_read_all', 'pg_lo_seek', 'pg_lo_tell', 'pg_lo_truncate', 'pg_lo_unlink', 'pg_lo_write', 'pg_meta_data', 'pg_num_fields', 'pg_num_rows', 'pg_options', 'pg_parameter_status', 'pg_ping', 'pg_port', 'pg_prepare', 'pg_put_line', 'pg_query', 'pg_query_params', 'pg_result_error', 'pg_result_error_field', 'pg_result_seek', 'pg_result_status', 'pg_select', 'pg_send_execute', 'pg_send_prepare', 'pg_send_query', 'pg_send_query_params', 'pg_set_client_encoding', 'pg_set_error_verbosity', 'pg_socket', 'pg_trace', 'pg_transaction_status', 'pg_tty', 'pg_untrace', 'pg_update', 'pg_version', 'php_user_filter::filter', 'proc_close', 'proc_get_status', 'proc_terminate', 'ps_add_bookmark', 'ps_add_launchlink', 'ps_add_locallink', 'ps_add_note', 'ps_add_pdflink', 'ps_add_weblink', 'ps_arc', 'ps_arcn', 'ps_begin_page', 'ps_begin_pattern', 'ps_begin_template', 'ps_circle', 'ps_clip', 'ps_close', 'ps_close_image', 'ps_closepath', 'ps_closepath_stroke', 'ps_continue_text', 'ps_curveto', 'ps_delete', 'ps_end_page', 'ps_end_pattern', 'ps_end_template', 'ps_fill', 'ps_fill_stroke', 'ps_findfont', 'ps_get_buffer', 'ps_get_parameter', 'ps_get_value', 'ps_hyphenate', 'ps_include_file', 'ps_lineto', 'ps_makespotcolor', 'ps_moveto', 'ps_new', 'ps_open_file', 'ps_open_image', 'ps_open_image_file', 'ps_open_memory_image', 'ps_place_image', 'ps_rect', 'ps_restore', 'ps_rotate', 'ps_save', 'ps_scale', 'ps_set_border_color', 'ps_set_border_dash', 'ps_set_border_style', 'ps_set_info', 'ps_set_parameter', 'ps_set_text_pos', 'ps_set_value', 'ps_setcolor', 'ps_setdash', 'ps_setflat', 'ps_setfont', 'ps_setgray', 'ps_setlinecap', 'ps_setlinejoin', 'ps_setlinewidth', 'ps_setmiterlimit', 'ps_setoverprintmode', 'ps_setpolydash', 'ps_shading', 'ps_shading_pattern', 'ps_shfill', 'ps_show', 'ps_show2', 'ps_show_boxed', 'ps_show_xy', 'ps_show_xy2', 'ps_string_geometry', 'ps_stringwidth', 'ps_stroke', 'ps_symbol', 'ps_symbol_name', 'ps_symbol_width', 'ps_translate', 'px_close', 'px_create_fp', 'px_date2string', 'px_delete', 'px_delete_record', 'px_get_field', 'px_get_info', 'px_get_parameter', 'px_get_record', 'px_get_schema', 'px_get_value', 'px_insert_record', 'px_new', 'px_numfields', 'px_numrecords', 'px_open_fp', 'px_put_record', 'px_retrieve_record', 'px_set_blob_file', 'px_set_parameter', 'px_set_tablename', 'px_set_targetencoding', 'px_set_value', 'px_timestamp2string', 'px_update_record', 'radius_acct_open', 'radius_add_server', 'radius_auth_open', 'radius_close', 'radius_config', 'radius_create_request', 'radius_demangle', 'radius_demangle_mppe_key', 'radius_get_attr', 'radius_put_addr', 'radius_put_attr', 'radius_put_int', 'radius_put_string', 'radius_put_vendor_addr', 'radius_put_vendor_attr', 'radius_put_vendor_int', 'radius_put_vendor_string', 'radius_request_authenticator', 'radius_salt_encrypt_attr', 'radius_send_request', 'radius_server_secret', 'radius_strerror', 'readdir', 'readfile', 'recode_file', 'rename', 'rewind', 'rewinddir', 'rmdir', 'rpm_close', 'rpm_get_tag', 'rpm_open', 'sapi_windows_vt100_support', 'scandir', 'sem_acquire', 'sem_get', 'sem_release', 'sem_remove', 'set_file_buffer', 'shm_attach', 'shm_detach', 'shm_get_var', 'shm_has_var', 'shm_put_var', 'shm_remove', 'shm_remove_var', 'shmop_close', 'shmop_delete', 'shmop_open', 'shmop_read', 'shmop_size', 'shmop_write', 'socket_accept', 'socket_addrinfo_bind', 'socket_addrinfo_connect', 'socket_addrinfo_explain', 'socket_bind', 'socket_clear_error', 'socket_close', 'socket_connect', 'socket_export_stream', 'socket_get_option', 'socket_get_status', 'socket_getopt', 'socket_getpeername', 'socket_getsockname', 'socket_import_stream', 'socket_last_error', 'socket_listen', 'socket_read', 'socket_recv', 'socket_recvfrom', 'socket_recvmsg', 'socket_send', 'socket_sendmsg', 'socket_sendto', 'socket_set_block', 'socket_set_blocking', 'socket_set_nonblock', 'socket_set_option', 'socket_set_timeout', 'socket_shutdown', 'socket_write', 'sqlite_close', 'sqlite_fetch_string', 'sqlite_has_more', 'sqlite_open', 'sqlite_popen', 'sqlsrv_begin_transaction', 'sqlsrv_cancel', 'sqlsrv_client_info', 'sqlsrv_close', 'sqlsrv_commit', 'sqlsrv_connect', 'sqlsrv_execute', 'sqlsrv_fetch', 'sqlsrv_fetch_array', 'sqlsrv_fetch_object', 'sqlsrv_field_metadata', 'sqlsrv_free_stmt', 'sqlsrv_get_field', 'sqlsrv_has_rows', 'sqlsrv_next_result', 'sqlsrv_num_fields', 'sqlsrv_num_rows', 'sqlsrv_prepare', 'sqlsrv_query', 'sqlsrv_rollback', 'sqlsrv_rows_affected', 'sqlsrv_send_stream_data', 'sqlsrv_server_info', 'ssh2_auth_agent', 'ssh2_auth_hostbased_file', 'ssh2_auth_none', 'ssh2_auth_password', 'ssh2_auth_pubkey_file', 'ssh2_disconnect', 'ssh2_exec', 'ssh2_fetch_stream', 'ssh2_fingerprint', 'ssh2_methods_negotiated', 'ssh2_publickey_add', 'ssh2_publickey_init', 'ssh2_publickey_list', 'ssh2_publickey_remove', 'ssh2_scp_recv', 'ssh2_scp_send', 'ssh2_sftp', 'ssh2_sftp_chmod', 'ssh2_sftp_lstat', 'ssh2_sftp_mkdir', 'ssh2_sftp_readlink', 'ssh2_sftp_realpath', 'ssh2_sftp_rename', 'ssh2_sftp_rmdir', 'ssh2_sftp_stat', 'ssh2_sftp_symlink', 'ssh2_sftp_unlink', 'ssh2_shell', 'ssh2_tunnel', 'stomp_connect', 'streamWrapper::stream_cast', 'stream_bucket_append', 'stream_bucket_make_writeable', 'stream_bucket_new', 'stream_bucket_prepend', 'stream_context_create', 'stream_context_get_default', 'stream_context_get_options', 'stream_context_get_params', 'stream_context_set_default', 'stream_context_set_params', 'stream_copy_to_stream', 'stream_encoding', 'stream_filter_append', 'stream_filter_prepend', 'stream_filter_remove', 'stream_get_contents', 'stream_get_line', 'stream_get_meta_data', 'stream_isatty', 'stream_set_blocking', 'stream_set_chunk_size', 'stream_set_read_buffer', 'stream_set_timeout', 'stream_set_write_buffer', 'stream_socket_accept', 'stream_socket_client', 'stream_socket_enable_crypto', 'stream_socket_get_name', 'stream_socket_recvfrom', 'stream_socket_sendto', 'stream_socket_server', 'stream_socket_shutdown', 'stream_supports_lock', 'svn_fs_abort_txn', 'svn_fs_apply_text', 'svn_fs_begin_txn2', 'svn_fs_change_node_prop', 'svn_fs_check_path', 'svn_fs_contents_changed', 'svn_fs_copy', 'svn_fs_delete', 'svn_fs_dir_entries', 'svn_fs_file_contents', 'svn_fs_file_length', 'svn_fs_is_dir', 'svn_fs_is_file', 'svn_fs_make_dir', 'svn_fs_make_file', 'svn_fs_node_created_rev', 'svn_fs_node_prop', 'svn_fs_props_changed', 'svn_fs_revision_prop', 'svn_fs_revision_root', 'svn_fs_txn_root', 'svn_fs_youngest_rev', 'svn_repos_create', 'svn_repos_fs', 'svn_repos_fs_begin_txn_for_commit', 'svn_repos_fs_commit_txn', 'svn_repos_open', 'sybase_affected_rows', 'sybase_close', 'sybase_connect', 'sybase_data_seek', 'sybase_fetch_array', 'sybase_fetch_assoc', 'sybase_fetch_field', 'sybase_fetch_object', 'sybase_fetch_row', 'sybase_field_seek', 'sybase_free_result', 'sybase_num_fields', 'sybase_num_rows', 'sybase_pconnect', 'sybase_query', 'sybase_result', 'sybase_select_db', 'sybase_set_message_handler', 'sybase_unbuffered_query', 'tmpfile', 'udm_add_search_limit', 'udm_alloc_agent', 'udm_alloc_agent_array', 'udm_cat_list', 'udm_cat_path', 'udm_check_charset', 'udm_clear_search_limits', 'udm_crc32', 'udm_errno', 'udm_error', 'udm_find', 'udm_free_agent', 'udm_free_res', 'udm_get_doc_count', 'udm_get_res_field', 'udm_get_res_param', 'udm_hash32', 'udm_load_ispell_data', 'udm_set_agent_param', 'unlink', 'vfprintf', 'w32api_init_dtype', 'wddx_add_vars', 'wddx_packet_end', 'wddx_packet_start', 'xml_get_current_byte_index', 'xml_get_current_column_number', 'xml_get_current_line_number', 'xml_get_error_code', 'xml_parse', 'xml_parse_into_struct', 'xml_parser_create', 'xml_parser_create_ns', 'xml_parser_free', 'xml_parser_get_option', 'xml_parser_set_option', 'xml_set_character_data_handler', 'xml_set_default_handler', 'xml_set_element_handler', 'xml_set_end_namespace_decl_handler', 'xml_set_external_entity_ref_handler', 'xml_set_notation_decl_handler', 'xml_set_object', 'xml_set_processing_instruction_handler', 'xml_set_start_namespace_decl_handler', 'xml_set_unparsed_entity_decl_handler', 'xmlrpc_server_add_introspection_data', 'xmlrpc_server_call_method', 'xmlrpc_server_create', 'xmlrpc_server_destroy', 'xmlrpc_server_register_introspection_callback', 'xmlrpc_server_register_method', 'xmlwriter_end_attribute', 'xmlwriter_end_cdata', 'xmlwriter_end_comment', 'xmlwriter_end_document', 'xmlwriter_end_dtd', 'xmlwriter_end_dtd_attlist', 'xmlwriter_end_dtd_element', 'xmlwriter_end_dtd_entity', 'xmlwriter_end_element', 'xmlwriter_end_pi', 'xmlwriter_flush', 'xmlwriter_full_end_element', 'xmlwriter_open_memory', 'xmlwriter_open_uri', 'xmlwriter_output_memory', 'xmlwriter_set_indent', 'xmlwriter_set_indent_string', 'xmlwriter_start_attribute', 'xmlwriter_start_attribute_ns', 'xmlwriter_start_cdata', 'xmlwriter_start_comment', 'xmlwriter_start_document', 'xmlwriter_start_dtd', 'xmlwriter_start_dtd_attlist', 'xmlwriter_start_dtd_element', 'xmlwriter_start_dtd_entity', 'xmlwriter_start_element', 'xmlwriter_start_element_ns', 'xmlwriter_start_pi', 'xmlwriter_text', 'xmlwriter_write_attribute', 'xmlwriter_write_attribute_ns', 'xmlwriter_write_cdata', 'xmlwriter_write_comment', 'xmlwriter_write_dtd', 'xmlwriter_write_dtd_attlist', 'xmlwriter_write_dtd_element', 'xmlwriter_write_dtd_entity', 'xmlwriter_write_element', 'xmlwriter_write_element_ns', 'xmlwriter_write_pi', 'xmlwriter_write_raw', 'xslt_create', 'yaz_addinfo', 'yaz_ccl_conf', 'yaz_ccl_parse', 'yaz_close', 'yaz_database', 'yaz_element', 'yaz_errno', 'yaz_error', 'yaz_es', 'yaz_es_result', 'yaz_get_option', 'yaz_hits', 'yaz_itemorder', 'yaz_present', 'yaz_range', 'yaz_record', 'yaz_scan', 'yaz_scan_result', 'yaz_schema', 'yaz_search', 'yaz_sort', 'yaz_syntax', 'zip_close', 'zip_entry_close', 'zip_entry_compressedsize', 'zip_entry_compressionmethod', 'zip_entry_filesize', 'zip_entry_name', 'zip_entry_open', 'zip_entry_read', 'zip_open', 'zip_read']; + $this->valueType = $valueType; + } + /** + * Returns the value for the keys of this array. + */ + public function getValueType() : Type + { + return $this->valueType; + } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + return '(' . $this->valueType . ')'; } } -Resource Operations - -Copyright (c) 2015-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace PHPUnit\phpDocumentor\Reflection\Types; +use PHPUnit\phpDocumentor\Reflection\Type; /** + * Value Object representing a Float. + * * @psalm-immutable */ -final class FunctionUnit extends CodeUnit +final class Float_ implements Type { /** - * @psalm-assert-if-true FunctionUnit $this + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - public function isFunction() : bool + public function __toString() : string { - return \true; + return 'float'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use Iterator; -final class CodeUnitCollectionIterator implements Iterator +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value object representing Integer type + * + * @psalm-immutable + */ +class Integer implements Type { /** - * @psalm-var list - */ - private $codeUnits; - /** - * @var int + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private $position = 0; - public function __construct(CodeUnitCollection $collection) - { - $this->codeUnits = $collection->asArray(); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return isset($this->codeUnits[$this->position]); - } - public function key() : int - { - return $this->position; - } - public function current() : CodeUnit - { - return $this->codeUnits[$this->position]; - } - public function next() : void + public function __toString() : string { - $this->position++; + return 'int'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use function array_keys; -use function array_merge; -use function array_unique; -use function array_values; -use function class_exists; -use function explode; -use function function_exists; -use function interface_exists; -use function ksort; -use function method_exists; -use function sort; -use function sprintf; -use function str_replace; -use function strpos; -use function trait_exists; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; -final class Mapper +use PHPUnit\phpDocumentor\Reflection\Fqsen; +use PHPUnit\phpDocumentor\Reflection\Type; +/** + * Value Object representing the type 'string'. + * + * @psalm-immutable + */ +final class InterfaceString implements Type { + /** @var Fqsen|null */ + private $fqsen; /** - * @psalm-return array> - */ - public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits) : array - { - $result = []; - foreach ($codeUnits as $codeUnit) { - $sourceFileName = $codeUnit->sourceFileName(); - if (!isset($result[$sourceFileName])) { - $result[$sourceFileName] = []; - } - $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines()); - } - foreach (array_keys($result) as $sourceFileName) { - $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName])); - sort($result[$sourceFileName]); - } - ksort($result); - return $result; - } - /** - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public function stringToCodeUnits(string $unit) : CodeUnitCollection - { - if (strpos($unit, '::') !== \false) { - [$firstPart, $secondPart] = explode('::', $unit); - if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) { - return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart)); - } - if ($this->isUserDefinedClass($firstPart)) { - if ($secondPart === '') { - return $this->publicMethodsOfClass($firstPart); - } - if ($secondPart === '') { - return $this->protectedAndPrivateMethodsOfClass($firstPart); - } - if ($secondPart === '') { - return $this->protectedMethodsOfClass($firstPart); - } - if ($secondPart === '') { - return $this->publicAndPrivateMethodsOfClass($firstPart); - } - if ($secondPart === '') { - return $this->privateMethodsOfClass($firstPart); - } - if ($secondPart === '') { - return $this->publicAndProtectedMethodsOfClass($firstPart); - } - if ($this->isUserDefinedMethod($firstPart, $secondPart)) { - return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart)); - } - } - if ($this->isUserDefinedInterface($firstPart)) { - return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart)); - } - if ($this->isUserDefinedTrait($firstPart)) { - return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart)); - } - } else { - if ($this->isUserDefinedClass($unit)) { - $units = [CodeUnit::forClass($unit)]; - foreach ($this->reflectorForClass($unit)->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - $units[] = CodeUnit::forTrait($trait->getName()); - } - return CodeUnitCollection::fromArray($units); - } - if ($this->isUserDefinedInterface($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forInterface($unit)); - } - if ($this->isUserDefinedTrait($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forTrait($unit)); - } - if ($this->isUserDefinedFunction($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forFunction($unit)); - } - $unit = str_replace('', '', $unit); - if ($this->isUserDefinedClass($unit)) { - return $this->classAndParentClassesAndTraits($unit); - } - } - throw new InvalidCodeUnitException(sprintf('"%s" is not a valid code unit', $unit)); - } - /** - * @psalm-param class-string $className - * - * @throws ReflectionException + * Initializes this representation of a class string with the given Fqsen. */ - private function publicMethodsOfClass(string $className) : CodeUnitCollection + public function __construct(?Fqsen $fqsen = null) { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC); + $this->fqsen = $fqsen; } /** - * @psalm-param class-string $className - * - * @throws ReflectionException + * Returns the FQSEN associated with this object. */ - private function publicAndProtectedMethodsOfClass(string $className) : CodeUnitCollection + public function getFqsen() : ?Fqsen { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); + return $this->fqsen; } /** - * @psalm-param class-string $className - * - * @throws ReflectionException + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private function publicAndPrivateMethodsOfClass(string $className) : CodeUnitCollection + public function __toString() : string { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE); + if ($this->fqsen === null) { + return 'interface-string'; + } + return 'interface-string<' . (string) $this->fqsen . '>'; } +} + $types */ - private function protectedMethodsOfClass(string $className) : CodeUnitCollection + public function __construct(array $types) { - return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED); + parent::__construct($types, '&'); } +} +methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE); + if ($this->keyType) { + return 'iterable<' . $this->keyType . ',' . $this->valueType . '>'; + } + if ($this->valueType instanceof Mixed_) { + return 'iterable'; + } + return 'iterable<' . $this->valueType . '>'; } +} +methodsOfClass($className, ReflectionMethod::IS_PRIVATE); + return 'mixed'; } +} +reflectorForClass($className)->getMethods($filter) as $method) { - if (!$method->isUserDefined()) { - continue; - } - $units[] = CodeUnit::forClassMethod($className, $method->getName()); - } - return CodeUnitCollection::fromArray($units); + return 'never'; } +} +reflectorForClass($className); - foreach ($this->reflectorForClass($className)->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - $units[] = CodeUnit::forTrait($trait->getName()); - } - while ($reflector = $reflector->getParentClass()) { - if (!$reflector->isUserDefined()) { - break; - } - $units[] = CodeUnit::forClass($reflector->getName()); - foreach ($reflector->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - $units[] = CodeUnit::forTrait($trait->getName()); - } - } - return CodeUnitCollection::fromArray($units); + return 'null'; } +} +getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + $this->realType = $realType; } /** - * @throws ReflectionException + * Provide access to the actual type directly, if needed. */ - private function isUserDefinedFunction(string $functionName) : bool + public function getActualType() : Type { - if (!function_exists($functionName)) { - return \false; - } - try { - return (new ReflectionFunction($functionName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return $this->realType; } /** - * @throws ReflectionException + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private function isUserDefinedClass(string $className) : bool + public function __toString() : string { - if (!class_exists($className)) { - return \false; - } - try { - return (new ReflectionClass($className))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return '?' . $this->realType->__toString(); } +} +isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + if (strpos((string) $fqsen, '::') !== \false || strpos((string) $fqsen, '()') !== \false) { + throw new InvalidArgumentException('Object types can only refer to a class, interface or trait but a method, function, constant or ' . 'property was received: ' . (string) $fqsen); } - // @codeCoverageIgnoreEnd + $this->fqsen = $fqsen; } /** - * @throws ReflectionException + * Returns the FQSEN associated with this object. */ - private function isUserDefinedTrait(string $traitName) : bool + public function getFqsen() : ?Fqsen { - if (!trait_exists($traitName)) { - return \false; - } - try { - return (new ReflectionClass($traitName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return $this->fqsen; } - /** - * @throws ReflectionException - */ - private function isUserDefinedMethod(string $className, string $methodName) : bool + public function __toString() : string { - if (!class_exists($className)) { - // @codeCoverageIgnoreStart - return \false; - // @codeCoverageIgnoreEnd - } - if (!method_exists($className, $methodName)) { - // @codeCoverageIgnoreStart - return \false; - // @codeCoverageIgnoreEnd - } - try { - return (new ReflectionMethod($className, $methodName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + if ($this->fqsen) { + return (string) $this->fqsen; } - // @codeCoverageIgnoreEnd + return 'object'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace PHPUnit\phpDocumentor\Reflection\Types; +use PHPUnit\phpDocumentor\Reflection\Type; /** + * Value Object representing the 'parent' type. + * + * Parent, as a Type, represents the parent class of class in which the associated element was defined. + * * @psalm-immutable */ -final class InterfaceUnit extends CodeUnit +final class Parent_ implements Type { /** - * @psalm-assert-if-true InterfaceUnit $this + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - public function isInterface() : bool + public function __toString() : string { - return \true; + return 'parent'; } } +/** + * This file is part of phpDocumentor. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @link http://phpdoc.org */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace PHPUnit\phpDocumentor\Reflection\Types; -use function range; -use function sprintf; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; +use PHPUnit\phpDocumentor\Reflection\Type; /** + * Value Object representing the 'resource' Type. + * * @psalm-immutable */ -abstract class CodeUnit +final class Resource_ implements Type { /** - * @var string + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private $name; + public function __toString() : string + { + return 'resource'; + } +} + + * Returns a rendered output of the Type as it would be used in a DocBlock. */ - private $sourceLines; + public function __toString() : string + { + return 'self'; + } +} +getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return 'static'; } +} +getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return 'string'; } +} +getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return '$this'; } +} +getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return 'void'; } +} +Copyright (c) 2013 Konstantin Kudryashov +Copyright (c) 2013 Marcello Duarte + +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. + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy; + +use Prophecy\Argument\Token; +/** + * Argument tokens shortcuts. + * + * @author Konstantin Kudryashov + */ +class Argument +{ /** - * @psalm-param class-string $traitName + * Checks that argument is exact value or object. * - * @throws InvalidCodeUnitException - * @throws ReflectionException + * @param mixed $value + * + * @return Token\ExactValueToken */ - public static function forTrait(string $traitName) : TraitUnit + public static function exact($value) { - self::ensureUserDefinedTrait($traitName); - $reflector = self::reflectorForClass($traitName); - return new TraitUnit($traitName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return new Token\ExactValueToken($value); } /** - * @psalm-param class-string $traitName + * Checks that argument is of specific type or instance of specific class. * - * @throws InvalidCodeUnitException - * @throws ReflectionException + * @param string $type Type name (`integer`, `string`) or full class name + * + * @return Token\TypeToken */ - public static function forTraitMethod(string $traitName, string $methodName) : TraitMethodUnit + public static function type($type) { - self::ensureUserDefinedTrait($traitName); - $reflector = self::reflectorForClassMethod($traitName, $methodName); - return new TraitMethodUnit($traitName . '::' . $methodName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return new Token\TypeToken($type); } /** - * @psalm-param callable-string $functionName + * Checks that argument object has specific state. * - * @throws InvalidCodeUnitException - * @throws ReflectionException + * @param string $methodName + * @param mixed $value + * + * @return Token\ObjectStateToken */ - public static function forFunction(string $functionName) : FunctionUnit + public static function which($methodName, $value) { - $reflector = self::reflectorForFunction($functionName); - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined function', $functionName)); - } - return new FunctionUnit($functionName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); + return new Token\ObjectStateToken($methodName, $value); } /** - * @psalm-param list $sourceLines + * Checks that argument matches provided callback. + * + * @param callable $callback + * + * @return Token\CallbackToken */ - private function __construct(string $name, string $sourceFileName, array $sourceLines) - { - $this->name = $name; - $this->sourceFileName = $sourceFileName; - $this->sourceLines = $sourceLines; - } - public function name() : string - { - return $this->name; - } - public function sourceFileName() : string + public static function that($callback) { - return $this->sourceFileName; + return new Token\CallbackToken($callback); } /** - * @psalm-return list + * Matches any single value. + * + * @return Token\AnyValueToken */ - public function sourceLines() : array + public static function any() { - return $this->sourceLines; + return new Token\AnyValueToken(); } - public function isClass() : bool + /** + * Matches all values to the rest of the signature. + * + * @return Token\AnyValuesToken + */ + public static function cetera() { - return \false; + return new Token\AnyValuesToken(); } - public function isClassMethod() : bool + /** + * Checks that argument matches all tokens + * + * @param mixed ... a list of tokens + * + * @return Token\LogicalAndToken + */ + public static function allOf() { - return \false; + return new Token\LogicalAndToken(\func_get_args()); } - public function isInterface() : bool + /** + * Checks that argument array or countable object has exact number of elements. + * + * @param integer $value array elements count + * + * @return Token\ArrayCountToken + */ + public static function size($value) { - return \false; + return new Token\ArrayCountToken($value); } - public function isInterfaceMethod() : bool + /** + * Checks that argument array contains (key, value) pair + * + * @param mixed $key exact value or token + * @param mixed $value exact value or token + * + * @return Token\ArrayEntryToken + */ + public static function withEntry($key, $value) { - return \false; + return new Token\ArrayEntryToken($key, $value); } - public function isTrait() : bool + /** + * Checks that arguments array entries all match value + * + * @param mixed $value + * + * @return Token\ArrayEveryEntryToken + */ + public static function withEveryEntry($value) { - return \false; + return new Token\ArrayEveryEntryToken($value); } - public function isTraitMethod() : bool + /** + * Checks that argument array contains value + * + * @param mixed $value + * + * @return Token\ArrayEntryToken + */ + public static function containing($value) { - return \false; + return new Token\ArrayEntryToken(self::any(), $value); } - public function isFunction() : bool + /** + * Checks that argument array has key + * + * @param mixed $key exact value or token + * + * @return Token\ArrayEntryToken + */ + public static function withKey($key) { - return \false; + return new Token\ArrayEntryToken($key, self::any()); } /** - * @psalm-param class-string $className + * Checks that argument does not match the value|token. * - * @throws InvalidCodeUnitException + * @param mixed $value either exact value or argument token + * + * @return Token\LogicalNotToken */ - private static function ensureUserDefinedClass(string $className) : void + public static function not($value) { - try { - $reflector = new ReflectionClass($className); - if ($reflector->isInterface()) { - throw new InvalidCodeUnitException(sprintf('"%s" is an interface and not a class', $className)); - } - if ($reflector->isTrait()) { - throw new InvalidCodeUnitException(sprintf('"%s" is a trait and not a class', $className)); - } - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined class', $className)); - } - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\LogicalNotToken($value); } /** - * @psalm-param class-string $interfaceName + * @param string $value * - * @throws InvalidCodeUnitException + * @return Token\StringContainsToken */ - private static function ensureUserDefinedInterface(string $interfaceName) : void + public static function containingString($value) { - try { - $reflector = new ReflectionClass($interfaceName); - if (!$reflector->isInterface()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not an interface', $interfaceName)); - } - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined interface', $interfaceName)); - } - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\StringContainsToken($value); } /** - * @psalm-param class-string $traitName + * Checks that argument is identical value. * - * @throws InvalidCodeUnitException + * @param mixed $value + * + * @return Token\IdenticalValueToken */ - private static function ensureUserDefinedTrait(string $traitName) : void + public static function is($value) { - try { - $reflector = new ReflectionClass($traitName); - if (!$reflector->isTrait()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not a trait', $traitName)); - } - // @codeCoverageIgnoreStart - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined trait', $traitName)); - } - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\IdenticalValueToken($value); } /** - * @psalm-param class-string $className + * Check that argument is same value when rounding to the + * given precision. * - * @throws ReflectionException + * @param float $value + * @param float $precision + * + * @return Token\ApproximateValueToken */ - private static function reflectorForClass(string $className) : ReflectionClass + public static function approximate($value, $precision = 0) { - try { - return new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\ApproximateValueToken($value, $precision); } /** - * @psalm-param class-string $className + * Checks that argument is in array. * - * @throws ReflectionException + * @param array $value + * + * @return Token\InArrayToken */ - private static function reflectorForClassMethod(string $className, string $methodName) : ReflectionMethod + public static function in($value) { - try { - return new ReflectionMethod($className, $methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\InArrayToken($value); } /** - * @psalm-param callable-string $functionName + * Checks that argument is not in array. * - * @throws ReflectionException + * @param array $value + * + * @return Token\NotInArrayToken */ - private static function reflectorForFunction(string $functionName) : ReflectionFunction + public static function notIn($value) { - try { - return new ReflectionFunction($functionName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return new Token\NotInArrayToken($value); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace Prophecy\Argument; /** - * @psalm-immutable + * Arguments wildcarding. + * + * @author Konstantin Kudryashov */ -final class TraitUnit extends CodeUnit +class ArgumentsWildcard { /** - * @psalm-assert-if-true TraitUnit $this + * @var Token\TokenInterface[] */ - public function isTrait() : bool + private $tokens = array(); + private $string; + /** + * Initializes wildcard. + * + * @param array $arguments Array of argument tokens or values + */ + public function __construct(array $arguments) { - return \true; + foreach ($arguments as $argument) { + if (!$argument instanceof \Prophecy\Argument\Token\TokenInterface) { + $argument = new \Prophecy\Argument\Token\ExactValueToken($argument); + } + $this->tokens[] = $argument; + } + } + /** + * Calculates wildcard match score for provided arguments. + * + * @param array $arguments + * + * @return false|int False OR integer score (higher - better) + */ + public function scoreArguments(array $arguments) + { + if (0 == \count($arguments) && 0 == \count($this->tokens)) { + return 1; + } + $arguments = \array_values($arguments); + $totalScore = 0; + foreach ($this->tokens as $i => $token) { + $argument = isset($arguments[$i]) ? $arguments[$i] : null; + if (1 >= ($score = $token->scoreArgument($argument))) { + return \false; + } + $totalScore += $score; + if (\true === $token->isLast()) { + return $totalScore; + } + } + if (\count($arguments) > \count($this->tokens)) { + return \false; + } + return $totalScore; + } + /** + * Returns string representation for wildcard. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = \implode(', ', \array_map(function ($token) { + return (string) $token; + }, $this->tokens)); + } + return $this->string; + } + /** + * @return array + */ + public function getTokens() + { + return $this->tokens; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace Prophecy\Argument\Token; /** - * @psalm-immutable + * Any single value token. + * + * @author Konstantin Kudryashov */ -final class InterfaceMethodUnit extends CodeUnit +class AnyValueToken implements \Prophecy\Argument\Token\TokenInterface { /** - * @psalm-assert-if-true InterfaceMethod $this + * Always scores 3 for any argument. + * + * @param $argument + * + * @return int */ - public function isInterfaceMethod() : bool + public function scoreArgument($argument) { - return \true; + return 3; + } + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return '*'; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace Prophecy\Argument\Token; /** - * @psalm-immutable + * Any values token. + * + * @author Konstantin Kudryashov */ -final class ClassMethodUnit extends CodeUnit +class AnyValuesToken implements \Prophecy\Argument\Token\TokenInterface { /** - * @psalm-assert-if-true ClassMethodUnit $this + * Always scores 2 for any argument. + * + * @param $argument + * + * @return int */ - public function isClassMethod() : bool + public function scoreArgument($argument) + { + return 2; + } + /** + * Returns true to stop wildcard from processing other tokens. + * + * @return bool + */ + public function isLast() { return \true; } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return '* [, ...]'; + } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeUnit; - -use RuntimeException; -final class NoTraitException extends RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeUnit; - -use RuntimeException; -final class ReflectionException extends RuntimeException implements Exception -{ -} - + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; - -use Throwable; -interface Exception extends Throwable -{ -} - +/** + * Approximate value token * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Daniel Leech */ -namespace PHPUnit\SebastianBergmann\CodeUnit; - -use RuntimeException; -final class InvalidCodeUnitException extends RuntimeException implements Exception +class ApproximateValueToken implements \Prophecy\Argument\Token\TokenInterface { + private $value; + private $precision; + public function __construct($value, $precision = 0) + { + $this->value = $value; + $this->precision = $precision; + } + /** + * {@inheritdoc} + */ + public function scoreArgument($argument) + { + return \round((float) $argument, $this->precision) === \round($this->value, $this->precision) ? 10 : \false; + } + /** + * {@inheritdoc} + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return \sprintf('≅%s', \round($this->value, $this->precision)); + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace Prophecy\Argument\Token; /** - * @psalm-immutable + * Array elements count token. + * + * @author Boris Mikhaylov */ -final class ClassUnit extends CodeUnit +class ArrayCountToken implements \Prophecy\Argument\Token\TokenInterface { + private $count; + /** + * @param integer $value + */ + public function __construct($value) + { + $this->count = $value; + } + /** + * Scores 6 when argument has preset number of elements. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return $this->isCountable($argument) && $this->hasProperCount($argument) ? 6 : \false; + } + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return \sprintf('count(%s)', $this->count); + } /** - * @psalm-assert-if-true ClassUnit $this + * Returns true if object is either array or instance of \Countable + * + * @param $argument + * @return bool */ - public function isClass() : bool + private function isCountable($argument) { - return \true; + return \is_array($argument) || $argument instanceof \Countable; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class TraitMethodUnit extends CodeUnit -{ /** - * @psalm-assert-if-true TraitMethodUnit $this + * Returns true if $argument has expected number of elements + * + * @param array|\Countable $argument + * + * @return bool */ - public function isTraitMethod() : bool + private function hasProperCount($argument) { - return \true; + return $this->count === \count($argument); } } -sebastian/code-unit - -Copyright (c) 2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeUnit; +namespace Prophecy\Argument\Token; -use function array_merge; -use function count; -use Countable; -use IteratorAggregate; -final class CodeUnitCollection implements Countable, IteratorAggregate +use Prophecy\Exception\InvalidArgumentException; +/** + * Array entry token. + * + * @author Boris Mikhaylov + */ +class ArrayEntryToken implements \Prophecy\Argument\Token\TokenInterface { + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $key; + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $value; /** - * @psalm-var list + * @param mixed $key exact value or token + * @param mixed $value exact value or token */ - private $codeUnits = []; + public function __construct($key, $value) + { + $this->key = $this->wrapIntoExactValueToken($key); + $this->value = $this->wrapIntoExactValueToken($value); + } /** - * @psalm-param list $items + * Scores half of combined scores from key and value tokens for same entry. Capped at 8. + * If argument implements \ArrayAccess without \Traversable, then key token is restricted to ExactValueToken. + * + * @param array|\ArrayAccess|\Traversable $argument + * + * @throws \Prophecy\Exception\InvalidArgumentException + * @return bool|int */ - public static function fromArray(array $items) : self + public function scoreArgument($argument) { - $collection = new self(); - foreach ($items as $item) { - $collection->add($item); + if ($argument instanceof \Traversable) { + $argument = \iterator_to_array($argument); } - return $collection; - } - public static function fromList(CodeUnit ...$items) : self - { - return self::fromArray($items); - } - private function __construct() - { + if ($argument instanceof \ArrayAccess) { + $argument = $this->convertArrayAccessToEntry($argument); + } + if (!\is_array($argument) || empty($argument)) { + return \false; + } + $keyScores = \array_map(array($this->key, 'scoreArgument'), \array_keys($argument)); + $valueScores = \array_map(array($this->value, 'scoreArgument'), $argument); + $scoreEntry = function ($value, $key) { + return $value && $key ? \min(8, ($key + $value) / 2) : \false; + }; + return \max(\array_map($scoreEntry, $valueScores, $keyScores)); } /** - * @psalm-return list + * Returns false. + * + * @return boolean */ - public function asArray() : array + public function isLast() { - return $this->codeUnits; + return \false; } - public function getIterator() : CodeUnitCollectionIterator + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() { - return new CodeUnitCollectionIterator($this); + return \sprintf('[..., %s => %s, ...]', $this->key, $this->value); } - public function count() : int + /** + * Returns key + * + * @return TokenInterface + */ + public function getKey() { - return count($this->codeUnits); + return $this->key; } - public function isEmpty() : bool + /** + * Returns value + * + * @return TokenInterface + */ + public function getValue() { - return empty($this->codeUnits); + return $this->value; } - public function mergeWith(self $other) : self + /** + * Wraps non token $value into ExactValueToken + * + * @param $value + * @return TokenInterface + */ + private function wrapIntoExactValueToken($value) { - return self::fromArray(array_merge($this->asArray(), $other->asArray())); + return $value instanceof \Prophecy\Argument\Token\TokenInterface ? $value : new \Prophecy\Argument\Token\ExactValueToken($value); } - private function add(CodeUnit $item) : void + /** + * Converts instance of \ArrayAccess to key => value array entry + * + * @param \ArrayAccess $object + * + * @return array|null + * @throws \Prophecy\Exception\InvalidArgumentException + */ + private function convertArrayAccessToEntry(\ArrayAccess $object) { - $this->codeUnits[] = $item; + if (!$this->key instanceof \Prophecy\Argument\Token\ExactValueToken) { + throw new InvalidArgumentException(\sprintf('You can only use exact value tokens to match key of ArrayAccess object' . \PHP_EOL . 'But you used `%s`.', $this->key)); + } + $key = $this->key->getValue(); + return $object->offsetExists($key) ? array($key => $object[$key]) : array(); } } "$0"]) - ); - - override( - \PHPUnit\Framework\TestCase::createStub(0), - map([""=>"$0"]) - ); - - override( - \PHPUnit\Framework\TestCase::createConfiguredMock(0), - map([""=>"$0"]) - ); - - override( - \PHPUnit\Framework\TestCase::createPartialMock(0), - map([""=>"$0"]) - ); - - override( - \PHPUnit\Framework\TestCase::createTestProxy(0), - map([""=>"$0"]) - ); - - override( - \PHPUnit\Framework\TestCase::getMockForAbstractClass(0), - map([""=>"$0"]) - ); -} - + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class WrongXdebugVersionException extends RuntimeException implements Exception -{ -} - +/** + * Array every entry token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Adrien Brault */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class PhpdbgNotAvailableException extends RuntimeException implements Exception +class ArrayEveryEntryToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + /** + * @var TokenInterface + */ + private $value; + /** + * @param mixed $value exact value or token + */ + public function __construct($value) { - parent::__construct('The PHPDBG SAPI is not available'); + if (!$value instanceof \Prophecy\Argument\Token\TokenInterface) { + $value = new \Prophecy\Argument\Token\ExactValueToken($value); + } + $this->value = $value; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class NoCodeCoverageDriverAvailableException extends RuntimeException implements Exception -{ - public function __construct() + /** + * {@inheritdoc} + */ + public function scoreArgument($argument) { - parent::__construct('No code coverage driver available'); + if (!$argument instanceof \Traversable && !\is_array($argument)) { + return \false; + } + $scores = array(); + foreach ($argument as $key => $argumentEntry) { + $scores[] = $this->value->scoreArgument($argumentEntry); + } + if (empty($scores) || \in_array(\false, $scores, \true)) { + return \false; + } + return \array_sum($scores) / \count($scores); + } + /** + * {@inheritdoc} + */ + public function isLast() + { + return \false; + } + /** + * {@inheritdoc} + */ + public function __toString() + { + return \sprintf('[%s, ..., %s]', $this->value, $this->value); + } + /** + * @return TokenInterface + */ + public function getValue() + { + return $this->value; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class XmlException extends RuntimeException implements Exception -{ -} - +use Prophecy\Exception\InvalidArgumentException; +/** + * Callback-verified token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Konstantin Kudryashov */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class ReportAlreadyFinalizedException extends RuntimeException implements Exception +class CallbackToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + private $callback; + /** + * Initializes token. + * + * @param callable $callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) { - parent::__construct('The code coverage report has already been finalized'); + if (!\is_callable($callback)) { + throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackToken, but got %s.', \gettype($callback))); + } + $this->callback = $callback; + } + /** + * Scores 7 if callback returns true, false otherwise. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return \call_user_func($this->callback, $argument) ? 7 : \false; + } + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return 'callback()'; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Argument\Token; -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class XdebugNotAvailableException extends RuntimeException implements Exception +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use Prophecy\Comparator\Factory as ComparatorFactory; +use Prophecy\Util\StringUtil; +/** + * Exact value token. + * + * @author Konstantin Kudryashov + */ +class ExactValueToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + private $value; + private $string; + private $util; + private $comparatorFactory; + /** + * Initializes token. + * + * @param mixed $value + * @param StringUtil $util + * @param ComparatorFactory $comparatorFactory + */ + public function __construct($value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null) { - parent::__construct('The Xdebug extension is not available'); + $this->value = $value; + $this->util = $util ?: new StringUtil(); + $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + } + /** + * Scores 10 if argument matches preset value. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + if (\is_object($argument) && \is_object($this->value)) { + $comparator = $this->comparatorFactory->getComparatorFor($argument, $this->value); + try { + $comparator->assertEquals($argument, $this->value); + return 10; + } catch (ComparisonFailure $failure) { + return \false; + } + } + // If either one is an object it should be castable to a string + if (\is_object($argument) xor \is_object($this->value)) { + if (\is_object($argument) && !\method_exists($argument, '__toString')) { + return \false; + } + if (\is_object($this->value) && !\method_exists($this->value, '__toString')) { + return \false; + } + } elseif (\is_numeric($argument) && \is_numeric($this->value)) { + // noop + } elseif (\gettype($argument) !== \gettype($this->value)) { + return \false; + } + return $argument == $this->value ? 10 : \false; + } + /** + * Returns preset value against which token checks arguments. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = \sprintf('exact(%s)', $this->util->stringify($this->value)); + } + return $this->string; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use function sprintf; -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class WriteOperationFailedException extends RuntimeException implements Exception -{ - public function __construct(string $path) - { - parent::__construct(sprintf('Cannot write to "%s"', $path)); - } -} - +use Prophecy\Util\StringUtil; +/** + * Identical value token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Florian Voutzinos */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class DirectoryCouldNotBeCreatedException extends RuntimeException implements Exception +class IdenticalValueToken implements \Prophecy\Argument\Token\TokenInterface { + private $value; + private $string; + private $util; + /** + * Initializes token. + * + * @param mixed $value + * @param StringUtil $util + */ + public function __construct($value, StringUtil $util = null) + { + $this->value = $value; + $this->util = $util ?: new StringUtil(); + } + /** + * Scores 11 if argument matches preset value. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return $argument === $this->value ? 11 : \false; + } + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = \sprintf('identical(%s)', $this->util->stringify($this->value)); + } + return $this->string; + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class DeadCodeDetectionNotSupportedException extends RuntimeException implements Exception -{ -} - +/** + * Check if values is in array * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Vinícius Alonso */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class Xdebug2NotEnabledException extends RuntimeException implements Exception +class InArrayToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + private $token = array(); + private $strict; + /** + * @param array $arguments tokens + * @param bool $strict + */ + public function __construct(array $arguments, $strict = \true) { - parent::__construct('xdebug.coverage_enable=On has to be set'); + $this->token = $arguments; + $this->strict = $strict; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class TestIdMissingException extends RuntimeException implements Exception -{ - public function __construct() + /** + * Return scores 8 score if argument is in array. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) { - parent::__construct('Test ID is missing'); + if (\count($this->token) === 0) { + return \false; + } + if (\in_array($argument, $this->token, $this->strict)) { + return 8; + } + return \false; + } + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + $arrayAsString = \implode(', ', $this->token); + return "[{$arrayAsString}]"; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class ReflectionException extends RuntimeException implements Exception -{ -} - +/** + * Logical AND token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Boris Mikhaylov */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class Xdebug3NotEnabledException extends RuntimeException implements Exception +class LogicalAndToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + private $tokens = array(); + /** + * @param array $arguments exact values or tokens + */ + public function __construct(array $arguments) { - parent::__construct('XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set'); + foreach ($arguments as $argument) { + if (!$argument instanceof \Prophecy\Argument\Token\TokenInterface) { + $argument = new \Prophecy\Argument\Token\ExactValueToken($argument); + } + $this->tokens[] = $argument; + } } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class NoCodeCoverageDriverWithPathCoverageSupportAvailableException extends RuntimeException implements Exception -{ - public function __construct() + /** + * Scores maximum score from scores returned by tokens for this argument if all of them score. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) { - parent::__construct('No code coverage driver with path coverage support available'); + if (0 === \count($this->tokens)) { + return \false; + } + $maxScore = 0; + foreach ($this->tokens as $token) { + $score = $token->scoreArgument($argument); + if (\false === $score) { + return \false; + } + $maxScore = \max($score, $maxScore); + } + return $maxScore; + } + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return \sprintf('bool(%s)', \implode(' AND ', $this->tokens)); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use Throwable; -interface Exception extends Throwable -{ -} - +/** + * Logical NOT token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Boris Mikhaylov */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class PcovNotAvailableException extends RuntimeException implements Exception +class LogicalNotToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct() + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $token; + /** + * @param mixed $value exact value or token + */ + public function __construct($value) { - parent::__construct('The PCOV extension is not available'); + $this->token = $value instanceof \Prophecy\Argument\Token\TokenInterface ? $value : new \Prophecy\Argument\Token\ExactValueToken($value); + } + /** + * Scores 4 when preset token does not match the argument. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return \false === $this->token->scoreArgument($argument) ? 4 : \false; + } + /** + * Returns true if preset token is last. + * + * @return bool|int + */ + public function isLast() + { + return $this->token->isLast(); + } + /** + * Returns originating token. + * + * @return TokenInterface + */ + public function getOriginatingToken() + { + return $this->token; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return \sprintf('not(%s)', $this->token); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Argument\Token; -use function sprintf; -use RuntimeException; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception; -final class PathExistsButIsNotDirectoryException extends RuntimeException implements Exception +/** + * Check if values is not in array + * + * @author Vinícius Alonso + */ +class NotInArrayToken implements \Prophecy\Argument\Token\TokenInterface { - public function __construct(string $path) + private $token = array(); + private $strict; + /** + * @param array $arguments tokens + * @param bool $strict + */ + public function __construct(array $arguments, $strict = \true) { - parent::__construct(sprintf('"%s" exists but is not a directory', $path)); + $this->token = $arguments; + $this->strict = $strict; + } + /** + * Return scores 8 score if argument is in array. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + if (\count($this->token) === 0) { + return \false; + } + if (!\in_array($argument, $this->token, $this->strict)) { + return 8; + } + return \false; + } + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + $arrayAsString = \implode(', ', $this->token); + return "[{$arrayAsString}]"; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class StaticAnalysisCacheNotConfiguredException extends RuntimeException implements Exception -{ -} - +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use Prophecy\Comparator\Factory as ComparatorFactory; +use Prophecy\Util\StringUtil; +/** + * Object state-checker token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Konstantin Kudryashov */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class UnintentionallyCoveredCodeException extends RuntimeException implements Exception +class ObjectStateToken implements \Prophecy\Argument\Token\TokenInterface { + private $name; + private $value; + private $util; + private $comparatorFactory; /** - * @var array + * Initializes token. + * + * @param string $methodName + * @param mixed $value Expected return value + * @param null|StringUtil $util + * @param ComparatorFactory $comparatorFactory */ - private $unintentionallyCoveredUnits; - public function __construct(array $unintentionallyCoveredUnits) + public function __construct($methodName, $value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null) { - $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits; - parent::__construct($this->toString()); + $this->name = $methodName; + $this->value = $value; + $this->util = $util ?: new StringUtil(); + $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); } - public function getUnintentionallyCoveredUnits() : array + /** + * Scores 8 if argument is an object, which method returns expected value. + * + * @param mixed $argument + * + * @return bool|int + */ + public function scoreArgument($argument) { - return $this->unintentionallyCoveredUnits; + if (\is_object($argument) && \method_exists($argument, $this->name)) { + $actual = \call_user_func(array($argument, $this->name)); + $comparator = $this->comparatorFactory->getComparatorFor($this->value, $actual); + try { + $comparator->assertEquals($this->value, $actual); + return 8; + } catch (ComparisonFailure $failure) { + return \false; + } + } + if (\is_object($argument) && \property_exists($argument, $this->name)) { + return $argument->{$this->name} === $this->value ? 8 : \false; + } + return \false; } - private function toString() : string + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() { - $message = ''; - foreach ($this->unintentionallyCoveredUnits as $unit) { - $message .= '- ' . $unit . "\n"; - } - return $message; + return \sprintf('state(%s(), %s)', $this->name, $this->util->stringify($this->value)); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use RuntimeException; -final class BranchAndPathCoverageNotSupportedException extends RuntimeException implements Exception -{ -} - +/** + * String contains token. * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Peter Mitchell */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -final class InvalidArgumentException extends \InvalidArgumentException implements Exception +class StringContainsToken implements \Prophecy\Argument\Token\TokenInterface { + private $value; + /** + * Initializes token. + * + * @param string $value + */ + public function __construct($value) + { + $this->value = $value; + } + public function scoreArgument($argument) + { + return \is_string($argument) && \strpos($argument, $this->value) !== \false ? 6 : \false; + } + /** + * Returns preset value against which token checks arguments. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return \false; + } + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return \sprintf('contains("%s")', $this->value); + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace Prophecy\Argument\Token; -use RuntimeException; -final class ParserException extends RuntimeException implements Exception +/** + * Argument token interface. + * + * @author Konstantin Kudryashov + */ +interface TokenInterface { + /** + * Calculates token match score for provided argument. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument); + /** + * Returns true if this token prevents check of other tokens (is last one). + * + * @return bool|int + */ + public function isLast(); + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString(); } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace Prophecy\Argument\Token; -use function sprintf; +use Prophecy\Exception\InvalidArgumentException; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Value type token. + * + * @author Konstantin Kudryashov */ -final class Percentage +class TypeToken implements \Prophecy\Argument\Token\TokenInterface { + private $type; /** - * @var float - */ - private $fraction; - /** - * @var float + * @param string $type */ - private $total; - public static function fromFractionAndTotal(float $fraction, float $total) : self - { - return new self($fraction, $total); - } - private function __construct(float $fraction, float $total) + public function __construct($type) { - $this->fraction = $fraction; - $this->total = $total; + $checker = "is_{$type}"; + if (!\function_exists($checker) && !\interface_exists($type) && !\class_exists($type)) { + throw new InvalidArgumentException(\sprintf('Type or class name expected as an argument to TypeToken, but got %s.', $type)); + } + $this->type = $type; } - public function asFloat() : float + /** + * Scores 5 if argument has the same type this token was constructed with. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) { - if ($this->total > 0) { - return $this->fraction / $this->total * 100; + $checker = "is_{$this->type}"; + if (\function_exists($checker)) { + return \call_user_func($checker, $argument) ? 5 : \false; } - return 100.0; + return $argument instanceof $this->type ? 5 : \false; } - public function asString() : string + /** + * Returns false. + * + * @return bool + */ + public function isLast() { - if ($this->total > 0) { - return sprintf('%01.2F%%', $this->asFloat()); - } - return ''; + return \false; } - public function asFixedWidthString() : string + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() { - if ($this->total > 0) { - return sprintf('%6.2F%%', $this->asFloat()); - } - return ''; + return \sprintf('type(%s)', $this->type); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace Prophecy\Call; -use function array_keys; -use function is_file; -use function realpath; -use function strpos; -use PHPUnit\SebastianBergmann\FileIterator\Facade as FileIteratorFacade; -final class Filter +use Exception; +use Prophecy\Argument\ArgumentsWildcard; +/** + * Call object. + * + * @author Konstantin Kudryashov + */ +class Call { + private $methodName; + private $arguments; + private $returnValue; + private $exception; + private $file; + private $line; + private $scores; /** - * @psalm-var array - */ - private $files = []; - /** - * @psalm-var array + * Initializes call. + * + * @param string $methodName + * @param array $arguments + * @param mixed $returnValue + * @param Exception $exception + * @param null|string $file + * @param null|int $line */ - private $isFileCache = []; - public function includeDirectory(string $directory, string $suffix = '.php', string $prefix = '') : void + public function __construct($methodName, array $arguments, $returnValue, Exception $exception = null, $file, $line) { - foreach ((new FileIteratorFacade())->getFilesAsArray($directory, $suffix, $prefix) as $file) { - $this->includeFile($file); + $this->methodName = $methodName; + $this->arguments = $arguments; + $this->returnValue = $returnValue; + $this->exception = $exception; + $this->scores = new \SplObjectStorage(); + if ($file) { + $this->file = $file; + $this->line = \intval($line); } } /** - * @psalm-param list $files + * Returns called method name. + * + * @return string */ - public function includeFiles(array $filenames) : void - { - foreach ($filenames as $filename) { - $this->includeFile($filename); - } - } - public function includeFile(string $filename) : void - { - $filename = realpath($filename); - if (!$filename) { - return; - } - $this->files[$filename] = \true; - } - public function excludeDirectory(string $directory, string $suffix = '.php', string $prefix = '') : void - { - foreach ((new FileIteratorFacade())->getFilesAsArray($directory, $suffix, $prefix) as $file) { - $this->excludeFile($file); - } - } - public function excludeFile(string $filename) : void + public function getMethodName() { - $filename = realpath($filename); - if (!$filename || !isset($this->files[$filename])) { - return; - } - unset($this->files[$filename]); + return $this->methodName; } - public function isFile(string $filename) : bool + /** + * Returns called method arguments. + * + * @return array + */ + public function getArguments() { - if (isset($this->isFileCache[$filename])) { - return $this->isFileCache[$filename]; - } - if ($filename === '-' || strpos($filename, 'vfs://') === 0 || strpos($filename, 'xdebug://debug-eval') !== \false || strpos($filename, 'eval()\'d code') !== \false || strpos($filename, 'runtime-created function') !== \false || strpos($filename, 'runkit created function') !== \false || strpos($filename, 'assert code') !== \false || strpos($filename, 'regexp code') !== \false || strpos($filename, 'Standard input code') !== \false) { - $isFile = \false; - } else { - $isFile = is_file($filename); - } - $this->isFileCache[$filename] = $isFile; - return $isFile; + return $this->arguments; } - public function isExcluded(string $filename) : bool + /** + * Returns called method return value. + * + * @return null|mixed + */ + public function getReturnValue() { - if (!$this->isFile($filename)) { - return \true; - } - return !isset($this->files[$filename]); + return $this->returnValue; } /** - * @psalm-return list + * Returns exception that call thrown. + * + * @return null|Exception */ - public function files() : array + public function getException() { - return array_keys($this->files); + return $this->exception; } - public function isEmpty() : bool + /** + * Returns callee filename. + * + * @return string + */ + public function getFile() { - return empty($this->files); + return $this->file; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; - -use function extension_loaded; -use function phpversion; -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class PcovDriver extends Driver -{ /** - * @var Filter + * Returns callee line number. + * + * @return int */ - private $filter; + public function getLine() + { + return $this->line; + } /** - * @throws PcovNotAvailableException + * Returns short notation for callee place. + * + * @return string */ - public function __construct(Filter $filter) + public function getCallPlace() { - if (!extension_loaded('pcov')) { - throw new PcovNotAvailableException(); + if (null === $this->file) { + return 'unknown'; } - $this->filter = $filter; - } - public function start() : void - { - \pcov\start(); + return \sprintf('%s:%d', $this->file, $this->line); } - public function stop() : RawCodeCoverageData + /** + * Adds the wildcard match score for the provided wildcard. + * + * @param ArgumentsWildcard $wildcard + * @param false|int $score + * + * @return $this + */ + public function addScore(ArgumentsWildcard $wildcard, $score) { - \pcov\stop(); - $collect = \pcov\collect(\pcov\inclusive, !$this->filter->isEmpty() ? $this->filter->files() : \pcov\waiting()); - \pcov\clear(); - return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collect); + $this->scores[$wildcard] = $score; + return $this; } - public function nameAndVersion() : string + /** + * Returns wildcard match score for the provided wildcard. The score is + * calculated if not already done. + * + * @param ArgumentsWildcard $wildcard + * + * @return false|int False OR integer score (higher - better) + */ + public function getScore(ArgumentsWildcard $wildcard) { - return 'PCOV ' . phpversion('pcov'); + if (isset($this->scores[$wildcard])) { + return $this->scores[$wildcard]; + } + return $this->scores[$wildcard] = $wildcard->scoreArguments($this->getArguments()); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Call; -use function sprintf; -use PHPUnit\SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException; -use PHPUnit\SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException; -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; -use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; -use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use Prophecy\Exception\Prophecy\MethodProphecyException; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Call\UnexpectedCallException; +use SplObjectStorage; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Calls receiver & manager. + * + * @author Konstantin Kudryashov */ -abstract class Driver +class CallCenter { + private $util; /** - * @var int - * - * @see http://xdebug.org/docs/code_coverage + * @var Call[] */ - public const LINE_NOT_EXECUTABLE = -2; + private $recordedCalls = array(); /** - * @var int - * - * @see http://xdebug.org/docs/code_coverage + * @var SplObjectStorage */ - public const LINE_NOT_EXECUTED = -1; + private $unexpectedCalls; /** - * @var int + * Initializes call center. * - * @see http://xdebug.org/docs/code_coverage + * @param StringUtil $util */ - public const LINE_EXECUTED = 1; + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil(); + $this->unexpectedCalls = new SplObjectStorage(); + } /** - * @var int + * Makes and records specific method call for object prophecy. * - * @see http://xdebug.org/docs/code_coverage - */ - public const BRANCH_NOT_HIT = 0; - /** - * @var int + * @param ObjectProphecy $prophecy + * @param string $methodName + * @param array $arguments * - * @see http://xdebug.org/docs/code_coverage - */ - public const BRANCH_HIT = 1; - /** - * @var bool - */ - private $collectBranchAndPathCoverage = \false; - /** - * @var bool - */ - private $detectDeadCode = \false; - /** - * @throws NoCodeCoverageDriverAvailableException - * @throws PcovNotAvailableException - * @throws PhpdbgNotAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException + * @return mixed Returns null if no promise for prophecy found or promise return value. * - * @deprecated Use DriverSelector::forLineCoverage() instead + * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found */ - public static function forLineCoverage(Filter $filter) : self + public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments) { - return (new Selector())->forLineCoverage($filter); + // For efficiency exclude 'args' from the generated backtrace + // Limit backtrace to last 3 calls as we don't use the rest + $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $file = $line = null; + if (isset($backtrace[2]) && isset($backtrace[2]['file'])) { + $file = $backtrace[2]['file']; + $line = $backtrace[2]['line']; + } + // If no method prophecies defined, then it's a dummy, so we'll just return null + if ('__destruct' === \strtolower($methodName) || 0 == \count($prophecy->getMethodProphecies())) { + $this->recordedCalls[] = new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line); + return null; + } + // There are method prophecies, so it's a fake/stub. Searching prophecy for this call + $matches = $this->findMethodProphecies($prophecy, $methodName, $arguments); + // If fake/stub doesn't have method prophecy for this call - throw exception + if (!\count($matches)) { + $this->unexpectedCalls->attach(new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line), $prophecy); + $this->recordedCalls[] = new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line); + return null; + } + // Sort matches by their score value + @\usort($matches, function ($match1, $match2) { + return $match2[0] - $match1[0]; + }); + $score = $matches[0][0]; + // If Highest rated method prophecy has a promise - execute it or return null instead + $methodProphecy = $matches[0][1]; + $returnValue = null; + $exception = null; + if ($promise = $methodProphecy->getPromise()) { + try { + $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy); + } catch (\Exception $e) { + $exception = $e; + } + } + if ($methodProphecy->hasReturnVoid() && $returnValue !== null) { + throw new MethodProphecyException("The method \"{$methodName}\" has a void return type, but the promise returned a value", $methodProphecy); + } + $this->recordedCalls[] = $call = new \Prophecy\Call\Call($methodName, $arguments, $returnValue, $exception, $file, $line); + $call->addScore($methodProphecy->getArgumentsWildcard(), $score); + if (null !== $exception) { + throw $exception; + } + return $returnValue; } /** - * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException + * Searches for calls by method name & arguments wildcard. * - * @deprecated Use DriverSelector::forLineAndPathCoverage() instead + * @param string $methodName + * @param ArgumentsWildcard $wildcard + * + * @return Call[] */ - public static function forLineAndPathCoverage(Filter $filter) : self - { - return (new Selector())->forLineAndPathCoverage($filter); - } - public function canCollectBranchAndPathCoverage() : bool - { - return \false; - } - public function collectsBranchAndPathCoverage() : bool + public function findCalls($methodName, ArgumentsWildcard $wildcard) { - return $this->collectBranchAndPathCoverage; + $methodName = \strtolower($methodName); + return \array_values(\array_filter($this->recordedCalls, function (\Prophecy\Call\Call $call) use($methodName, $wildcard) { + return $methodName === \strtolower($call->getMethodName()) && 0 < $call->getScore($wildcard); + })); } /** - * @throws BranchAndPathCoverageNotSupportedException + * @throws UnexpectedCallException */ - public function enableBranchAndPathCoverage() : void + public function checkUnexpectedCalls() { - if (!$this->canCollectBranchAndPathCoverage()) { - throw new BranchAndPathCoverageNotSupportedException(sprintf('%s does not support branch and path coverage', $this->nameAndVersion())); + /** @var Call $call */ + foreach ($this->unexpectedCalls as $call) { + $prophecy = $this->unexpectedCalls[$call]; + // If fake/stub doesn't have method prophecy for this call - throw exception + if (!\count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) { + throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments()); + } } - $this->collectBranchAndPathCoverage = \true; - } - public function disableBranchAndPathCoverage() : void - { - $this->collectBranchAndPathCoverage = \false; } - public function canDetectDeadCode() : bool + private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName, array $arguments) { - return \false; + $classname = \get_class($prophecy->reveal()); + $indentationLength = 8; + // looks good + $argstring = \implode(",\n", $this->indentArguments(\array_map(array($this->util, 'stringify'), $arguments), $indentationLength)); + $expected = array(); + foreach (\array_merge(...\array_values($prophecy->getMethodProphecies())) as $methodProphecy) { + $expected[] = \sprintf(" - %s(\n" . "%s\n" . " )", $methodProphecy->getMethodName(), \implode(",\n", $this->indentArguments(\array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()), $indentationLength))); + } + return new UnexpectedCallException(\sprintf("Unexpected method call on %s:\n" . " - %s(\n" . "%s\n" . " )\n" . "expected calls were:\n" . "%s", $classname, $methodName, $argstring, \implode("\n", $expected)), $prophecy, $methodName, $arguments); } - public function detectsDeadCode() : bool + private function indentArguments(array $arguments, $indentationLength) { - return $this->detectDeadCode; + return \preg_replace_callback('/^/m', function () use($indentationLength) { + return \str_repeat(' ', $indentationLength); + }, $arguments); } /** - * @throws DeadCodeDetectionNotSupportedException + * @param ObjectProphecy $prophecy + * @param string $methodName + * @param array $arguments + * + * @return array */ - public function enableDeadCodeDetection() : void + private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments) { - if (!$this->canDetectDeadCode()) { - throw new DeadCodeDetectionNotSupportedException(sprintf('%s does not support dead code detection', $this->nameAndVersion())); + $matches = array(); + foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) { + if (0 < ($score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments))) { + $matches[] = array($score, $methodProphecy); + } } - $this->detectDeadCode = \true; - } - public function disableDeadCodeDetection() : void - { - $this->detectDeadCode = \false; + return $matches; } - public abstract function nameAndVersion() : string; - public abstract function start() : void; - public abstract function stop() : RawCodeCoverageData; } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Comparator; -use const XDEBUG_CC_BRANCH_CHECK; -use const XDEBUG_CC_DEAD_CODE; -use const XDEBUG_CC_UNUSED; -use const XDEBUG_FILTER_CODE_COVERAGE; -use const PHPUnit\XDEBUG_PATH_INCLUDE; -use function explode; -use function extension_loaded; -use function getenv; -use function in_array; -use function ini_get; -use function phpversion; -use function sprintf; -use function version_compare; -use function xdebug_get_code_coverage; -use function xdebug_set_filter; -use function xdebug_start_code_coverage; -use function xdebug_stop_code_coverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use PHPUnit\SebastianBergmann\Comparator\Comparator; +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Closure comparator. + * + * @author Konstantin Kudryashov */ -final class Xdebug3Driver extends Driver +final class ClosureComparator extends Comparator { - /** - * @throws WrongXdebugVersionException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException - */ - public function __construct(Filter $filter) - { - if (!extension_loaded('xdebug')) { - throw new XdebugNotAvailableException(); - } - if (version_compare(phpversion('xdebug'), '3', '<')) { - throw new WrongXdebugVersionException(sprintf('This driver requires Xdebug 3 but version %s is loaded', phpversion('xdebug'))); - } - $mode = getenv('XDEBUG_MODE'); - if ($mode === \false || $mode === '') { - $mode = ini_get('xdebug.mode'); - } - if ($mode === \false || !in_array('coverage', explode(',', $mode), \true)) { - throw new Xdebug3NotEnabledException(); - } - if (!$filter->isEmpty()) { - xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_INCLUDE, $filter->files()); - } - } - public function canCollectBranchAndPathCoverage() : bool - { - return \true; - } - public function canDetectDeadCode() : bool - { - return \true; - } - public function start() : void + public function accepts($expected, $actual) : bool { - $flags = \XDEBUG_CC_UNUSED; - if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { - $flags |= \XDEBUG_CC_DEAD_CODE; - } - if ($this->collectsBranchAndPathCoverage()) { - $flags |= \XDEBUG_CC_BRANCH_CHECK; - } - xdebug_start_code_coverage($flags); + return \is_object($expected) && $expected instanceof \Closure && \is_object($actual) && $actual instanceof \Closure; } - public function stop() : RawCodeCoverageData + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = array()) : void { - $data = xdebug_get_code_coverage(); - xdebug_stop_code_coverage(); - if ($this->collectsBranchAndPathCoverage()) { - return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + if ($expected !== $actual) { + throw new ComparisonFailure( + $expected, + $actual, + // we don't need a diff + '', + '', + \false, + 'all closures are different if not identical' + ); } - return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); - } - public function nameAndVersion() : string - { - return 'Xdebug ' . phpversion('xdebug'); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Comparator; -use const XDEBUG_CC_BRANCH_CHECK; -use const XDEBUG_CC_DEAD_CODE; -use const XDEBUG_CC_UNUSED; -use const XDEBUG_FILTER_CODE_COVERAGE; -use const PHPUnit\XDEBUG_PATH_INCLUDE; -use const XDEBUG_PATH_WHITELIST; -use function defined; -use function extension_loaded; -use function ini_get; -use function phpversion; -use function sprintf; -use function version_compare; -use function xdebug_get_code_coverage; -use function xdebug_set_filter; -use function xdebug_start_code_coverage; -use function xdebug_stop_code_coverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use PHPUnit\SebastianBergmann\Comparator\Factory as BaseFactory; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Prophecy comparator factory. + * + * @author Konstantin Kudryashov */ -final class Xdebug2Driver extends Driver +final class Factory extends BaseFactory { /** - * @var bool - */ - private $pathCoverageIsMixedCoverage; - /** - * @throws WrongXdebugVersionException - * @throws Xdebug2NotEnabledException - * @throws XdebugNotAvailableException + * @var Factory */ - public function __construct(Filter $filter) - { - if (!extension_loaded('xdebug')) { - throw new XdebugNotAvailableException(); - } - if (version_compare(phpversion('xdebug'), '3', '>=')) { - throw new WrongXdebugVersionException(sprintf('This driver requires Xdebug 2 but version %s is loaded', phpversion('xdebug'))); - } - if (!ini_get('xdebug.coverage_enable')) { - throw new Xdebug2NotEnabledException(); - } - if (!$filter->isEmpty()) { - if (defined('XDEBUG_PATH_WHITELIST')) { - $listType = \XDEBUG_PATH_WHITELIST; - } else { - $listType = \XDEBUG_PATH_INCLUDE; - } - xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, $listType, $filter->files()); - } - $this->pathCoverageIsMixedCoverage = version_compare(phpversion('xdebug'), '2.9.6', '<'); - } - public function canCollectBranchAndPathCoverage() : bool - { - return \true; - } - public function canDetectDeadCode() : bool + private static $instance; + public function __construct() { - return \true; + parent::__construct(); + $this->register(new \Prophecy\Comparator\ClosureComparator()); + $this->register(new \Prophecy\Comparator\ProphecyComparator()); } - public function start() : void + /** + * @return Factory + */ + public static function getInstance() { - $flags = \XDEBUG_CC_UNUSED; - if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { - $flags |= \XDEBUG_CC_DEAD_CODE; - } - if ($this->collectsBranchAndPathCoverage()) { - $flags |= \XDEBUG_CC_BRANCH_CHECK; + if (self::$instance === null) { + self::$instance = new \Prophecy\Comparator\Factory(); } - xdebug_start_code_coverage($flags); + return self::$instance; } - public function stop() : RawCodeCoverageData +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Comparator; + +use Prophecy\Prophecy\ProphecyInterface; +use PHPUnit\SebastianBergmann\Comparator\ObjectComparator; +/** + * @final + */ +class ProphecyComparator extends ObjectComparator +{ + public function accepts($expected, $actual) : bool { - $data = xdebug_get_code_coverage(); - xdebug_stop_code_coverage(); - if ($this->collectsBranchAndPathCoverage()) { - if ($this->pathCoverageIsMixedCoverage) { - return RawCodeCoverageData::fromXdebugWithMixedCoverage($data); - } - return RawCodeCoverageData::fromXdebugWithPathCoverage($data); - } - return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); + return \is_object($expected) && \is_object($actual) && $actual instanceof ProphecyInterface; } - public function nameAndVersion() : string + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = array()) : void { - return 'Xdebug ' . phpversion('xdebug'); + parent::assertEquals($expected, $actual->reveal(), $delta, $canonicalize, $ignoreCase, $processed); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Doubler; -use const PHP_SAPI; -use const PHP_VERSION; -use function array_diff; -use function array_keys; -use function array_merge; -use function get_included_files; -use function phpdbg_end_oplog; -use function phpdbg_get_executable; -use function phpdbg_start_oplog; -use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use ReflectionClass; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov */ -final class PhpdbgDriver extends Driver +class CachedDoubler extends \Prophecy\Doubler\Doubler { + private static $classes = array(); /** - * @throws PhpdbgNotAvailableException + * {@inheritdoc} */ - public function __construct() + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) { - if (\PHP_SAPI !== 'phpdbg') { - throw new PhpdbgNotAvailableException(); + $classId = $this->generateClassId($class, $interfaces); + if (isset(self::$classes[$classId])) { + return self::$classes[$classId]; } + return self::$classes[$classId] = parent::createDoubleClass($class, $interfaces); } - public function start() : void - { - phpdbg_start_oplog(); - } - public function stop() : RawCodeCoverageData + /** + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + private function generateClassId(ReflectionClass $class = null, array $interfaces) { - static $fetchedLines = []; - $dbgData = phpdbg_end_oplog(); - if ($fetchedLines === []) { - $sourceLines = phpdbg_get_executable(); - } else { - $newFiles = array_diff(get_included_files(), array_keys($fetchedLines)); - $sourceLines = []; - if ($newFiles) { - $sourceLines = phpdbg_get_executable(['files' => $newFiles]); - } + $parts = array(); + if (null !== $class) { + $parts[] = $class->getName(); } - foreach ($sourceLines as $file => $lines) { - foreach ($lines as $lineNo => $numExecuted) { - $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; - } + foreach ($interfaces as $interface) { + $parts[] = $interface->getName(); } - $fetchedLines = array_merge($fetchedLines, $sourceLines); - return RawCodeCoverageData::fromXdebugWithoutPathCoverage($this->detectExecutedLines($fetchedLines, $dbgData)); - } - public function nameAndVersion() : string - { - return 'PHPDBG ' . \PHP_VERSION; + foreach ($this->getClassPatches() as $patch) { + $parts[] = \get_class($patch); + } + \sort($parts); + return \md5(\implode('', $parts)); } - private function detectExecutedLines(array $sourceLines, array $dbgData) : array + public function resetCache() { - foreach ($dbgData as $file => $coveredLines) { - foreach ($coveredLines as $lineNo => $numExecuted) { - // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. - // make sure we only mark lines executed which are actually executable. - if (isset($sourceLines[$file][$lineNo])) { - $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; - } - } - } - return $sourceLines; + self::$classes = array(); } } + * Marcello Duarte * - * (c) Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +/** + * Class patch interface. + * Class patches extend doubles functionality or help + * Prophecy to avoid some internal PHP bugs. + * + * @author Konstantin Kudryashov + */ +interface ClassPatchInterface +{ + /** + * Checks if patch supports specific class node. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node); + /** + * Applies patch to the specific class node. + * + * @param ClassNode $node + * @return void + */ + public function apply(ClassNode $node); + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority(); +} + + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Driver; +namespace Prophecy\Doubler\ClassPatch; -use function phpversion; -use function version_compare; -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; -use PHPUnit\SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; -use PHPUnit\SebastianBergmann\Environment\Runtime; -final class Selector +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +/** + * Disable constructor. + * Makes all constructor arguments optional. + * + * @author Konstantin Kudryashov + */ +class DisableConstructorPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { /** - * @throws NoCodeCoverageDriverAvailableException - * @throws PcovNotAvailableException - * @throws PhpdbgNotAvailableException - * @throws Xdebug2NotEnabledException - * @throws Xdebug3NotEnabledException - * @throws XdebugNotAvailableException + * Checks if class has `__construct` method. + * + * @param ClassNode $node + * + * @return bool */ - public function forLineCoverage(Filter $filter) : Driver + public function supports(ClassNode $node) { - $runtime = new Runtime(); - if ($runtime->hasPHPDBGCodeCoverage()) { - return new PhpdbgDriver(); - } - if ($runtime->hasPCOV()) { - return new PcovDriver($filter); + return \true; + } + /** + * Makes all class constructor arguments optional. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + if (!$node->isExtendable('__construct')) { + return; } - if ($runtime->hasXdebug()) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - $driver = new Xdebug3Driver($filter); - } else { - $driver = new Xdebug2Driver($filter); - } - $driver->enableDeadCodeDetection(); - return $driver; + if (!$node->hasMethod('__construct')) { + $node->addMethod(new MethodNode('__construct', '')); + return; } - throw new NoCodeCoverageDriverAvailableException(); + $constructor = $node->getMethod('__construct'); + foreach ($constructor->getArguments() as $argument) { + $argument->setDefault(null); + } + $constructor->setCode(<<hasXdebug()) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - $driver = new Xdebug3Driver($filter); - } else { - $driver = new Xdebug2Driver($filter); - } - $driver->enableDeadCodeDetection(); - $driver->enableBranchAndPathCoverage(); - return $driver; - } - throw new NoCodeCoverageDriverWithPathCoverageSupportAvailableException(); + return 100; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; +use Prophecy\Doubler\Generator\Node\ClassNode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Exception patch for HHVM to remove the stubs from special methods + * + * @author Christophe Coevoet */ -final class CachingCoveredFileAnalyser extends Cache implements CoveredFileAnalyser +class HhvmExceptionPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { /** - * @var CoveredFileAnalyser - */ - private $coveredFileAnalyser; - /** - * @var array + * Supports exceptions on HHVM. + * + * @param ClassNode $node + * + * @return bool */ - private $cache = []; - public function __construct(string $directory, CoveredFileAnalyser $coveredFileAnalyser) - { - parent::__construct($directory); - $this->coveredFileAnalyser = $coveredFileAnalyser; - } - public function classesIn(string $filename) : array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - return $this->cache[$filename]['classesIn']; - } - public function traitsIn(string $filename) : array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - return $this->cache[$filename]['traitsIn']; - } - public function functionsIn(string $filename) : array + public function supports(ClassNode $node) { - if (!isset($this->cache[$filename])) { - $this->process($filename); + if (!\defined('PHPUnit\\HHVM_VERSION')) { + return \false; } - return $this->cache[$filename]['functionsIn']; + return 'Exception' === $node->getParentClass() || \is_subclass_of($node->getParentClass(), 'Exception'); } - public function linesOfCodeFor(string $filename) : LinesOfCode + /** + * Removes special exception static methods from the doubled methods. + * + * @param ClassNode $node + * + * @return void + */ + public function apply(ClassNode $node) { - if (!isset($this->cache[$filename])) { - $this->process($filename); + if ($node->hasMethod('setTraceOptions')) { + $node->getMethod('setTraceOptions')->useParentCode(); } - return $this->cache[$filename]['linesOfCodeFor']; - } - public function ignoredLinesFor(string $filename) : array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); + if ($node->hasMethod('getTraceOptions')) { + $node->getMethod('getTraceOptions')->useParentCode(); } - return $this->cache[$filename]['ignoredLinesFor']; } - public function process(string $filename) : void + /** + * {@inheritdoc} + */ + public function getPriority() { - if ($this->has($filename, __CLASS__)) { - $this->cache[$filename] = $this->read($filename, __CLASS__, [LinesOfCode::class]); - return; - } - $this->cache[$filename] = ['classesIn' => $this->coveredFileAnalyser->classesIn($filename), 'traitsIn' => $this->coveredFileAnalyser->traitsIn($filename), 'functionsIn' => $this->coveredFileAnalyser->functionsIn($filename), 'linesOfCodeFor' => $this->coveredFileAnalyser->linesOfCodeFor($filename), 'ignoredLinesFor' => $this->coveredFileAnalyser->ignoredLinesFor($filename)]; - $this->write($filename, __CLASS__, $this->cache[$filename]); + return -50; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; +use Prophecy\Doubler\Generator\Node\ClassNode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Remove method functionality from the double which will clash with php keywords. + * + * @author Milan Magudia */ -interface UncoveredFileAnalyser +class KeywordPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { - public function executableLinesIn(string $filename) : array; + /** + * Support any class + * + * @param ClassNode $node + * + * @return boolean + */ + public function supports(ClassNode $node) + { + return \true; + } + /** + * Remove methods that clash with php keywords + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $methodNames = \array_keys($node->getMethods()); + $methodsToRemove = \array_intersect($methodNames, $this->getKeywords()); + foreach ($methodsToRemove as $methodName) { + $node->removeMethod($methodName); + } + } + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 49; + } + /** + * Returns array of php keywords. + * + * @return array + */ + private function getKeywords() + { + return ['__halt_compiler']; + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; -use function array_unique; -use function assert; -use function file_get_contents; -use function is_array; -use function sprintf; -use function substr_count; -use function token_get_all; -use function trim; -use PHPUnit\PhpParser\Error; -use PHPUnit\PhpParser\Lexer; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\NodeVisitor\NameResolver; -use PHPUnit\PhpParser\NodeVisitor\ParentConnectingVisitor; -use PHPUnit\PhpParser\ParserFactory; -use PHPUnit\SebastianBergmann\CodeCoverage\ParserException; -use PHPUnit\SebastianBergmann\LinesOfCode\LineCountingVisitor; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; +use Prophecy\Doubler\Generator\Node\ArgumentNode; +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever; +use Prophecy\PhpDocumentor\MethodTagRetrieverInterface; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Discover Magical API using "@method" PHPDoc format. + * + * @author Thomas Tourlourat + * @author Kévin Dunglas + * @author Théo FIDRY */ -final class ParsingCoveredFileAnalyser implements CoveredFileAnalyser +class MagicCallPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { - /** - * @var array - */ - private $classes = []; - /** - * @var array - */ - private $traits = []; - /** - * @var array - */ - private $functions = []; - /** - * @var LinesOfCode[] - */ - private $linesOfCode = []; - /** - * @var array - */ - private $ignoredLines = []; - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - /** - * @var bool - */ - private $ignoreDeprecatedCode; - public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) - { - $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; - $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; - } - public function classesIn(string $filename) : array - { - $this->analyse($filename); - return $this->classes[$filename]; - } - public function traitsIn(string $filename) : array - { - $this->analyse($filename); - return $this->traits[$filename]; - } - public function functionsIn(string $filename) : array - { - $this->analyse($filename); - return $this->functions[$filename]; - } - public function linesOfCodeFor(string $filename) : LinesOfCode - { - $this->analyse($filename); - return $this->linesOfCode[$filename]; - } - public function ignoredLinesFor(string $filename) : array + const MAGIC_METHODS_WITH_ARGUMENTS = ['__call', '__callStatic', '__get', '__isset', '__set', '__set_state', '__unserialize', '__unset']; + private $tagRetriever; + public function __construct(MethodTagRetrieverInterface $tagRetriever = null) { - $this->analyse($filename); - return $this->ignoredLines[$filename]; + $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever; } /** - * @throws ParserException + * Support any class + * + * @param ClassNode $node + * + * @return boolean */ - private function analyse(string $filename) : void + public function supports(ClassNode $node) { - if (isset($this->classes[$filename])) { - return; - } - $source = file_get_contents($filename); - $linesOfCode = substr_count($source, "\n"); - if ($linesOfCode === 0 && !empty($source)) { - $linesOfCode = 1; - } - $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); - try { - $nodes = $parser->parse($source); - assert($nodes !== null); - $traverser = new NodeTraverser(); - $codeUnitFindingVisitor = new CodeUnitFindingVisitor(); - $lineCountingVisitor = new LineCountingVisitor($linesOfCode); - $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); - $traverser->addVisitor(new NameResolver()); - $traverser->addVisitor(new ParentConnectingVisitor()); - $traverser->addVisitor($codeUnitFindingVisitor); - $traverser->addVisitor($lineCountingVisitor); - $traverser->addVisitor($ignoredLinesFindingVisitor); - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new ParserException(sprintf('Cannot parse %s: %s', $filename, $error->getMessage()), (int) $error->getCode(), $error); - } - // @codeCoverageIgnoreEnd - $this->classes[$filename] = $codeUnitFindingVisitor->classes(); - $this->traits[$filename] = $codeUnitFindingVisitor->traits(); - $this->functions[$filename] = $codeUnitFindingVisitor->functions(); - $this->linesOfCode[$filename] = $lineCountingVisitor->result(); - $this->ignoredLines[$filename] = []; - $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode); - $this->ignoredLines[$filename] = array_unique(\array_merge($this->ignoredLines[$filename], $ignoredLinesFindingVisitor->ignoredLines())); - \sort($this->ignoredLines[$filename]); + return \true; } - private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode) : void + /** + * Discover Magical API + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) { - $ignore = \false; - $stop = \false; - foreach (token_get_all($source) as $token) { - if (!is_array($token)) { - continue; - } - switch ($token[0]) { - case \T_COMMENT: - case \T_DOC_COMMENT: - if (!$useAnnotationsForIgnoringCode) { - break; + $types = \array_filter($node->getInterfaces(), function ($interface) { + return 0 !== \strpos($interface, 'Prophecy\\'); + }); + $types[] = $node->getParentClass(); + foreach ($types as $type) { + $reflectionClass = new \ReflectionClass($type); + while ($reflectionClass) { + $tagList = $this->tagRetriever->getTagList($reflectionClass); + foreach ($tagList as $tag) { + $methodName = $tag->getMethodName(); + if (empty($methodName)) { + continue; } - $comment = trim($token[1]); - if ($comment === '// @codeCoverageIgnore' || $comment === '//@codeCoverageIgnore') { - $ignore = \true; - $stop = \true; - } elseif ($comment === '// @codeCoverageIgnoreStart' || $comment === '//@codeCoverageIgnoreStart') { - $ignore = \true; - } elseif ($comment === '// @codeCoverageIgnoreEnd' || $comment === '//@codeCoverageIgnoreEnd') { - $stop = \true; + if (!$reflectionClass->hasMethod($methodName)) { + $methodNode = new MethodNode($methodName); + // only magic methods can have a contract that needs to be enforced + if (\in_array($methodName, self::MAGIC_METHODS_WITH_ARGUMENTS)) { + foreach ($tag->getArguments() as $argument) { + $argumentNode = new ArgumentNode($argument['name']); + $methodNode->addArgument($argumentNode); + } + } + $methodNode->setStatic($tag->isStatic()); + $node->addMethod($methodNode); } - break; - } - if ($ignore) { - $this->ignoredLines[$filename][] = $token[2]; - if ($stop) { - $ignore = \false; - $stop = \false; } + $reflectionClass = $reflectionClass->getParentClass(); } } } + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return integer Priority number (higher - earlier) + */ + public function getPriority() + { + return 50; + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; -use function array_unique; -use function sort; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Stmt\Break_; -use PHPUnit\PhpParser\Node\Stmt\Case_; -use PHPUnit\PhpParser\Node\Stmt\Catch_; -use PHPUnit\PhpParser\Node\Stmt\Continue_; -use PHPUnit\PhpParser\Node\Stmt\Do_; -use PHPUnit\PhpParser\Node\Stmt\Echo_; -use PHPUnit\PhpParser\Node\Stmt\Else_; -use PHPUnit\PhpParser\Node\Stmt\ElseIf_; -use PHPUnit\PhpParser\Node\Stmt\Expression; -use PHPUnit\PhpParser\Node\Stmt\Finally_; -use PHPUnit\PhpParser\Node\Stmt\For_; -use PHPUnit\PhpParser\Node\Stmt\Foreach_; -use PHPUnit\PhpParser\Node\Stmt\Goto_; -use PHPUnit\PhpParser\Node\Stmt\If_; -use PHPUnit\PhpParser\Node\Stmt\Return_; -use PHPUnit\PhpParser\Node\Stmt\Switch_; -use PHPUnit\PhpParser\Node\Stmt\Throw_; -use PHPUnit\PhpParser\Node\Stmt\TryCatch; -use PHPUnit\PhpParser\Node\Stmt\Unset_; -use PHPUnit\PhpParser\Node\Stmt\While_; -use PHPUnit\PhpParser\NodeVisitorAbstract; +use Prophecy\Doubler\Generator\Node\ArgumentTypeNode; +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\Doubler\Generator\Node\ArgumentNode; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Add Prophecy functionality to the double. + * This is a core class patch for Prophecy. + * + * @author Konstantin Kudryashov */ -final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract +class ProphecySubjectPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { /** - * @psalm-var list + * Always returns true. + * + * @param ClassNode $node + * + * @return bool */ - private $executableLines = []; - public function enterNode(Node $node) : void + public function supports(ClassNode $node) { - if (!$this->isExecutable($node)) { - return; - } - $this->executableLines[] = $node->getStartLine(); + return \true; } /** - * @psalm-return list + * Apply Prophecy functionality to class node. + * + * @param ClassNode $node */ - public function executableLines() : array + public function apply(ClassNode $node) { - $executableLines = array_unique($this->executableLines); - sort($executableLines); - return $executableLines; + $node->addInterface('Prophecy\\Prophecy\\ProphecySubjectInterface'); + $node->addProperty('objectProphecyClosure', 'private'); + foreach ($node->getMethods() as $name => $method) { + if ('__construct' === \strtolower($name)) { + continue; + } + if (!$method->getReturnTypeNode()->hasReturnStatement()) { + $method->setCode('$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'); + } else { + $method->setCode('return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'); + } + } + $prophecySetter = new MethodNode('setProphecy'); + $prophecyArgument = new ArgumentNode('prophecy'); + $prophecyArgument->setTypeNode(new ArgumentTypeNode('Prophecy\\Prophecy\\ProphecyInterface')); + $prophecySetter->addArgument($prophecyArgument); + $prophecySetter->setCode(<<objectProphecyClosure) { + \$this->objectProphecyClosure = static function () use (\$prophecy) { + return \$prophecy; + }; +} +PHP +); + $prophecyGetter = new MethodNode('getProphecy'); + $prophecyGetter->setCode('return \\call_user_func($this->objectProphecyClosure);'); + if ($node->hasMethod('__call')) { + $__call = $node->getMethod('__call'); + } else { + $__call = new MethodNode('__call'); + $__call->addArgument(new ArgumentNode('name')); + $__call->addArgument(new ArgumentNode('arguments')); + $node->addMethod($__call, \true); + } + $__call->setCode(<<addMethod($prophecySetter, \true); + $node->addMethod($prophecyGetter, \true); } - private function isExecutable(Node $node) : bool + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() { - return $node instanceof Break_ || $node instanceof Case_ || $node instanceof Catch_ || $node instanceof Continue_ || $node instanceof Do_ || $node instanceof Echo_ || $node instanceof ElseIf_ || $node instanceof Else_ || $node instanceof Expression || $node instanceof Finally_ || $node instanceof Foreach_ || $node instanceof For_ || $node instanceof Goto_ || $node instanceof If_ || $node instanceof Return_ || $node instanceof Switch_ || $node instanceof Throw_ || $node instanceof TryCatch || $node instanceof Unset_ || $node instanceof While_; + return 0; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; -use const DIRECTORY_SEPARATOR; -use function file_get_contents; -use function file_put_contents; -use function filemtime; -use function hash; -use function is_file; -use function serialize; -use function unserialize; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory; +use Prophecy\Doubler\Generator\Node\ClassNode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * ReflectionClass::newInstance patch. + * Makes first argument of newInstance optional, since it works but signature is misleading + * + * @author Florian Klein */ -abstract class Cache +class ReflectionClassNewInstancePatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { /** - * @var string + * Supports ReflectionClass + * + * @param ClassNode $node + * + * @return bool */ - private $directory; - public function __construct(string $directory) - { - Directory::create($directory); - $this->directory = $directory; - } - protected function has(string $filename, string $key) : bool + public function supports(ClassNode $node) { - $cacheFile = $this->cacheFile($filename, $key); - if (!is_file($cacheFile)) { - return \false; - } - if (filemtime($cacheFile) < filemtime($filename)) { - return \false; - } - return \true; + return 'ReflectionClass' === $node->getParentClass(); } /** - * @psalm-param list $allowedClasses + * Updates newInstance's first argument to make it optional * - * @return mixed + * @param ClassNode $node */ - protected function read(string $filename, string $key, array $allowedClasses = []) + public function apply(ClassNode $node) { - $options = ['allowed_classes' => \false]; - if (!empty($allowedClasses)) { - $options = ['allowed_classes' => $allowedClasses]; + foreach ($node->getMethod('newInstance')->getArguments() as $argument) { + $argument->setDefault(null); } - return unserialize(file_get_contents($this->cacheFile($filename, $key)), $options); } /** - * @param mixed $data + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher = earlier) */ - protected function write(string $filename, string $key, $data) : void - { - file_put_contents($this->cacheFile($filename, $key), serialize($data)); - } - private function cacheFile(string $filename, string $key) : string + public function getPriority() { - return $this->directory . \DIRECTORY_SEPARATOR . hash('sha256', $filename . $key); + return 50; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\ClassPatch; -use function implode; -use function rtrim; -use function trim; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Identifier; -use PHPUnit\PhpParser\Node\Name; -use PHPUnit\PhpParser\Node\NullableType; -use PHPUnit\PhpParser\Node\Stmt\Class_; -use PHPUnit\PhpParser\Node\Stmt\ClassMethod; -use PHPUnit\PhpParser\Node\Stmt\Function_; -use PHPUnit\PhpParser\Node\Stmt\Interface_; -use PHPUnit\PhpParser\Node\Stmt\Trait_; -use PHPUnit\PhpParser\Node\UnionType; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\NodeVisitorAbstract; -use PHPUnit\SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * SplFileInfo patch. + * Makes SplFileInfo and derivative classes usable with Prophecy. + * + * @author Konstantin Kudryashov */ -final class CodeUnitFindingVisitor extends NodeVisitorAbstract +class SplFileInfoPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface { /** - * @var array - */ - private $classes = []; - /** - * @var array + * Supports everything that extends SplFileInfo. + * + * @param ClassNode $node + * + * @return bool */ - private $traits = []; + public function supports(ClassNode $node) + { + if (null === $node->getParentClass()) { + return \false; + } + return 'SplFileInfo' === $node->getParentClass() || \is_subclass_of($node->getParentClass(), 'SplFileInfo'); + } /** - * @var array + * Updated constructor code to call parent one with dummy file argument. + * + * @param ClassNode $node */ - private $functions = []; - public function enterNode(Node $node) + public function apply(ClassNode $node) { - if ($node instanceof Class_) { - if ($node->isAnonymous()) { - return; - } - $this->processClass($node); + if ($node->hasMethod('__construct')) { + $constructor = $node->getMethod('__construct'); + } else { + $constructor = new MethodNode('__construct'); + $node->addMethod($constructor); } - if ($node instanceof Trait_) { - $this->processTrait($node); + if ($this->nodeIsDirectoryIterator($node)) { + $constructor->setCode('return parent::__construct("' . __DIR__ . '");'); + return; } - if (!$node instanceof ClassMethod && !$node instanceof Function_) { - return null; + if ($this->nodeIsSplFileObject($node)) { + $filePath = \str_replace('\\', '\\\\', __FILE__); + $constructor->setCode('return parent::__construct("' . $filePath . '");'); + return; } - if ($node instanceof ClassMethod) { - $parentNode = $node->getAttribute('parent'); - if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) { - return; - } - $this->processMethod($node); + if ($this->nodeIsSymfonySplFileInfo($node)) { + $filePath = \str_replace('\\', '\\\\', __FILE__); + $constructor->setCode('return parent::__construct("' . $filePath . '", "", "");'); return; } - $this->processFunction($node); + $constructor->useParentCode(); } - public function classes() : array + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() { - return $this->classes; + return 50; } - public function traits() : array + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsDirectoryIterator(ClassNode $node) { - return $this->traits; + $parent = $node->getParentClass(); + return 'DirectoryIterator' === $parent || \is_subclass_of($parent, 'DirectoryIterator'); } - public function functions() : array + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSplFileObject(ClassNode $node) { - return $this->functions; + $parent = $node->getParentClass(); + return 'SplFileObject' === $parent || \is_subclass_of($parent, 'SplFileObject'); } /** - * @psalm-param ClassMethod|Function_ $node + * @param ClassNode $node + * @return boolean */ - private function cyclomaticComplexity(Node $node) : int + private function nodeIsSymfonySplFileInfo(ClassNode $node) { - \assert($node instanceof ClassMethod || $node instanceof Function_); - $nodes = $node->getStmts(); - if ($nodes === null) { - return 0; - } - $traverser = new NodeTraverser(); - $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor(); - $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + $parent = $node->getParentClass(); + return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; } +} +returnsByRef() ? '&' : '') . $node->name->toString() . '('; - $parameters = []; - foreach ($node->getParams() as $parameter) { - \assert(isset($parameter->var->name)); - $parameterAsString = ''; - if ($parameter->type !== null) { - $parameterAsString = $this->type($parameter->type) . ' '; - } - $parameterAsString .= '$' . $parameter->var->name; - /* @todo Handle default values */ - $parameters[] = $parameterAsString; - } - $signature .= implode(', ', $parameters) . ')'; - $returnType = $node->getReturnType(); - if ($returnType !== null) { - $signature .= ': ' . $this->type($returnType); - } - return $signature; + return $this->implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node); } /** - * @psalm-param Identifier|Name|NullableType|UnionType $type + * @param ClassNode $node + * @return bool */ - private function type(Node $type) : string + private function implementsAThrowableInterface(ClassNode $node) { - \assert($type instanceof Identifier || $type instanceof Name || $type instanceof NullableType || $type instanceof UnionType); - if ($type instanceof NullableType) { - return '?' . $type->type; - } - if ($type instanceof UnionType) { - $types = []; - foreach ($type->types as $_type) { - $types[] = $_type->toString(); + foreach ($node->getInterfaces() as $type) { + if (\is_a($type, 'Throwable', \true)) { + return \true; } - return implode('|', $types); } - return $type->toString(); + return \false; } - private function visibility(ClassMethod $node) : string + /** + * @param ClassNode $node + * @return bool + */ + private function doesNotExtendAThrowableClass(ClassNode $node) { - if ($node->isPrivate()) { - return 'private'; - } - if ($node->isProtected()) { - return 'protected'; + return !\is_a($node->getParentClass(), 'Throwable', \true); + } + /** + * Applies patch to the specific class node. + * + * @param ClassNode $node + * + * @return void + */ + public function apply(ClassNode $node) + { + $this->checkItCanBeDoubled($node); + $this->setParentClassToException($node); + } + private function checkItCanBeDoubled(ClassNode $node) + { + $className = $node->getParentClass(); + if ($className !== 'stdClass') { + throw new ClassCreatorException(\sprintf('Cannot double concrete class %s as well as implement Traversable', $className), $node); } - return 'public'; } - private function processClass(Class_ $node) : void + private function setParentClassToException(ClassNode $node) { - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - $this->classes[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'methods' => []]; + $node->setParentClass('Exception'); + $node->removeMethod('getMessage'); + $node->removeMethod('getCode'); + $node->removeMethod('getFile'); + $node->removeMethod('getLine'); + $node->removeMethod('getTrace'); + $node->removeMethod('getPrevious'); + $node->removeMethod('getNext'); + $node->removeMethod('getTraceAsString'); } - private function processTrait(Trait_ $node) : void + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() { - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - $this->traits[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'methods' => []]; + return 100; } - private function processMethod(ClassMethod $node) : void +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; +/** + * Traversable interface patch. + * Forces classes that implement interfaces, that extend Traversable to also implement Iterator. + * + * @author Konstantin Kudryashov + */ +class TraversablePatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface +{ + /** + * Supports nodetree, that implement Traversable, but not Iterator or IteratorAggregate. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) { - $parentNode = $node->getAttribute('parent'); - if ($parentNode instanceof Interface_) { - return; + if (\in_array('Iterator', $node->getInterfaces())) { + return \false; } - \assert($parentNode instanceof Class_ || $parentNode instanceof Trait_); - \assert(isset($parentNode->name)); - \assert(isset($parentNode->namespacedName)); - \assert($parentNode->namespacedName instanceof Name); - $parentName = $parentNode->name->toString(); - $parentNamespacedName = $parentNode->namespacedName->toString(); - if ($parentNode instanceof Class_) { - $storage =& $this->classes; - } else { - $storage =& $this->traits; + if (\in_array('IteratorAggregate', $node->getInterfaces())) { + return \false; } - if (!isset($storage[$parentNamespacedName])) { - $storage[$parentNamespacedName] = ['name' => $parentName, 'namespacedName' => $parentNamespacedName, 'namespace' => $this->namespace($parentNamespacedName, $parentName), 'startLine' => $parentNode->getStartLine(), 'endLine' => $parentNode->getEndLine(), 'methods' => []]; + foreach ($node->getInterfaces() as $interface) { + if ('Traversable' !== $interface && !\is_subclass_of($interface, 'Traversable')) { + continue; + } + if ('Iterator' === $interface || \is_subclass_of($interface, 'Iterator')) { + continue; + } + if ('IteratorAggregate' === $interface || \is_subclass_of($interface, 'IteratorAggregate')) { + continue; + } + return \true; } - $storage[$parentNamespacedName]['methods'][$node->name->toString()] = ['methodName' => $node->name->toString(), 'signature' => $this->signature($node), 'visibility' => $this->visibility($node), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'ccn' => $this->cyclomaticComplexity($node)]; + return \false; } - private function processFunction(Function_ $node) : void + /** + * Forces class to implement Iterator interface. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) { - \assert(isset($node->name)); - \assert(isset($node->namespacedName)); - \assert($node->namespacedName instanceof Name); - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - $this->functions[$namespacedName] = ['name' => $name, 'namespacedName' => $namespacedName, 'namespace' => $this->namespace($namespacedName, $name), 'signature' => $this->signature($node), 'startLine' => $node->getStartLine(), 'endLine' => $node->getEndLine(), 'ccn' => $this->cyclomaticComplexity($node)]; + $node->addInterface('Iterator'); + $currentMethod = new MethodNode('current'); + \PHP_VERSION_ID >= 80100 && $currentMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); + $node->addMethod($currentMethod); + $keyMethod = new MethodNode('key'); + \PHP_VERSION_ID >= 80100 && $keyMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); + $node->addMethod($keyMethod); + $nextMethod = new MethodNode('next'); + \PHP_VERSION_ID >= 80100 && $nextMethod->setReturnTypeNode(new ReturnTypeNode('void')); + $node->addMethod($nextMethod); + $rewindMethod = new MethodNode('rewind'); + \PHP_VERSION_ID >= 80100 && $rewindMethod->setReturnTypeNode(new ReturnTypeNode('void')); + $node->addMethod($rewindMethod); + $validMethod = new MethodNode('valid'); + \PHP_VERSION_ID >= 80100 && $validMethod->setReturnTypeNode(new ReturnTypeNode('bool')); + $node->addMethod($validMethod); } - private function namespace(string $namespacedName, string $name) : string + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() { - return trim(rtrim($namespacedName, $name), '\\'); + return 100; } } + * Marcello Duarte * - * (c) Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler; + +/** + * Core double interface. + * All doubled classes will implement this one. + * + * @author Konstantin Kudryashov + */ +interface DoubleInterface +{ +} + + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler; -use PHPUnit\PhpParser\Error; -use PHPUnit\PhpParser\Lexer; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\ParserFactory; +use PHPUnit\Doctrine\Instantiator\Instantiator; +use Prophecy\Doubler\ClassPatch\ClassPatchInterface; +use Prophecy\Doubler\Generator\ClassMirror; +use Prophecy\Doubler\Generator\ClassCreator; +use Prophecy\Exception\InvalidArgumentException; +use ReflectionClass; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov */ -final class ParsingUncoveredFileAnalyser implements UncoveredFileAnalyser +class Doubler { - public function executableLinesIn(string $filename) : array + private $mirror; + private $creator; + private $namer; + /** + * @var ClassPatchInterface[] + */ + private $patches = array(); + /** + * @var \Doctrine\Instantiator\Instantiator + */ + private $instantiator; + /** + * Initializes doubler. + * + * @param ClassMirror $mirror + * @param ClassCreator $creator + * @param NameGenerator $namer + */ + public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null, \Prophecy\Doubler\NameGenerator $namer = null) { - $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); - try { - $nodes = $parser->parse(\file_get_contents($filename)); - \assert($nodes !== null); - $traverser = new NodeTraverser(); - $visitor = new ExecutableLinesFindingVisitor(); - $traverser->addVisitor($visitor); - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - return $visitor->executableLines(); - // @codeCoverageIgnoreStart - } catch (Error $error) { + $this->mirror = $mirror ?: new ClassMirror(); + $this->creator = $creator ?: new ClassCreator(); + $this->namer = $namer ?: new \Prophecy\Doubler\NameGenerator(); + } + /** + * Returns list of registered class patches. + * + * @return ClassPatchInterface[] + */ + public function getClassPatches() + { + return $this->patches; + } + /** + * Registers new class patch. + * + * @param ClassPatchInterface $patch + */ + public function registerClassPatch(ClassPatchInterface $patch) + { + $this->patches[] = $patch; + @\usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { + return $patch2->getPriority() - $patch1->getPriority(); + }); + } + /** + * Creates double from specific class or/and list of interfaces. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces Array of ReflectionClass instances + * @param array $args Constructor arguments + * + * @return DoubleInterface + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function double(ReflectionClass $class = null, array $interfaces, array $args = null) + { + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(\sprintf("[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n" . "a second argument to `Doubler::double(...)`, but got %s.", \is_object($interface) ? \get_class($interface) . ' class' : \gettype($interface))); + } + } + $classname = $this->createDoubleClass($class, $interfaces); + $reflection = new ReflectionClass($classname); + if (null !== $args) { + return $reflection->newInstanceArgs($args); + } + if (null === ($constructor = $reflection->getConstructor()) || $constructor->isPublic() && !$constructor->isFinal()) { + return $reflection->newInstance(); + } + if (!$this->instantiator) { + $this->instantiator = new Instantiator(); + } + return $this->instantiator->instantiate($classname); + } + /** + * Creates double class and returns its FQN. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $name = $this->namer->name($class, $interfaces); + $node = $this->mirror->reflect($class, $interfaces); + foreach ($this->patches as $patch) { + if ($patch->supports($node)) { + $patch->apply($node); + } } - // @codeCoverageIgnoreEnd - return []; + $this->creator->create($name, $node); + return $name; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\Generator; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; +use Prophecy\Doubler\Generator\Node\TypeNodeAbstract; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Class code creator. + * Generates PHP code for specific class node tree. + * + * @author Konstantin Kudryashov */ -final class CachingUncoveredFileAnalyser extends Cache implements UncoveredFileAnalyser +class ClassCodeGenerator { + public function __construct(\Prophecy\Doubler\Generator\TypeHintReference $typeHintReference = null) + { + } /** - * @var UncoveredFileAnalyser + * Generates PHP code for class node. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return string */ - private $uncoveredFileAnalyser; - public function __construct(string $directory, UncoveredFileAnalyser $uncoveredFileAnalyser) + public function generate($classname, \Prophecy\Doubler\Generator\Node\ClassNode $class) + { + $parts = \explode('\\', $classname); + $classname = \array_pop($parts); + $namespace = \implode('\\', $parts); + $code = \sprintf("class %s extends \\%s implements %s {\n", $classname, $class->getParentClass(), \implode(', ', \array_map(function ($interface) { + return '\\' . $interface; + }, $class->getInterfaces()))); + foreach ($class->getProperties() as $name => $visibility) { + $code .= \sprintf("%s \$%s;\n", $visibility, $name); + } + $code .= "\n"; + foreach ($class->getMethods() as $method) { + $code .= $this->generateMethod($method) . "\n"; + } + $code .= "\n}"; + return \sprintf("namespace %s {\n%s\n}", $namespace, $code); + } + private function generateMethod(\Prophecy\Doubler\Generator\Node\MethodNode $method) { - parent::__construct($directory); - $this->uncoveredFileAnalyser = $uncoveredFileAnalyser; + $php = \sprintf("%s %s function %s%s(%s)%s {\n", $method->getVisibility(), $method->isStatic() ? 'static' : '', $method->returnsReference() ? '&' : '', $method->getName(), \implode(', ', $this->generateArguments($method->getArguments())), ($ret = $this->generateTypes($method->getReturnTypeNode())) ? ': ' . $ret : ''); + $php .= $method->getCode() . "\n"; + return $php . '}'; } - public function executableLinesIn(string $filename) : array + private function generateTypes(TypeNodeAbstract $typeNode) : string { - if ($this->has($filename, __METHOD__)) { - return $this->read($filename, __METHOD__); + if (!$typeNode->getTypes()) { + return ''; } - $data = $this->uncoveredFileAnalyser->executableLinesIn($filename); - $this->write($filename, __METHOD__, $data); - return $data; + // When we require PHP 8 we can stop generating ?foo nullables and remove this first block + if ($typeNode->canUseNullShorthand()) { + return \sprintf('?%s', $typeNode->getNonNullTypes()[0]); + } else { + return \join('|', $typeNode->getTypes()); + } + } + private function generateArguments(array $arguments) + { + return \array_map(function (\Prophecy\Doubler\Generator\Node\ArgumentNode $argument) { + $php = $this->generateTypes($argument->getTypeNode()); + $php .= ' ' . ($argument->isPassedByReference() ? '&' : ''); + $php .= $argument->isVariadic() ? '...' : ''; + $php .= '$' . $argument->getName(); + if ($argument->isOptional() && !$argument->isVariadic()) { + $php .= ' = ' . \var_export($argument->getDefault(), \true); + } + return $php; + }, $arguments); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\Generator; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; +use Prophecy\Exception\Doubler\ClassCreatorException; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Class creator. + * Creates specific class in current environment. + * + * @author Konstantin Kudryashov */ -interface CoveredFileAnalyser +class ClassCreator { - public function classesIn(string $filename) : array; - public function traitsIn(string $filename) : array; - public function functionsIn(string $filename) : array; - public function linesOfCodeFor(string $filename) : LinesOfCode; - public function ignoredLinesFor(string $filename) : array; + private $generator; + /** + * Initializes creator. + * + * @param ClassCodeGenerator $generator + */ + public function __construct(\Prophecy\Doubler\Generator\ClassCodeGenerator $generator = null) + { + $this->generator = $generator ?: new \Prophecy\Doubler\Generator\ClassCodeGenerator(); + } + /** + * Creates class. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return mixed + * + * @throws \Prophecy\Exception\Doubler\ClassCreatorException + */ + public function create($classname, \Prophecy\Doubler\Generator\Node\ClassNode $class) + { + $code = $this->generator->generate($classname, $class); + $return = eval($code); + if (!\class_exists($classname, \false)) { + if (\count($class->getInterfaces())) { + throw new ClassCreatorException(\sprintf('Could not double `%s` and implement interfaces: [%s].', $class->getParentClass(), \implode(', ', $class->getInterfaces())), $class); + } + throw new ClassCreatorException(\sprintf('Could not double `%s`.', $class->getParentClass()), $class); + } + return $return; + } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; +namespace Prophecy\Doubler\Generator; -use function array_merge; -use function range; -use function strpos; -use PHPUnit\PhpParser\Node; -use PHPUnit\PhpParser\Node\Stmt\Class_; -use PHPUnit\PhpParser\Node\Stmt\ClassMethod; -use PHPUnit\PhpParser\Node\Stmt\Function_; -use PHPUnit\PhpParser\Node\Stmt\Interface_; -use PHPUnit\PhpParser\Node\Stmt\Trait_; -use PHPUnit\PhpParser\NodeTraverser; -use PHPUnit\PhpParser\NodeVisitorAbstract; +use Prophecy\Doubler\Generator\Node\ArgumentTypeNode; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Doubler\ClassMirrorException; +use ReflectionClass; +use ReflectionIntersectionType; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; +use ReflectionType; +use ReflectionUnionType; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Class mirror. + * Core doubler class. Mirrors specific class and/or interfaces into class node tree. + * + * @author Konstantin Kudryashov */ -final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract +class ClassMirror { + private static $reflectableMethods = array('__construct', '__destruct', '__sleep', '__wakeup', '__toString', '__call', '__invoke'); /** - * @psalm-var list - */ - private $ignoredLines = []; - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - /** - * @var bool + * Reflects provided arguments into class node. + * + * @param ReflectionClass|null $class + * @param ReflectionClass[] $interfaces + * + * @return Node\ClassNode + * */ - private $ignoreDeprecated; - public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) + public function reflect(?ReflectionClass $class, array $interfaces) { - $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; - $this->ignoreDeprecated = $ignoreDeprecated; + $node = new \Prophecy\Doubler\Generator\Node\ClassNode(); + if (null !== $class) { + if (\true === $class->isInterface()) { + throw new InvalidArgumentException(\sprintf("Could not reflect %s as a class, because it\n" . "is interface - use the second argument instead.", $class->getName())); + } + $this->reflectClassToNode($class, $node); + } + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(\sprintf("[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n" . "a second argument to `ClassMirror::reflect(...)`, but got %s.", \is_object($interface) ? \get_class($interface) . ' class' : \gettype($interface))); + } + if (\false === $interface->isInterface()) { + throw new InvalidArgumentException(\sprintf("Could not reflect %s as an interface, because it\n" . "is class - use the first argument instead.", $interface->getName())); + } + $this->reflectInterfaceToNode($interface, $node); + } + $node->addInterface('Prophecy\\Doubler\\Generator\\ReflectionInterface'); + return $node; } - public function enterNode(Node $node) : ?int + private function reflectClassToNode(ReflectionClass $class, \Prophecy\Doubler\Generator\Node\ClassNode $node) { - if (!$node instanceof Class_ && !$node instanceof Trait_ && !$node instanceof Interface_ && !$node instanceof ClassMethod && !$node instanceof Function_) { - return null; + if (\true === $class->isFinal()) { + throw new ClassMirrorException(\sprintf('Could not reflect class %s as it is marked final.', $class->getName()), $class); } - if ($node instanceof Class_ && $node->isAnonymous()) { - return null; + $node->setParentClass($class->getName()); + foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { + if (\false === $method->isProtected()) { + continue; + } + $this->reflectMethodToNode($method, $node); } - // Workaround for https://bugs.xdebug.org/view.php?id=1798 - if ($node instanceof Class_ || $node instanceof Trait_ || $node instanceof Interface_) { - $this->ignoredLines[] = $node->getStartLine(); + foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (0 === \strpos($method->getName(), '_') && !\in_array($method->getName(), self::$reflectableMethods)) { + continue; + } + if (\true === $method->isFinal()) { + $node->addUnextendableMethod($method->getName()); + continue; + } + $this->reflectMethodToNode($method, $node); } - if (!$this->useAnnotationsForIgnoringCode) { - return null; + } + private function reflectInterfaceToNode(ReflectionClass $interface, \Prophecy\Doubler\Generator\Node\ClassNode $node) + { + $node->addInterface($interface->getName()); + foreach ($interface->getMethods() as $method) { + $this->reflectMethodToNode($method, $node); } - if ($node instanceof Interface_) { - return null; + } + private function reflectMethodToNode(ReflectionMethod $method, \Prophecy\Doubler\Generator\Node\ClassNode $classNode) + { + $node = new \Prophecy\Doubler\Generator\Node\MethodNode($method->getName()); + if (\true === $method->isProtected()) { + $node->setVisibility('protected'); } - $docComment = $node->getDocComment(); - if ($docComment === null) { - return null; + if (\true === $method->isStatic()) { + $node->setStatic(); } - if (strpos($docComment->getText(), '@codeCoverageIgnore') !== \false) { - $this->ignoredLines = array_merge($this->ignoredLines, range($node->getStartLine(), $node->getEndLine())); + if (\true === $method->returnsReference()) { + $node->setReturnsReference(); } - if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== \false) { - $this->ignoredLines = array_merge($this->ignoredLines, range($node->getStartLine(), $node->getEndLine())); + if ($method->hasReturnType()) { + $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()->allowsNull()); + $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); + } elseif (\method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) { + $returnTypes = $this->getTypeHints($method->getTentativeReturnType(), $method->getDeclaringClass(), $method->getTentativeReturnType()->allowsNull()); + $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); } - if ($node instanceof ClassMethod || $node instanceof Function_) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + if (\is_array($params = $method->getParameters()) && \count($params)) { + foreach ($params as $param) { + $this->reflectArgumentToNode($param, $node); + } } - return null; + $classNode->addMethod($node); } - /** - * @psalm-return list - */ - public function ignoredLines() : array + private function reflectArgumentToNode(ReflectionParameter $parameter, \Prophecy\Doubler\Generator\Node\MethodNode $methodNode) { - return $this->ignoredLines; + $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); + $node = new \Prophecy\Doubler\Generator\Node\ArgumentNode($name); + $typeHints = $this->getTypeHints($parameter->getType(), $parameter->getDeclaringClass(), $parameter->allowsNull()); + $node->setTypeNode(new ArgumentTypeNode(...$typeHints)); + if ($parameter->isVariadic()) { + $node->setAsVariadic(); + } + if ($this->hasDefaultValue($parameter)) { + $node->setDefault($this->getDefaultValue($parameter)); + } + if ($parameter->isPassedByReference()) { + $node->setAsPassedByReference(); + } + $methodNode->addArgument($node); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis; - -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -final class CacheWarmer -{ - public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter) : void + private function hasDefaultValue(ReflectionParameter $parameter) { - $coveredFileAnalyser = new CachingCoveredFileAnalyser($cacheDirectory, new ParsingCoveredFileAnalyser($useAnnotationsForIgnoringCode, $ignoreDeprecatedCode)); - $uncoveredFileAnalyser = new CachingUncoveredFileAnalyser($cacheDirectory, new ParsingUncoveredFileAnalyser()); - foreach ($filter->files() as $file) { - $coveredFileAnalyser->process($file); - /* @noinspection UnusedFunctionResultInspection */ - $uncoveredFileAnalyser->executableLinesIn($file); + if ($parameter->isVariadic()) { + return \false; + } + if ($parameter->isDefaultValueAvailable()) { + return \true; } + return $parameter->isOptional() || $parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use function dirname; -use PHPUnit\SebastianBergmann\Version as VersionId; -final class Version -{ - /** - * @var string - */ - private static $version; - public static function id() : string + private function getDefaultValue(ReflectionParameter $parameter) { - if (self::$version === null) { - self::$version = (new VersionId('9.2.7', dirname(__DIR__)))->getVersion(); + if (!$parameter->isDefaultValueAvailable()) { + return null; } - return self::$version; + return $parameter->getDefaultValue(); + } + private function getTypeHints(?ReflectionType $type, ?ReflectionClass $class, bool $allowsNull) : array + { + $types = []; + if ($type instanceof ReflectionNamedType) { + $types = [$type->getName()]; + } elseif ($type instanceof ReflectionUnionType) { + $types = $type->getTypes(); + } elseif ($type instanceof ReflectionIntersectionType) { + throw new ClassMirrorException('Doubling intersection types is not supported', $class); + } elseif (\is_object($type)) { + throw new ClassMirrorException('Unknown reflection type ' . \get_class($type), $class); + } + $types = \array_map(function (string $type) use($class) { + if ($type === 'self') { + return $class->getName(); + } + if ($type === 'parent') { + return $class->getParentClass()->getName(); + } + return $type; + }, $types); + if ($types && $types != ['mixed'] && $allowsNull) { + $types[] = 'null'; + } + return $types; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace Prophecy\Doubler\Generator\Node; -use function array_diff; -use function array_diff_key; -use function array_flip; -use function array_keys; -use function array_merge; -use function array_unique; -use function array_values; -use function count; -use function explode; -use function get_class; -use function is_array; -use function is_file; -use function sort; -use PHPUnit\Framework\TestCase; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Test; -use ReflectionClass; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Builder; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\CachingCoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\CachingUncoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\CoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingCoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingUncoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\UncoveredFileAnalyser; -use PHPUnit\SebastianBergmann\CodeUnitReverseLookup\Wizard; /** - * Provides collection functionality for PHP code coverage information. + * Argument node. + * + * @author Konstantin Kudryashov */ -final class CodeCoverage +class ArgumentNode { - private const UNCOVERED_FILES = 'UNCOVERED_FILES'; - /** - * @var Driver - */ - private $driver; - /** - * @var Filter - */ - private $filter; - /** - * @var Wizard - */ - private $wizard; - /** - * @var bool - */ - private $checkForUnintentionallyCoveredCode = \false; - /** - * @var bool - */ - private $includeUncoveredFiles = \true; - /** - * @var bool - */ - private $processUncoveredFiles = \false; - /** - * @var bool - */ - private $ignoreDeprecatedCode = \false; - /** - * @var PhptTestCase|string|TestCase - */ - private $currentId; - /** - * Code coverage data. - * - * @var ProcessedCodeCoverageData - */ - private $data; - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode = \true; + private $name; + private $default; + private $optional = \false; + private $byReference = \false; + private $isVariadic = \false; + /** @var ArgumentTypeNode */ + private $typeNode; /** - * Test data. - * - * @var array + * @param string $name */ - private $tests = []; + public function __construct($name) + { + $this->name = $name; + $this->typeNode = new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode(); + } + public function getName() + { + return $this->name; + } + public function setTypeNode(\Prophecy\Doubler\Generator\Node\ArgumentTypeNode $typeNode) + { + $this->typeNode = $typeNode; + } + public function getTypeNode() : \Prophecy\Doubler\Generator\Node\ArgumentTypeNode + { + return $this->typeNode; + } + public function hasDefault() + { + return $this->isOptional() && !$this->isVariadic(); + } + public function getDefault() + { + return $this->default; + } + public function setDefault($default = null) + { + $this->optional = \true; + $this->default = $default; + } + public function isOptional() + { + return $this->optional; + } + public function setAsPassedByReference($byReference = \true) + { + $this->byReference = $byReference; + } + public function isPassedByReference() + { + return $this->byReference; + } + public function setAsVariadic($isVariadic = \true) + { + $this->isVariadic = $isVariadic; + } + public function isVariadic() + { + return $this->isVariadic; + } /** - * @psalm-var list + * @deprecated use getArgumentTypeNode instead + * @return string|null */ - private $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; + public function getTypeHint() + { + $type = $this->typeNode->getNonNullTypes() ? $this->typeNode->getNonNullTypes()[0] : null; + return $type ? \ltrim($type, '\\') : null; + } /** - * @var ?CoveredFileAnalyser + * @deprecated use setArgumentTypeNode instead + * @param string|null $typeHint */ - private $coveredFileAnalyser; + public function setTypeHint($typeHint = null) + { + $this->typeNode = $typeHint === null ? new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode() : new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode($typeHint); + } /** - * @var ?UncoveredFileAnalyser + * @deprecated use getArgumentTypeNode instead + * @return bool */ - private $uncoveredFileAnalyser; + public function isNullable() + { + return $this->typeNode->canUseNullShorthand(); + } /** - * @var ?string + * @deprecated use getArgumentTypeNode instead + * @param bool $isNullable */ - private $cacheDirectory; - public function __construct(Driver $driver, Filter $filter) + public function setAsNullable($isNullable = \true) { - $this->driver = $driver; - $this->filter = $filter; - $this->data = new ProcessedCodeCoverageData(); - $this->wizard = new Wizard(); + $nonNullTypes = $this->typeNode->getNonNullTypes(); + $this->typeNode = $isNullable ? new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode('null', ...$nonNullTypes) : new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode(...$nonNullTypes); } +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\MethodNotExtendableException; +use Prophecy\Exception\InvalidArgumentException; +/** + * Class node. + * + * @author Konstantin Kudryashov + */ +class ClassNode +{ + private $parentClass = 'stdClass'; + private $interfaces = array(); + private $properties = array(); + private $unextendableMethods = array(); /** - * Returns the code coverage information as a graph of node objects. + * @var MethodNode[] */ - public function getReport() : Directory + private $methods = array(); + public function getParentClass() { - return (new Builder($this->coveredFileAnalyser()))->build($this); + return $this->parentClass; } /** - * Clears collected code coverage data. + * @param string $class */ - public function clear() : void + public function setParentClass($class) { - $this->currentId = null; - $this->data = new ProcessedCodeCoverageData(); - $this->tests = []; + $this->parentClass = $class ?: 'stdClass'; } /** - * Returns the filter object used. + * @return string[] */ - public function filter() : Filter + public function getInterfaces() { - return $this->filter; + return $this->interfaces; } /** - * Returns the collected code coverage data. + * @param string $interface */ - public function getData(bool $raw = \false) : ProcessedCodeCoverageData + public function addInterface($interface) { - if (!$raw) { - if ($this->processUncoveredFiles) { - $this->processUncoveredFilesFromFilter(); - } elseif ($this->includeUncoveredFiles) { - $this->addUncoveredFilesFromFilter(); - } + if ($this->hasInterface($interface)) { + return; } - return $this->data; + \array_unshift($this->interfaces, $interface); } /** - * Sets the coverage data. + * @param string $interface + * + * @return bool */ - public function setData(ProcessedCodeCoverageData $data) : void + public function hasInterface($interface) { - $this->data = $data; + return \in_array($interface, $this->interfaces); + } + public function getProperties() + { + return $this->properties; + } + public function addProperty($name, $visibility = 'public') + { + $visibility = \strtolower($visibility); + if (!\in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(\sprintf('`%s` property visibility is not supported.', $visibility)); + } + $this->properties[$name] = $visibility; } /** - * Returns the test data. + * @return MethodNode[] */ - public function getTests() : array + public function getMethods() { - return $this->tests; + return $this->methods; + } + public function addMethod(\Prophecy\Doubler\Generator\Node\MethodNode $method, $force = \false) + { + if (!$this->isExtendable($method->getName())) { + $message = \sprintf('Method `%s` is not extendable, so can not be added.', $method->getName()); + throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); + } + if ($force || !isset($this->methods[$method->getName()])) { + $this->methods[$method->getName()] = $method; + } + } + public function removeMethod($name) + { + unset($this->methods[$name]); } /** - * Sets the test data. + * @param string $name + * + * @return MethodNode|null */ - public function setTests(array $tests) : void + public function getMethod($name) { - $this->tests = $tests; + return $this->hasMethod($name) ? $this->methods[$name] : null; } /** - * Start collection of code coverage information. + * @param string $name * - * @param PhptTestCase|string|TestCase $id + * @return bool */ - public function start($id, bool $clear = \false) : void + public function hasMethod($name) { - if ($clear) { - $this->clear(); - } - $this->currentId = $id; - $this->driver->start(); + return isset($this->methods[$name]); } /** - * Stop collection of code coverage information. - * - * @param array|false $linesToBeCovered + * @return string[] */ - public function stop(bool $append = \true, $linesToBeCovered = [], array $linesToBeUsed = []) : RawCodeCoverageData + public function getUnextendableMethods() { - if (!is_array($linesToBeCovered) && $linesToBeCovered !== \false) { - throw new InvalidArgumentException('$linesToBeCovered must be an array or false'); - } - $data = $this->driver->stop(); - $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); - $this->currentId = null; - return $data; + return $this->unextendableMethods; } /** - * Appends code coverage data. - * - * @param PhptTestCase|string|TestCase $id - * @param array|false $linesToBeCovered - * - * @throws ReflectionException - * @throws TestIdMissingException - * @throws UnintentionallyCoveredCodeException + * @param string $unextendableMethod */ - public function append(RawCodeCoverageData $rawData, $id = null, bool $append = \true, $linesToBeCovered = [], array $linesToBeUsed = []) : void + public function addUnextendableMethod($unextendableMethod) { - if ($id === null) { - $id = $this->currentId; - } - if ($id === null) { - throw new TestIdMissingException(); - } - $this->applyFilter($rawData); - if ($this->useAnnotationsForIgnoringCode) { - $this->applyIgnoredLinesFilter($rawData); - } - $this->data->initializeUnseenData($rawData); - if (!$append) { + if (!$this->isExtendable($unextendableMethod)) { return; } - if ($id !== self::UNCOVERED_FILES) { - $this->applyCoversAnnotationFilter($rawData, $linesToBeCovered, $linesToBeUsed); - if (empty($rawData->lineCoverage())) { - return; - } - $size = 'unknown'; - $status = -1; - $fromTestcase = \false; - if ($id instanceof TestCase) { - $fromTestcase = \true; - $_size = $id->getSize(); - if ($_size === Test::SMALL) { - $size = 'small'; - } elseif ($_size === Test::MEDIUM) { - $size = 'medium'; - } elseif ($_size === Test::LARGE) { - $size = 'large'; - } - $status = $id->getStatus(); - $id = get_class($id) . '::' . $id->getName(); - } elseif ($id instanceof PhptTestCase) { - $fromTestcase = \true; - $size = 'large'; - $id = $id->getName(); - } - $this->tests[$id] = ['size' => $size, 'status' => $status, 'fromTestcase' => $fromTestcase]; - $this->data->markCodeAsExecutedByTestCase($id, $rawData); - } + $this->unextendableMethods[] = $unextendableMethod; } /** - * Merges the data from another instance. + * @param string $method + * @return bool */ - public function merge(self $that) : void + public function isExtendable($method) { - $this->filter->includeFiles($that->filter()->files()); - $this->data->merge($that->data); - $this->tests = array_merge($this->tests, $that->getTests()); + return !\in_array($method, $this->unextendableMethods); } - public function enableCheckForUnintentionallyCoveredCode() : void +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Doubler\Generator\TypeHintReference; +use Prophecy\Exception\InvalidArgumentException; +/** + * Method node. + * + * @author Konstantin Kudryashov + */ +class MethodNode +{ + private $name; + private $code; + private $visibility = 'public'; + private $static = \false; + private $returnsReference = \false; + /** @var ReturnTypeNode */ + private $returnTypeNode; + /** + * @var ArgumentNode[] + */ + private $arguments = array(); + /** + * @param string $name + * @param string $code + */ + public function __construct($name, $code = null, TypeHintReference $typeHintReference = null) { - $this->checkForUnintentionallyCoveredCode = \true; + $this->name = $name; + $this->code = $code; + $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode(); } - public function disableCheckForUnintentionallyCoveredCode() : void + public function getVisibility() { - $this->checkForUnintentionallyCoveredCode = \false; + return $this->visibility; } - public function includeUncoveredFiles() : void + /** + * @param string $visibility + */ + public function setVisibility($visibility) { - $this->includeUncoveredFiles = \true; + $visibility = \strtolower($visibility); + if (!\in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(\sprintf('`%s` method visibility is not supported.', $visibility)); + } + $this->visibility = $visibility; } - public function excludeUncoveredFiles() : void + public function isStatic() { - $this->includeUncoveredFiles = \false; + return $this->static; } - public function processUncoveredFiles() : void + public function setStatic($static = \true) { - $this->processUncoveredFiles = \true; + $this->static = (bool) $static; } - public function doNotProcessUncoveredFiles() : void + public function returnsReference() { - $this->processUncoveredFiles = \false; + return $this->returnsReference; } - public function enableAnnotationsForIgnoringCode() : void + public function setReturnsReference() { - $this->useAnnotationsForIgnoringCode = \true; + $this->returnsReference = \true; } - public function disableAnnotationsForIgnoringCode() : void + public function getName() { - $this->useAnnotationsForIgnoringCode = \false; + return $this->name; } - public function ignoreDeprecatedCode() : void + public function addArgument(\Prophecy\Doubler\Generator\Node\ArgumentNode $argument) { - $this->ignoreDeprecatedCode = \true; + $this->arguments[] = $argument; } - public function doNotIgnoreDeprecatedCode() : void + /** + * @return ArgumentNode[] + */ + public function getArguments() { - $this->ignoreDeprecatedCode = \false; + return $this->arguments; } /** - * @psalm-assert-if-true !null $this->cacheDirectory + * @deprecated use getReturnTypeNode instead + * @return bool */ - public function cachesStaticAnalysis() : bool + public function hasReturnType() { - return $this->cacheDirectory !== null; + return (bool) $this->returnTypeNode->getNonNullTypes(); } - public function cacheStaticAnalysis(string $directory) : void + public function setReturnTypeNode(\Prophecy\Doubler\Generator\Node\ReturnTypeNode $returnTypeNode) : void { - $this->cacheDirectory = $directory; + $this->returnTypeNode = $returnTypeNode; } - public function doNotCacheStaticAnalysis() : void + /** + * @deprecated use setReturnTypeNode instead + * @param string $type + */ + public function setReturnType($type = null) { - $this->cacheDirectory = null; + $this->returnTypeNode = $type === '' || $type === null ? new \Prophecy\Doubler\Generator\Node\ReturnTypeNode() : new \Prophecy\Doubler\Generator\Node\ReturnTypeNode($type); } /** - * @throws StaticAnalysisCacheNotConfiguredException + * @deprecated use setReturnTypeNode instead + * @param bool $bool */ - public function cacheDirectory() : string + public function setNullableReturnType($bool = \true) { - if (!$this->cachesStaticAnalysis()) { - throw new StaticAnalysisCacheNotConfiguredException('The static analysis cache is not configured'); + if ($bool) { + $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode('null', ...$this->returnTypeNode->getTypes()); + } else { + $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode(...$this->returnTypeNode->getNonNullTypes()); } - return $this->cacheDirectory; } /** - * @psalm-param class-string $className + * @deprecated use getReturnTypeNode instead + * @return string|null */ - public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(string $className) : void + public function getReturnType() { - $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck[] = $className; + if ($types = $this->returnTypeNode->getNonNullTypes()) { + return $types[0]; + } + return null; } - public function enableBranchAndPathCoverage() : void + public function getReturnTypeNode() : \Prophecy\Doubler\Generator\Node\ReturnTypeNode { - $this->driver->enableBranchAndPathCoverage(); + return $this->returnTypeNode; } - public function disableBranchAndPathCoverage() : void + /** + * @deprecated use getReturnTypeNode instead + * @return bool + */ + public function hasNullableReturnType() { - $this->driver->disableBranchAndPathCoverage(); + return $this->returnTypeNode->canUseNullShorthand(); } - public function collectsBranchAndPathCoverage() : bool + /** + * @param string $code + */ + public function setCode($code) { - return $this->driver->collectsBranchAndPathCoverage(); + $this->code = $code; } - public function detectsDeadCode() : bool + public function getCode() { - return $this->driver->detectsDeadCode(); + if ($this->returnsReference) { + return "throw new \\Prophecy\\Exception\\Doubler\\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');"; + } + return (string) $this->code; } - /** - * Applies the @covers annotation filtering. - * - * @param array|false $linesToBeCovered - * - * @throws ReflectionException - * @throws UnintentionallyCoveredCodeException - */ - private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $linesToBeCovered, array $linesToBeUsed) : void + public function useParentCode() { - if ($linesToBeCovered === \false) { - $rawData->clear(); - return; - } - if (empty($linesToBeCovered)) { - return; - } - if ($this->checkForUnintentionallyCoveredCode && (!$this->currentId instanceof TestCase || !$this->currentId->isMedium() && !$this->currentId->isLarge())) { - $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed); - } - $rawLineData = $rawData->lineCoverage(); - $filesWithNoCoverage = array_diff_key($rawLineData, $linesToBeCovered); - foreach (array_keys($filesWithNoCoverage) as $fileWithNoCoverage) { - $rawData->removeCoverageDataForFile($fileWithNoCoverage); - } - if (is_array($linesToBeCovered)) { - foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) { - $rawData->keepCoverageDataOnlyForLines($fileToBeCovered, $includedLines); - } - } + $this->code = \sprintf('return parent::%s(%s);', $this->getName(), \implode(', ', \array_map(array($this, 'generateArgument'), $this->arguments))); } - private function applyFilter(RawCodeCoverageData $data) : void + private function generateArgument(\Prophecy\Doubler\Generator\Node\ArgumentNode $arg) { - if ($this->filter->isEmpty()) { - return; + $argument = '$' . $arg->getName(); + if ($arg->isVariadic()) { + $argument = '...' . $argument; } - foreach (array_keys($data->lineCoverage()) as $filename) { - if ($this->filter->isExcluded($filename)) { - $data->removeCoverageDataForFile($filename); - } + return $argument; + } +} +lineCoverage()) as $filename) { - if (!$this->filter->isFile($filename)) { - continue; - } - $data->removeCoverageDataForLines($filename, $this->coveredFileAnalyser()->ignoredLinesFor($filename)); + if (isset($this->types['void']) && \count($this->types) !== 1) { + throw new DoubleException('void cannot be part of a union'); + } + if (isset($this->types['never']) && \count($this->types) !== 1) { + throw new DoubleException('never cannot be part of a union'); } + parent::guardIsValidType(); } /** - * @throws UnintentionallyCoveredCodeException + * @deprecated use hasReturnStatement */ - private function addUncoveredFilesFromFilter() : void + public function isVoid() { - $uncoveredFiles = array_diff($this->filter->files(), $this->data->coveredFiles()); - foreach ($uncoveredFiles as $uncoveredFile) { - if (is_file($uncoveredFile)) { - $this->append(RawCodeCoverageData::fromUncoveredFile($uncoveredFile, $this->uncoveredFileAnalyser()), self::UNCOVERED_FILES); - } + return $this->types == ['void' => 'void']; + } + public function hasReturnStatement() : bool + { + return $this->types !== ['void' => 'void'] && $this->types !== ['never' => 'never']; + } +} +getRealType($type); + $this->types[$type] = $type; } + $this->guardIsValidType(); + } + public function canUseNullShorthand() : bool + { + return isset($this->types['null']) && \count($this->types) <= 2; + } + public function getTypes() : array + { + return \array_values($this->types); + } + public function getNonNullTypes() : array + { + $nonNullTypes = $this->types; + unset($nonNullTypes['null']); + return \array_values($nonNullTypes); } - /** - * @throws UnintentionallyCoveredCodeException - */ - private function processUncoveredFilesFromFilter() : void + protected function prefixWithNsSeparator(string $type) : string { - $uncoveredFiles = array_diff($this->filter->files(), $this->data->coveredFiles()); - $this->driver->start(); - foreach ($uncoveredFiles as $uncoveredFile) { - if (is_file($uncoveredFile)) { - include_once $uncoveredFile; - } - } - $this->append($this->driver->stop(), self::UNCOVERED_FILES); + return '\\' . \ltrim($type, '\\'); } - /** - * @throws ReflectionException - * @throws UnintentionallyCoveredCodeException - */ - private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed) : void + protected function getRealType(string $type) : string { - $allowedLines = $this->getAllowedLines($linesToBeCovered, $linesToBeUsed); - $unintentionallyCoveredUnits = []; - foreach ($data->lineCoverage() as $file => $_data) { - foreach ($_data as $line => $flag) { - if ($flag === 1 && !isset($allowedLines[$file][$line])) { - $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); - } - } - } - $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); - if (!empty($unintentionallyCoveredUnits)) { - throw new UnintentionallyCoveredCodeException($unintentionallyCoveredUnits); + switch ($type) { + // type aliases + case 'double': + case 'real': + return 'float'; + case 'boolean': + return 'bool'; + case 'integer': + return 'int'; + // built in types + case 'self': + case 'static': + case 'array': + case 'callable': + case 'bool': + case 'false': + case 'float': + case 'int': + case 'string': + case 'iterable': + case 'object': + case 'null': + return $type; + case 'mixed': + return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; + default: + return $this->prefixWithNsSeparator($type); } } - private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) : array + protected function guardIsValidType() { - $allowedLines = []; - foreach (array_keys($linesToBeCovered) as $file) { - if (!isset($allowedLines[$file])) { - $allowedLines[$file] = []; - } - $allowedLines[$file] = array_merge($allowedLines[$file], $linesToBeCovered[$file]); + if ($this->types == ['null' => 'null']) { + throw new DoubleException('Type cannot be standalone null'); } - foreach (array_keys($linesToBeUsed) as $file) { - if (!isset($allowedLines[$file])) { - $allowedLines[$file] = []; - } - $allowedLines[$file] = array_merge($allowedLines[$file], $linesToBeUsed[$file]); + if ($this->types == ['false' => 'false']) { + throw new DoubleException('Type cannot be standalone false'); } - foreach (array_keys($allowedLines) as $file) { - $allowedLines[$file] = array_flip(array_unique($allowedLines[$file])); + if ($this->types == ['false' => 'false', 'null' => 'null']) { + throw new DoubleException('Type cannot be nullable false'); } - return $allowedLines; - } - /** - * @throws ReflectionException - */ - private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits) : array - { - $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); - sort($unintentionallyCoveredUnits); - foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { - $unit = explode('::', $unintentionallyCoveredUnits[$k]); - if (count($unit) !== 2) { - continue; - } - try { - $class = new ReflectionClass($unit[0]); - foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) { - if ($class->isSubclassOf($parentClass)) { - unset($unintentionallyCoveredUnits[$k]); - break; - } - } - } catch (\ReflectionException $e) { - throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } + if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && \count($this->types) !== 1) { + throw new DoubleException('mixed cannot be part of a union'); } - return array_values($unintentionallyCoveredUnits); } - private function coveredFileAnalyser() : CoveredFileAnalyser +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Doubler\Generator; + +/** + * Reflection interface. + * All reflected classes implement this interface. + * + * @author Konstantin Kudryashov + */ +interface ReflectionInterface +{ +} +coveredFileAnalyser !== null) { - return $this->coveredFileAnalyser; - } - $this->coveredFileAnalyser = new ParsingCoveredFileAnalyser($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); - if ($this->cachesStaticAnalysis()) { - $this->coveredFileAnalyser = new CachingCoveredFileAnalyser($this->cacheDirectory, $this->coveredFileAnalyser); + switch ($type) { + case 'self': + case 'array': + case 'callable': + case 'bool': + case 'float': + case 'int': + case 'string': + case 'iterable': + case 'object': + return \true; + case 'mixed': + return \PHP_VERSION_ID >= 80000; + default: + return \false; } - return $this->coveredFileAnalyser; } - private function uncoveredFileAnalyser() : UncoveredFileAnalyser + public function isBuiltInReturnTypeHint($type) { - if ($this->uncoveredFileAnalyser !== null) { - return $this->uncoveredFileAnalyser; - } - $this->uncoveredFileAnalyser = new ParsingUncoveredFileAnalyser(); - if ($this->cachesStaticAnalysis()) { - $this->uncoveredFileAnalyser = new CachingUncoveredFileAnalyser($this->cacheDirectory, $this->uncoveredFileAnalyser); + if ($type === 'void') { + return \true; } - return $this->uncoveredFileAnalyser; + return $this->isBuiltInParamTypeHint($type); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace Prophecy\Doubler; -use function array_key_exists; -use function array_keys; -use function array_merge; -use function array_unique; -use function count; -use function is_array; -use function ksort; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; +use Prophecy\Exception\Doubler\DoubleException; +use Prophecy\Exception\Doubler\ClassNotFoundException; +use Prophecy\Exception\Doubler\InterfaceNotFoundException; +use ReflectionClass; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * Lazy double. + * Gives simple interface to describe double before creating it. + * + * @author Konstantin Kudryashov */ -final class ProcessedCodeCoverageData +class LazyDouble { + private $doubler; + private $class; + private $interfaces = array(); + private $arguments = null; + private $double; /** - * Line coverage data. - * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids. + * Initializes lazy double. * - * @var array + * @param Doubler $doubler */ - private $lineCoverage = []; + public function __construct(\Prophecy\Doubler\Doubler $doubler) + { + $this->doubler = $doubler; + } /** - * Function coverage data. - * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array - * of testcase ids. + * Tells doubler to use specific class as parent one for double. * - * @var array + * @param string|ReflectionClass $class + * + * @throws \Prophecy\Exception\Doubler\ClassNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException */ - private $functionCoverage = []; - public function initializeUnseenData(RawCodeCoverageData $rawData) : void - { - foreach ($rawData->lineCoverage() as $file => $lines) { - if (!isset($this->lineCoverage[$file])) { - $this->lineCoverage[$file] = []; - foreach ($lines as $k => $v) { - $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : []; - } - } - } - foreach ($rawData->functionCoverage() as $file => $functions) { - foreach ($functions as $functionName => $functionData) { - if (isset($this->functionCoverage[$file][$functionName])) { - $this->initPreviouslySeenFunction($file, $functionName, $functionData); - } else { - $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); - } - } - } - } - public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode) : void - { - foreach ($executedCode->lineCoverage() as $file => $lines) { - foreach ($lines as $k => $v) { - if ($v === Driver::LINE_EXECUTED) { - $this->lineCoverage[$file][$k][] = $testCaseId; - } - } - } - foreach ($executedCode->functionCoverage() as $file => $functions) { - foreach ($functions as $functionName => $functionData) { - foreach ($functionData['branches'] as $branchId => $branchData) { - if ($branchData['hit'] === Driver::BRANCH_HIT) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId; - } - } - foreach ($functionData['paths'] as $pathId => $pathData) { - if ($pathData['hit'] === Driver::BRANCH_HIT) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId; - } - } - } - } - } - public function setLineCoverage(array $lineCoverage) : void - { - $this->lineCoverage = $lineCoverage; - } - public function lineCoverage() : array - { - ksort($this->lineCoverage); - return $this->lineCoverage; - } - public function setFunctionCoverage(array $functionCoverage) : void - { - $this->functionCoverage = $functionCoverage; - } - public function functionCoverage() : array - { - ksort($this->functionCoverage); - return $this->functionCoverage; - } - public function coveredFiles() : array - { - ksort($this->lineCoverage); - return array_keys($this->lineCoverage); - } - public function renameFile(string $oldFile, string $newFile) : void - { - $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile]; - if (isset($this->functionCoverage[$oldFile])) { - $this->functionCoverage[$newFile] = $this->functionCoverage[$oldFile]; - } - unset($this->lineCoverage[$oldFile], $this->functionCoverage[$oldFile]); - } - public function merge(self $newData) : void + public function setParentClass($class) { - foreach ($newData->lineCoverage as $file => $lines) { - if (!isset($this->lineCoverage[$file])) { - $this->lineCoverage[$file] = $lines; - continue; - } - // we should compare the lines if any of two contains data - $compareLineNumbers = array_unique(array_merge(array_keys($this->lineCoverage[$file]), array_keys($newData->lineCoverage[$file]))); - foreach ($compareLineNumbers as $line) { - $thatPriority = $this->priorityForLine($newData->lineCoverage[$file], $line); - $thisPriority = $this->priorityForLine($this->lineCoverage[$file], $line); - if ($thatPriority > $thisPriority) { - $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line]; - } elseif ($thatPriority === $thisPriority && is_array($this->lineCoverage[$file][$line])) { - $this->lineCoverage[$file][$line] = array_unique(array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line])); - } - } + if (null !== $this->double) { + throw new DoubleException('Can not extend class with already instantiated double.'); } - foreach ($newData->functionCoverage as $file => $functions) { - if (!isset($this->functionCoverage[$file])) { - $this->functionCoverage[$file] = $functions; - continue; - } - foreach ($functions as $functionName => $functionData) { - if (isset($this->functionCoverage[$file][$functionName])) { - $this->initPreviouslySeenFunction($file, $functionName, $functionData); - } else { - $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); - } - foreach ($functionData['branches'] as $branchId => $branchData) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit'])); - } - foreach ($functionData['paths'] as $pathId => $pathData) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit'])); - } + if (!$class instanceof ReflectionClass) { + if (!\class_exists($class)) { + throw new ClassNotFoundException(\sprintf('Class %s not found.', $class), $class); } + $class = new ReflectionClass($class); } + $this->class = $class; } /** - * Determine the priority for a line. + * Tells doubler to implement specific interface with double. * - * 1 = the line is not set - * 2 = the line has not been tested - * 3 = the line is dead code - * 4 = the line has been tested + * @param string|ReflectionClass $interface * - * During a merge, a higher number is better. + * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException */ - private function priorityForLine(array $data, int $line) : int + public function addInterface($interface) { - if (!array_key_exists($line, $data)) { - return 1; - } - if (is_array($data[$line]) && count($data[$line]) === 0) { - return 2; + if (null !== $this->double) { + throw new DoubleException('Can not implement interface with already instantiated double.'); } - if ($data[$line] === null) { - return 3; + if (!$interface instanceof ReflectionClass) { + if (!\interface_exists($interface)) { + throw new InterfaceNotFoundException(\sprintf('Interface %s not found.', $interface), $interface); + } + $interface = new ReflectionClass($interface); } - return 4; + $this->interfaces[] = $interface; } /** - * For a function we have never seen before, copy all data over and simply init the 'hit' array. + * Sets constructor arguments. + * + * @param array $arguments */ - private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData) : void + public function setArguments(array $arguments = null) { - $this->functionCoverage[$file][$functionName] = $functionData; - foreach (array_keys($functionData['branches']) as $branchId) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; - } - foreach (array_keys($functionData['paths']) as $pathId) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; - } + $this->arguments = $arguments; } /** - * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths. - * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling - * containers) mean that the functions inside a file cannot be relied upon to be static. + * Creates double instance or returns already created one. + * + * @return DoubleInterface */ - private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData) : void + public function getInstance() { - foreach ($functionData['branches'] as $branchId => $branchData) { - if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData; - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; - } - } - foreach ($functionData['paths'] as $pathId => $pathData) { - if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData; - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; + if (null === $this->double) { + if (null !== $this->arguments) { + return $this->double = $this->doubler->double($this->class, $this->interfaces, $this->arguments); } + $this->double = $this->doubler->double($this->class, $this->interfaces); } + return $this->double; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; +namespace Prophecy\Doubler; -use function date; -use function dirname; -use function file_put_contents; -use function htmlspecialchars; -use function is_string; -use function round; -use DOMDocument; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; -final class Crap4j +use ReflectionClass; +/** + * Name generator. + * Generates classname for double. + * + * @author Konstantin Kudryashov + */ +class NameGenerator { + private static $counter = 1; /** - * @var int - */ - private $threshold; - public function __construct(int $threshold = 30) - { - $this->threshold = $threshold; - } - /** - * @throws WriteOperationFailedException - */ - public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null) : string - { - $document = new DOMDocument('1.0', 'UTF-8'); - $document->formatOutput = \true; - $root = $document->createElement('crap_result'); - $document->appendChild($root); - $project = $document->createElement('project', is_string($name) ? $name : ''); - $root->appendChild($project); - $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s'))); - $stats = $document->createElement('stats'); - $methodsNode = $document->createElement('methods'); - $report = $coverage->getReport(); - unset($coverage); - $fullMethodCount = 0; - $fullCrapMethodCount = 0; - $fullCrapLoad = 0; - $fullCrap = 0; - foreach ($report as $item) { - $namespace = 'global'; - if (!$item instanceof File) { - continue; - } - $file = $document->createElement('file'); - $file->setAttribute('name', $item->pathAsString()); - $classes = $item->classesAndTraits(); - foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']); - $fullCrap += $method['crap']; - $fullCrapLoad += $crapLoad; - $fullMethodCount++; - if ($method['crap'] >= $this->threshold) { - $fullCrapMethodCount++; - } - $methodNode = $document->createElement('method'); - if (!empty($class['namespace'])) { - $namespace = $class['namespace']; - } - $methodNode->appendChild($document->createElement('package', $namespace)); - $methodNode->appendChild($document->createElement('className', $className)); - $methodNode->appendChild($document->createElement('methodName', $methodName)); - $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap']))); - $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); - $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); - $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad))); - $methodsNode->appendChild($methodNode); - } - } - } - $stats->appendChild($document->createElement('name', 'Method Crap Stats')); - $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount)); - $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount)); - $stats->appendChild($document->createElement('crapLoad', (string) round($fullCrapLoad))); - $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap)); - $crapMethodPercent = 0; - if ($fullMethodCount > 0) { - $crapMethodPercent = $this->roundValue(100 * $fullCrapMethodCount / $fullMethodCount); - } - $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent)); - $root->appendChild($stats); - $root->appendChild($methodsNode); - $buffer = $document->saveXML(); - if ($target !== null) { - Directory::create(dirname($target)); - if (@file_put_contents($target, $buffer) === \false) { - throw new WriteOperationFailedException($target); + * Generates name. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + public function name(ReflectionClass $class = null, array $interfaces) + { + $parts = array(); + if (null !== $class) { + $parts[] = $class->getName(); + } else { + foreach ($interfaces as $interface) { + $parts[] = $interface->getShortName(); } } - return $buffer; - } - private function crapLoad(float $crapValue, int $cyclomaticComplexity, float $coveragePercent) : float - { - $crapLoad = 0; - if ($crapValue >= $this->threshold) { - $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); - $crapLoad += $cyclomaticComplexity / $this->threshold; + if (!\count($parts)) { + $parts[] = 'stdClass'; } - return $crapLoad; - } - private function roundValue(float $value) : float - { - return round($value, 2); + return \sprintf('Double\\%s\\P%d', \implode('\\', $parts), self::$counter++); } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; +namespace Prophecy\Exception\Call; -use const PHP_EOL; -use function array_map; -use function date; -use function ksort; -use function max; -use function sprintf; -use function str_pad; -use function strlen; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; -use PHPUnit\SebastianBergmann\CodeCoverage\Percentage; -final class Text +use Prophecy\Exception\Prophecy\ObjectProphecyException; +use Prophecy\Prophecy\ObjectProphecy; +class UnexpectedCallException extends ObjectProphecyException { - /** - * @var string - */ - private const COLOR_GREEN = "\33[30;42m"; - /** - * @var string - */ - private const COLOR_YELLOW = "\33[30;43m"; - /** - * @var string - */ - private const COLOR_RED = "\33[37;41m"; - /** - * @var string - */ - private const COLOR_HEADER = "\33[1;37;40m"; - /** - * @var string - */ - private const COLOR_RESET = "\33[0m"; - /** - * @var string - */ - private const COLOR_EOL = "\33[2K"; - /** - * @var int - */ - private $lowUpperBound; - /** - * @var int - */ - private $highLowerBound; - /** - * @var bool - */ - private $showUncoveredFiles; - /** - * @var bool - */ - private $showOnlySummary; - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = \false, bool $showOnlySummary = \false) + private $methodName; + private $arguments; + public function __construct($message, ObjectProphecy $objectProphecy, $methodName, array $arguments) { - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; + parent::__construct($message, $objectProphecy); + $this->methodName = $methodName; + $this->arguments = $arguments; } - public function process(CodeCoverage $coverage, bool $showColors = \false) : string + public function getMethodName() { - $hasBranchCoverage = !empty($coverage->getData(\true)->functionCoverage()); - $output = \PHP_EOL . \PHP_EOL; - $report = $coverage->getReport(); - $colors = ['header' => '', 'classes' => '', 'methods' => '', 'lines' => '', 'branches' => '', 'paths' => '', 'reset' => '', 'eol' => '']; - if ($showColors) { - $colors['classes'] = $this->coverageColor($report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits()); - $colors['methods'] = $this->coverageColor($report->numberOfTestedMethods(), $report->numberOfMethods()); - $colors['lines'] = $this->coverageColor($report->numberOfExecutedLines(), $report->numberOfExecutableLines()); - $colors['branches'] = $this->coverageColor($report->numberOfExecutedBranches(), $report->numberOfExecutableBranches()); - $colors['paths'] = $this->coverageColor($report->numberOfExecutedPaths(), $report->numberOfExecutablePaths()); - $colors['reset'] = self::COLOR_RESET; - $colors['header'] = self::COLOR_HEADER; - $colors['eol'] = self::COLOR_EOL; - } - $classes = sprintf(' Classes: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits())->asString(), $report->numberOfTestedClassesAndTraits(), $report->numberOfClassesAndTraits()); - $methods = sprintf(' Methods: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfTestedMethods(), $report->numberOfMethods())->asString(), $report->numberOfTestedMethods(), $report->numberOfMethods()); - $paths = ''; - $branches = ''; - if ($hasBranchCoverage) { - $paths = sprintf(' Paths: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedPaths(), $report->numberOfExecutablePaths())->asString(), $report->numberOfExecutedPaths(), $report->numberOfExecutablePaths()); - $branches = sprintf(' Branches: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedBranches(), $report->numberOfExecutableBranches())->asString(), $report->numberOfExecutedBranches(), $report->numberOfExecutableBranches()); - } - $lines = sprintf(' Lines: %6s (%d/%d)', Percentage::fromFractionAndTotal($report->numberOfExecutedLines(), $report->numberOfExecutableLines())->asString(), $report->numberOfExecutedLines(), $report->numberOfExecutableLines()); - $padding = max(array_map('strlen', [$classes, $methods, $lines])); - if ($this->showOnlySummary) { - $title = 'Code Coverage Report Summary:'; - $padding = max($padding, strlen($title)); - $output .= $this->format($colors['header'], $padding, $title); - } else { - $date = date(' Y-m-d H:i:s'); - $title = 'Code Coverage Report:'; - $output .= $this->format($colors['header'], $padding, $title); - $output .= $this->format($colors['header'], $padding, $date); - $output .= $this->format($colors['header'], $padding, ''); - $output .= $this->format($colors['header'], $padding, ' Summary:'); - } - $output .= $this->format($colors['classes'], $padding, $classes); - $output .= $this->format($colors['methods'], $padding, $methods); - if ($hasBranchCoverage) { - $output .= $this->format($colors['paths'], $padding, $paths); - $output .= $this->format($colors['branches'], $padding, $branches); - } - $output .= $this->format($colors['lines'], $padding, $lines); - if ($this->showOnlySummary) { - return $output . \PHP_EOL; - } - $classCoverage = []; - foreach ($report as $item) { - if (!$item instanceof File) { - continue; - } - $classes = $item->classesAndTraits(); - foreach ($classes as $className => $class) { - $classExecutableLines = 0; - $classExecutedLines = 0; - $classExecutableBranches = 0; - $classExecutedBranches = 0; - $classExecutablePaths = 0; - $classExecutedPaths = 0; - $coveredMethods = 0; - $classMethods = 0; - foreach ($class['methods'] as $method) { - if ($method['executableLines'] == 0) { - continue; - } - $classMethods++; - $classExecutableLines += $method['executableLines']; - $classExecutedLines += $method['executedLines']; - $classExecutableBranches += $method['executableBranches']; - $classExecutedBranches += $method['executedBranches']; - $classExecutablePaths += $method['executablePaths']; - $classExecutedPaths += $method['executedPaths']; - if ($method['coverage'] == 100) { - $coveredMethods++; - } - } - $classCoverage[$className] = ['namespace' => $class['namespace'], 'className' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, 'statementsCovered' => $classExecutedLines, 'statementCount' => $classExecutableLines, 'branchesCovered' => $classExecutedBranches, 'branchesCount' => $classExecutableBranches, 'pathsCovered' => $classExecutedPaths, 'pathsCount' => $classExecutablePaths]; - } - } - ksort($classCoverage); - $methodColor = ''; - $pathsColor = ''; - $branchesColor = ''; - $linesColor = ''; - $resetColor = ''; - foreach ($classCoverage as $fullQualifiedPath => $classInfo) { - if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { - if ($showColors) { - $methodColor = $this->coverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); - $pathsColor = $this->coverageColor($classInfo['pathsCovered'], $classInfo['pathsCount']); - $branchesColor = $this->coverageColor($classInfo['branchesCovered'], $classInfo['branchesCount']); - $linesColor = $this->coverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); - $resetColor = $colors['reset']; - } - $output .= \PHP_EOL . $fullQualifiedPath . \PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' '; - if ($hasBranchCoverage) { - $output .= ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathsCount'], 3) . $resetColor . ' ' . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchesCount'], 3) . $resetColor . ' '; - } - $output .= ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor; - } - } - return $output . \PHP_EOL; + return $this->methodName; } - private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfElements) : string + public function getArguments() { - $coverage = Percentage::fromFractionAndTotal($numberOfCoveredElements, $totalNumberOfElements); - if ($coverage->asFloat() >= $this->highLowerBound) { - return self::COLOR_GREEN; - } - if ($coverage->asFloat() > $this->lowUpperBound) { - return self::COLOR_YELLOW; - } - return self::COLOR_RED; + return $this->arguments; } - private function printCoverageCounts(int $numberOfCoveredElements, int $totalNumberOfElements, int $precision) : string +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Doubler; + +use Prophecy\Doubler\Generator\Node\ClassNode; +class ClassCreatorException extends \RuntimeException implements \Prophecy\Exception\Doubler\DoublerException +{ + private $node; + public function __construct($message, ClassNode $node) { - $format = '%' . $precision . 's'; - return Percentage::fromFractionAndTotal($numberOfCoveredElements, $totalNumberOfElements)->asFixedWidthString() . ' (' . sprintf($format, $numberOfCoveredElements) . '/' . sprintf($format, $totalNumberOfElements) . ')'; + parent::__construct($message); + $this->node = $node; } - /** - * @param false|string $string - */ - private function format(string $color, int $padding, $string) : string + public function getClassNode() { - $reset = $color ? self::COLOR_RESET : ''; - return $color . str_pad((string) $string, $padding) . $reset . \PHP_EOL; + return $this->node; } } + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; +namespace Prophecy\Exception\Doubler; -use const DIRECTORY_SEPARATOR; -use function copy; -use function date; -use function dirname; -use function substr; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory as DirectoryUtil; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -final class Facade +use ReflectionClass; +class ClassMirrorException extends \RuntimeException implements \Prophecy\Exception\Doubler\DoublerException { - /** - * @var string - */ - private $templatePath; - /** - * @var string - */ - private $generator; - /** - * @var int - */ - private $lowUpperBound; - /** - * @var int - */ - private $highLowerBound; - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, string $generator = '') + private $class; + public function __construct($message, ReflectionClass $class) { - $this->generator = $generator; - $this->highLowerBound = $highLowerBound; - $this->lowUpperBound = $lowUpperBound; - $this->templatePath = __DIR__ . '/Renderer/Template/'; + parent::__construct($message); + $this->class = $class; } - public function process(CodeCoverage $coverage, string $target) : void + public function getReflectedClass() { - $target = $this->directory($target); - $report = $coverage->getReport(); - $date = date('D M j G:i:s T Y'); - $dashboard = new Dashboard($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); - $directory = new Directory($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); - $file = new File($this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound, $coverage->collectsBranchAndPathCoverage()); - $directory->render($report, $target . 'index.html'); - $dashboard->render($report, $target . 'dashboard.html'); - foreach ($report as $node) { - $id = $node->id(); - if ($node instanceof DirectoryNode) { - DirectoryUtil::create($target . $id); - $directory->render($node, $target . $id . '/index.html'); - $dashboard->render($node, $target . $id . '/dashboard.html'); - } else { - $dir = dirname($target . $id); - DirectoryUtil::create($dir); - $file->render($node, $target . $id); - } - } - $this->copyFiles($target); + return $this->class; } - private function copyFiles(string $target) : void +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Doubler; + +class ClassNotFoundException extends \Prophecy\Exception\Doubler\DoubleException +{ + private $classname; + /** + * @param string $message + * @param string $classname + */ + public function __construct($message, $classname) { - $dir = $this->directory($target . '_css'); - copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); - copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); - copy($this->templatePath . 'css/style.css', $dir . 'style.css'); - copy($this->templatePath . 'css/custom.css', $dir . 'custom.css'); - copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css'); - $dir = $this->directory($target . '_icons'); - copy($this->templatePath . 'icons/file-code.svg', $dir . 'file-code.svg'); - copy($this->templatePath . 'icons/file-directory.svg', $dir . 'file-directory.svg'); - $dir = $this->directory($target . '_js'); - copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); - copy($this->templatePath . 'js/popper.min.js', $dir . 'popper.min.js'); - copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); - copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); - copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); - copy($this->templatePath . 'js/file.js', $dir . 'file.js'); + parent::__construct($message); + $this->classname = $classname; } - private function directory(string $directory) : string + public function getClassname() { - if (substr($directory, -1, 1) != \DIRECTORY_SEPARATOR) { - $directory .= \DIRECTORY_SEPARATOR; - } - DirectoryUtil::create($directory); - return $directory; + return $this->classname; } } + * Marcello Duarte * - * (c) Sebastian Bergmann + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Doubler; + +use RuntimeException; +class DoubleException extends RuntimeException implements \Prophecy\Exception\Doubler\DoublerException +{ +} + + * Marcello Duarte * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; +namespace Prophecy\Exception\Doubler; -use function array_pop; -use function count; -use function sprintf; -use function str_repeat; -use function substr_count; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Version; -use PHPUnit\SebastianBergmann\Environment\Runtime; -use PHPUnit\SebastianBergmann\Template\Template; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage +use Prophecy\Exception\Exception; +interface DoublerException extends Exception +{ +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -abstract class Renderer +namespace Prophecy\Exception\Doubler; + +class InterfaceNotFoundException extends \Prophecy\Exception\Doubler\ClassNotFoundException { + public function getInterfaceName() + { + return $this->getClassname(); + } +} +templatePath = $templatePath; - $this->generator = $generator; - $this->date = $date; - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - $this->version = Version::id(); - $this->hasBranchCoverage = $hasBranchCoverage; + parent::__construct($message); + $this->methodName = $methodName; + $this->className = $className; } - protected function renderItemTemplate(Template $template, array $data) : string + /** + * @return string + */ + public function getMethodName() { - $numSeparator = ' / '; - if (isset($data['numClasses']) && $data['numClasses'] > 0) { - $classesLevel = $this->colorLevel($data['testedClassesPercent']); - $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; - $classesBar = $this->coverageBar($data['testedClassesPercent']); - } else { - $classesLevel = ''; - $classesNumber = '0' . $numSeparator . '0'; - $classesBar = ''; - $data['testedClassesPercentAsString'] = 'n/a'; - } - if ($data['numMethods'] > 0) { - $methodsLevel = $this->colorLevel($data['testedMethodsPercent']); - $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; - $methodsBar = $this->coverageBar($data['testedMethodsPercent']); - } else { - $methodsLevel = ''; - $methodsNumber = '0' . $numSeparator . '0'; - $methodsBar = ''; - $data['testedMethodsPercentAsString'] = 'n/a'; - } - if ($data['numExecutableLines'] > 0) { - $linesLevel = $this->colorLevel($data['linesExecutedPercent']); - $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; - $linesBar = $this->coverageBar($data['linesExecutedPercent']); - } else { - $linesLevel = ''; - $linesNumber = '0' . $numSeparator . '0'; - $linesBar = ''; - $data['linesExecutedPercentAsString'] = 'n/a'; - } - if ($data['numExecutablePaths'] > 0) { - $pathsLevel = $this->colorLevel($data['pathsExecutedPercent']); - $pathsNumber = $data['numExecutedPaths'] . $numSeparator . $data['numExecutablePaths']; - $pathsBar = $this->coverageBar($data['pathsExecutedPercent']); - } else { - $pathsLevel = ''; - $pathsNumber = '0' . $numSeparator . '0'; - $pathsBar = ''; - $data['pathsExecutedPercentAsString'] = 'n/a'; - } - if ($data['numExecutableBranches'] > 0) { - $branchesLevel = $this->colorLevel($data['branchesExecutedPercent']); - $branchesNumber = $data['numExecutedBranches'] . $numSeparator . $data['numExecutableBranches']; - $branchesBar = $this->coverageBar($data['branchesExecutedPercent']); - } else { - $branchesLevel = ''; - $branchesNumber = '0' . $numSeparator . '0'; - $branchesBar = ''; - $data['branchesExecutedPercentAsString'] = 'n/a'; - } - $template->setVar(['icon' => $data['icon'] ?? '', 'crap' => $data['crap'] ?? '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'paths_bar' => $pathsBar, 'paths_executed_percent' => $data['pathsExecutedPercentAsString'], 'paths_level' => $pathsLevel, 'paths_number' => $pathsNumber, 'branches_bar' => $branchesBar, 'branches_executed_percent' => $data['branchesExecutedPercentAsString'], 'branches_level' => $branchesLevel, 'branches_number' => $branchesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber]); - return $template->render(); + return $this->methodName; } - protected function setCommonTemplateVariables(Template $template, AbstractNode $node) : void + /** + * @return string + */ + public function getClassName() { - $template->setVar(['id' => $node->id(), 'full_path' => $node->pathAsString(), 'path_to_root' => $this->pathToRoot($node), 'breadcrumbs' => $this->breadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime' => $this->runtimeString(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound]); + return $this->className; } - protected function breadcrumbs(AbstractNode $node) : string +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Doubler; + +class MethodNotFoundException extends \Prophecy\Exception\Doubler\DoubleException +{ + /** + * @var string|object + */ + private $classname; + /** + * @var string + */ + private $methodName; + /** + * @var array + */ + private $arguments; + /** + * @param string $message + * @param string|object $classname + * @param string $methodName + * @param null|Argument\ArgumentsWildcard|array $arguments + */ + public function __construct($message, $classname, $methodName, $arguments = null) { - $breadcrumbs = ''; - $path = $node->pathAsArray(); - $pathToRoot = []; - $max = count($path); - if ($node instanceof FileNode) { - $max--; - } - for ($i = 0; $i < $max; $i++) { - $pathToRoot[] = str_repeat('../', $i); - } - foreach ($path as $step) { - if ($step !== $node) { - $breadcrumbs .= $this->inactiveBreadcrumb($step, array_pop($pathToRoot)); - } else { - $breadcrumbs .= $this->activeBreadcrumb($step); - } - } - return $breadcrumbs; + parent::__construct($message); + $this->classname = $classname; + $this->methodName = $methodName; + $this->arguments = $arguments; } - protected function activeBreadcrumb(AbstractNode $node) : string + public function getClassname() { - $buffer = sprintf(' ' . "\n", $node->name()); - if ($node instanceof DirectoryNode) { - $buffer .= ' ' . "\n"; - } - return $buffer; + return $this->classname; } - protected function inactiveBreadcrumb(AbstractNode $node, string $pathToRoot) : string + public function getMethodName() { - return sprintf(' ' . "\n", $pathToRoot, $node->name()); + return $this->methodName; } - protected function pathToRoot(AbstractNode $node) : string + public function getArguments() { - $id = $node->id(); - $depth = substr_count($id, '/'); - if ($id !== 'index' && $node instanceof DirectoryNode) { - $depth++; - } - return str_repeat('../', $depth); + return $this->arguments; } - protected function coverageBar(float $percent) : string +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Doubler; + +class ReturnByReferenceException extends \Prophecy\Exception\Doubler\DoubleException +{ + private $classname; + private $methodName; + /** + * @param string $message + * @param string $classname + * @param string $methodName + */ + public function __construct($message, $classname, $methodName) { - $level = $this->colorLevel($percent); - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'coverage_bar_branch.html' : 'coverage_bar.html'); - $template = new Template($templateName, '{{', '}}'); - $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); - return $template->render(); + parent::__construct($message); + $this->classname = $classname; + $this->methodName = $methodName; } - protected function colorLevel(float $percent) : string + public function getClassname() { - if ($percent <= $this->lowUpperBound) { - return 'danger'; - } - if ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { - return 'warning'; - } - return 'success'; + return $this->classname; } - private function runtimeString() : string + public function getMethodName() { - $runtime = new Runtime(); - return sprintf('%s %s', $runtime->getVendorUrl(), $runtime->getName(), $runtime->getVersion()); + return $this->methodName; } } - - - - - Code Coverage for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - - - - - - - - - - - -{{items}} - -
       
      Code Coverage
       
      Classes and Traits
      Functions and Methods
      Lines
      -
      -{{lines}} -{{structure}} - -
      - - - - - - -
      -
      - {{percent}}% covered ({{level}}) -
      -
      - - - - - Dashboard for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -

      Classes

      -
      -
      -
      -
      -

      Coverage Distribution

      -
      - -
      -
      -
      -

      Complexity

      -
      - -
      -
      -
      -
      -
      -

      Insufficient Coverage

      -
      - - - - - - - - -{{insufficient_coverage_classes}} - -
      ClassCoverage
      -
      -
      -
      -

      Project Risks

      -
      - - - - - - - - -{{project_risks_classes}} - -
      ClassCRAP
      -
      -
      -
      -
      -
      -

      Methods

      -
      -
      -
      -
      -

      Coverage Distribution

      -
      - -
      -
      -
      -

      Complexity

      -
      - -
      -
      -
      -
      -
      -

      Insufficient Coverage

      -
      - - - - - - - - -{{insufficient_coverage_methods}} - -
      MethodCoverage
      -
      -
      -
      -

      Project Risks

      -
      - - - - - - - - -{{project_risks_methods}} - -
      MethodCRAP
      -
      -
      -
      - -
      - - - - - - -
      -

      Paths

      -

      - Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not - necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. - Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement - always has an else as part of its logical flow even if you didn't write one. -

      -{{paths}} - - - - - Code Coverage for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - - - - - - - - - - - -{{items}} - -
       
      Code Coverage
       
      Lines
      Functions and Methods
      Classes and Traits
      -
      -
      -
      -

      Legend

      -

      - Low: 0% to {{low_upper_bound}}% - Medium: {{low_upper_bound}}% to {{high_lower_bound}}% - High: {{high_lower_bound}}% to 100% -

      -

      - Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. -

      -
      -
      - - -/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */ -!function(){var a={};a.dev=!1,a.tooltip=a.tooltip||{},a.utils=a.utils||{},a.models=a.models||{},a.charts={},a.logs={},a.dom={},a.dispatch=d3.dispatch("render_start","render_end"),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),a.dev&&(a.dispatch.on("render_start",function(){a.logs.startTime=+new Date}),a.dispatch.on("render_end",function(){a.logs.endTime=+new Date,a.logs.totalTime=a.logs.endTime-a.logs.startTime,a.log("total",a.logs.totalTime)})),a.log=function(){if(a.dev&&window.console&&console.log&&console.log.apply)console.log.apply(console,arguments);else if(a.dev&&window.console&&"function"==typeof console.log&&Function.prototype.bind){var b=Function.prototype.bind.call(console.log,console);b.apply(console,arguments)}return arguments[arguments.length-1]},a.deprecated=function(a,b){console&&console.warn&&console.warn("nvd3 warning: `"+a+"` has been deprecated. ",b||"")},a.render=function(b){b=b||1,a.render.active=!0,a.dispatch.render_start();var c=function(){for(var d,e,f=0;b>f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); -x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); -var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] -}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); -var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left -}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) -}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}(); $(function() { - var $window = $(window) - , $top_link = $('#toplink') - , $body = $('body, html') - , offset = $('#code').offset().top - , hidePopover = function ($target) { - $target.data('popover-hover', false); +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prediction; - setTimeout(function () { - if (!$target.data('popover-hover')) { - $target.popover('hide'); - } - }, 300); - }; +use Prophecy\Exception\Exception; +interface PredictionException extends Exception +{ +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prediction; - $window.scroll(function() { - if($window.scrollTop() > offset) { - $top_link.fadeIn(); - } else { - $top_link.fadeOut(); +use Prophecy\Prophecy\MethodProphecy; +class UnexpectedCallsCountException extends \Prophecy\Exception\Prediction\UnexpectedCallsException +{ + private $expectedCount; + public function __construct($message, MethodProphecy $methodProphecy, $count, array $calls) + { + parent::__construct($message, $methodProphecy, $calls); + $this->expectedCount = \intval($count); } - }).scroll(); + public function getExpectedCount() + { + return $this->expectedCount; + } +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prediction; - $target.data('popover-hover', true); +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\Prophecy\MethodProphecyException; +class UnexpectedCallsException extends MethodProphecyException implements \Prophecy\Exception\Prediction\PredictionException +{ + private $calls = array(); + public function __construct($message, MethodProphecy $methodProphecy, array $calls) + { + parent::__construct($message, $methodProphecy); + $this->calls = $calls; + } + public function getCalls() + { + return $this->calls; + } +} + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prophecy; - // show the popover - $container.popover('show'); +use Prophecy\Prophecy\MethodProphecy; +class MethodProphecyException extends \Prophecy\Exception\Prophecy\ObjectProphecyException +{ + private $methodProphecy; + public function __construct($message, MethodProphecy $methodProphecy) + { + parent::__construct($message, $methodProphecy->getObjectProphecy()); + $this->methodProphecy = $methodProphecy; + } + /** + * @return MethodProphecy + */ + public function getMethodProphecy() + { + return $this->methodProphecy; + } +} +n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; -if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}();/* - Copyright (C) Federico Zivolo 2019 - Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). - */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?pe:10===e?se:pe||se}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),le({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=fe({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},le(n,m,$(v)),le(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ge.FLIP:p=[n,i];break;case ge.CLOCKWISE:p=G(n);break;case ge.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=fe({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!me),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=fe({},E,e.attributes),e.styles=fe({},m,e.styles),e.arrowStyles=fe({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ue}); -//# sourceMappingURL=popper.min.js.map -/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};c.jQueryDetection(),e.fn.emulateTransitionEnd=l,e.event.special[c.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(t){if(e(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var h="alert",u=e.fn[h],d=function(){function t(t){this._element=t}var n=t.prototype;return n.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},n.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},n._getRootElement=function(t){var n=c.getSelectorFromElement(t),i=!1;return n&&(i=document.querySelector(n)),i||(i=e(t).closest(".alert")[0]),i},n._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},n._removeElement=function(t){var n=this;if(e(t).removeClass("show"),e(t).hasClass("fade")){var i=c.getTransitionDurationFromElement(t);e(t).one(c.TRANSITION_END,(function(e){return n._destroyElement(t,e)})).emulateTransitionEnd(i)}else this._destroyElement(t)},n._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.alert");o||(o=new t(this),i.data("bs.alert",o)),"close"===n&&o[n](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}}]),t}();e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',d._handleDismiss(new d)),e.fn[h]=d._jQueryInterface,e.fn[h].Constructor=d,e.fn[h].noConflict=function(){return e.fn[h]=u,d._jQueryInterface};var f=e.fn.button,g=function(){function t(t){this._element=t}var n=t.prototype;return n.toggle=function(){var t=!0,n=!0,i=e(this._element).closest('[data-toggle="buttons"]')[0];if(i){var o=this._element.querySelector('input:not([type="hidden"])');if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains("active"))t=!1;else{var s=i.querySelector(".active");s&&e(s).removeClass("active")}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains("active")),e(o).trigger("change")),o.focus(),n=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(n&&this._element.setAttribute("aria-pressed",!this._element.classList.contains("active")),t&&e(this._element).toggleClass("active"))},n.dispose=function(){e.removeData(this._element,"bs.button"),this._element=null},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.button");i||(i=new t(this),e(this).data("bs.button",i)),"toggle"===n&&i[n]()}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}}]),t}();e(document).on("click.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=t.target,i=n;if(e(n).hasClass("btn")||(n=e(n).closest(".btn")[0]),!n||n.hasAttribute("disabled")||n.classList.contains("disabled"))t.preventDefault();else{var o=n.querySelector('input:not([type="hidden"])');if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();"LABEL"===i.tagName&&o&&"checkbox"===o.type&&t.preventDefault(),g._jQueryInterface.call(e(n),"toggle")}})).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=e(t.target).closest(".btn")[0];e(n).toggleClass("focus",/^focus(in)?$/.test(t.type))})),e(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var n=t.prototype;return n.next=function(){this._isSliding||this._slide("next")},n.nextWhenVisible=function(){!document.hidden&&e(this._element).is(":visible")&&"hidden"!==e(this._element).css("visibility")&&this.next()},n.prev=function(){this._isSliding||this._slide("prev")},n.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(c.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},n.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},n.to=function(t){var n=this;this._activeElement=this._element.querySelector(".active.carousel-item");var i=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)e(this._element).one("slid.bs.carousel",(function(){return n.to(t)}));else{if(i===t)return this.pause(),void this.cycle();var o=t>i?"next":"prev";this._slide(o,this._items[t])}},n.dispose=function(){e(this._element).off(p),e.removeData(this._element,"bs.carousel"),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},n._getConfig=function(t){return t=a(a({},v),t),c.typeCheckConfig(m,t,b),t},n._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},n._addEventListeners=function(){var t=this;this._config.keyboard&&e(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&e(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},n._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var n=function(e){t._pointerEvent&&y[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},i=function(e){t._pointerEvent&&y[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};e(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(e(this._element).on("pointerdown.bs.carousel",(function(t){return n(t)})),e(this._element).on("pointerup.bs.carousel",(function(t){return i(t)})),this._element.classList.add("pointer-event")):(e(this._element).on("touchstart.bs.carousel",(function(t){return n(t)})),e(this._element).on("touchmove.bs.carousel",(function(e){return function(e){e.originalEvent.touches&&e.originalEvent.touches.length>1?t.touchDeltaX=0:t.touchDeltaX=e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),e(this._element).on("touchend.bs.carousel",(function(t){return i(t)})))}},n._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},n._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},n._getItemByDirection=function(t,e){var n="next"===t,i="prev"===t,o=this._getItemIndex(e),s=this._items.length-1;if((i&&0===o||n&&o===s)&&!this._config.wrap)return e;var r=(o+("prev"===t?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]},n._triggerSlideEvent=function(t,n){var i=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(".active.carousel-item")),s=e.Event("slide.bs.carousel",{relatedTarget:t,direction:n,from:o,to:i});return e(this._element).trigger(s),s},n._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var n=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));e(n).removeClass("active");var i=this._indicatorsElement.children[this._getItemIndex(t)];i&&e(i).addClass("active")}},n._slide=function(t,n){var i,o,s,r=this,a=this._element.querySelector(".active.carousel-item"),l=this._getItemIndex(a),h=n||a&&this._getItemByDirection(t,a),u=this._getItemIndex(h),d=Boolean(this._interval);if("next"===t?(i="carousel-item-left",o="carousel-item-next",s="left"):(i="carousel-item-right",o="carousel-item-prev",s="right"),h&&e(h).hasClass("active"))this._isSliding=!1;else if(!this._triggerSlideEvent(h,s).isDefaultPrevented()&&a&&h){this._isSliding=!0,d&&this.pause(),this._setActiveIndicatorElement(h);var f=e.Event("slid.bs.carousel",{relatedTarget:h,direction:s,from:l,to:u});if(e(this._element).hasClass("slide")){e(h).addClass(o),c.reflow(h),e(a).addClass(i),e(h).addClass(i);var g=parseInt(h.getAttribute("data-interval"),10);g?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=g):this._config.interval=this._config.defaultInterval||this._config.interval;var m=c.getTransitionDurationFromElement(a);e(a).one(c.TRANSITION_END,(function(){e(h).removeClass(i+" "+o).addClass("active"),e(a).removeClass("active "+o+" "+i),r._isSliding=!1,setTimeout((function(){return e(r._element).trigger(f)}),0)})).emulateTransitionEnd(m)}else e(a).removeClass("active"),e(h).addClass("active"),this._isSliding=!1,e(this._element).trigger(f);d&&this.cycle()}},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.carousel"),o=a(a({},v),e(this).data());"object"==typeof n&&(o=a(a({},o),n));var s="string"==typeof n?n:o.slide;if(i||(i=new t(this,o),e(this).data("bs.carousel",i)),"number"==typeof n)i.to(n);else if("string"==typeof s){if("undefined"==typeof i[s])throw new TypeError('No method named "'+s+'"');i[s]()}else o.interval&&o.ride&&(i.pause(),i.cycle())}))},t._dataApiClickHandler=function(n){var i=c.getSelectorFromElement(this);if(i){var o=e(i)[0];if(o&&e(o).hasClass("carousel")){var s=a(a({},e(o).data()),e(this).data()),r=this.getAttribute("data-slide-to");r&&(s.interval=!1),t._jQueryInterface.call(e(o),s),r&&e(o).data("bs.carousel").to(r),n.preventDefault()}}},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}},{key:"Default",get:function(){return v}}]),t}();e(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",E._dataApiClickHandler),e(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),n=0,i=t.length;n0&&(this._selector=r,this._triggerArray.push(s))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var n=t.prototype;return n.toggle=function(){e(this._element).hasClass("show")?this.hide():this.show()},n.show=function(){var n,i,o=this;if(!this._isTransitioning&&!e(this._element).hasClass("show")&&(this._parent&&0===(n=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains("collapse")}))).length&&(n=null),!(n&&(i=e(n).not(this._selector).data("bs.collapse"))&&i._isTransitioning))){var s=e.Event("show.bs.collapse");if(e(this._element).trigger(s),!s.isDefaultPrevented()){n&&(t._jQueryInterface.call(e(n).not(this._selector),"hide"),i||e(n).data("bs.collapse",null));var r=this._getDimension();e(this._element).removeClass("collapse").addClass("collapsing"),this._element.style[r]=0,this._triggerArray.length&&e(this._triggerArray).removeClass("collapsed").attr("aria-expanded",!0),this.setTransitioning(!0);var a="scroll"+(r[0].toUpperCase()+r.slice(1)),l=c.getTransitionDurationFromElement(this._element);e(this._element).one(c.TRANSITION_END,(function(){e(o._element).removeClass("collapsing").addClass("collapse show"),o._element.style[r]="",o.setTransitioning(!1),e(o._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(l),this._element.style[r]=this._element[a]+"px"}}},n.hide=function(){var t=this;if(!this._isTransitioning&&e(this._element).hasClass("show")){var n=e.Event("hide.bs.collapse");if(e(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",c.reflow(this._element),e(this._element).addClass("collapsing").removeClass("collapse show");var o=this._triggerArray.length;if(o>0)for(var s=0;s0},i._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=a(a({},e.offsets),t._config.offset(e.offsets,t._element)||{}),e}:e.offset=this._config.offset,e},i._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),a(a({},t),this._config.popperConfig)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.dropdown");if(i||(i=new t(this,"object"==typeof n?n:null),e(this).data("bs.dropdown",i)),"string"==typeof n){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},t._clearMenus=function(n){if(!n||3!==n.which&&("keyup"!==n.type||9===n.which))for(var i=[].slice.call(document.querySelectorAll('[data-toggle="dropdown"]')),o=0,s=i.length;o0&&r--,40===n.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},n._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},n._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
      ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:F,popperConfig:null},Y={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},$=function(){function t(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var i=t.prototype;return i.enable=function(){this._isEnabled=!0},i.disable=function(){this._isEnabled=!1},i.toggleEnabled=function(){this._isEnabled=!this._isEnabled},i.toggle=function(t){if(this._isEnabled)if(t){var n=this.constructor.DATA_KEY,i=e(t.currentTarget).data(n);i||(i=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(e(this.getTipElement()).hasClass("show"))return void this._leave(null,this);this._enter(null,this)}},i.dispose=function(){clearTimeout(this._timeout),e.removeData(this.element,this.constructor.DATA_KEY),e(this.element).off(this.constructor.EVENT_KEY),e(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&e(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},i.show=function(){var t=this;if("none"===e(this.element).css("display"))throw new Error("Please use show on visible elements");var i=e.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){e(this.element).trigger(i);var o=c.findShadowRoot(this.element),s=e.contains(null!==o?o:this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),a=c.getUID(this.constructor.NAME);r.setAttribute("id",a),this.element.setAttribute("aria-describedby",a),this.setContent(),this.config.animation&&e(r).addClass("fade");var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var u=this._getContainer();e(r).data(this.constructor.DATA_KEY,this),e.contains(this.element.ownerDocument.documentElement,this.tip)||e(r).appendTo(u),e(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,this._getPopperConfig(h)),e(r).addClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().on("mouseover",null,e.noop);var d=function(){t.config.animation&&t._fixTransition();var n=t._hoverState;t._hoverState=null,e(t.element).trigger(t.constructor.Event.SHOWN),"out"===n&&t._leave(null,t)};if(e(this.tip).hasClass("fade")){var f=c.getTransitionDurationFromElement(this.tip);e(this.tip).one(c.TRANSITION_END,d).emulateTransitionEnd(f)}else d()}},i.hide=function(t){var n=this,i=this.getTipElement(),o=e.Event(this.constructor.Event.HIDE),s=function(){"show"!==n._hoverState&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),e(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()};if(e(this.element).trigger(o),!o.isDefaultPrevented()){if(e(i).removeClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().off("mouseover",null,e.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,e(this.tip).hasClass("fade")){var r=c.getTransitionDurationFromElement(i);e(i).one(c.TRANSITION_END,s).emulateTransitionEnd(r)}else s();this._hoverState=""}},i.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},i.isWithContent=function(){return Boolean(this.getTitle())},i.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-tooltip-"+t)},i.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},i.setContent=function(){var t=this.getTipElement();this.setElementContent(e(t.querySelectorAll(".tooltip-inner")),this.getTitle()),e(t).removeClass("fade show")},i.setElementContent=function(t,n){"object"!=typeof n||!n.nodeType&&!n.jquery?this.config.html?(this.config.sanitize&&(n=H(n,this.config.whiteList,this.config.sanitizeFn)),t.html(n)):t.text(n):this.config.html?e(n).parent().is(t)||t.empty().append(n):t.text(e(n).text())},i.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},i._getPopperConfig=function(t){var e=this;return a(a({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),this.config.popperConfig)},i._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=a(a({},e.offsets),t.config.offset(e.offsets,t.element)||{}),e}:e.offset=this.config.offset,e},i._getContainer=function(){return!1===this.config.container?document.body:c.isElement(this.config.container)?e(this.config.container):e(document).find(this.config.container)},i._getAttachment=function(t){return K[t.toUpperCase()]},i._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(n){if("click"===n)e(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==n){var i="hover"===n?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o="hover"===n?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;e(t.element).on(i,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},e(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=a(a({},this.config),{},{trigger:"manual",selector:""}):this._fixTitle()},i._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},i._enter=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e(n.getTipElement()).hasClass("show")||"show"===n._hoverState?n._hoverState="show":(clearTimeout(n._timeout),n._hoverState="show",n.config.delay&&n.config.delay.show?n._timeout=setTimeout((function(){"show"===n._hoverState&&n.show()}),n.config.delay.show):n.show())},i._leave=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusout"===t.type?"focus":"hover"]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState="out",n.config.delay&&n.config.delay.hide?n._timeout=setTimeout((function(){"out"===n._hoverState&&n.hide()}),n.config.delay.hide):n.hide())},i._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},i._getConfig=function(t){var n=e(this.element).data();return Object.keys(n).forEach((function(t){-1!==V.indexOf(t)&&delete n[t]})),"number"==typeof(t=a(a(a({},this.constructor.Default),n),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),c.typeCheckConfig(U,t,this.constructor.DefaultType),t.sanitize&&(t.template=H(t.template,t.whiteList,t.sanitizeFn)),t},i._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},i._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(W);null!==n&&n.length&&t.removeClass(n.join(""))},i._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},i._fixTransition=function(){var t=this.getTipElement(),n=this.config.animation;null===t.getAttribute("x-placement")&&(e(t).removeClass("fade"),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.tooltip"),o="object"==typeof n&&n;if((i||!/dispose|hide/.test(n))&&(i||(i=new t(this,o),e(this).data("bs.tooltip",i)),"string"==typeof n)){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}},{key:"Default",get:function(){return X}},{key:"NAME",get:function(){return U}},{key:"DATA_KEY",get:function(){return"bs.tooltip"}},{key:"Event",get:function(){return Y}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return z}}]),t}();e.fn[U]=$._jQueryInterface,e.fn[U].Constructor=$,e.fn[U].noConflict=function(){return e.fn[U]=M,$._jQueryInterface};var J="popover",G=e.fn[J],Z=new RegExp("(^|\\s)bs-popover\\S+","g"),tt=a(a({},$.Default),{},{placement:"right",trigger:"click",content:"",template:''}),et=a(a({},$.DefaultType),{},{content:"(string|element|function)"}),nt={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},it=function(t){var n,i;function s(){return t.apply(this,arguments)||this}i=t,(n=s).prototype=Object.create(i.prototype),n.prototype.constructor=n,n.__proto__=i;var r=s.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-popover-"+t)},r.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},r.setContent=function(){var t=e(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(t.find(".popover-body"),n),t.removeClass("fade show")},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(Z);null!==n&&n.length>0&&t.removeClass(n.join(""))},s._jQueryInterface=function(t){return this.each((function(){var n=e(this).data("bs.popover"),i="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new s(this,i),e(this).data("bs.popover",n)),"string"==typeof t)){if("undefined"==typeof n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},o(s,null,[{key:"VERSION",get:function(){return"4.5.0"}},{key:"Default",get:function(){return tt}},{key:"NAME",get:function(){return J}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return nt}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return et}}]),s}($);e.fn[J]=it._jQueryInterface,e.fn[J].Constructor=it,e.fn[J].noConflict=function(){return e.fn[J]=G,it._jQueryInterface};var ot="scrollspy",st=e.fn[ot],rt={offset:10,method:"auto",target:""},at={offset:"number",method:"string",target:"(string|element)"},lt=function(){function t(t,n){var i=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(n),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,e(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return i._process(t)})),this.refresh(),this._process()}var n=t.prototype;return n.refresh=function(){var t=this,n=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?n:this._config.method,o="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var n,s=c.getSelectorFromElement(t);if(s&&(n=document.querySelector(s)),n){var r=n.getBoundingClientRect();if(r.width||r.height)return[e(n)[i]().top+o,s]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},n.dispose=function(){e.removeData(this._element,"bs.scrollspy"),e(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},n._getConfig=function(t){if("string"!=typeof(t=a(a({},rt),"object"==typeof t&&t?t:{})).target&&c.isElement(t.target)){var n=e(t.target).attr("id");n||(n=c.getUID(ot),e(t.target).attr("id",n)),t.target="#"+n}return c.typeCheckConfig(ot,t,at),t},n._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},n._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},n._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},n._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active":".active";i=(i=e.makeArray(e(o).find(r)))[i.length-1]}var a=e.Event("hide.bs.tab",{relatedTarget:this._element}),l=e.Event("show.bs.tab",{relatedTarget:i});if(i&&e(i).trigger(a),e(this._element).trigger(l),!l.isDefaultPrevented()&&!a.isDefaultPrevented()){s&&(n=document.querySelector(s)),this._activate(this._element,o);var h=function(){var n=e.Event("hidden.bs.tab",{relatedTarget:t._element}),o=e.Event("shown.bs.tab",{relatedTarget:i});e(i).trigger(n),e(t._element).trigger(o)};n?this._activate(n,n.parentNode,h):h()}}},n.dispose=function(){e.removeData(this._element,"bs.tab"),this._element=null},n._activate=function(t,n,i){var o=this,s=(!n||"UL"!==n.nodeName&&"OL"!==n.nodeName?e(n).children(".active"):e(n).find("> li > .active"))[0],r=i&&s&&e(s).hasClass("fade"),a=function(){return o._transitionComplete(t,s,i)};if(s&&r){var l=c.getTransitionDurationFromElement(s);e(s).removeClass("show").one(c.TRANSITION_END,a).emulateTransitionEnd(l)}else a()},n._transitionComplete=function(t,n,i){if(n){e(n).removeClass("active");var o=e(n.parentNode).find("> .dropdown-menu .active")[0];o&&e(o).removeClass("active"),"tab"===n.getAttribute("role")&&n.setAttribute("aria-selected",!1)}if(e(t).addClass("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),c.reflow(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&e(t.parentNode).hasClass("dropdown-menu")){var s=e(t).closest(".dropdown")[0];if(s){var r=[].slice.call(s.querySelectorAll(".dropdown-toggle"));e(r).addClass("active")}t.setAttribute("aria-expanded",!0)}i&&i()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.tab");if(o||(o=new t(this),i.data("bs.tab",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}}]),t}();e(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ht._jQueryInterface.call(e(this),"show")})),e.fn.tab=ht._jQueryInterface,e.fn.tab.Constructor=ht,e.fn.tab.noConflict=function(){return e.fn.tab=ct,ht._jQueryInterface};var ut=e.fn.toast,dt={animation:"boolean",autohide:"boolean",delay:"number"},ft={animation:!0,autohide:!0,delay:500},gt=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var n=t.prototype;return n.show=function(){var t=this,n=e.Event("show.bs.toast");if(e(this._element).trigger(n),!n.isDefaultPrevented()){this._config.animation&&this._element.classList.add("fade");var i=function(){t._element.classList.remove("showing"),t._element.classList.add("show"),e(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove("hide"),c.reflow(this._element),this._element.classList.add("showing"),this._config.animation){var o=c.getTransitionDurationFromElement(this._element);e(this._element).one(c.TRANSITION_END,i).emulateTransitionEnd(o)}else i()}},n.hide=function(){if(this._element.classList.contains("show")){var t=e.Event("hide.bs.toast");e(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},n.dispose=function(){clearTimeout(this._timeout),this._timeout=null,this._element.classList.contains("show")&&this._element.classList.remove("show"),e(this._element).off("click.dismiss.bs.toast"),e.removeData(this._element,"bs.toast"),this._element=null,this._config=null},n._getConfig=function(t){return t=a(a(a({},ft),e(this._element).data()),"object"==typeof t&&t?t:{}),c.typeCheckConfig("toast",t,this.constructor.DefaultType),t},n._setListeners=function(){var t=this;e(this._element).on("click.dismiss.bs.toast",'[data-dismiss="toast"]',(function(){return t.hide()}))},n._close=function(){var t=this,n=function(){t._element.classList.add("hide"),e(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){var i=c.getTransitionDurationFromElement(this._element);e(this._element).one(c.TRANSITION_END,n).emulateTransitionEnd(i)}else n()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.toast");if(o||(o=new t(this,"object"==typeof n&&n),i.data("bs.toast",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n](this)}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.0"}},{key:"DefaultType",get:function(){return dt}},{key:"Default",get:function(){return ft}}]),t}();e.fn.toast=gt._jQueryInterface,e.fn.toast.Constructor=gt,e.fn.toast.noConflict=function(){return e.fn.toast=ut,gt._jQueryInterface},t.Alert=d,t.Button=g,t.Carousel=E,t.Collapse=D,t.Dropdown=j,t.Modal=R,t.Popover=it,t.Scrollspy=lt,t.Tab=ht,t.Toast=gt,t.Tooltip=$,t.Util=c,Object.defineProperty(t,"__esModule",{value:!0})})); -//# sourceMappingURL=bootstrap.min.js.map - {{name}} - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{crap}} - {{paths_bar}} -
      {{paths_executed_percent}}
      -
      {{paths_number}}
      - {{branches_bar}} -
      {{branches_executed_percent}}
      -
      {{branches_number}}
      - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prophecy; - - - - - Code Coverage for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - - - - - - - - - - - - - -{{items}} - -
       
      Code Coverage
       
      Classes and Traits
      Functions and Methods
      Paths
      Branches
      Lines
      -
      -{{lines}} -{{structure}} - -
      - - - - - - - - {{name}} - {{classes_bar}} -
      {{classes_tested_percent}}
      -
      {{classes_number}}
      - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{crap}} - {{paths_bar}} -
      {{paths_executed_percent}}
      -
      {{paths_number}}
      - {{branches_bar}} -
      {{branches_executed_percent}}
      -
      {{branches_number}}
      - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - +use Prophecy\Prophecy\ObjectProphecy; +class ObjectProphecyException extends \RuntimeException implements \Prophecy\Exception\Prophecy\ProphecyException +{ + private $objectProphecy; + public function __construct($message, ObjectProphecy $objectProphecy) + { + parent::__construct($message); + $this->objectProphecy = $objectProphecy; + } + /** + * @return ObjectProphecy + */ + public function getObjectProphecy() + { + return $this->objectProphecy; + } +} + - -{{lines}} - - - {{lineNumber}}{{lineContent}} - - {{icon}}{{name}} - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{classes_bar}} -
      {{classes_tested_percent}}
      -
      {{classes_number}}
      - +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Exception\Prophecy; -
      -
      - {{percent}}% covered ({{level}}) -
      -
      - - {{name}} - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{crap}} - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - +use Prophecy\Exception\Exception; +interface ProphecyException extends Exception +{ +} +code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */body { - padding-top: 10px; +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\PhpDocumentor; + +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; +/** + * @author Théo FIDRY + * + * @internal + */ +final class ClassAndInterfaceTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface +{ + private $classRetriever; + public function __construct(\Prophecy\PhpDocumentor\MethodTagRetrieverInterface $classRetriever = null) + { + if (null !== $classRetriever) { + $this->classRetriever = $classRetriever; + return; + } + $this->classRetriever = \class_exists('PHPUnit\\phpDocumentor\\Reflection\\DocBlockFactory') && \class_exists('PHPUnit\\phpDocumentor\\Reflection\\Types\\ContextFactory') ? new \Prophecy\PhpDocumentor\ClassTagRetriever() : new \Prophecy\PhpDocumentor\LegacyClassTagRetriever(); + } + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + return \array_merge($this->classRetriever->getTagList($reflectionClass), $this->getInterfacesTagList($reflectionClass)); + } + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + private function getInterfacesTagList(\ReflectionClass $reflectionClass) + { + $interfaces = $reflectionClass->getInterfaces(); + $tagList = array(); + foreach ($interfaces as $interface) { + $tagList = \array_merge($tagList, $this->classRetriever->getTagList($interface)); + } + return $tagList; + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\PhpDocumentor; -.octicon { - margin-right:.25em; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; +use PHPUnit\phpDocumentor\Reflection\DocBlockFactory; +use PHPUnit\phpDocumentor\Reflection\Types\ContextFactory; +/** + * @author Théo FIDRY + * + * @internal + */ +final class ClassTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface +{ + private $docBlockFactory; + private $contextFactory; + public function __construct() + { + $this->docBlockFactory = DocBlockFactory::createInstance(); + $this->contextFactory = new ContextFactory(); + } + /** + * @param \ReflectionClass $reflectionClass + * + * @return Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + try { + $phpdoc = $this->docBlockFactory->create($reflectionClass, $this->contextFactory->createFromReflector($reflectionClass)); + $methods = array(); + foreach ($phpdoc->getTagsByName('method') as $tag) { + if ($tag instanceof Method) { + $methods[] = $tag; + } + } + return $methods; + } catch (\InvalidArgumentException $e) { + return array(); + } + } } +thead>tr>td { - border-bottom-width: 1px; -} +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\PhpDocumentor; -.table tbody>tr>td, .table thead>tr>td { - padding-top: 3px; - padding-bottom: 3px; +use PHPUnit\phpDocumentor\Reflection\DocBlock; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; +/** + * @author Théo FIDRY + * + * @internal + */ +final class LegacyClassTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface +{ + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + $phpdoc = new DocBlock($reflectionClass->getDocComment()); + return $phpdoc->getTagsByName('method'); + } } +tr>td { - padding-top: 0; - padding-bottom: 0; -} +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\PhpDocumentor; -.table .progress { - margin-bottom: inherit; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; +use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; +/** + * @author Théo FIDRY + * + * @internal + */ +interface MethodTagRetrieverInterface +{ + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass); } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prediction; -.table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { - background-color: #dff0d8; +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Argument\Token\AnyValuesToken; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\NoCallsException; +/** + * Call prediction. + * + * @author Konstantin Kudryashov + */ +class CallPrediction implements \Prophecy\Prediction\PredictionInterface +{ + private $util; + /** + * Initializes prediction. + * + * @param StringUtil $util + */ + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil(); + } + /** + * Tests that there was at least one call. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\NoCallsException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if (\count($calls)) { + return; + } + $methodCalls = $object->findProphecyMethodCalls($method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken()))); + if (\count($methodCalls)) { + throw new NoCallsException(\sprintf("No calls have been made that match:\n" . " %s->%s(%s)\n" . "but expected at least one.\n" . "Recorded `%s(...)` calls:\n%s", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls)), $method); + } + throw new NoCallsException(\sprintf("No calls have been made that match:\n" . " %s->%s(%s)\n" . "but expected at least one.", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard()), $method); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prediction; -.table tbody tr.covered-by-small-tests, li.covered-by-small-tests { - background-color: #99cb84; +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Argument\Token\AnyValuesToken; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\UnexpectedCallsCountException; +/** + * Prediction interface. + * Predictions are logical test blocks, tied to `should...` keyword. + * + * @author Konstantin Kudryashov + */ +class CallTimesPrediction implements \Prophecy\Prediction\PredictionInterface +{ + private $times; + private $util; + /** + * Initializes prediction. + * + * @param int $times + * @param StringUtil $util + */ + public function __construct($times, StringUtil $util = null) + { + $this->times = \intval($times); + $this->util = $util ?: new StringUtil(); + } + /** + * Tests that there was exact amount of calls made. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\UnexpectedCallsCountException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if ($this->times == \count($calls)) { + return; + } + $methodCalls = $object->findProphecyMethodCalls($method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken()))); + if (\count($calls)) { + $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but %d were made:\n%s", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), \count($calls), $this->util->stringifyCalls($calls)); + } elseif (\count($methodCalls)) { + $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but none were made.\n" . "Recorded `%s(...)` calls:\n%s", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls)); + } else { + $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but none were made.", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard()); + } + throw new UnexpectedCallsCountException($message, $method, $this->times, $calls); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prediction; -.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { - background-color: #fcf8e3; +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use Closure; +use ReflectionFunction; +/** + * Callback prediction. + * + * @author Konstantin Kudryashov + */ +class CallbackPrediction implements \Prophecy\Prediction\PredictionInterface +{ + private $callback; + /** + * Initializes callback prediction. + * + * @param callable $callback Custom callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) + { + if (!\is_callable($callback)) { + throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackPrediction, but got %s.', \gettype($callback))); + } + $this->callback = $callback; + } + /** + * Executes preset callback. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + $callback = $this->callback; + if ($callback instanceof Closure && \method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { + $callback = Closure::bind($callback, $object); + } + \call_user_func($callback, $calls, $object, $method); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prediction; -td.big { - width: 117px; +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\UnexpectedCallsException; +/** + * No calls prediction. + * + * @author Konstantin Kudryashov + */ +class NoCallsPrediction implements \Prophecy\Prediction\PredictionInterface +{ + private $util; + /** + * Initializes prediction. + * + * @param null|StringUtil $util + */ + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil(); + } + /** + * Tests that there were no calls made. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\UnexpectedCallsException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if (!\count($calls)) { + return; + } + $verb = \count($calls) === 1 ? 'was' : 'were'; + throw new UnexpectedCallsException(\sprintf("No calls expected that match:\n" . " %s->%s(%s)\n" . "but %d %s made:\n%s", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), \count($calls), $verb, $this->util->stringifyCalls($calls)), $method, $calls); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prediction; -td.codeLine { - font-family: "Source Code Pro", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - white-space: pre-wrap; +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +/** + * Prediction interface. + * Predictions are logical test blocks, tied to `should...` keyword. + * + * @author Konstantin Kudryashov + */ +interface PredictionInterface +{ + /** + * Tests that double fulfilled prediction. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws object + * @return void + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method); } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Promise; -td span.default { - color: #2e3436; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use Closure; +use ReflectionFunction; +/** + * Callback promise. + * + * @author Konstantin Kudryashov + */ +class CallbackPromise implements \Prophecy\Promise\PromiseInterface +{ + private $callback; + /** + * Initializes callback promise. + * + * @param callable $callback Custom callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) + { + if (!\is_callable($callback)) { + throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackPromise, but got %s.', \gettype($callback))); + } + $this->callback = $callback; + } + /** + * Evaluates promise callback. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + $callback = $this->callback; + if ($callback instanceof Closure && \method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { + $callback = Closure::bind($callback, $object); + } + return \call_user_func($callback, $args, $object, $method); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Promise; -td span.keyword { - color: #2e3436; - font-weight: bold; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +/** + * Promise interface. + * Promises are logical blocks, tied to `will...` keyword. + * + * @author Konstantin Kudryashov + */ +interface PromiseInterface +{ + /** + * Evaluates promise. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method); } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Promise; + +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +/** + * Return argument promise. + * + * @author Konstantin Kudryashov + */ +class ReturnArgumentPromise implements \Prophecy\Promise\PromiseInterface +{ + /** + * @var int + */ + private $index; + /** + * Initializes callback promise. + * + * @param int $index The zero-indexed number of the argument to return + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($index = 0) + { + if (!\is_int($index) || $index < 0) { + throw new InvalidArgumentException(\sprintf('Zero-based index expected as argument to ReturnArgumentPromise, but got %s.', $index)); + } + $this->index = $index; + } + /** + * Returns nth argument if has one, null otherwise. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return null|mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + return \count($args) > $this->index ? $args[$this->index] : null; + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Promise; -#classCoverageDistribution, #classComplexity { - height: 200px; - width: 475px; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +/** + * Return promise. + * + * @author Konstantin Kudryashov + */ +class ReturnPromise implements \Prophecy\Promise\PromiseInterface +{ + private $returnValues = array(); + /** + * Initializes promise. + * + * @param array $returnValues Array of values + */ + public function __construct(array $returnValues) + { + $this->returnValues = $returnValues; + } + /** + * Returns saved values one by one until last one, then continuously returns last value. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + $value = \array_shift($this->returnValues); + if (!\count($this->returnValues)) { + $this->returnValues[] = $value; + } + return $value; + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Promise; -svg text { - font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; - font-size: 11px; - color: #666; - fill: #666; +use PHPUnit\Doctrine\Instantiator\Instantiator; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use ReflectionClass; +/** + * Throw promise. + * + * @author Konstantin Kudryashov + */ +class ThrowPromise implements \Prophecy\Promise\PromiseInterface +{ + private $exception; + /** + * @var \Doctrine\Instantiator\Instantiator + */ + private $instantiator; + /** + * Initializes promise. + * + * @param string|\Exception|\Throwable $exception Exception class name or instance + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($exception) + { + if (\is_string($exception)) { + if (!\class_exists($exception) && !\interface_exists($exception) || !$this->isAValidThrowable($exception)) { + throw new InvalidArgumentException(\sprintf('Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', $exception)); + } + } elseif (!$exception instanceof \Exception && !$exception instanceof \Throwable) { + throw new InvalidArgumentException(\sprintf('Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', \is_object($exception) ? \get_class($exception) : \gettype($exception))); + } + $this->exception = $exception; + } + /** + * Throws predefined exception. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws object + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + if (\is_string($this->exception)) { + $classname = $this->exception; + $reflection = new ReflectionClass($classname); + $constructor = $reflection->getConstructor(); + if ($constructor->isPublic() && 0 == $constructor->getNumberOfRequiredParameters()) { + throw $reflection->newInstance(); + } + if (!$this->instantiator) { + $this->instantiator = new Instantiator(); + } + throw $this->instantiator->instantiate($classname); + } + throw $this->exception; + } + /** + * @param string $exception + * + * @return bool + */ + private function isAValidThrowable($exception) + { + return \is_a($exception, 'Exception', \true) || \is_a($exception, 'Throwable', \true); + } } + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Prophecy\Prophecy; -table + .structure-heading { - border-top: 1px solid lightgrey; - padding-top: 0.5em; -} -.octicon { - display: inline-block; - vertical-align: text-top; - fill: currentColor; +use Prophecy\Argument; +use Prophecy\Prophet; +use Prophecy\Promise; +use Prophecy\Prediction; +use Prophecy\Exception\Doubler\MethodNotFoundException; +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Prophecy\MethodProphecyException; +use ReflectionNamedType; +use ReflectionType; +use ReflectionUnionType; +/** + * Method prophecy. + * + * @author Konstantin Kudryashov + */ +class MethodProphecy +{ + private $objectProphecy; + private $methodName; + private $argumentsWildcard; + private $promise; + private $prediction; + private $checkedPredictions = array(); + private $bound = \false; + private $voidReturnType = \false; + /** + * Initializes method prophecy. + * + * @param ObjectProphecy $objectProphecy + * @param string $methodName + * @param null|Argument\ArgumentsWildcard|array $arguments + * + * @throws \Prophecy\Exception\Doubler\MethodNotFoundException If method not found + */ + public function __construct(\Prophecy\Prophecy\ObjectProphecy $objectProphecy, $methodName, $arguments = null) + { + $double = $objectProphecy->reveal(); + if (!\method_exists($double, $methodName)) { + throw new MethodNotFoundException(\sprintf('Method `%s::%s()` is not defined.', \get_class($double), $methodName), \get_class($double), $methodName, $arguments); + } + $this->objectProphecy = $objectProphecy; + $this->methodName = $methodName; + $reflectedMethod = new \ReflectionMethod($double, $methodName); + if ($reflectedMethod->isFinal()) { + throw new MethodProphecyException(\sprintf("Can not add prophecy for a method `%s::%s()`\n" . "as it is a final method.", \get_class($double), $methodName), $this); + } + if (null !== $arguments) { + $this->withArguments($arguments); + } + $hasTentativeReturnType = \method_exists($reflectedMethod, 'hasTentativeReturnType') && $reflectedMethod->hasTentativeReturnType(); + if (\true === $reflectedMethod->hasReturnType() || $hasTentativeReturnType) { + if ($hasTentativeReturnType) { + $reflectionType = $reflectedMethod->getTentativeReturnType(); + } else { + $reflectionType = $reflectedMethod->getReturnType(); + } + if ($reflectionType instanceof ReflectionNamedType) { + $types = [$reflectionType]; + } elseif ($reflectionType instanceof ReflectionUnionType) { + $types = $reflectionType->getTypes(); + } + $types = \array_map(function (ReflectionType $type) { + return $type->getName(); + }, $types); + \usort($types, static function (string $type1, string $type2) { + // null is lowest priority + if ($type2 == 'null') { + return -1; + } elseif ($type1 == 'null') { + return 1; + } + // objects are higher priority than scalars + $isObject = static function ($type) { + return \class_exists($type) || \interface_exists($type); + }; + if ($isObject($type1) && !$isObject($type2)) { + return -1; + } elseif (!$isObject($type1) && $isObject($type2)) { + return 1; + } + // don't sort both-scalars or both-objects + return 0; + }); + $defaultType = $types[0]; + if ('void' === $defaultType) { + $this->voidReturnType = \true; + } + $this->will(function () use($defaultType) { + switch ($defaultType) { + case 'void': + return; + case 'string': + return ''; + case 'float': + return 0.0; + case 'int': + return 0; + case 'bool': + return \false; + case 'array': + return array(); + case 'callable': + case 'Closure': + return function () { + }; + case 'Traversable': + case 'Generator': + return (function () { + yield; + })(); + default: + $prophet = new Prophet(); + return $prophet->prophesize($defaultType)->reveal(); + } + }); + } + } + /** + * Sets argument wildcard. + * + * @param array|Argument\ArgumentsWildcard $arguments + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function withArguments($arguments) + { + if (\is_array($arguments)) { + $arguments = new Argument\ArgumentsWildcard($arguments); + } + if (!$arguments instanceof Argument\ArgumentsWildcard) { + throw new InvalidArgumentException(\sprintf("Either an array or an instance of ArgumentsWildcard expected as\n" . 'a `MethodProphecy::withArguments()` argument, but got %s.', \gettype($arguments))); + } + $this->argumentsWildcard = $arguments; + return $this; + } + /** + * Sets custom promise to the prophecy. + * + * @param callable|Promise\PromiseInterface $promise + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function will($promise) + { + if (\is_callable($promise)) { + $promise = new Promise\CallbackPromise($promise); + } + if (!$promise instanceof Promise\PromiseInterface) { + throw new InvalidArgumentException(\sprintf('Expected callable or instance of PromiseInterface, but got %s.', \gettype($promise))); + } + $this->bindToObjectProphecy(); + $this->promise = $promise; + return $this; + } + /** + * Sets return promise to the prophecy. + * + * @see \Prophecy\Promise\ReturnPromise + * + * @return $this + */ + public function willReturn() + { + if ($this->voidReturnType) { + throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type, and so cannot return anything", $this); + } + return $this->will(new Promise\ReturnPromise(\func_get_args())); + } + /** + * @param array $items + * @param mixed $return + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function willYield($items, $return = null) + { + if ($this->voidReturnType) { + throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type, and so cannot yield anything", $this); + } + if (!\is_array($items)) { + throw new InvalidArgumentException(\sprintf('Expected array, but got %s.', \gettype($items))); + } + $generator = function () use($items, $return) { + yield from $items; + return $return; + }; + return $this->will($generator); + } + /** + * Sets return argument promise to the prophecy. + * + * @param int $index The zero-indexed number of the argument to return + * + * @see \Prophecy\Promise\ReturnArgumentPromise + * + * @return $this + */ + public function willReturnArgument($index = 0) + { + if ($this->voidReturnType) { + throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type", $this); + } + return $this->will(new Promise\ReturnArgumentPromise($index)); + } + /** + * Sets throw promise to the prophecy. + * + * @see \Prophecy\Promise\ThrowPromise + * + * @param string|\Exception $exception Exception class or instance + * + * @return $this + */ + public function willThrow($exception) + { + return $this->will(new Promise\ThrowPromise($exception)); + } + /** + * Sets custom prediction to the prophecy. + * + * @param callable|Prediction\PredictionInterface $prediction + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function should($prediction) + { + if (\is_callable($prediction)) { + $prediction = new Prediction\CallbackPrediction($prediction); + } + if (!$prediction instanceof Prediction\PredictionInterface) { + throw new InvalidArgumentException(\sprintf('Expected callable or instance of PredictionInterface, but got %s.', \gettype($prediction))); + } + $this->bindToObjectProphecy(); + $this->prediction = $prediction; + return $this; + } + /** + * Sets call prediction to the prophecy. + * + * @see \Prophecy\Prediction\CallPrediction + * + * @return $this + */ + public function shouldBeCalled() + { + return $this->should(new Prediction\CallPrediction()); + } + /** + * Sets no calls prediction to the prophecy. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * + * @return $this + */ + public function shouldNotBeCalled() + { + return $this->should(new Prediction\NoCallsPrediction()); + } + /** + * Sets call times prediction to the prophecy. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @param $count + * + * @return $this + */ + public function shouldBeCalledTimes($count) + { + return $this->should(new Prediction\CallTimesPrediction($count)); + } + /** + * Sets call times prediction to the prophecy. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @return $this + */ + public function shouldBeCalledOnce() + { + return $this->shouldBeCalledTimes(1); + } + /** + * Checks provided prediction immediately. + * + * @param callable|Prediction\PredictionInterface $prediction + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function shouldHave($prediction) + { + if (\is_callable($prediction)) { + $prediction = new Prediction\CallbackPrediction($prediction); + } + if (!$prediction instanceof Prediction\PredictionInterface) { + throw new InvalidArgumentException(\sprintf('Expected callable or instance of PredictionInterface, but got %s.', \gettype($prediction))); + } + if (null === $this->promise && !$this->voidReturnType) { + $this->willReturn(); + } + $calls = $this->getObjectProphecy()->findProphecyMethodCalls($this->getMethodName(), $this->getArgumentsWildcard()); + try { + $prediction->check($calls, $this->getObjectProphecy(), $this); + $this->checkedPredictions[] = $prediction; + } catch (\Exception $e) { + $this->checkedPredictions[] = $prediction; + throw $e; + } + return $this; + } + /** + * Checks call prediction. + * + * @see \Prophecy\Prediction\CallPrediction + * + * @return $this + */ + public function shouldHaveBeenCalled() + { + return $this->shouldHave(new Prediction\CallPrediction()); + } + /** + * Checks no calls prediction. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * + * @return $this + */ + public function shouldNotHaveBeenCalled() + { + return $this->shouldHave(new Prediction\NoCallsPrediction()); + } + /** + * Checks no calls prediction. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * @deprecated + * + * @return $this + */ + public function shouldNotBeenCalled() + { + return $this->shouldNotHaveBeenCalled(); + } + /** + * Checks call times prediction. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @param int $count + * + * @return $this + */ + public function shouldHaveBeenCalledTimes($count) + { + return $this->shouldHave(new Prediction\CallTimesPrediction($count)); + } + /** + * Checks call times prediction. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @return $this + */ + public function shouldHaveBeenCalledOnce() + { + return $this->shouldHaveBeenCalledTimes(1); + } + /** + * Checks currently registered [with should(...)] prediction. + */ + public function checkPrediction() + { + if (null === $this->prediction) { + return; + } + $this->shouldHave($this->prediction); + } + /** + * Returns currently registered promise. + * + * @return null|Promise\PromiseInterface + */ + public function getPromise() + { + return $this->promise; + } + /** + * Returns currently registered prediction. + * + * @return null|Prediction\PredictionInterface + */ + public function getPrediction() + { + return $this->prediction; + } + /** + * Returns predictions that were checked on this object. + * + * @return Prediction\PredictionInterface[] + */ + public function getCheckedPredictions() + { + return $this->checkedPredictions; + } + /** + * Returns object prophecy this method prophecy is tied to. + * + * @return ObjectProphecy + */ + public function getObjectProphecy() + { + return $this->objectProphecy; + } + /** + * Returns method name. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + /** + * Returns arguments wildcard. + * + * @return Argument\ArgumentsWildcard + */ + public function getArgumentsWildcard() + { + return $this->argumentsWildcard; + } + /** + * @return bool + */ + public function hasReturnVoid() + { + return $this->voidReturnType; + } + private function bindToObjectProphecy() + { + if ($this->bound) { + return; + } + $this->getObjectProphecy()->addMethodProphecy($this); + $this->bound = \true; + } } -.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} - - - - Code Coverage for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      - - - - - - - - - - - - - - - - -{{items}} - -
       
      Code Coverage
       
      Lines
      Branches
      Paths
      Functions and Methods
      Classes and Traits
      -
      -
      -
      -

      Legend

      -

      - Low: 0% to {{low_upper_bound}}% - Medium: {{low_upper_bound}}% to {{high_lower_bound}}% - High: {{high_lower_bound}}% to 100% -

      -

      - Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. -

      -
      -
      - - -
      -

      Branches

      -

      - Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not - necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. - Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement - always has an else as part of its logical flow even if you didn't write one. -

      -{{branches}} - - {{name}} - {{classes_bar}} -
      {{classes_tested_percent}}
      -
      {{classes_number}}
      - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{crap}} - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - - - - {{icon}}{{name}} - {{lines_bar}} -
      {{lines_executed_percent}}
      -
      {{lines_number}}
      - {{branches_bar}} -
      {{branches_executed_percent}}
      -
      {{branches_number}}
      - {{paths_bar}} -
      {{paths_executed_percent}}
      -
      {{paths_number}}
      - {{methods_bar}} -
      {{methods_tested_percent}}
      -
      {{methods_number}}
      - {{classes_bar}} -
      {{classes_tested_percent}}
      -
      {{classes_number}}
      - - - - - - - Dashboard for {{full_path}} - - - - - - - -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -

      Classes

      -
      -
      -
      -
      -

      Coverage Distribution

      -
      - -
      -
      -
      -

      Complexity

      -
      - -
      -
      -
      -
      -
      -

      Insufficient Coverage

      -
      - - - - - - - - -{{insufficient_coverage_classes}} - -
      ClassCoverage
      -
      -
      -
      -

      Project Risks

      -
      - - - - - - - - -{{project_risks_classes}} - -
      ClassCRAP
      -
      -
      -
      -
      -
      -

      Methods

      -
      -
      -
      -
      -

      Coverage Distribution

      -
      - -
      -
      -
      -

      Complexity

      -
      - -
      -
      -
      -
      -
      -

      Insufficient Coverage

      -
      - - - - - - - - -{{insufficient_coverage_methods}} - -
      MethodCoverage
      -
      -
      -
      -

      Project Risks

      -
      - - - - - - - - -{{project_risks_methods}} - -
      MethodCRAP
      -
      -
      -
      - -
      - - - - - - - $expected + * + * @psalm-assert =ExpectedType $actual + */ + public static function assertInstanceOf(string $expected, $actual, string $message = '') : void + { + if (!class_exists($expected) && !interface_exists($expected)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class or interface name'); + } + static::assertThat($actual, new IsInstanceOf($expected), $message); + } + /** + * Asserts that a variable is not of a given type. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert !ExpectedType $actual + */ + public static function assertNotInstanceOf(string $expected, $actual, string $message = '') : void + { + if (!class_exists($expected) && !interface_exists($expected)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class or interface name'); + } + static::assertThat($actual, new LogicalNot(new IsInstanceOf($expected)), $message); + } + /** + * Asserts that a variable is of type array. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert array $actual + */ + public static function assertIsArray($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_ARRAY), $message); + } + /** + * Asserts that a variable is of type bool. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert bool $actual + */ + public static function assertIsBool($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_BOOL), $message); + } + /** + * Asserts that a variable is of type float. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert float $actual + */ + public static function assertIsFloat($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_FLOAT), $message); + } + /** + * Asserts that a variable is of type int. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert int $actual + */ + public static function assertIsInt($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_INT), $message); + } + /** + * Asserts that a variable is of type numeric. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert numeric $actual + */ + public static function assertIsNumeric($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_NUMERIC), $message); + } + /** + * Asserts that a variable is of type object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert object $actual + */ + public static function assertIsObject($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_OBJECT), $message); + } + /** + * Asserts that a variable is of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert resource $actual + */ + public static function assertIsResource($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_RESOURCE), $message); + } + /** + * Asserts that a variable is of type resource and is closed. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert resource $actual + */ + public static function assertIsClosedResource($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_CLOSED_RESOURCE), $message); + } + /** + * Asserts that a variable is of type string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert string $actual + */ + public static function assertIsString($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_STRING), $message); + } + /** + * Asserts that a variable is of type scalar. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert scalar $actual + */ + public static function assertIsScalar($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_SCALAR), $message); + } + /** + * Asserts that a variable is of type callable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert callable $actual + */ + public static function assertIsCallable($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_CALLABLE), $message); + } + /** + * Asserts that a variable is of type iterable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert iterable $actual + */ + public static function assertIsIterable($actual, string $message = '') : void + { + static::assertThat($actual, new IsType(IsType::TYPE_ITERABLE), $message); + } + /** + * Asserts that a variable is not of type array. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !array $actual + */ + public static function assertIsNotArray($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_ARRAY)), $message); + } + /** + * Asserts that a variable is not of type bool. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !bool $actual + */ + public static function assertIsNotBool($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_BOOL)), $message); + } + /** + * Asserts that a variable is not of type float. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !float $actual + */ + public static function assertIsNotFloat($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_FLOAT)), $message); + } + /** + * Asserts that a variable is not of type int. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !int $actual + */ + public static function assertIsNotInt($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_INT)), $message); + } + /** + * Asserts that a variable is not of type numeric. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !numeric $actual + */ + public static function assertIsNotNumeric($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_NUMERIC)), $message); + } + /** + * Asserts that a variable is not of type object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !object $actual + */ + public static function assertIsNotObject($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_OBJECT)), $message); + } + /** + * Asserts that a variable is not of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !resource $actual + */ + public static function assertIsNotResource($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_RESOURCE)), $message); + } + /** + * Asserts that a variable is not of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !resource $actual + */ + public static function assertIsNotClosedResource($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_CLOSED_RESOURCE)), $message); + } + /** + * Asserts that a variable is not of type string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !string $actual + */ + public static function assertIsNotString($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_STRING)), $message); + } + /** + * Asserts that a variable is not of type scalar. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !scalar $actual + */ + public static function assertIsNotScalar($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_SCALAR)), $message); + } + /** + * Asserts that a variable is not of type callable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !callable $actual + */ + public static function assertIsNotCallable($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_CALLABLE)), $message); + } + /** + * Asserts that a variable is not of type iterable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !iterable $actual + */ + public static function assertIsNotIterable($actual, string $message = '') : void + { + static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_ITERABLE)), $message); + } + /** + * Asserts that a string matches a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = '') : void + { + static::assertThat($string, new RegularExpression($pattern), $message); + } + /** + * Asserts that a string matches a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + */ + public static function assertRegExp(string $pattern, string $string, string $message = '') : void + { + self::createWarning('assertRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertMatchesRegularExpression() instead.'); + static::assertThat($string, new RegularExpression($pattern), $message); + } + /** + * Asserts that a string does not match a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = '') : void + { + static::assertThat($string, new LogicalNot(new RegularExpression($pattern)), $message); + } + /** + * Asserts that a string does not match a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + */ + public static function assertNotRegExp(string $pattern, string $string, string $message = '') : void + { + self::createWarning('assertNotRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDoesNotMatchRegularExpression() instead.'); + static::assertThat($string, new LogicalNot(new RegularExpression($pattern)), $message); + } + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertSameSize($expected, $actual, string $message = '') : void + { + if (!$expected instanceof Countable && !is_iterable($expected)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'countable or iterable'); + } + if (!$actual instanceof Countable && !is_iterable($actual)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); + } + static::assertThat($actual, new SameSize($expected), $message); + } + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is not the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertNotSameSize($expected, $actual, string $message = '') : void + { + if (!$expected instanceof Countable && !is_iterable($expected)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'countable or iterable'); + } + if (!$actual instanceof Countable && !is_iterable($actual)) { + throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); + } + static::assertThat($actual, new LogicalNot(new SameSize($expected)), $message); + } + /** + * Asserts that a string matches a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringMatchesFormat(string $format, string $string, string $message = '') : void + { + static::assertThat($string, new StringMatchesFormatDescription($format), $message); + } + /** + * Asserts that a string does not match a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotMatchesFormat(string $format, string $string, string $message = '') : void + { + static::assertThat($string, new LogicalNot(new StringMatchesFormatDescription($format)), $message); + } + /** + * Asserts that a string matches a given format file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = '') : void + { + static::assertFileExists($formatFile, $message); + static::assertThat($string, new StringMatchesFormatDescription(file_get_contents($formatFile)), $message); + } + /** + * Asserts that a string does not match a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = '') : void + { + static::assertFileExists($formatFile, $message); + static::assertThat($string, new LogicalNot(new StringMatchesFormatDescription(file_get_contents($formatFile))), $message); + } + /** + * Asserts that a string starts with a given prefix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringStartsWith(string $prefix, string $string, string $message = '') : void + { + static::assertThat($string, new StringStartsWith($prefix), $message); + } + /** + * Asserts that a string starts not with a given prefix. + * + * @param string $prefix + * @param string $string + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringStartsNotWith($prefix, $string, string $message = '') : void + { + static::assertThat($string, new LogicalNot(new StringStartsWith($prefix)), $message); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringContainsString(string $needle, string $haystack, string $message = '') : void + { + $constraint = new StringContains($needle, \false); + static::assertThat($haystack, $constraint, $message); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void + { + $constraint = new StringContains($needle, \true); + static::assertThat($haystack, $constraint, $message); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotContainsString(string $needle, string $haystack, string $message = '') : void + { + $constraint = new LogicalNot(new StringContains($needle)); + static::assertThat($haystack, $constraint, $message); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void + { + $constraint = new LogicalNot(new StringContains($needle, \true)); + static::assertThat($haystack, $constraint, $message); + } + /** + * Asserts that a string ends with a given suffix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEndsWith(string $suffix, string $string, string $message = '') : void + { + static::assertThat($string, new StringEndsWith($suffix), $message); + } + /** + * Asserts that a string ends not with a given suffix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEndsNotWith(string $suffix, string $string, string $message = '') : void + { + static::assertThat($string, new LogicalNot(new StringEndsWith($suffix)), $message); + } + /** + * Asserts that two XML files are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void + { + $expected = (new XmlLoader())->loadFile($expectedFile); + $actual = (new XmlLoader())->loadFile($actualFile); + static::assertEquals($expected, $actual, $message); + } + /** + * Asserts that two XML files are not equal. + * + * @throws \PHPUnit\Util\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void + { + $expected = (new XmlLoader())->loadFile($expectedFile); + $actual = (new XmlLoader())->loadFile($actualFile); + static::assertNotEquals($expected, $actual, $message); + } + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $actualXml + * + * @throws \PHPUnit\Util\Xml\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void + { + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $actual = $actualXml; + } else { + $actual = (new XmlLoader())->load($actualXml); + } + $expected = (new XmlLoader())->loadFile($expectedFile); + static::assertEquals($expected, $actual, $message); + } + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $actualXml + * + * @throws \PHPUnit\Util\Xml\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void + { + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $actual = $actualXml; + } else { + $actual = (new XmlLoader())->load($actualXml); + } + $expected = (new XmlLoader())->loadFile($expectedFile); + static::assertNotEquals($expected, $actual, $message); + } + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws \PHPUnit\Util\Xml\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = '') : void + { + if (!is_string($expectedXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $expected = $expectedXml; + } else { + $expected = (new XmlLoader())->load($expectedXml); + } + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $actual = $actualXml; + } else { + $actual = (new XmlLoader())->load($actualXml); + } + static::assertEquals($expected, $actual, $message); + } + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws \PHPUnit\Util\Xml\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = '') : void + { + if (!is_string($expectedXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $expected = $expectedXml; + } else { + $expected = (new XmlLoader())->load($expectedXml); + } + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + $actual = $actualXml; + } else { + $actual = (new XmlLoader())->load($actualXml); + } + static::assertNotEquals($expected, $actual, $message); + } + /** + * Asserts that a hierarchy of DOMElements matches. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws AssertionFailedError + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + */ + public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = \false, string $message = '') : void + { + self::createWarning('assertEqualXMLStructure() is deprecated and will be removed in PHPUnit 10.'); + $expectedElement = Xml::import($expectedElement); + $actualElement = Xml::import($actualElement); + static::assertSame($expectedElement->tagName, $actualElement->tagName, $message); + if ($checkAttributes) { + static::assertSame($expectedElement->attributes->length, $actualElement->attributes->length, sprintf('%s%sNumber of attributes on node "%s" does not match', $message, !empty($message) ? "\n" : '', $expectedElement->tagName)); + for ($i = 0; $i < $expectedElement->attributes->length; $i++) { + $expectedAttribute = $expectedElement->attributes->item($i); + $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); + assert($expectedAttribute instanceof DOMAttr); + if (!$actualAttribute) { + static::fail(sprintf('%s%sCould not find attribute "%s" on node "%s"', $message, !empty($message) ? "\n" : '', $expectedAttribute->name, $expectedElement->tagName)); + } + } + } + Xml::removeCharacterDataNodes($expectedElement); + Xml::removeCharacterDataNodes($actualElement); + static::assertSame($expectedElement->childNodes->length, $actualElement->childNodes->length, sprintf('%s%sNumber of child nodes of "%s" differs', $message, !empty($message) ? "\n" : '', $expectedElement->tagName)); + for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { + static::assertEqualXMLStructure($expectedElement->childNodes->item($i), $actualElement->childNodes->item($i), $checkAttributes, $message); + } + } + /** + * Evaluates a PHPUnit\Framework\Constraint matcher object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertThat($value, Constraint $constraint, string $message = '') : void + { + self::$count += count($constraint); + $constraint->evaluate($value, $message); + } + /** + * Asserts that a string is a valid JSON string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJson(string $actualJson, string $message = '') : void + { + static::assertThat($actualJson, static::isJson(), $message); + } + /** + * Asserts that two given JSON encoded objects or arrays are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = '') : void + { + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + } + /** + * Asserts that two given JSON encoded objects or arrays are not equal. + * + * @param string $expectedJson + * @param string $actualJson + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = '') : void + { + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + static::assertThat($actualJson, new LogicalNot(new JsonMatches($expectedJson)), $message); + } + /** + * Asserts that the generated JSON encoded object and the content of the given file are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void + { + static::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + } + /** + * Asserts that the generated JSON encoded object and the content of the given file are not equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void + { + static::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + static::assertThat($actualJson, new LogicalNot(new JsonMatches($expectedJson)), $message); + } + /** + * Asserts that two JSON files are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void + { + static::assertFileExists($expectedFile, $message); + static::assertFileExists($actualFile, $message); + $actualJson = file_get_contents($actualFile); + $expectedJson = file_get_contents($expectedFile); + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + $constraintExpected = new JsonMatches($expectedJson); + $constraintActual = new JsonMatches($actualJson); + static::assertThat($expectedJson, $constraintActual, $message); + static::assertThat($actualJson, $constraintExpected, $message); + } + /** + * Asserts that two JSON files are not equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void + { + static::assertFileExists($expectedFile, $message); + static::assertFileExists($actualFile, $message); + $actualJson = file_get_contents($actualFile); + $expectedJson = file_get_contents($expectedFile); + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + $constraintExpected = new JsonMatches($expectedJson); + $constraintActual = new JsonMatches($actualJson); + static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); + static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); + } + /** + * @throws Exception + */ + public static function logicalAnd() : LogicalAnd + { + $constraints = func_get_args(); + $constraint = new LogicalAnd(); + $constraint->setConstraints($constraints); + return $constraint; + } + public static function logicalOr() : LogicalOr + { + $constraints = func_get_args(); + $constraint = new LogicalOr(); + $constraint->setConstraints($constraints); + return $constraint; + } + public static function logicalNot(Constraint $constraint) : LogicalNot + { + return new LogicalNot($constraint); + } + public static function logicalXor() : LogicalXor + { + $constraints = func_get_args(); + $constraint = new LogicalXor(); + $constraint->setConstraints($constraints); + return $constraint; + } + public static function anything() : IsAnything + { + return new IsAnything(); + } + public static function isTrue() : IsTrue + { + return new IsTrue(); + } + /** + * @psalm-template CallbackInput of mixed + * + * @psalm-param callable(CallbackInput $callback): bool $callback + * + * @psalm-return Callback + */ + public static function callback(callable $callback) : Callback + { + return new Callback($callback); + } + public static function isFalse() : IsFalse + { + return new IsFalse(); + } + public static function isJson() : IsJson + { + return new IsJson(); + } + public static function isNull() : IsNull + { + return new IsNull(); + } + public static function isFinite() : IsFinite + { + return new IsFinite(); + } + public static function isInfinite() : IsInfinite + { + return new IsInfinite(); + } + public static function isNan() : IsNan + { + return new IsNan(); + } + public static function containsEqual($value) : TraversableContainsEqual + { + return new TraversableContainsEqual($value); + } + public static function containsIdentical($value) : TraversableContainsIdentical + { + return new TraversableContainsIdentical($value); + } + public static function containsOnly(string $type) : TraversableContainsOnly + { + return new TraversableContainsOnly($type); + } + public static function containsOnlyInstancesOf(string $className) : TraversableContainsOnly + { + return new TraversableContainsOnly($className, \false); + } + /** + * @param int|string $key + */ + public static function arrayHasKey($key) : ArrayHasKey + { + return new ArrayHasKey($key); + } + public static function equalTo($value) : IsEqual + { + return new IsEqual($value, 0.0, \false, \false); + } + public static function equalToCanonicalizing($value) : IsEqualCanonicalizing + { + return new IsEqualCanonicalizing($value); + } + public static function equalToIgnoringCase($value) : IsEqualIgnoringCase + { + return new IsEqualIgnoringCase($value); + } + public static function equalToWithDelta($value, float $delta) : IsEqualWithDelta + { + return new IsEqualWithDelta($value, $delta); + } + public static function isEmpty() : IsEmpty + { + return new IsEmpty(); + } + public static function isWritable() : IsWritable + { + return new IsWritable(); + } + public static function isReadable() : IsReadable + { + return new IsReadable(); + } + public static function directoryExists() : DirectoryExists + { + return new DirectoryExists(); + } + public static function fileExists() : FileExists + { + return new FileExists(); + } + public static function greaterThan($value) : GreaterThan + { + return new GreaterThan($value); + } + public static function greaterThanOrEqual($value) : LogicalOr + { + return static::logicalOr(new IsEqual($value), new GreaterThan($value)); + } + public static function classHasAttribute(string $attributeName) : ClassHasAttribute + { + return new ClassHasAttribute($attributeName); + } + public static function classHasStaticAttribute(string $attributeName) : ClassHasStaticAttribute + { + return new ClassHasStaticAttribute($attributeName); + } + public static function objectHasAttribute($attributeName) : ObjectHasAttribute + { + return new ObjectHasAttribute($attributeName); + } + public static function identicalTo($value) : IsIdentical + { + return new IsIdentical($value); + } + public static function isInstanceOf(string $className) : IsInstanceOf + { + return new IsInstanceOf($className); + } + public static function isType(string $type) : IsType + { + return new IsType($type); + } + public static function lessThan($value) : LessThan + { + return new LessThan($value); + } + public static function lessThanOrEqual($value) : LogicalOr + { + return static::logicalOr(new IsEqual($value), new LessThan($value)); + } + public static function matchesRegularExpression(string $pattern) : RegularExpression + { + return new RegularExpression($pattern); + } + public static function matches(string $string) : StringMatchesFormatDescription + { + return new StringMatchesFormatDescription($string); + } + public static function stringStartsWith($prefix) : StringStartsWith + { + return new StringStartsWith($prefix); + } + public static function stringContains(string $string, bool $case = \true) : StringContains + { + return new StringContains($string, $case); + } + public static function stringEndsWith(string $suffix) : StringEndsWith + { + return new StringEndsWith($suffix); + } + public static function countOf(int $count) : Count + { + return new Count($count); + } + public static function objectEquals(object $object, string $method = 'equals') : ObjectEquals + { + return new ObjectEquals($object, $method); + } + /** + * Fails a test with the given message. + * + * @throws AssertionFailedError + * + * @psalm-return never-return + */ + public static function fail(string $message = '') : void + { + self::$count++; + throw new \PHPUnit\Framework\AssertionFailedError($message); + } + /** + * Mark the test as incomplete. + * + * @throws IncompleteTestError + * + * @psalm-return never-return + */ + public static function markTestIncomplete(string $message = '') : void + { + throw new \PHPUnit\Framework\IncompleteTestError($message); + } + /** + * Mark the test as skipped. + * + * @throws SkippedTestError + * @throws SyntheticSkippedError + * + * @psalm-return never-return + */ + public static function markTestSkipped(string $message = '') : void + { + if ($hint = self::detectLocationHint($message)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_unshift($trace, $hint); + throw new \PHPUnit\Framework\SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); + } + throw new \PHPUnit\Framework\SkippedTestError($message); + } + /** + * Return the current assertion count. + */ + public static function getCount() : int + { + return self::$count; + } + /** + * Reset the assertion counter. + */ + public static function resetCount() : void + { + self::$count = 0; + } + private static function detectLocationHint(string $message) : ?array + { + $hint = null; + $lines = preg_split('/\\r\\n|\\r|\\n/', $message); + while (strpos($lines[0], '__OFFSET') !== \false) { + $offset = explode('=', array_shift($lines)); + if ($offset[0] === '__OFFSET_FILE') { + $hint['file'] = $offset[1]; + } + if ($offset[0] === '__OFFSET_LINE') { + $hint['line'] = $offset[1]; + } + } + if ($hint) { + $hint['message'] = implode(PHP_EOL, $lines); + } + return $hint; + } + private static function isValidObjectAttributeName(string $attributeName) : bool + { + return (bool) preg_match('/[^\\x00-\\x1f\\x7f-\\x9f]+/', $attributeName); + } + private static function isValidClassAttributeName(string $attributeName) : bool + { + return (bool) preg_match('/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/', $attributeName); + } + /** + * @codeCoverageIgnore + */ + private static function createWarning(string $warning) : void + { + foreach (debug_backtrace() as $step) { + if (isset($step['object']) && $step['object'] instanceof \PHPUnit\Framework\TestCase) { + assert($step['object'] instanceof \PHPUnit\Framework\TestCase); + $step['object']->addWarning($warning); + break; + } + } + } +} + * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; +namespace PHPUnit\Framework; -use const ENT_COMPAT; -use const ENT_HTML401; -use const ENT_SUBSTITUTE; -use const T_ABSTRACT; -use const T_ARRAY; -use const T_AS; -use const T_BREAK; -use const T_CALLABLE; -use const T_CASE; -use const T_CATCH; -use const T_CLASS; -use const T_CLONE; -use const T_COMMENT; -use const T_CONST; -use const T_CONTINUE; -use const T_DECLARE; -use const T_DEFAULT; -use const T_DO; -use const T_DOC_COMMENT; -use const T_ECHO; -use const T_ELSE; -use const T_ELSEIF; -use const T_EMPTY; -use const T_ENDDECLARE; -use const T_ENDFOR; -use const T_ENDFOREACH; -use const T_ENDIF; -use const T_ENDSWITCH; -use const T_ENDWHILE; -use const T_EVAL; -use const T_EXIT; -use const T_EXTENDS; -use const T_FINAL; -use const T_FINALLY; -use const T_FOR; -use const T_FOREACH; -use const T_FUNCTION; -use const T_GLOBAL; -use const T_GOTO; -use const T_HALT_COMPILER; -use const T_IF; -use const T_IMPLEMENTS; -use const T_INCLUDE; -use const T_INCLUDE_ONCE; -use const T_INLINE_HTML; -use const T_INSTANCEOF; -use const T_INSTEADOF; -use const T_INTERFACE; -use const T_ISSET; -use const T_LIST; -use const T_NAMESPACE; -use const T_NEW; -use const T_PRINT; -use const T_PRIVATE; -use const T_PROTECTED; -use const T_PUBLIC; -use const T_REQUIRE; -use const T_REQUIRE_ONCE; -use const T_RETURN; -use const T_STATIC; -use const T_SWITCH; -use const T_THROW; -use const T_TRAIT; -use const T_TRY; -use const T_UNSET; -use const T_USE; -use const T_VAR; -use const T_WHILE; -use const T_YIELD; -use const T_YIELD_FROM; -use function array_key_exists; -use function array_pop; -use function array_unique; -use function constant; -use function count; -use function defined; -use function explode; -use function file_get_contents; -use function htmlspecialchars; -use function is_string; -use function sprintf; -use function str_replace; -use function substr; -use function token_get_all; -use function trim; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Percentage; -use PHPUnit\SebastianBergmann\Template\Template; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class File extends Renderer -{ +use function func_get_args; +use function function_exists; +use ArrayAccess; +use Countable; +use DOMDocument; +use DOMElement; +use PHPUnit\Framework\Constraint\ArrayHasKey; +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Constraint\ClassHasAttribute; +use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\Count; +use PHPUnit\Framework\Constraint\DirectoryExists; +use PHPUnit\Framework\Constraint\FileExists; +use PHPUnit\Framework\Constraint\GreaterThan; +use PHPUnit\Framework\Constraint\IsAnything; +use PHPUnit\Framework\Constraint\IsEmpty; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\Constraint\IsEqualCanonicalizing; +use PHPUnit\Framework\Constraint\IsEqualIgnoringCase; +use PHPUnit\Framework\Constraint\IsEqualWithDelta; +use PHPUnit\Framework\Constraint\IsFalse; +use PHPUnit\Framework\Constraint\IsFinite; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\Constraint\IsInfinite; +use PHPUnit\Framework\Constraint\IsInstanceOf; +use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsNan; +use PHPUnit\Framework\Constraint\IsNull; +use PHPUnit\Framework\Constraint\IsReadable; +use PHPUnit\Framework\Constraint\IsTrue; +use PHPUnit\Framework\Constraint\IsType; +use PHPUnit\Framework\Constraint\IsWritable; +use PHPUnit\Framework\Constraint\LessThan; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use PHPUnit\Framework\Constraint\LogicalOr; +use PHPUnit\Framework\Constraint\LogicalXor; +use PHPUnit\Framework\Constraint\ObjectEquals; +use PHPUnit\Framework\Constraint\ObjectHasAttribute; +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\StringContains; +use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; +use PHPUnit\Framework\Constraint\StringStartsWith; +use PHPUnit\Framework\Constraint\TraversableContainsEqual; +use PHPUnit\Framework\Constraint\TraversableContainsIdentical; +use PHPUnit\Framework\Constraint\TraversableContainsOnly; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; +use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; +use Throwable; +if (!function_exists('PHPUnit\\Framework\\assertArrayHasKey')) { + /** + * Asserts that an array has a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayHasKey + */ + function assertArrayHasKey($key, $array, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertArrayHasKey(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertArrayNotHasKey')) { + /** + * Asserts that an array does not have a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayNotHasKey + */ + function assertArrayNotHasKey($key, $array, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertArrayNotHasKey(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertContains')) { + /** + * Asserts that a haystack contains a needle. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContains + */ + function assertContains($needle, iterable $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertContains(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertContainsEquals')) { + function assertContainsEquals($needle, iterable $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertContainsEquals(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotContains')) { + /** + * Asserts that a haystack does not contain a needle. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContains + */ + function assertNotContains($needle, iterable $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertNotContains(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotContainsEquals')) { + function assertNotContainsEquals($needle, iterable $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertNotContainsEquals(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertContainsOnly')) { + /** + * Asserts that a haystack contains only values of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnly + */ + function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertContainsOnly(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertContainsOnlyInstancesOf')) { + /** + * Asserts that a haystack contains only instances of a given class name. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInstancesOf + */ + function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertContainsOnlyInstancesOf(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotContainsOnly')) { + /** + * Asserts that a haystack does not contain only values of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContainsOnly + */ + function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertNotContainsOnly(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertCount + */ + function assertCount(int $expectedCount, $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertCount(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotCount + */ + function assertNotCount(int $expectedCount, $haystack, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertNotCount(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertEquals')) { + /** + * Asserts that two variables are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEquals + */ + function assertEquals($expected, $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertEquals(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertEqualsCanonicalizing')) { + /** + * Asserts that two variables are equal (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsCanonicalizing + */ + function assertEqualsCanonicalizing($expected, $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertEqualsCanonicalizing(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertEqualsIgnoringCase')) { /** - * @psalm-var array + * Asserts that two variables are equal (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsIgnoringCase */ - private static $keywordTokens = []; + function assertEqualsIgnoringCase($expected, $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertEqualsIgnoringCase(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertEqualsWithDelta')) { /** - * @var array + * Asserts that two variables are equal (with delta). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsWithDelta */ - private static $formattedSourceCache = []; + function assertEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertEqualsWithDelta(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotEquals')) { /** - * @var int + * Asserts that two variables are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEquals */ - private $htmlSpecialCharsFlags = \ENT_COMPAT | \ENT_HTML401 | \ENT_SUBSTITUTE; - public function render(FileNode $node, string $file) : void + function assertNotEquals($expected, $actual, string $message = '') : void { - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_branch.html' : 'file.html'); - $template = new Template($templateName, '{{', '}}'); - $this->setCommonTemplateVariables($template, $node); - $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithLineCoverage($node), 'legend' => '

      ExecutedNot ExecutedDead Code

      ', 'structure' => '']); - $template->renderTo($file . '.html'); - if ($this->hasBranchCoverage) { - $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithBranchCoverage($node), 'legend' => '

      Fully coveredPartially coveredNot covered

      ', 'structure' => $this->renderBranchStructure($node)]); - $template->renderTo($file . '_branch.html'); - $template->setVar(['items' => $this->renderItems($node), 'lines' => $this->renderSourceWithPathCoverage($node), 'legend' => '

      Fully coveredPartially coveredNot covered

      ', 'structure' => $this->renderPathStructure($node)]); - $template->renderTo($file . '_path.html'); - } + \PHPUnit\Framework\Assert::assertNotEquals(...func_get_args()); } - private function renderItems(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertNotEqualsCanonicalizing')) { + /** + * Asserts that two variables are not equal (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsCanonicalizing + */ + function assertNotEqualsCanonicalizing($expected, $actual, string $message = '') : void { - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_item_branch.html' : 'file_item.html'); - $template = new Template($templateName, '{{', '}}'); - $methodTemplateName = $this->templatePath . ($this->hasBranchCoverage ? 'method_item_branch.html' : 'method_item.html'); - $methodItemTemplate = new Template($methodTemplateName, '{{', '}}'); - $items = $this->renderItemTemplate($template, ['name' => 'Total', 'numClasses' => $node->numberOfClassesAndTraits(), 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), 'numMethods' => $node->numberOfFunctionsAndMethods(), 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), 'numExecutedLines' => $node->numberOfExecutedLines(), 'numExecutableLines' => $node->numberOfExecutableLines(), 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), 'numExecutedBranches' => $node->numberOfExecutedBranches(), 'numExecutableBranches' => $node->numberOfExecutableBranches(), 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), 'numExecutedPaths' => $node->numberOfExecutedPaths(), 'numExecutablePaths' => $node->numberOfExecutablePaths(), 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString(), 'crap' => 'CRAP']); - $items .= $this->renderFunctionItems($node->functions(), $methodItemTemplate); - $items .= $this->renderTraitOrClassItems($node->traits(), $template, $methodItemTemplate); - $items .= $this->renderTraitOrClassItems($node->classes(), $template, $methodItemTemplate); - return $items; + \PHPUnit\Framework\Assert::assertNotEqualsCanonicalizing(...func_get_args()); } - private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate) : string +} +if (!function_exists('PHPUnit\\Framework\\assertNotEqualsIgnoringCase')) { + /** + * Asserts that two variables are not equal (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsIgnoringCase + */ + function assertNotEqualsIgnoringCase($expected, $actual, string $message = '') : void { - $buffer = ''; - if (empty($items)) { - return $buffer; - } - foreach ($items as $name => $item) { - $numMethods = 0; - $numTestedMethods = 0; - foreach ($item['methods'] as $method) { - if ($method['executableLines'] > 0) { - $numMethods++; - if ($method['executedLines'] === $method['executableLines']) { - $numTestedMethods++; - } - } - } - if ($item['executableLines'] > 0) { - $numClasses = 1; - $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; - $linesExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines'])->asString(); - $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches'])->asString(); - $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths'])->asString(); - } else { - $numClasses = 0; - $numTestedClasses = 0; - $linesExecutedPercentAsString = 'n/a'; - $branchesExecutedPercentAsString = 'n/a'; - $pathsExecutedPercentAsString = 'n/a'; - } - $testedMethodsPercentage = Percentage::fromFractionAndTotal($numTestedMethods, $numMethods); - $testedClassesPercentage = Percentage::fromFractionAndTotal($numTestedMethods === $numMethods ? 1 : 0, 1); - $buffer .= $this->renderItemTemplate($template, ['name' => $this->abbreviateClassName($name), 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines'])->asFloat(), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches'])->asFloat(), 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths'])->asFloat(), 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'testedClassesPercent' => $testedClassesPercentage->asFloat(), 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), 'crap' => $item['crap']]); - foreach ($item['methods'] as $method) { - $buffer .= $this->renderFunctionOrMethodItem($methodItemTemplate, $method, ' '); - } - } - return $buffer; + \PHPUnit\Framework\Assert::assertNotEqualsIgnoringCase(...func_get_args()); } - private function renderFunctionItems(array $functions, Template $template) : string +} +if (!function_exists('PHPUnit\\Framework\\assertNotEqualsWithDelta')) { + /** + * Asserts that two variables are not equal (with delta). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsWithDelta + */ + function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void { - if (empty($functions)) { - return ''; - } - $buffer = ''; - foreach ($functions as $function) { - $buffer .= $this->renderFunctionOrMethodItem($template, $function); - } - return $buffer; + \PHPUnit\Framework\Assert::assertNotEqualsWithDelta(...func_get_args()); } - private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = '') : string +} +if (!function_exists('PHPUnit\\Framework\\assertObjectEquals')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectEquals + */ + function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = '') : void { - $numMethods = 0; - $numTestedMethods = 0; - if ($item['executableLines'] > 0) { - $numMethods = 1; - if ($item['executedLines'] === $item['executableLines']) { - $numTestedMethods = 1; - } - } - $executedLinesPercentage = Percentage::fromFractionAndTotal($item['executedLines'], $item['executableLines']); - $executedBranchesPercentage = Percentage::fromFractionAndTotal($item['executedBranches'], $item['executableBranches']); - $executedPathsPercentage = Percentage::fromFractionAndTotal($item['executedPaths'], $item['executablePaths']); - $testedMethodsPercentage = Percentage::fromFractionAndTotal($numTestedMethods, 1); - return $this->renderItemTemplate($template, ['name' => sprintf('%s%s', $indent, $item['startLine'], htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName']), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'crap' => $item['crap']]); + \PHPUnit\Framework\Assert::assertObjectEquals(...func_get_args()); } - private function renderSourceWithLineCoverage(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertEmpty')) { + /** + * Asserts that a variable is empty. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert empty $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEmpty + */ + function assertEmpty($actual, string $message = '') : void { - $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); - $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); - $coverageData = $node->lineCoverageData(); - $testData = $node->testData(); - $codeLines = $this->loadFile($node->pathAsString()); - $lines = ''; - $i = 1; - foreach ($codeLines as $line) { - $trClass = ''; - $popoverContent = ''; - $popoverTitle = ''; - if (array_key_exists($i, $coverageData)) { - $numTests = $coverageData[$i] ? count($coverageData[$i]) : 0; - if ($coverageData[$i] === null) { - $trClass = 'warning'; - } elseif ($numTests === 0) { - $trClass = 'danger'; - } else { - if ($numTests > 1) { - $popoverTitle = $numTests . ' tests cover line ' . $i; - } else { - $popoverTitle = '1 test covers line ' . $i; - } - $lineCss = 'covered-by-large-tests'; - $popoverContent = '
        '; - foreach ($coverageData[$i] as $test) { - if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { - $lineCss = 'covered-by-medium-tests'; - } elseif ($testData[$test]['size'] === 'small') { - $lineCss = 'covered-by-small-tests'; - } - $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); - } - $popoverContent .= '
      '; - $trClass = $lineCss . ' popin'; - } - } - $popover = ''; - if (!empty($popoverTitle)) { - $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); - } - $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); - $i++; - } - $linesTemplate->setVar(['lines' => $lines]); - return $linesTemplate->render(); + \PHPUnit\Framework\Assert::assertEmpty(...func_get_args()); } - private function renderSourceWithBranchCoverage(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertNotEmpty')) { + /** + * Asserts that a variable is not empty. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !empty $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEmpty + */ + function assertNotEmpty($actual, string $message = '') : void { - $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); - $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); - $functionCoverageData = $node->functionCoverageData(); - $testData = $node->testData(); - $codeLines = $this->loadFile($node->pathAsString()); - $lineData = []; - /** @var int $line */ - foreach (\array_keys($codeLines) as $line) { - $lineData[$line + 1] = ['includedInBranches' => 0, 'includedInHitBranches' => 0, 'tests' => []]; - } - foreach ($functionCoverageData as $method) { - foreach ($method['branches'] as $branch) { - foreach (\range($branch['line_start'], $branch['line_end']) as $line) { - if (!isset($lineData[$line])) { - // blank line at end of file is sometimes included here - continue; - } - $lineData[$line]['includedInBranches']++; - if ($branch['hit']) { - $lineData[$line]['includedInHitBranches']++; - $lineData[$line]['tests'] = array_unique(\array_merge($lineData[$line]['tests'], $branch['hit'])); - } - } - } - } - $lines = ''; - $i = 1; - /** @var string $line */ - foreach ($codeLines as $line) { - $trClass = ''; - $popover = ''; - if ($lineData[$i]['includedInBranches'] > 0) { - $lineCss = 'success'; - if ($lineData[$i]['includedInHitBranches'] === 0) { - $lineCss = 'danger'; - } elseif ($lineData[$i]['includedInHitBranches'] !== $lineData[$i]['includedInBranches']) { - $lineCss = 'warning'; - } - $popoverContent = '
        '; - if (count($lineData[$i]['tests']) === 1) { - $popoverTitle = '1 test covers line ' . $i; - } else { - $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; - } - $popoverTitle .= '. These are covering ' . $lineData[$i]['includedInHitBranches'] . ' out of the ' . $lineData[$i]['includedInBranches'] . ' code branches.'; - foreach ($lineData[$i]['tests'] as $test) { - $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); - } - $popoverContent .= '
      '; - $trClass = $lineCss . ' popin'; - $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); - } - $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); - $i++; - } - $linesTemplate->setVar(['lines' => $lines]); - return $linesTemplate->render(); + \PHPUnit\Framework\Assert::assertNotEmpty(...func_get_args()); } - private function renderSourceWithPathCoverage(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertGreaterThan')) { + /** + * Asserts that a value is greater than another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThan + */ + function assertGreaterThan($expected, $actual, string $message = '') : void { - $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); - $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); - $functionCoverageData = $node->functionCoverageData(); - $testData = $node->testData(); - $codeLines = $this->loadFile($node->pathAsString()); - $lineData = []; - /** @var int $line */ - foreach (\array_keys($codeLines) as $line) { - $lineData[$line + 1] = ['includedInPaths' => [], 'includedInHitPaths' => [], 'tests' => []]; - } - foreach ($functionCoverageData as $method) { - foreach ($method['paths'] as $pathId => $path) { - foreach ($path['path'] as $branchTaken) { - foreach (\range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) { - if (!isset($lineData[$line])) { - continue; - } - $lineData[$line]['includedInPaths'][] = $pathId; - if ($path['hit']) { - $lineData[$line]['includedInHitPaths'][] = $pathId; - $lineData[$line]['tests'] = array_unique(\array_merge($lineData[$line]['tests'], $path['hit'])); - } - } - } - } - } - $lines = ''; - $i = 1; - /** @var string $line */ - foreach ($codeLines as $line) { - $trClass = ''; - $popover = ''; - $includedInPathsCount = count(array_unique($lineData[$i]['includedInPaths'])); - $includedInHitPathsCount = count(array_unique($lineData[$i]['includedInHitPaths'])); - if ($includedInPathsCount > 0) { - $lineCss = 'success'; - if ($includedInHitPathsCount === 0) { - $lineCss = 'danger'; - } elseif ($includedInHitPathsCount !== $includedInPathsCount) { - $lineCss = 'warning'; - } - $popoverContent = '
        '; - if (count($lineData[$i]['tests']) === 1) { - $popoverTitle = '1 test covers line ' . $i; - } else { - $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; - } - $popoverTitle .= '. These are covering ' . $includedInHitPathsCount . ' out of the ' . $includedInPathsCount . ' code paths.'; - foreach ($lineData[$i]['tests'] as $test) { - $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); - } - $popoverContent .= '
      '; - $trClass = $lineCss . ' popin'; - $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); - } - $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); - $i++; - } - $linesTemplate->setVar(['lines' => $lines]); - return $linesTemplate->render(); + \PHPUnit\Framework\Assert::assertGreaterThan(...func_get_args()); } - private function renderBranchStructure(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertGreaterThanOrEqual')) { + /** + * Asserts that a value is greater than or equal to another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThanOrEqual + */ + function assertGreaterThanOrEqual($expected, $actual, string $message = '') : void { - $branchesTemplate = new Template($this->templatePath . 'branches.html.dist', '{{', '}}'); - $coverageData = $node->functionCoverageData(); - $testData = $node->testData(); - $codeLines = $this->loadFile($node->pathAsString()); - $branches = ''; - \ksort($coverageData); - foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['branches']) { - continue; - } - $branchStructure = ''; - foreach ($methodData['branches'] as $branch) { - $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData); - } - if ($branchStructure !== '') { - // don't show empty branches - $branches .= '
      ' . $this->abbreviateMethodName($methodName) . '
      ' . "\n"; - $branches .= $branchStructure; - } - } - $branchesTemplate->setVar(['branches' => $branches]); - return $branchesTemplate->render(); + \PHPUnit\Framework\Assert::assertGreaterThanOrEqual(...func_get_args()); } - private function renderBranchLines(array $branch, array $codeLines, array $testData) : string +} +if (!function_exists('PHPUnit\\Framework\\assertLessThan')) { + /** + * Asserts that a value is smaller than another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThan + */ + function assertLessThan($expected, $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertLessThan(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertLessThanOrEqual')) { + /** + * Asserts that a value is smaller than or equal to another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThanOrEqual + */ + function assertLessThanOrEqual($expected, $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertLessThanOrEqual(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileEquals')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEquals + */ + function assertFileEquals(string $expected, string $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertFileEquals(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsCanonicalizing + */ + function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertFileEqualsCanonicalizing(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsIgnoringCase + */ + function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertFileEqualsIgnoringCase(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileNotEquals')) { + /** + * Asserts that the contents of one file is not equal to the contents of + * another file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEquals + */ + function assertFileNotEquals(string $expected, string $actual, string $message = '') : void { - $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); - $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); - $lines = ''; - $branchLines = \range($branch['line_start'], $branch['line_end']); - \sort($branchLines); - // sometimes end_line < start_line - /** @var int $line */ - foreach ($branchLines as $line) { - if (!isset($codeLines[$line])) { - // blank line at end of file is sometimes included here - continue; - } - $popoverContent = ''; - $popoverTitle = ''; - $numTests = count($branch['hit']); - if ($numTests === 0) { - $trClass = 'danger'; - } else { - $lineCss = 'covered-by-large-tests'; - $popoverContent = '
        '; - if ($numTests > 1) { - $popoverTitle = $numTests . ' tests cover this branch'; - } else { - $popoverTitle = '1 test covers this branch'; - } - foreach ($branch['hit'] as $test) { - if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { - $lineCss = 'covered-by-medium-tests'; - } elseif ($testData[$test]['size'] === 'small') { - $lineCss = 'covered-by-small-tests'; - } - $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); - } - $trClass = $lineCss . ' popin'; - } - $popover = ''; - if (!empty($popoverTitle)) { - $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); - } - $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); - } - if ($lines === '') { - return ''; - } - $linesTemplate->setVar(['lines' => $lines]); - return $linesTemplate->render(); + \PHPUnit\Framework\Assert::assertFileNotEquals(...func_get_args()); } - private function renderPathStructure(FileNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertFileNotEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsCanonicalizing + */ + function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void { - $pathsTemplate = new Template($this->templatePath . 'paths.html.dist', '{{', '}}'); - $coverageData = $node->functionCoverageData(); - $testData = $node->testData(); - $codeLines = $this->loadFile($node->pathAsString()); - $paths = ''; - \ksort($coverageData); - foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['paths']) { - continue; - } - $pathStructure = ''; - if (count($methodData['paths']) > 100) { - $pathStructure .= '

        ' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

        '; - continue; - } - foreach ($methodData['paths'] as $path) { - $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); - } - if ($pathStructure !== '') { - $paths .= '
        ' . $this->abbreviateMethodName($methodName) . '
        ' . "\n"; - $paths .= $pathStructure; - } - } - $pathsTemplate->setVar(['paths' => $paths]); - return $pathsTemplate->render(); + \PHPUnit\Framework\Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); } - private function renderPathLines(array $path, array $branches, array $codeLines, array $testData) : string +} +if (!function_exists('PHPUnit\\Framework\\assertFileNotEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsIgnoringCase + */ + function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void { - $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); - $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); - $lines = ''; - foreach ($path['path'] as $branchId) { - $branchLines = \range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']); - \sort($branchLines); - // sometimes end_line < start_line - /** @var int $line */ - foreach ($branchLines as $line) { - if (!isset($codeLines[$line])) { - // blank line at end of file is sometimes included here - continue; - } - $popoverContent = ''; - $popoverTitle = ''; - $numTests = count($path['hit']); - if ($numTests === 0) { - $trClass = 'danger'; - } else { - $lineCss = 'covered-by-large-tests'; - $popoverContent = '
          '; - if ($numTests > 1) { - $popoverTitle = $numTests . ' tests cover this path'; - } else { - $popoverTitle = '1 test covers this path'; - } - foreach ($path['hit'] as $test) { - if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { - $lineCss = 'covered-by-medium-tests'; - } elseif ($testData[$test]['size'] === 'small') { - $lineCss = 'covered-by-small-tests'; - } - $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); - } - $trClass = $lineCss . ' popin'; - } - $popover = ''; - if (!empty($popoverTitle)) { - $popover = sprintf(' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags)); - } - $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); - } - } - if ($lines === '') { - return ''; - } - $linesTemplate->setVar(['lines' => $lines]); - return $linesTemplate->render(); + \PHPUnit\Framework\Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); } - private function renderLine(Template $template, int $lineNumber, string $lineContent, string $class, string $popover) : string +} +if (!function_exists('PHPUnit\\Framework\\assertStringEqualsFile')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFile + */ + function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = '') : void { - $template->setVar(['lineNumber' => $lineNumber, 'lineContent' => $lineContent, 'class' => $class, 'popover' => $popover]); - return $template->render(); + \PHPUnit\Framework\Assert::assertStringEqualsFile(...func_get_args()); } - private function loadFile(string $file) : array +} +if (!function_exists('PHPUnit\\Framework\\assertStringEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileCanonicalizing + */ + function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void { - if (isset(self::$formattedSourceCache[$file])) { - return self::$formattedSourceCache[$file]; - } - $buffer = file_get_contents($file); - $tokens = token_get_all($buffer); - $result = ['']; - $i = 0; - $stringFlag = \false; - $fileEndsWithNewLine = substr($buffer, -1) === "\n"; - unset($buffer); - foreach ($tokens as $j => $token) { - if (is_string($token)) { - if ($token === '"' && $tokens[$j - 1] !== '\\') { - $result[$i] .= sprintf('%s', htmlspecialchars($token, $this->htmlSpecialCharsFlags)); - $stringFlag = !$stringFlag; - } else { - $result[$i] .= sprintf('%s', htmlspecialchars($token, $this->htmlSpecialCharsFlags)); - } - continue; - } - [$token, $value] = $token; - $value = str_replace(["\t", ' '], ['    ', ' '], htmlspecialchars($value, $this->htmlSpecialCharsFlags)); - if ($value === "\n") { - $result[++$i] = ''; - } else { - $lines = explode("\n", $value); - foreach ($lines as $jj => $line) { - $line = trim($line); - if ($line !== '') { - if ($stringFlag) { - $colour = 'string'; - } else { - $colour = 'default'; - if ($this->isInlineHtml($token)) { - $colour = 'html'; - } elseif ($this->isComment($token)) { - $colour = 'comment'; - } elseif ($this->isKeyword($token)) { - $colour = 'keyword'; - } - } - $result[$i] .= sprintf('%s', $colour, $line); - } - if (isset($lines[$jj + 1])) { - $result[++$i] = ''; - } - } - } - } - if ($fileEndsWithNewLine) { - unset($result[count($result) - 1]); - } - self::$formattedSourceCache[$file] = $result; - return $result; + \PHPUnit\Framework\Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); } - private function abbreviateClassName(string $className) : string +} +if (!function_exists('PHPUnit\\Framework\\assertStringEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileIgnoringCase + */ + function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void { - $tmp = explode('\\', $className); - if (count($tmp) > 1) { - $className = sprintf('%s', $className, array_pop($tmp)); - } - return $className; + \PHPUnit\Framework\Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); } - private function abbreviateMethodName(string $methodName) : string +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotEqualsFile')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFile + */ + function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = '') : void { - $parts = explode('->', $methodName); - if (count($parts) === 2) { - return $this->abbreviateClassName($parts[0]) . '->' . $parts[1]; - } - return $methodName; + \PHPUnit\Framework\Assert::assertStringNotEqualsFile(...func_get_args()); } - private function createPopoverContentForTest(string $test, array $testData) : string +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileCanonicalizing + */ + function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void { - $testCSS = ''; - if ($testData['fromTestcase']) { - switch ($testData['status']) { - case BaseTestRunner::STATUS_PASSED: - switch ($testData['size']) { - case 'small': - $testCSS = ' class="covered-by-small-tests"'; - break; - case 'medium': - $testCSS = ' class="covered-by-medium-tests"'; - break; - default: - $testCSS = ' class="covered-by-large-tests"'; - break; - } - break; - case BaseTestRunner::STATUS_SKIPPED: - case BaseTestRunner::STATUS_INCOMPLETE: - case BaseTestRunner::STATUS_RISKY: - case BaseTestRunner::STATUS_WARNING: - $testCSS = ' class="warning"'; - break; - case BaseTestRunner::STATUS_FAILURE: - case BaseTestRunner::STATUS_ERROR: - $testCSS = ' class="danger"'; - break; - } - } - return sprintf('%s', $testCSS, htmlspecialchars($test, $this->htmlSpecialCharsFlags)); + \PHPUnit\Framework\Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); } - private function isComment(int $token) : bool +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileIgnoringCase + */ + function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void { - return $token === \T_COMMENT || $token === \T_DOC_COMMENT; + \PHPUnit\Framework\Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); } - private function isInlineHtml(int $token) : bool +} +if (!function_exists('PHPUnit\\Framework\\assertIsReadable')) { + /** + * Asserts that a file/dir is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsReadable + */ + function assertIsReadable(string $filename, string $message = '') : void { - return $token === \T_INLINE_HTML; + \PHPUnit\Framework\Assert::assertIsReadable(...func_get_args()); } - private function isKeyword(int $token) : bool +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotReadable')) { + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotReadable + */ + function assertIsNotReadable(string $filename, string $message = '') : void { - return isset(self::keywordTokens()[$token]); + \PHPUnit\Framework\Assert::assertIsNotReadable(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertNotIsReadable')) { /** - * @psalm-return array + * Asserts that a file/dir exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotIsReadable */ - private static function keywordTokens() : array + function assertNotIsReadable(string $filename, string $message = '') : void { - if (self::$keywordTokens !== []) { - return self::$keywordTokens; - } - self::$keywordTokens = [\T_ABSTRACT => \true, \T_ARRAY => \true, \T_AS => \true, \T_BREAK => \true, \T_CALLABLE => \true, \T_CASE => \true, \T_CATCH => \true, \T_CLASS => \true, \T_CLONE => \true, \T_CONST => \true, \T_CONTINUE => \true, \T_DECLARE => \true, \T_DEFAULT => \true, \T_DO => \true, \T_ECHO => \true, \T_ELSE => \true, \T_ELSEIF => \true, \T_EMPTY => \true, \T_ENDDECLARE => \true, \T_ENDFOR => \true, \T_ENDFOREACH => \true, \T_ENDIF => \true, \T_ENDSWITCH => \true, \T_ENDWHILE => \true, \T_EVAL => \true, \T_EXIT => \true, \T_EXTENDS => \true, \T_FINAL => \true, \T_FINALLY => \true, \T_FOR => \true, \T_FOREACH => \true, \T_FUNCTION => \true, \T_GLOBAL => \true, \T_GOTO => \true, \T_HALT_COMPILER => \true, \T_IF => \true, \T_IMPLEMENTS => \true, \T_INCLUDE => \true, \T_INCLUDE_ONCE => \true, \T_INSTANCEOF => \true, \T_INSTEADOF => \true, \T_INTERFACE => \true, \T_ISSET => \true, \T_LIST => \true, \T_NAMESPACE => \true, \T_NEW => \true, \T_PRINT => \true, \T_PRIVATE => \true, \T_PROTECTED => \true, \T_PUBLIC => \true, \T_REQUIRE => \true, \T_REQUIRE_ONCE => \true, \T_RETURN => \true, \T_STATIC => \true, \T_SWITCH => \true, \T_THROW => \true, \T_TRAIT => \true, \T_TRY => \true, \T_UNSET => \true, \T_USE => \true, \T_VAR => \true, \T_WHILE => \true, \T_YIELD => \true, \T_YIELD_FROM => \true]; - if (defined('T_FN')) { - self::$keywordTokens[constant('T_FN')] = \true; - } - if (defined('T_MATCH')) { - self::$keywordTokens[constant('T_MATCH')] = \true; - } - if (defined('T_ENUM')) { - self::$keywordTokens[constant('T_ENUM')] = \true; - } - if (defined('T_READONLY')) { - self::$keywordTokens[constant('T_READONLY')] = \true; - } - return self::$keywordTokens; + \PHPUnit\Framework\Assert::assertNotIsReadable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; - -use function array_values; -use function arsort; -use function asort; -use function count; -use function explode; -use function floor; -use function json_encode; -use function sprintf; -use function str_replace; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use PHPUnit\SebastianBergmann\Template\Template; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Dashboard extends Renderer -{ - public function render(DirectoryNode $node, string $file) : void +if (!function_exists('PHPUnit\\Framework\\assertIsWritable')) { + /** + * Asserts that a file/dir exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsWritable + */ + function assertIsWritable(string $filename, string $message = '') : void { - $classes = $node->classesAndTraits(); - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'dashboard_branch.html' : 'dashboard.html'); - $template = new Template($templateName, '{{', '}}'); - $this->setCommonTemplateVariables($template, $node); - $baseLink = $node->id() . '/'; - $complexity = $this->complexity($classes, $baseLink); - $coverageDistribution = $this->coverageDistribution($classes); - $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); - $projectRisks = $this->projectRisks($classes, $baseLink); - $template->setVar(['insufficient_coverage_classes' => $insufficientCoverage['class'], 'insufficient_coverage_methods' => $insufficientCoverage['method'], 'project_risks_classes' => $projectRisks['class'], 'project_risks_methods' => $projectRisks['method'], 'complexity_class' => $complexity['class'], 'complexity_method' => $complexity['method'], 'class_coverage_distribution' => $coverageDistribution['class'], 'method_coverage_distribution' => $coverageDistribution['method']]); - $template->renderTo($file); + \PHPUnit\Framework\Assert::assertIsWritable(...func_get_args()); } - protected function activeBreadcrumb(AbstractNode $node) : string +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotWritable')) { + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotWritable + */ + function assertIsNotWritable(string $filename, string $message = '') : void { - return sprintf(' ' . "\n" . ' ' . "\n", $node->name()); + \PHPUnit\Framework\Assert::assertIsNotWritable(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertNotIsWritable')) { /** - * Returns the data for the Class/Method Complexity charts. + * Asserts that a file/dir exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotIsWritable */ - private function complexity(array $classes, string $baseLink) : array + function assertNotIsWritable(string $filename, string $message = '') : void { - $result = ['class' => [], 'method' => []]; - foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($className !== '*') { - $methodName = $className . '::' . $methodName; - } - $result['method'][] = [$method['coverage'], $method['ccn'], sprintf('%s', str_replace($baseLink, '', $method['link']), $methodName)]; - } - $result['class'][] = [$class['coverage'], $class['ccn'], sprintf('%s', str_replace($baseLink, '', $class['link']), $className)]; - } - return ['class' => json_encode($result['class']), 'method' => json_encode($result['method'])]; + \PHPUnit\Framework\Assert::assertNotIsWritable(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryExists')) { + /** + * Asserts that a directory exists. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryExists + */ + function assertDirectoryExists(string $directory, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertDirectoryExists(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryDoesNotExist')) { /** - * Returns the data for the Class / Method Coverage Distribution chart. + * Asserts that a directory does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryDoesNotExist */ - private function coverageDistribution(array $classes) : array + function assertDirectoryDoesNotExist(string $directory, string $message = '') : void { - $result = ['class' => ['0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0], 'method' => ['0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0]]; - foreach ($classes as $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] === 0) { - $result['method']['0%']++; - } elseif ($method['coverage'] === 100) { - $result['method']['100%']++; - } else { - $key = floor($method['coverage'] / 10) * 10; - $key = $key . '-' . ($key + 10) . '%'; - $result['method'][$key]++; - } - } - if ($class['coverage'] === 0) { - $result['class']['0%']++; - } elseif ($class['coverage'] === 100) { - $result['class']['100%']++; - } else { - $key = floor($class['coverage'] / 10) * 10; - $key = $key . '-' . ($key + 10) . '%'; - $result['class'][$key]++; - } - } - return ['class' => json_encode(array_values($result['class'])), 'method' => json_encode(array_values($result['method']))]; + \PHPUnit\Framework\Assert::assertDirectoryDoesNotExist(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryNotExists')) { /** - * Returns the classes / methods with insufficient coverage. + * Asserts that a directory does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotExists */ - private function insufficientCoverage(array $classes, string $baseLink) : array + function assertDirectoryNotExists(string $directory, string $message = '') : void { - $leastTestedClasses = []; - $leastTestedMethods = []; - $result = ['class' => '', 'method' => '']; - foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->highLowerBound) { - $key = $methodName; - if ($className !== '*') { - $key = $className . '::' . $methodName; - } - $leastTestedMethods[$key] = $method['coverage']; - } - } - if ($class['coverage'] < $this->highLowerBound) { - $leastTestedClasses[$className] = $class['coverage']; - } - } - asort($leastTestedClasses); - asort($leastTestedMethods); - foreach ($leastTestedClasses as $className => $coverage) { - $result['class'] .= sprintf(' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $coverage); - } - foreach ($leastTestedMethods as $methodName => $coverage) { - [$class, $method] = explode('::', $methodName); - $result['method'] .= sprintf(' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $coverage); - } - return $result; + \PHPUnit\Framework\Assert::assertDirectoryNotExists(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryIsReadable')) { /** - * Returns the project risks according to the CRAP index. + * Asserts that a directory exists and is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsReadable */ - private function projectRisks(array $classes, string $baseLink) : array + function assertDirectoryIsReadable(string $directory, string $message = '') : void { - $classRisks = []; - $methodRisks = []; - $result = ['class' => '', 'method' => '']; - foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { - $key = $methodName; - if ($className !== '*') { - $key = $className . '::' . $methodName; - } - $methodRisks[$key] = $method['crap']; - } - } - if ($class['coverage'] < $this->highLowerBound && $class['ccn'] > count($class['methods'])) { - $classRisks[$className] = $class['crap']; - } - } - arsort($classRisks); - arsort($methodRisks); - foreach ($classRisks as $className => $crap) { - $result['class'] .= sprintf(' %s%d' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $crap); - } - foreach ($methodRisks as $methodName => $crap) { - [$class, $method] = explode('::', $methodName); - $result['method'] .= sprintf(' %s%d' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $crap); - } - return $result; + \PHPUnit\Framework\Assert::assertDirectoryIsReadable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Html; - -use function count; -use function sprintf; -use function str_repeat; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode as Node; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use PHPUnit\SebastianBergmann\Template\Template; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Directory extends Renderer -{ - public function render(DirectoryNode $node, string $file) : void +if (!function_exists('PHPUnit\\Framework\\assertDirectoryIsNotReadable')) { + /** + * Asserts that a directory exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotReadable + */ + function assertDirectoryIsNotReadable(string $directory, string $message = '') : void { - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_branch.html' : 'directory.html'); - $template = new Template($templateName, '{{', '}}'); - $this->setCommonTemplateVariables($template, $node); - $items = $this->renderItem($node, \true); - foreach ($node->directories() as $item) { - $items .= $this->renderItem($item); - } - foreach ($node->files() as $item) { - $items .= $this->renderItem($item); - } - $template->setVar(['id' => $node->id(), 'items' => $items]); - $template->renderTo($file); + \PHPUnit\Framework\Assert::assertDirectoryIsNotReadable(...func_get_args()); } - private function renderItem(Node $node, bool $total = \false) : string +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryNotIsReadable')) { + /** + * Asserts that a directory exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotIsReadable + */ + function assertDirectoryNotIsReadable(string $directory, string $message = '') : void { - $data = ['numClasses' => $node->numberOfClassesAndTraits(), 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), 'numMethods' => $node->numberOfFunctionsAndMethods(), 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), 'numExecutedLines' => $node->numberOfExecutedLines(), 'numExecutableLines' => $node->numberOfExecutableLines(), 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), 'numExecutedBranches' => $node->numberOfExecutedBranches(), 'numExecutableBranches' => $node->numberOfExecutableBranches(), 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), 'numExecutedPaths' => $node->numberOfExecutedPaths(), 'numExecutablePaths' => $node->numberOfExecutablePaths(), 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString()]; - if ($total) { - $data['name'] = 'Total'; - } else { - $up = str_repeat('../', count($node->pathAsArray()) - 2); - $data['icon'] = sprintf('', $up); - if ($node instanceof DirectoryNode) { - $data['name'] = sprintf('%s', $node->name(), $node->name()); - $data['icon'] = sprintf('', $up); - } elseif ($this->hasBranchCoverage) { - $data['name'] = sprintf('%s [line] [branch] [path]', $node->name(), $node->name(), $node->name(), $node->name()); - } else { - $data['name'] = sprintf('%s', $node->name(), $node->name()); - } - } - $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_item_branch.html' : 'directory_item.html'); - return $this->renderItemTemplate(new Template($templateName, '{{', '}}'), $data); + \PHPUnit\Framework\Assert::assertDirectoryNotIsReadable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; - -use function dirname; -use function file_put_contents; -use function serialize; -use function sprintf; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; -final class PHP -{ - public function process(CodeCoverage $coverage, ?string $target = null) : string +if (!function_exists('PHPUnit\\Framework\\assertDirectoryIsWritable')) { + /** + * Asserts that a directory exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsWritable + */ + function assertDirectoryIsWritable(string $directory, string $message = '') : void { - $buffer = sprintf(" - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use function constant; -use function phpversion; -use DateTimeImmutable; -use DOMElement; -use PHPUnit\SebastianBergmann\Environment\Runtime; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class BuildInformation -{ +if (!function_exists('PHPUnit\\Framework\\assertDirectoryIsNotWritable')) { /** - * @var DOMElement + * Asserts that a directory exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotWritable */ - private $contextNode; - public function __construct(DOMElement $contextNode) + function assertDirectoryIsNotWritable(string $directory, string $message = '') : void { - $this->contextNode = $contextNode; + \PHPUnit\Framework\Assert::assertDirectoryIsNotWritable(...func_get_args()); } - public function setRuntimeInformation(Runtime $runtime) : void +} +if (!function_exists('PHPUnit\\Framework\\assertDirectoryNotIsWritable')) { + /** + * Asserts that a directory exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotIsWritable + */ + function assertDirectoryNotIsWritable(string $directory, string $message = '') : void { - $runtimeNode = $this->nodeByName('runtime'); - $runtimeNode->setAttribute('name', $runtime->getName()); - $runtimeNode->setAttribute('version', $runtime->getVersion()); - $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); - $driverNode = $this->nodeByName('driver'); - if ($runtime->hasPHPDBGCodeCoverage()) { - $driverNode->setAttribute('name', 'phpdbg'); - $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); - } - if ($runtime->hasXdebug()) { - $driverNode->setAttribute('name', 'xdebug'); - $driverNode->setAttribute('version', phpversion('xdebug')); - } - if ($runtime->hasPCOV()) { - $driverNode->setAttribute('name', 'pcov'); - $driverNode->setAttribute('version', phpversion('pcov')); - } + \PHPUnit\Framework\Assert::assertDirectoryNotIsWritable(...func_get_args()); } - public function setBuildTime(DateTimeImmutable $date) : void +} +if (!function_exists('PHPUnit\\Framework\\assertFileExists')) { + /** + * Asserts that a file exists. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileExists + */ + function assertFileExists(string $filename, string $message = '') : void { - $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); + \PHPUnit\Framework\Assert::assertFileExists(...func_get_args()); } - public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion) : void +} +if (!function_exists('PHPUnit\\Framework\\assertFileDoesNotExist')) { + /** + * Asserts that a file does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileDoesNotExist + */ + function assertFileDoesNotExist(string $filename, string $message = '') : void { - $this->contextNode->setAttribute('phpunit', $phpUnitVersion); - $this->contextNode->setAttribute('coverage', $coverageVersion); + \PHPUnit\Framework\Assert::assertFileDoesNotExist(...func_get_args()); } - private function nodeByName(string $name) : DOMElement +} +if (!function_exists('PHPUnit\\Framework\\assertFileNotExists')) { + /** + * Asserts that a file does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotExists + */ + function assertFileNotExists(string $filename, string $message = '') : void { - $node = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', $name)->item(0); - if (!$node) { - $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', $name)); - } - return $node; + \PHPUnit\Framework\Assert::assertFileNotExists(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMElement; -use PHPUnit\TheSeer\Tokenizer\NamespaceUri; -use PHPUnit\TheSeer\Tokenizer\Tokenizer; -use PHPUnit\TheSeer\Tokenizer\XMLSerializer; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Source -{ - /** @var DOMElement */ - private $context; - public function __construct(DOMElement $context) +if (!function_exists('PHPUnit\\Framework\\assertFileIsReadable')) { + /** + * Asserts that a file exists and is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsReadable + */ + function assertFileIsReadable(string $file, string $message = '') : void { - $this->context = $context; + \PHPUnit\Framework\Assert::assertFileIsReadable(...func_get_args()); } - public function setSourceCode(string $source) : void +} +if (!function_exists('PHPUnit\\Framework\\assertFileIsNotReadable')) { + /** + * Asserts that a file exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotReadable + */ + function assertFileIsNotReadable(string $file, string $message = '') : void { - $context = $this->context; - $tokens = (new Tokenizer())->parse($source); - $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); - $context->parentNode->replaceChild($context->ownerDocument->importNode($srcDom->documentElement, \true), $context); + \PHPUnit\Framework\Assert::assertFileIsNotReadable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use const DIRECTORY_SEPARATOR; -use const PHP_EOL; -use function count; -use function dirname; -use function file_get_contents; -use function file_put_contents; -use function is_array; -use function is_dir; -use function is_file; -use function is_writable; -use function libxml_clear_errors; -use function libxml_get_errors; -use function libxml_use_internal_errors; -use function sprintf; -use function strlen; -use function substr; -use DateTimeImmutable; -use DOMDocument; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory as DirectoryUtil; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\AbstractNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File as FileNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Version; -use PHPUnit\SebastianBergmann\CodeCoverage\XmlException; -use PHPUnit\SebastianBergmann\Environment\Runtime; -final class Facade -{ +if (!function_exists('PHPUnit\\Framework\\assertFileNotIsReadable')) { /** - * @var string + * Asserts that a file exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotIsReadable */ - private $target; + function assertFileNotIsReadable(string $file, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertFileNotIsReadable(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileIsWritable')) { /** - * @var Project + * Asserts that a file exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsWritable */ - private $project; + function assertFileIsWritable(string $file, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertFileIsWritable(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertFileIsNotWritable')) { /** - * @var string + * Asserts that a file exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotWritable */ - private $phpUnitVersion; - public function __construct(string $version) + function assertFileIsNotWritable(string $file, string $message = '') : void { - $this->phpUnitVersion = $version; + \PHPUnit\Framework\Assert::assertFileIsNotWritable(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertFileNotIsWritable')) { /** - * @throws XmlException + * Asserts that a file exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotIsWritable */ - public function process(CodeCoverage $coverage, string $target) : void + function assertFileNotIsWritable(string $file, string $message = '') : void { - if (substr($target, -1, 1) !== \DIRECTORY_SEPARATOR) { - $target .= \DIRECTORY_SEPARATOR; - } - $this->target = $target; - $this->initTargetDirectory($target); - $report = $coverage->getReport(); - $this->project = new Project($coverage->getReport()->name()); - $this->setBuildInformation(); - $this->processTests($coverage->getTests()); - $this->processDirectory($report, $this->project); - $this->saveDocument($this->project->asDom(), 'index'); + \PHPUnit\Framework\Assert::assertFileNotIsWritable(...func_get_args()); } - private function setBuildInformation() : void +} +if (!function_exists('PHPUnit\\Framework\\assertTrue')) { + /** + * Asserts that a condition is true. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertTrue + */ + function assertTrue($condition, string $message = '') : void { - $buildNode = $this->project->buildInformation(); - $buildNode->setRuntimeInformation(new Runtime()); - $buildNode->setBuildTime(new DateTimeImmutable()); - $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); + \PHPUnit\Framework\Assert::assertTrue(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertNotTrue')) { /** - * @throws PathExistsButIsNotDirectoryException - * @throws WriteOperationFailedException + * Asserts that a condition is not true. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotTrue */ - private function initTargetDirectory(string $directory) : void + function assertNotTrue($condition, string $message = '') : void { - if (is_file($directory)) { - if (!is_dir($directory)) { - throw new PathExistsButIsNotDirectoryException($directory); - } - if (!is_writable($directory)) { - throw new WriteOperationFailedException($directory); - } - } - DirectoryUtil::create($directory); + \PHPUnit\Framework\Assert::assertNotTrue(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertFalse')) { /** - * @throws XmlException + * Asserts that a condition is false. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFalse */ - private function processDirectory(DirectoryNode $directory, Node $context) : void + function assertFalse($condition, string $message = '') : void { - $directoryName = $directory->name(); - if ($this->project->projectSourceDirectory() === $directoryName) { - $directoryName = '/'; - } - $directoryObject = $context->addDirectory($directoryName); - $this->setTotals($directory, $directoryObject->totals()); - foreach ($directory->directories() as $node) { - $this->processDirectory($node, $directoryObject); - } - foreach ($directory->files() as $node) { - $this->processFile($node, $directoryObject); - } + \PHPUnit\Framework\Assert::assertFalse(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertNotFalse')) { /** - * @throws XmlException + * Asserts that a condition is not false. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotFalse */ - private function processFile(FileNode $file, Directory $context) : void + function assertNotFalse($condition, string $message = '') : void { - $fileObject = $context->addFile($file->name(), $file->id() . '.xml'); - $this->setTotals($file, $fileObject->totals()); - $path = substr($file->pathAsString(), strlen($this->project->projectSourceDirectory())); - $fileReport = new Report($path); - $this->setTotals($file, $fileReport->totals()); - foreach ($file->classesAndTraits() as $unit) { - $this->processUnit($unit, $fileReport); - } - foreach ($file->functions() as $function) { - $this->processFunction($function, $fileReport); - } - foreach ($file->lineCoverageData() as $line => $tests) { - if (!is_array($tests) || count($tests) === 0) { - continue; - } - $coverage = $fileReport->lineCoverage((string) $line); - foreach ($tests as $test) { - $coverage->addTest($test); - } - $coverage->finalize(); - } - $fileReport->source()->setSourceCode(file_get_contents($file->pathAsString())); - $this->saveDocument($fileReport->asDom(), $file->id()); + \PHPUnit\Framework\Assert::assertNotFalse(...func_get_args()); } - private function processUnit(array $unit, Report $report) : void +} +if (!function_exists('PHPUnit\\Framework\\assertNull')) { + /** + * Asserts that a variable is null. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNull + */ + function assertNull($actual, string $message = '') : void { - if (isset($unit['className'])) { - $unitObject = $report->classObject($unit['className']); - } else { - $unitObject = $report->traitObject($unit['traitName']); - } - $unitObject->setLines($unit['startLine'], $unit['executableLines'], $unit['executedLines']); - $unitObject->setCrap((float) $unit['crap']); - $unitObject->setNamespace($unit['namespace']); - foreach ($unit['methods'] as $method) { - $methodObject = $unitObject->addMethod($method['methodName']); - $methodObject->setSignature($method['signature']); - $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); - $methodObject->setCrap($method['crap']); - $methodObject->setTotals((string) $method['executableLines'], (string) $method['executedLines'], (string) $method['coverage']); - } + \PHPUnit\Framework\Assert::assertNull(...func_get_args()); } - private function processFunction(array $function, Report $report) : void +} +if (!function_exists('PHPUnit\\Framework\\assertNotNull')) { + /** + * Asserts that a variable is not null. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotNull + */ + function assertNotNull($actual, string $message = '') : void { - $functionObject = $report->functionObject($function['functionName']); - $functionObject->setSignature($function['signature']); - $functionObject->setLines((string) $function['startLine']); - $functionObject->setCrap($function['crap']); - $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); + \PHPUnit\Framework\Assert::assertNotNull(...func_get_args()); } - private function processTests(array $tests) : void +} +if (!function_exists('PHPUnit\\Framework\\assertFinite')) { + /** + * Asserts that a variable is finite. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFinite + */ + function assertFinite($actual, string $message = '') : void { - $testsObject = $this->project->tests(); - foreach ($tests as $test => $result) { - $testsObject->addTest($test, $result); - } + \PHPUnit\Framework\Assert::assertFinite(...func_get_args()); } - private function setTotals(AbstractNode $node, Totals $totals) : void +} +if (!function_exists('PHPUnit\\Framework\\assertInfinite')) { + /** + * Asserts that a variable is infinite. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInfinite + */ + function assertInfinite($actual, string $message = '') : void { - $loc = $node->linesOfCode(); - $totals->setNumLines($loc->linesOfCode(), $loc->commentLinesOfCode(), $loc->nonCommentLinesOfCode(), $node->numberOfExecutableLines(), $node->numberOfExecutedLines()); - $totals->setNumClasses($node->numberOfClasses(), $node->numberOfTestedClasses()); - $totals->setNumTraits($node->numberOfTraits(), $node->numberOfTestedTraits()); - $totals->setNumMethods($node->numberOfMethods(), $node->numberOfTestedMethods()); - $totals->setNumFunctions($node->numberOfFunctions(), $node->numberOfTestedFunctions()); + \PHPUnit\Framework\Assert::assertInfinite(...func_get_args()); } - private function targetDirectory() : string +} +if (!function_exists('PHPUnit\\Framework\\assertNan')) { + /** + * Asserts that a variable is nan. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNan + */ + function assertNan($actual, string $message = '') : void { - return $this->target; + \PHPUnit\Framework\Assert::assertNan(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertClassHasAttribute')) { /** - * @throws XmlException + * Asserts that a class has a specified attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassHasAttribute */ - private function saveDocument(DOMDocument $document, string $name) : void + function assertClassHasAttribute(string $attributeName, string $className, string $message = '') : void { - $filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name); - $document->formatOutput = \true; - $document->preserveWhiteSpace = \false; - $this->initTargetDirectory(dirname($filename)); - file_put_contents($filename, $this->documentAsString($document)); + \PHPUnit\Framework\Assert::assertClassHasAttribute(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertClassNotHasAttribute')) { /** - * @throws XmlException + * Asserts that a class does not have a specified attribute. * - * @see https://bugs.php.net/bug.php?id=79191 + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassNotHasAttribute */ - private function documentAsString(DOMDocument $document) : string + function assertClassNotHasAttribute(string $attributeName, string $className, string $message = '') : void { - $xmlErrorHandling = libxml_use_internal_errors(\true); - $xml = $document->saveXML(); - if ($xml === \false) { - $message = 'Unable to generate the XML'; - foreach (libxml_get_errors() as $error) { - $message .= \PHP_EOL . $error->message; - } - throw new XmlException($message); - } - libxml_clear_errors(); - libxml_use_internal_errors($xmlErrorHandling); - return $xml; + \PHPUnit\Framework\Assert::assertClassNotHasAttribute(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Method -{ +if (!function_exists('PHPUnit\\Framework\\assertClassHasStaticAttribute')) { /** - * @var DOMElement + * Asserts that a class has a specified static attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassHasStaticAttribute */ - private $contextNode; - public function __construct(DOMElement $context, string $name) + function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = '') : void { - $this->contextNode = $context; - $this->setName($name); + \PHPUnit\Framework\Assert::assertClassHasStaticAttribute(...func_get_args()); } - public function setSignature(string $signature) : void +} +if (!function_exists('PHPUnit\\Framework\\assertClassNotHasStaticAttribute')) { + /** + * Asserts that a class does not have a specified static attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassNotHasStaticAttribute + */ + function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = '') : void { - $this->contextNode->setAttribute('signature', $signature); + \PHPUnit\Framework\Assert::assertClassNotHasStaticAttribute(...func_get_args()); } - public function setLines(string $start, ?string $end = null) : void +} +if (!function_exists('PHPUnit\\Framework\\assertObjectHasAttribute')) { + /** + * Asserts that an object has a specified attribute. + * + * @param object $object + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectHasAttribute + */ + function assertObjectHasAttribute(string $attributeName, $object, string $message = '') : void { - $this->contextNode->setAttribute('start', $start); - if ($end !== null) { - $this->contextNode->setAttribute('end', $end); - } + \PHPUnit\Framework\Assert::assertObjectHasAttribute(...func_get_args()); } - public function setTotals(string $executable, string $executed, string $coverage) : void +} +if (!function_exists('PHPUnit\\Framework\\assertObjectNotHasAttribute')) { + /** + * Asserts that an object does not have a specified attribute. + * + * @param object $object + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotHasAttribute + */ + function assertObjectNotHasAttribute(string $attributeName, $object, string $message = '') : void { - $this->contextNode->setAttribute('executable', $executable); - $this->contextNode->setAttribute('executed', $executed); - $this->contextNode->setAttribute('coverage', $coverage); + \PHPUnit\Framework\Assert::assertObjectNotHasAttribute(...func_get_args()); } - public function setCrap(string $crap) : void +} +if (!function_exists('PHPUnit\\Framework\\assertSame')) { + /** + * Asserts that two variables have the same type and value. + * Used on objects, it asserts that two variables reference + * the same object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-template ExpectedType + * + * @psalm-param ExpectedType $expected + * + * @psalm-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSame + */ + function assertSame($expected, $actual, string $message = '') : void { - $this->contextNode->setAttribute('crap', $crap); + \PHPUnit\Framework\Assert::assertSame(...func_get_args()); } - private function setName(string $name) : void +} +if (!function_exists('PHPUnit\\Framework\\assertNotSame')) { + /** + * Asserts that two variables do not have the same type and value. + * Used on objects, it asserts that two variables do not reference + * the same object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSame + */ + function assertNotSame($expected, $actual, string $message = '') : void { - $this->contextNode->setAttribute('name', $name); + \PHPUnit\Framework\Assert::assertNotSame(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use function basename; -use function dirname; -use DOMDocument; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Report extends File -{ - public function __construct(string $name) +if (!function_exists('PHPUnit\\Framework\\assertInstanceOf')) { + /** + * Asserts that a variable is of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInstanceOf + */ + function assertInstanceOf(string $expected, $actual, string $message = '') : void { - $dom = new DOMDocument(); - $dom->loadXML(''); - $contextNode = $dom->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'file')->item(0); - parent::__construct($contextNode); - $this->setName($name); + \PHPUnit\Framework\Assert::assertInstanceOf(...func_get_args()); } - public function asDom() : DOMDocument +} +if (!function_exists('PHPUnit\\Framework\\assertNotInstanceOf')) { + /** + * Asserts that a variable is not of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert !ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotInstanceOf + */ + function assertNotInstanceOf(string $expected, $actual, string $message = '') : void { - return $this->dom(); + \PHPUnit\Framework\Assert::assertNotInstanceOf(...func_get_args()); } - public function functionObject($name) : Method +} +if (!function_exists('PHPUnit\\Framework\\assertIsArray')) { + /** + * Asserts that a variable is of type array. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsArray + */ + function assertIsArray($actual, string $message = '') : void { - $node = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'function')); - return new Method($node, $name); + \PHPUnit\Framework\Assert::assertIsArray(...func_get_args()); } - public function classObject($name) : Unit +} +if (!function_exists('PHPUnit\\Framework\\assertIsBool')) { + /** + * Asserts that a variable is of type bool. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsBool + */ + function assertIsBool($actual, string $message = '') : void { - return $this->unitObject('class', $name); + \PHPUnit\Framework\Assert::assertIsBool(...func_get_args()); } - public function traitObject($name) : Unit +} +if (!function_exists('PHPUnit\\Framework\\assertIsFloat')) { + /** + * Asserts that a variable is of type float. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsFloat + */ + function assertIsFloat($actual, string $message = '') : void { - return $this->unitObject('trait', $name); + \PHPUnit\Framework\Assert::assertIsFloat(...func_get_args()); } - public function source() : Source +} +if (!function_exists('PHPUnit\\Framework\\assertIsInt')) { + /** + * Asserts that a variable is of type int. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsInt + */ + function assertIsInt($actual, string $message = '') : void { - $source = $this->contextNode()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'source')->item(0); - if (!$source) { - $source = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'source')); - } - return new Source($source); + \PHPUnit\Framework\Assert::assertIsInt(...func_get_args()); } - private function setName(string $name) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNumeric')) { + /** + * Asserts that a variable is of type numeric. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNumeric + */ + function assertIsNumeric($actual, string $message = '') : void { - $this->contextNode()->setAttribute('name', basename($name)); - $this->contextNode()->setAttribute('path', dirname($name)); + \PHPUnit\Framework\Assert::assertIsNumeric(...func_get_args()); } - private function unitObject(string $tagName, $name) : Unit +} +if (!function_exists('PHPUnit\\Framework\\assertIsObject')) { + /** + * Asserts that a variable is of type object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsObject + */ + function assertIsObject($actual, string $message = '') : void { - $node = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', $tagName)); - return new Unit($node, $name); + \PHPUnit\Framework\Assert::assertIsObject(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMDocument; -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -class File -{ +if (!function_exists('PHPUnit\\Framework\\assertIsResource')) { /** - * @var DOMDocument + * Asserts that a variable is of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsResource */ - private $dom; + function assertIsResource($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsResource(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsClosedResource')) { /** - * @var DOMElement + * Asserts that a variable is of type resource and is closed. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsClosedResource */ - private $contextNode; - public function __construct(DOMElement $context) + function assertIsClosedResource($actual, string $message = '') : void { - $this->dom = $context->ownerDocument; - $this->contextNode = $context; + \PHPUnit\Framework\Assert::assertIsClosedResource(...func_get_args()); } - public function totals() : Totals +} +if (!function_exists('PHPUnit\\Framework\\assertIsString')) { + /** + * Asserts that a variable is of type string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsString + */ + function assertIsString($actual, string $message = '') : void { - $totalsContainer = $this->contextNode->firstChild; - if (!$totalsContainer) { - $totalsContainer = $this->contextNode->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'totals')); - } - return new Totals($totalsContainer); + \PHPUnit\Framework\Assert::assertIsString(...func_get_args()); } - public function lineCoverage(string $line) : Coverage +} +if (!function_exists('PHPUnit\\Framework\\assertIsScalar')) { + /** + * Asserts that a variable is of type scalar. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsScalar + */ + function assertIsScalar($actual, string $message = '') : void { - $coverage = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'coverage')->item(0); - if (!$coverage) { - $coverage = $this->contextNode->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'coverage')); - } - $lineNode = $coverage->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'line')); - return new Coverage($lineNode, $line); + \PHPUnit\Framework\Assert::assertIsScalar(...func_get_args()); } - protected function contextNode() : DOMElement +} +if (!function_exists('PHPUnit\\Framework\\assertIsCallable')) { + /** + * Asserts that a variable is of type callable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsCallable + */ + function assertIsCallable($actual, string $message = '') : void { - return $this->contextNode; + \PHPUnit\Framework\Assert::assertIsCallable(...func_get_args()); } - protected function dom() : DOMDocument +} +if (!function_exists('PHPUnit\\Framework\\assertIsIterable')) { + /** + * Asserts that a variable is of type iterable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsIterable + */ + function assertIsIterable($actual, string $message = '') : void { - return $this->dom; + \PHPUnit\Framework\Assert::assertIsIterable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use function sprintf; -use DOMElement; -use DOMNode; -use PHPUnit\SebastianBergmann\CodeCoverage\Percentage; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Totals -{ +if (!function_exists('PHPUnit\\Framework\\assertIsNotArray')) { /** - * @var DOMNode + * Asserts that a variable is not of type array. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotArray */ - private $container; + function assertIsNotArray($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsNotArray(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotBool')) { /** - * @var DOMElement + * Asserts that a variable is not of type bool. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotBool */ - private $linesNode; + function assertIsNotBool($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsNotBool(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotFloat')) { /** - * @var DOMElement + * Asserts that a variable is not of type float. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotFloat */ - private $methodsNode; + function assertIsNotFloat($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsNotFloat(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotInt')) { /** - * @var DOMElement + * Asserts that a variable is not of type int. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotInt */ - private $functionsNode; + function assertIsNotInt($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsNotInt(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotNumeric')) { /** - * @var DOMElement + * Asserts that a variable is not of type numeric. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotNumeric */ - private $classesNode; + function assertIsNotNumeric($actual, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertIsNotNumeric(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotObject')) { /** - * @var DOMElement + * Asserts that a variable is not of type object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotObject */ - private $traitsNode; - public function __construct(DOMElement $container) + function assertIsNotObject($actual, string $message = '') : void { - $this->container = $container; - $dom = $container->ownerDocument; - $this->linesNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'lines'); - $this->methodsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'methods'); - $this->functionsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'functions'); - $this->classesNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'classes'); - $this->traitsNode = $dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'traits'); - $container->appendChild($this->linesNode); - $container->appendChild($this->methodsNode); - $container->appendChild($this->functionsNode); - $container->appendChild($this->classesNode); - $container->appendChild($this->traitsNode); + \PHPUnit\Framework\Assert::assertIsNotObject(...func_get_args()); } - public function container() : DOMNode +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotResource + */ + function assertIsNotResource($actual, string $message = '') : void { - return $this->container; + \PHPUnit\Framework\Assert::assertIsNotResource(...func_get_args()); } - public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotClosedResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotClosedResource + */ + function assertIsNotClosedResource($actual, string $message = '') : void { - $this->linesNode->setAttribute('total', (string) $loc); - $this->linesNode->setAttribute('comments', (string) $cloc); - $this->linesNode->setAttribute('code', (string) $ncloc); - $this->linesNode->setAttribute('executable', (string) $executable); - $this->linesNode->setAttribute('executed', (string) $executed); - $this->linesNode->setAttribute('percent', $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat())); + \PHPUnit\Framework\Assert::assertIsNotClosedResource(...func_get_args()); } - public function setNumClasses(int $count, int $tested) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotString')) { + /** + * Asserts that a variable is not of type string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotString + */ + function assertIsNotString($actual, string $message = '') : void { - $this->classesNode->setAttribute('count', (string) $count); - $this->classesNode->setAttribute('tested', (string) $tested); - $this->classesNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + \PHPUnit\Framework\Assert::assertIsNotString(...func_get_args()); } - public function setNumTraits(int $count, int $tested) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotScalar')) { + /** + * Asserts that a variable is not of type scalar. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotScalar + */ + function assertIsNotScalar($actual, string $message = '') : void { - $this->traitsNode->setAttribute('count', (string) $count); - $this->traitsNode->setAttribute('tested', (string) $tested); - $this->traitsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + \PHPUnit\Framework\Assert::assertIsNotScalar(...func_get_args()); } - public function setNumMethods(int $count, int $tested) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotCallable')) { + /** + * Asserts that a variable is not of type callable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotCallable + */ + function assertIsNotCallable($actual, string $message = '') : void { - $this->methodsNode->setAttribute('count', (string) $count); - $this->methodsNode->setAttribute('tested', (string) $tested); - $this->methodsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + \PHPUnit\Framework\Assert::assertIsNotCallable(...func_get_args()); } - public function setNumFunctions(int $count, int $tested) : void +} +if (!function_exists('PHPUnit\\Framework\\assertIsNotIterable')) { + /** + * Asserts that a variable is not of type iterable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotIterable + */ + function assertIsNotIterable($actual, string $message = '') : void { - $this->functionsNode->setAttribute('count', (string) $count); - $this->functionsNode->setAttribute('tested', (string) $tested); - $this->functionsNode->setAttribute('percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat())); + \PHPUnit\Framework\Assert::assertIsNotIterable(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Tests -{ - private $contextNode; - private $codeMap = [ - -1 => 'UNKNOWN', - // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN - 0 => 'PASSED', - // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED - 1 => 'SKIPPED', - // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED - 2 => 'INCOMPLETE', - // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE - 3 => 'FAILURE', - // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE - 4 => 'ERROR', - // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR - 5 => 'RISKY', - // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY - 6 => 'WARNING', - ]; - public function __construct(DOMElement $context) +if (!function_exists('PHPUnit\\Framework\\assertMatchesRegularExpression')) { + /** + * Asserts that a string matches a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertMatchesRegularExpression + */ + function assertMatchesRegularExpression(string $pattern, string $string, string $message = '') : void { - $this->contextNode = $context; + \PHPUnit\Framework\Assert::assertMatchesRegularExpression(...func_get_args()); } - public function addTest(string $test, array $result) : void +} +if (!function_exists('PHPUnit\\Framework\\assertRegExp')) { + /** + * Asserts that a string matches a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertRegExp + */ + function assertRegExp(string $pattern, string $string, string $message = '') : void { - $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'test')); - $node->setAttribute('name', $test); - $node->setAttribute('size', $result['size']); - $node->setAttribute('result', (string) $result['status']); - $node->setAttribute('status', $this->codeMap[(int) $result['status']]); + \PHPUnit\Framework\Assert::assertRegExp(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMDocument; -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -abstract class Node -{ +if (!function_exists('PHPUnit\\Framework\\assertDoesNotMatchRegularExpression')) { /** - * @var DOMDocument + * Asserts that a string does not match a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDoesNotMatchRegularExpression */ - private $dom; + function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertDoesNotMatchRegularExpression(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertNotRegExp')) { /** - * @var DOMElement + * Asserts that a string does not match a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotRegExp */ - private $contextNode; - public function __construct(DOMElement $context) + function assertNotRegExp(string $pattern, string $string, string $message = '') : void { - $this->setContextNode($context); + \PHPUnit\Framework\Assert::assertNotRegExp(...func_get_args()); } - public function dom() : DOMDocument +} +if (!function_exists('PHPUnit\\Framework\\assertSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSameSize + */ + function assertSameSize($expected, $actual, string $message = '') : void { - return $this->dom; + \PHPUnit\Framework\Assert::assertSameSize(...func_get_args()); } - public function totals() : Totals +} +if (!function_exists('PHPUnit\\Framework\\assertNotSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is not the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSameSize + */ + function assertNotSameSize($expected, $actual, string $message = '') : void { - $totalsContainer = $this->contextNode()->firstChild; - if (!$totalsContainer) { - $totalsContainer = $this->contextNode()->appendChild($this->dom->createElementNS('https://schema.phpunit.de/coverage/1.0', 'totals')); - } - return new Totals($totalsContainer); + \PHPUnit\Framework\Assert::assertNotSameSize(...func_get_args()); } - public function addDirectory(string $name) : Directory +} +if (!function_exists('PHPUnit\\Framework\\assertStringMatchesFormat')) { + /** + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormat + */ + function assertStringMatchesFormat(string $format, string $string, string $message = '') : void { - $dirNode = $this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'directory'); - $dirNode->setAttribute('name', $name); - $this->contextNode()->appendChild($dirNode); - return new Directory($dirNode); + \PHPUnit\Framework\Assert::assertStringMatchesFormat(...func_get_args()); } - public function addFile(string $name, string $href) : File +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotMatchesFormat')) { + /** + * Asserts that a string does not match a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotMatchesFormat + */ + function assertStringNotMatchesFormat(string $format, string $string, string $message = '') : void { - $fileNode = $this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'file'); - $fileNode->setAttribute('name', $name); - $fileNode->setAttribute('href', $href); - $this->contextNode()->appendChild($fileNode); - return new File($fileNode); + \PHPUnit\Framework\Assert::assertStringNotMatchesFormat(...func_get_args()); } - protected function setContextNode(DOMElement $context) : void +} +if (!function_exists('PHPUnit\\Framework\\assertStringMatchesFormatFile')) { + /** + * Asserts that a string matches a given format file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormatFile + */ + function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = '') : void { - $this->dom = $context->ownerDocument; - $this->contextNode = $context; + \PHPUnit\Framework\Assert::assertStringMatchesFormatFile(...func_get_args()); } - protected function contextNode() : DOMElement +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotMatchesFormatFile')) { + /** + * Asserts that a string does not match a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotMatchesFormatFile + */ + function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = '') : void { - return $this->contextNode; + \PHPUnit\Framework\Assert::assertStringNotMatchesFormatFile(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMDocument; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Project extends Node -{ - public function __construct(string $directory) +if (!function_exists('PHPUnit\\Framework\\assertStringStartsWith')) { + /** + * Asserts that a string starts with a given prefix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsWith + */ + function assertStringStartsWith(string $prefix, string $string, string $message = '') : void { - $this->init(); - $this->setProjectSourceDirectory($directory); + \PHPUnit\Framework\Assert::assertStringStartsWith(...func_get_args()); } - public function projectSourceDirectory() : string +} +if (!function_exists('PHPUnit\\Framework\\assertStringStartsNotWith')) { + /** + * Asserts that a string starts not with a given prefix. + * + * @param string $prefix + * @param string $string + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsNotWith + */ + function assertStringStartsNotWith($prefix, $string, string $message = '') : void { - return $this->contextNode()->getAttribute('source'); + \PHPUnit\Framework\Assert::assertStringStartsNotWith(...func_get_args()); } - public function buildInformation() : BuildInformation +} +if (!function_exists('PHPUnit\\Framework\\assertStringContainsString')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsString + */ + function assertStringContainsString(string $needle, string $haystack, string $message = '') : void { - $buildNode = $this->dom()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'build')->item(0); - if (!$buildNode) { - $buildNode = $this->dom()->documentElement->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'build')); - } - return new BuildInformation($buildNode); + \PHPUnit\Framework\Assert::assertStringContainsString(...func_get_args()); } - public function tests() : Tests +} +if (!function_exists('PHPUnit\\Framework\\assertStringContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsStringIgnoringCase + */ + function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void { - $testsNode = $this->contextNode()->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'tests')->item(0); - if (!$testsNode) { - $testsNode = $this->contextNode()->appendChild($this->dom()->createElementNS('https://schema.phpunit.de/coverage/1.0', 'tests')); - } - return new Tests($testsNode); + \PHPUnit\Framework\Assert::assertStringContainsStringIgnoringCase(...func_get_args()); } - public function asDom() : DOMDocument +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotContainsString')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsString + */ + function assertStringNotContainsString(string $needle, string $haystack, string $message = '') : void { - return $this->dom(); + \PHPUnit\Framework\Assert::assertStringNotContainsString(...func_get_args()); } - private function init() : void +} +if (!function_exists('PHPUnit\\Framework\\assertStringNotContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsStringIgnoringCase + */ + function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void { - $dom = new DOMDocument(); - $dom->loadXML(''); - $this->setContextNode($dom->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'project')->item(0)); + \PHPUnit\Framework\Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); } - private function setProjectSourceDirectory(string $name) : void +} +if (!function_exists('PHPUnit\\Framework\\assertStringEndsWith')) { + /** + * Asserts that a string ends with a given suffix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsWith + */ + function assertStringEndsWith(string $suffix, string $string, string $message = '') : void { - $this->contextNode()->setAttribute('source', $name); + \PHPUnit\Framework\Assert::assertStringEndsWith(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMElement; -use PHPUnit\SebastianBergmann\CodeCoverage\ReportAlreadyFinalizedException; -use XMLWriter; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Coverage -{ +if (!function_exists('PHPUnit\\Framework\\assertStringEndsNotWith')) { /** - * @var XMLWriter + * Asserts that a string ends not with a given suffix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsNotWith */ - private $writer; + function assertStringEndsNotWith(string $suffix, string $string, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertStringEndsNotWith(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertXmlFileEqualsXmlFile')) { /** - * @var DOMElement + * Asserts that two XML files are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileEqualsXmlFile */ - private $contextNode; + function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertXmlFileEqualsXmlFile(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertXmlFileNotEqualsXmlFile')) { /** - * @var bool + * Asserts that two XML files are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileNotEqualsXmlFile */ - private $finalized = \false; - public function __construct(DOMElement $context, string $line) + function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void { - $this->contextNode = $context; - $this->writer = new XMLWriter(); - $this->writer->openMemory(); - $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); - $this->writer->writeAttribute('nr', $line); + \PHPUnit\Framework\Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); } +} +if (!function_exists('PHPUnit\\Framework\\assertXmlStringEqualsXmlFile')) { /** - * @throws ReportAlreadyFinalizedException + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlFile */ - public function addTest(string $test) : void + function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void { - if ($this->finalized) { - throw new ReportAlreadyFinalizedException(); - } - $this->writer->startElement('covered'); - $this->writer->writeAttribute('by', $test); - $this->writer->endElement(); + \PHPUnit\Framework\Assert::assertXmlStringEqualsXmlFile(...func_get_args()); } - public function finalize() : void +} +if (!function_exists('PHPUnit\\Framework\\assertXmlStringNotEqualsXmlFile')) { + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlFile + */ + function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void { - $this->writer->endElement(); - $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); - $fragment->appendXML($this->writer->outputMemory()); - $this->contextNode->parentNode->replaceChild($fragment, $this->contextNode); - $this->finalized = \true; + \PHPUnit\Framework\Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Directory extends Node -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report\Xml; - -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class Unit -{ +if (!function_exists('PHPUnit\\Framework\\assertXmlStringEqualsXmlString')) { /** - * @var DOMElement + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlString */ - private $contextNode; - public function __construct(DOMElement $context, string $name) + function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = '') : void { - $this->contextNode = $context; - $this->setName($name); + \PHPUnit\Framework\Assert::assertXmlStringEqualsXmlString(...func_get_args()); } - public function setLines(int $start, int $executable, int $executed) : void +} +if (!function_exists('PHPUnit\\Framework\\assertXmlStringNotEqualsXmlString')) { + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlString + */ + function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = '') : void { - $this->contextNode->setAttribute('start', (string) $start); - $this->contextNode->setAttribute('executable', (string) $executable); - $this->contextNode->setAttribute('executed', (string) $executed); + \PHPUnit\Framework\Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); } - public function setCrap(float $crap) : void +} +if (!function_exists('PHPUnit\\Framework\\assertEqualXMLStructure')) { + /** + * Asserts that a hierarchy of DOMElements matches. + * + * @throws AssertionFailedError + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualXMLStructure + */ + function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = \false, string $message = '') : void { - $this->contextNode->setAttribute('crap', (string) $crap); + \PHPUnit\Framework\Assert::assertEqualXMLStructure(...func_get_args()); } - public function setNamespace(string $namespace) : void +} +if (!function_exists('PHPUnit\\Framework\\assertThat')) { + /** + * Evaluates a PHPUnit\Framework\Constraint matcher object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertThat + */ + function assertThat($value, Constraint $constraint, string $message = '') : void { - $node = $this->contextNode->getElementsByTagNameNS('https://schema.phpunit.de/coverage/1.0', 'namespace')->item(0); - if (!$node) { - $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'namespace')); - } - $node->setAttribute('name', $namespace); + \PHPUnit\Framework\Assert::assertThat(...func_get_args()); } - public function addMethod(string $name) : Method +} +if (!function_exists('PHPUnit\\Framework\\assertJson')) { + /** + * Asserts that a string is a valid JSON string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJson + */ + function assertJson(string $actualJson, string $message = '') : void { - $node = $this->contextNode->appendChild($this->contextNode->ownerDocument->createElementNS('https://schema.phpunit.de/coverage/1.0', 'method')); - return new Method($node, $name); + \PHPUnit\Framework\Assert::assertJson(...func_get_args()); } - private function setName(string $name) : void +} +if (!function_exists('PHPUnit\\Framework\\assertJsonStringEqualsJsonString')) { + /** + * Asserts that two given JSON encoded objects or arrays are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonString + */ + function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = '') : void { - $this->contextNode->setAttribute('name', $name); + \PHPUnit\Framework\Assert::assertJsonStringEqualsJsonString(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; - -use function count; -use function dirname; -use function file_put_contents; -use function is_string; -use function ksort; -use function max; -use function range; -use function time; -use DOMDocument; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; -final class Clover -{ +if (!function_exists('PHPUnit\\Framework\\assertJsonStringNotEqualsJsonString')) { /** - * @throws WriteOperationFailedException + * Asserts that two given JSON encoded objects or arrays are not equal. + * + * @param string $expectedJson + * @param string $actualJson + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonString */ - public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null) : string + function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = '') : void { - $time = (string) time(); - $xmlDocument = new DOMDocument('1.0', 'UTF-8'); - $xmlDocument->formatOutput = \true; - $xmlCoverage = $xmlDocument->createElement('coverage'); - $xmlCoverage->setAttribute('generated', $time); - $xmlDocument->appendChild($xmlCoverage); - $xmlProject = $xmlDocument->createElement('project'); - $xmlProject->setAttribute('timestamp', $time); - if (is_string($name)) { - $xmlProject->setAttribute('name', $name); - } - $xmlCoverage->appendChild($xmlProject); - $packages = []; - $report = $coverage->getReport(); - foreach ($report as $item) { - if (!$item instanceof File) { - continue; - } - /* @var File $item */ - $xmlFile = $xmlDocument->createElement('file'); - $xmlFile->setAttribute('name', $item->pathAsString()); - $classes = $item->classesAndTraits(); - $coverageData = $item->lineCoverageData(); - $lines = []; - $namespace = 'global'; - foreach ($classes as $className => $class) { - $classStatements = 0; - $coveredClassStatements = 0; - $coveredMethods = 0; - $classMethods = 0; - foreach ($class['methods'] as $methodName => $method) { - if ($method['executableLines'] == 0) { - continue; - } - $classMethods++; - $classStatements += $method['executableLines']; - $coveredClassStatements += $method['executedLines']; - if ($method['coverage'] == 100) { - $coveredMethods++; - } - $methodCount = 0; - foreach (range($method['startLine'], $method['endLine']) as $line) { - if (isset($coverageData[$line]) && $coverageData[$line] !== null) { - $methodCount = max($methodCount, count($coverageData[$line])); - } - } - $lines[$method['startLine']] = ['ccn' => $method['ccn'], 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'visibility' => $method['visibility'], 'name' => $methodName]; - } - if (!empty($class['package']['namespace'])) { - $namespace = $class['package']['namespace']; - } - $xmlClass = $xmlDocument->createElement('class'); - $xmlClass->setAttribute('name', $className); - $xmlClass->setAttribute('namespace', $namespace); - if (!empty($class['package']['fullPackage'])) { - $xmlClass->setAttribute('fullPackage', $class['package']['fullPackage']); - } - if (!empty($class['package']['category'])) { - $xmlClass->setAttribute('category', $class['package']['category']); - } - if (!empty($class['package']['package'])) { - $xmlClass->setAttribute('package', $class['package']['package']); - } - if (!empty($class['package']['subpackage'])) { - $xmlClass->setAttribute('subpackage', $class['package']['subpackage']); - } - $xmlFile->appendChild($xmlClass); - $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); - $xmlMetrics->setAttribute('methods', (string) $classMethods); - $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); - $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); - $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); - $xmlMetrics->setAttribute('statements', (string) $classStatements); - $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); - $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); - $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); - $xmlClass->appendChild($xmlMetrics); - } - foreach ($coverageData as $line => $data) { - if ($data === null || isset($lines[$line])) { - continue; - } - $lines[$line] = ['count' => count($data), 'type' => 'stmt']; - } - ksort($lines); - foreach ($lines as $line => $data) { - $xmlLine = $xmlDocument->createElement('line'); - $xmlLine->setAttribute('num', (string) $line); - $xmlLine->setAttribute('type', $data['type']); - if (isset($data['name'])) { - $xmlLine->setAttribute('name', $data['name']); - } - if (isset($data['visibility'])) { - $xmlLine->setAttribute('visibility', $data['visibility']); - } - if (isset($data['ccn'])) { - $xmlLine->setAttribute('complexity', (string) $data['ccn']); - } - if (isset($data['crap'])) { - $xmlLine->setAttribute('crap', (string) $data['crap']); - } - $xmlLine->setAttribute('count', (string) $data['count']); - $xmlFile->appendChild($xmlLine); - } - $linesOfCode = $item->linesOfCode(); - $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode()); - $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode()); - $xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits()); - $xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods()); - $xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods()); - $xmlMetrics->setAttribute('conditionals', (string) $item->numberOfExecutableBranches()); - $xmlMetrics->setAttribute('coveredconditionals', (string) $item->numberOfExecutedBranches()); - $xmlMetrics->setAttribute('statements', (string) $item->numberOfExecutableLines()); - $xmlMetrics->setAttribute('coveredstatements', (string) $item->numberOfExecutedLines()); - $xmlMetrics->setAttribute('elements', (string) ($item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches())); - $xmlMetrics->setAttribute('coveredelements', (string) ($item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches())); - $xmlFile->appendChild($xmlMetrics); - if ($namespace === 'global') { - $xmlProject->appendChild($xmlFile); - } else { - if (!isset($packages[$namespace])) { - $packages[$namespace] = $xmlDocument->createElement('package'); - $packages[$namespace]->setAttribute('name', $namespace); - $xmlProject->appendChild($packages[$namespace]); - } - $packages[$namespace]->appendChild($xmlFile); - } - } - $linesOfCode = $report->linesOfCode(); - $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('files', (string) count($report)); - $xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode()); - $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode()); - $xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits()); - $xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods()); - $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods()); - $xmlMetrics->setAttribute('conditionals', (string) $report->numberOfExecutableBranches()); - $xmlMetrics->setAttribute('coveredconditionals', (string) $report->numberOfExecutedBranches()); - $xmlMetrics->setAttribute('statements', (string) $report->numberOfExecutableLines()); - $xmlMetrics->setAttribute('coveredstatements', (string) $report->numberOfExecutedLines()); - $xmlMetrics->setAttribute('elements', (string) ($report->numberOfMethods() + $report->numberOfExecutableLines() + $report->numberOfExecutableBranches())); - $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches())); - $xmlProject->appendChild($xmlMetrics); - $buffer = $xmlDocument->saveXML(); - if ($target !== null) { - Directory::create(dirname($target)); - if (@file_put_contents($target, $buffer) === \false) { - throw new WriteOperationFailedException($target); - } - } - return $buffer; + \PHPUnit\Framework\Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Report; - -use function count; -use function dirname; -use function file_put_contents; -use function range; -use function time; -use DOMImplementation; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Directory; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; -use PHPUnit\SebastianBergmann\CodeCoverage\Node\File; -final class Cobertura -{ +if (!function_exists('PHPUnit\\Framework\\assertJsonStringEqualsJsonFile')) { /** - * @throws WriteOperationFailedException + * Asserts that the generated JSON encoded object and the content of the given file are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonFile */ - public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null) : string + function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void { - $time = (string) time(); - $report = $coverage->getReport(); - $implementation = new DOMImplementation(); - $documentType = $implementation->createDocumentType('coverage', '', 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'); - $document = $implementation->createDocument('', '', $documentType); - $document->xmlVersion = '1.0'; - $document->encoding = 'UTF-8'; - $document->formatOutput = \true; - $coverageElement = $document->createElement('coverage'); - $linesValid = $report->numberOfExecutableLines(); - $linesCovered = $report->numberOfExecutedLines(); - $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; - $coverageElement->setAttribute('line-rate', (string) $lineRate); - $branchesValid = $report->numberOfExecutableBranches(); - $branchesCovered = $report->numberOfExecutedBranches(); - $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; - $coverageElement->setAttribute('branch-rate', (string) $branchRate); - $coverageElement->setAttribute('lines-covered', (string) $report->numberOfExecutedLines()); - $coverageElement->setAttribute('lines-valid', (string) $report->numberOfExecutableLines()); - $coverageElement->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches()); - $coverageElement->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches()); - $coverageElement->setAttribute('complexity', ''); - $coverageElement->setAttribute('version', '0.4'); - $coverageElement->setAttribute('timestamp', $time); - $document->appendChild($coverageElement); - $sourcesElement = $document->createElement('sources'); - $coverageElement->appendChild($sourcesElement); - $sourceElement = $document->createElement('source', $report->pathAsString()); - $sourcesElement->appendChild($sourceElement); - $packagesElement = $document->createElement('packages'); - $coverageElement->appendChild($packagesElement); - $complexity = 0; - foreach ($report as $item) { - if (!$item instanceof File) { - continue; - } - $packageElement = $document->createElement('package'); - $packageComplexity = 0; - $packageName = $name ?? ''; - $packageElement->setAttribute('name', $packageName); - $linesValid = $item->numberOfExecutableLines(); - $linesCovered = $item->numberOfExecutedLines(); - $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; - $packageElement->setAttribute('line-rate', (string) $lineRate); - $branchesValid = $item->numberOfExecutableBranches(); - $branchesCovered = $item->numberOfExecutedBranches(); - $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; - $packageElement->setAttribute('branch-rate', (string) $branchRate); - $packageElement->setAttribute('complexity', ''); - $packagesElement->appendChild($packageElement); - $classesElement = $document->createElement('classes'); - $packageElement->appendChild($classesElement); - $classes = $item->classesAndTraits(); - $coverageData = $item->lineCoverageData(); - foreach ($classes as $className => $class) { - $complexity += $class['ccn']; - $packageComplexity += $class['ccn']; - if (!empty($class['package']['namespace'])) { - $className = $class['package']['namespace'] . '\\' . $className; - } - $linesValid = $class['executableLines']; - $linesCovered = $class['executedLines']; - $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; - $branchesValid = $class['executableBranches']; - $branchesCovered = $class['executedBranches']; - $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; - $classElement = $document->createElement('class'); - $classElement->setAttribute('name', $className); - $classElement->setAttribute('filename', \str_replace($report->pathAsString() . \DIRECTORY_SEPARATOR, '', $item->pathAsString())); - $classElement->setAttribute('line-rate', (string) $lineRate); - $classElement->setAttribute('branch-rate', (string) $branchRate); - $classElement->setAttribute('complexity', (string) $class['ccn']); - $classesElement->appendChild($classElement); - $methodsElement = $document->createElement('methods'); - $classElement->appendChild($methodsElement); - $classLinesElement = $document->createElement('lines'); - $classElement->appendChild($classLinesElement); - foreach ($class['methods'] as $methodName => $method) { - if ($method['executableLines'] === 0) { - continue; - } - \preg_match("/\\((.*?)\\)/", $method['signature'], $signature); - $linesValid = $method['executableLines']; - $linesCovered = $method['executedLines']; - $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; - $branchesValid = $method['executableBranches']; - $branchesCovered = $method['executedBranches']; - $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; - $methodElement = $document->createElement('method'); - $methodElement->setAttribute('name', $methodName); - $methodElement->setAttribute('signature', $signature[1]); - $methodElement->setAttribute('line-rate', (string) $lineRate); - $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $method['ccn']); - $methodLinesElement = $document->createElement('lines'); - $methodElement->appendChild($methodLinesElement); - foreach (range($method['startLine'], $method['endLine']) as $line) { - if (!isset($coverageData[$line]) || $coverageData[$line] === null) { - continue; - } - $methodLineElement = $document->createElement('line'); - $methodLineElement->setAttribute('number', (string) $line); - $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); - $methodLinesElement->appendChild($methodLineElement); - $classLineElement = $methodLineElement->cloneNode(); - $classLinesElement->appendChild($classLineElement); - } - $methodsElement->appendChild($methodElement); - } - } - if ($report->numberOfFunctions() === 0) { - $packageElement->setAttribute('complexity', (string) $packageComplexity); - continue; - } - $functionsComplexity = 0; - $functionsLinesValid = 0; - $functionsLinesCovered = 0; - $functionsBranchesValid = 0; - $functionsBranchesCovered = 0; - $classElement = $document->createElement('class'); - $classElement->setAttribute('name', \basename($item->pathAsString())); - $classElement->setAttribute('filename', \str_replace($report->pathAsString() . \DIRECTORY_SEPARATOR, '', $item->pathAsString())); - $methodsElement = $document->createElement('methods'); - $classElement->appendChild($methodsElement); - $classLinesElement = $document->createElement('lines'); - $classElement->appendChild($classLinesElement); - $functions = $report->functions(); - foreach ($functions as $functionName => $function) { - if ($function['executableLines'] === 0) { - continue; - } - $complexity += $function['ccn']; - $packageComplexity += $function['ccn']; - $functionsComplexity += $function['ccn']; - $linesValid = $function['executableLines']; - $linesCovered = $function['executedLines']; - $lineRate = $linesValid === 0 ? 0 : $linesCovered / $linesValid; - $functionsLinesValid += $linesValid; - $functionsLinesCovered += $linesCovered; - $branchesValid = $function['executableBranches']; - $branchesCovered = $function['executedBranches']; - $branchRate = $branchesValid === 0 ? 0 : $branchesCovered / $branchesValid; - $functionsBranchesValid += $branchesValid; - $functionsBranchesCovered += $branchesValid; - $methodElement = $document->createElement('method'); - $methodElement->setAttribute('name', $functionName); - $methodElement->setAttribute('signature', $function['signature']); - $methodElement->setAttribute('line-rate', (string) $lineRate); - $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $function['ccn']); - $methodLinesElement = $document->createElement('lines'); - $methodElement->appendChild($methodLinesElement); - foreach (range($function['startLine'], $function['endLine']) as $line) { - if (!isset($coverageData[$line]) || $coverageData[$line] === null) { - continue; - } - $methodLineElement = $document->createElement('line'); - $methodLineElement->setAttribute('number', (string) $line); - $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); - $methodLinesElement->appendChild($methodLineElement); - $classLineElement = $methodLineElement->cloneNode(); - $classLinesElement->appendChild($classLineElement); - } - $methodsElement->appendChild($methodElement); - } - $packageElement->setAttribute('complexity', (string) $packageComplexity); - if ($functionsLinesValid === 0) { - continue; - } - $lineRate = $functionsLinesCovered / $functionsLinesValid; - $branchRate = $functionsBranchesValid === 0 ? 0 : $functionsBranchesCovered / $functionsBranchesValid; - $classElement->setAttribute('line-rate', (string) $lineRate); - $classElement->setAttribute('branch-rate', (string) $branchRate); - $classElement->setAttribute('complexity', (string) $functionsComplexity); - $classesElement->appendChild($classElement); - } - $coverageElement->setAttribute('complexity', (string) $complexity); - $buffer = $document->saveXML(); - if ($target !== null) { - Directory::create(dirname($target)); - if (@file_put_contents($target, $buffer) === \false) { - throw new WriteOperationFailedException($target); - } - } - return $buffer; + \PHPUnit\Framework\Assert::assertJsonStringEqualsJsonFile(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use function array_diff; -use function array_diff_key; -use function array_flip; -use function array_intersect; -use function array_intersect_key; -use function count; -use function file; -use function in_array; -use function range; -use PHPUnit\SebastianBergmann\CodeCoverage\Driver\Driver; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\UncoveredFileAnalyser; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class RawCodeCoverageData -{ +if (!function_exists('PHPUnit\\Framework\\assertJsonStringNotEqualsJsonFile')) { /** - * @var array> + * Asserts that the generated JSON encoded object and the content of the given file are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonFile */ - private static $emptyLineCache = []; + function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertJsonFileEqualsJsonFile')) { /** - * @var array + * Asserts that two JSON files are equal. * - * @see https://xdebug.org/docs/code_coverage for format + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileEqualsJsonFile */ - private $lineCoverage; + function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void + { + \PHPUnit\Framework\Assert::assertJsonFileEqualsJsonFile(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\assertJsonFileNotEqualsJsonFile')) { /** - * @var array + * Asserts that two JSON files are not equal. * - * @see https://xdebug.org/docs/code_coverage for format + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileNotEqualsJsonFile */ - private $functionCoverage; - public static function fromXdebugWithoutPathCoverage(array $rawCoverage) : self + function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void { - return new self($rawCoverage, []); + \PHPUnit\Framework\Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); } - public static function fromXdebugWithPathCoverage(array $rawCoverage) : self +} +if (!function_exists('PHPUnit\\Framework\\logicalAnd')) { + function logicalAnd() : LogicalAnd { - $lineCoverage = []; - $functionCoverage = []; - foreach ($rawCoverage as $file => $fileCoverageData) { - $lineCoverage[$file] = $fileCoverageData['lines']; - $functionCoverage[$file] = $fileCoverageData['functions']; - } - return new self($lineCoverage, $functionCoverage); + return \PHPUnit\Framework\Assert::logicalAnd(...func_get_args()); } - public static function fromXdebugWithMixedCoverage(array $rawCoverage) : self +} +if (!function_exists('PHPUnit\\Framework\\logicalOr')) { + function logicalOr() : LogicalOr { - $lineCoverage = []; - $functionCoverage = []; - foreach ($rawCoverage as $file => $fileCoverageData) { - if (!isset($fileCoverageData['functions'])) { - // Current file does not have functions, so line coverage - // is stored in $fileCoverageData, not in $fileCoverageData['lines'] - $lineCoverage[$file] = $fileCoverageData; - continue; - } - $lineCoverage[$file] = $fileCoverageData['lines']; - $functionCoverage[$file] = $fileCoverageData['functions']; - } - return new self($lineCoverage, $functionCoverage); + return \PHPUnit\Framework\Assert::logicalOr(...func_get_args()); } - public static function fromUncoveredFile(string $filename, UncoveredFileAnalyser $uncoveredFileAnalyser) : self +} +if (!function_exists('PHPUnit\\Framework\\logicalNot')) { + function logicalNot(Constraint $constraint) : LogicalNot { - $lineCoverage = []; - foreach ($uncoveredFileAnalyser->executableLinesIn($filename) as $line) { - $lineCoverage[$line] = Driver::LINE_NOT_EXECUTED; - } - return new self([$filename => $lineCoverage], []); + return \PHPUnit\Framework\Assert::logicalNot(...func_get_args()); } - private function __construct(array $lineCoverage, array $functionCoverage) +} +if (!function_exists('PHPUnit\\Framework\\logicalXor')) { + function logicalXor() : LogicalXor { - $this->lineCoverage = $lineCoverage; - $this->functionCoverage = $functionCoverage; - $this->skipEmptyLines(); + return \PHPUnit\Framework\Assert::logicalXor(...func_get_args()); } - public function clear() : void +} +if (!function_exists('PHPUnit\\Framework\\anything')) { + function anything() : IsAnything { - $this->lineCoverage = $this->functionCoverage = []; + return \PHPUnit\Framework\Assert::anything(...func_get_args()); } - public function lineCoverage() : array +} +if (!function_exists('PHPUnit\\Framework\\isTrue')) { + function isTrue() : IsTrue { - return $this->lineCoverage; + return \PHPUnit\Framework\Assert::isTrue(...func_get_args()); } - public function functionCoverage() : array +} +if (!function_exists('PHPUnit\\Framework\\callback')) { + function callback(callable $callback) : Callback { - return $this->functionCoverage; + return \PHPUnit\Framework\Assert::callback(...func_get_args()); } - public function removeCoverageDataForFile(string $filename) : void +} +if (!function_exists('PHPUnit\\Framework\\isFalse')) { + function isFalse() : IsFalse { - unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]); + return \PHPUnit\Framework\Assert::isFalse(...func_get_args()); } - /** - * @param int[] $lines - */ - public function keepCoverageDataOnlyForLines(string $filename, array $lines) : void +} +if (!function_exists('PHPUnit\\Framework\\isJson')) { + function isJson() : IsJson { - if (!isset($this->lineCoverage[$filename])) { - return; - } - $this->lineCoverage[$filename] = array_intersect_key($this->lineCoverage[$filename], array_flip($lines)); - if (isset($this->functionCoverage[$filename])) { - foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { - foreach ($functionData['branches'] as $branchId => $branch) { - if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) { - unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); - foreach ($functionData['paths'] as $pathId => $path) { - if (in_array($branchId, $path['path'], \true)) { - unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); - } - } - } - } - } - } + return \PHPUnit\Framework\Assert::isJson(...func_get_args()); } - /** - * @param int[] $lines - */ - public function removeCoverageDataForLines(string $filename, array $lines) : void +} +if (!function_exists('PHPUnit\\Framework\\isNull')) { + function isNull() : IsNull { - if (empty($lines)) { - return; - } - if (!isset($this->lineCoverage[$filename])) { - return; - } - $this->lineCoverage[$filename] = array_diff_key($this->lineCoverage[$filename], array_flip($lines)); - if (isset($this->functionCoverage[$filename])) { - foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { - foreach ($functionData['branches'] as $branchId => $branch) { - if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) { - unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); - foreach ($functionData['paths'] as $pathId => $path) { - if (in_array($branchId, $path['path'], \true)) { - unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); - } - } - } - } - } - } + return \PHPUnit\Framework\Assert::isNull(...func_get_args()); } - /** - * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has - * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine - * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines - * are skipped over for coverage purposes. - * - * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799 - */ - private function skipEmptyLines() : void +} +if (!function_exists('PHPUnit\\Framework\\isFinite')) { + function isFinite() : IsFinite { - foreach ($this->lineCoverage as $filename => $coverage) { - foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) { - unset($this->lineCoverage[$filename][$emptyLine]); - } - } + return \PHPUnit\Framework\Assert::isFinite(...func_get_args()); } - private function getEmptyLinesForFile(string $filename) : array +} +if (!function_exists('PHPUnit\\Framework\\isInfinite')) { + function isInfinite() : IsInfinite { - if (!isset(self::$emptyLineCache[$filename])) { - self::$emptyLineCache[$filename] = []; - if (\is_file($filename)) { - $sourceLines = \explode("\n", \file_get_contents($filename)); - foreach ($sourceLines as $line => $source) { - if (\trim($source) === '') { - self::$emptyLineCache[$filename][] = $line + 1; - } - } - } - } - return self::$emptyLineCache[$filename]; + return \PHPUnit\Framework\Assert::isInfinite(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class CrapIndex -{ - /** - * @var int - */ - private $cyclomaticComplexity; - /** - * @var float - */ - private $codeCoverage; - public static function fromCyclomaticComplexityAndCoveragePercentage(int $cyclomaticComplexity, float $codeCoverage) : self +if (!function_exists('PHPUnit\\Framework\\isNan')) { + function isNan() : IsNan { - return new self($cyclomaticComplexity, $codeCoverage); + return \PHPUnit\Framework\Assert::isNan(...func_get_args()); } - public function __construct(int $cyclomaticComplexity, float $codeCoverage) +} +if (!function_exists('PHPUnit\\Framework\\containsEqual')) { + function containsEqual($value) : TraversableContainsEqual { - $this->cyclomaticComplexity = $cyclomaticComplexity; - $this->codeCoverage = $codeCoverage; + return \PHPUnit\Framework\Assert::containsEqual(...func_get_args()); } - public function asString() : string +} +if (!function_exists('PHPUnit\\Framework\\containsIdentical')) { + function containsIdentical($value) : TraversableContainsIdentical { - if ($this->codeCoverage === 0.0) { - return (string) ($this->cyclomaticComplexity ** 2 + $this->cyclomaticComplexity); - } - if ($this->codeCoverage >= 95) { - return (string) $this->cyclomaticComplexity; - } - return sprintf('%01.2F', $this->cyclomaticComplexity ** 2 * (1 - $this->codeCoverage / 100) ** 3 + $this->cyclomaticComplexity); + return \PHPUnit\Framework\Assert::containsIdentical(...func_get_args()); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; - -use function array_filter; -use function count; -use function range; -use PHPUnit\SebastianBergmann\CodeCoverage\CrapIndex; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class File extends AbstractNode -{ - /** - * @var array - */ - private $lineCoverageData; - /** - * @var array - */ - private $functionCoverageData; - /** - * @var array - */ - private $testData; - /** - * @var int - */ - private $numExecutableLines = 0; - /** - * @var int - */ - private $numExecutedLines = 0; - /** - * @var int - */ - private $numExecutableBranches = 0; - /** - * @var int - */ - private $numExecutedBranches = 0; - /** - * @var int - */ - private $numExecutablePaths = 0; - /** - * @var int - */ - private $numExecutedPaths = 0; - /** - * @var array - */ - private $classes = []; - /** - * @var array - */ - private $traits = []; - /** - * @var array - */ - private $functions = []; - /** - * @var LinesOfCode - */ - private $linesOfCode; - /** - * @var int - */ - private $numClasses; - /** - * @var int - */ - private $numTestedClasses = 0; - /** - * @var int - */ - private $numTraits; - /** - * @var int - */ - private $numTestedTraits = 0; - /** - * @var int - */ - private $numMethods; - /** - * @var int - */ - private $numTestedMethods; - /** - * @var int - */ - private $numTestedFunctions; - /** - * @var array - */ - private $codeUnitsByLine = []; - public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, LinesOfCode $linesOfCode) +if (!function_exists('PHPUnit\\Framework\\containsOnly')) { + function containsOnly(string $type) : TraversableContainsOnly { - parent::__construct($name, $parent); - $this->lineCoverageData = $lineCoverageData; - $this->functionCoverageData = $functionCoverageData; - $this->testData = $testData; - $this->linesOfCode = $linesOfCode; - $this->calculateStatistics($classes, $traits, $functions); + return \PHPUnit\Framework\Assert::containsOnly(...func_get_args()); } - public function count() : int +} +if (!function_exists('PHPUnit\\Framework\\containsOnlyInstancesOf')) { + function containsOnlyInstancesOf(string $className) : TraversableContainsOnly { - return 1; + return \PHPUnit\Framework\Assert::containsOnlyInstancesOf(...func_get_args()); } - public function lineCoverageData() : array +} +if (!function_exists('PHPUnit\\Framework\\arrayHasKey')) { + function arrayHasKey($key) : ArrayHasKey { - return $this->lineCoverageData; + return \PHPUnit\Framework\Assert::arrayHasKey(...func_get_args()); } - public function functionCoverageData() : array +} +if (!function_exists('PHPUnit\\Framework\\equalTo')) { + function equalTo($value) : IsEqual { - return $this->functionCoverageData; + return \PHPUnit\Framework\Assert::equalTo(...func_get_args()); } - public function testData() : array +} +if (!function_exists('PHPUnit\\Framework\\equalToCanonicalizing')) { + function equalToCanonicalizing($value) : IsEqualCanonicalizing { - return $this->testData; + return \PHPUnit\Framework\Assert::equalToCanonicalizing(...func_get_args()); } - public function classes() : array +} +if (!function_exists('PHPUnit\\Framework\\equalToIgnoringCase')) { + function equalToIgnoringCase($value) : IsEqualIgnoringCase { - return $this->classes; + return \PHPUnit\Framework\Assert::equalToIgnoringCase(...func_get_args()); } - public function traits() : array +} +if (!function_exists('PHPUnit\\Framework\\equalToWithDelta')) { + function equalToWithDelta($value, float $delta) : IsEqualWithDelta { - return $this->traits; + return \PHPUnit\Framework\Assert::equalToWithDelta(...func_get_args()); } - public function functions() : array +} +if (!function_exists('PHPUnit\\Framework\\isEmpty')) { + function isEmpty() : IsEmpty { - return $this->functions; + return \PHPUnit\Framework\Assert::isEmpty(...func_get_args()); } - public function linesOfCode() : LinesOfCode +} +if (!function_exists('PHPUnit\\Framework\\isWritable')) { + function isWritable() : IsWritable { - return $this->linesOfCode; + return \PHPUnit\Framework\Assert::isWritable(...func_get_args()); } - public function numberOfExecutableLines() : int +} +if (!function_exists('PHPUnit\\Framework\\isReadable')) { + function isReadable() : IsReadable { - return $this->numExecutableLines; + return \PHPUnit\Framework\Assert::isReadable(...func_get_args()); } - public function numberOfExecutedLines() : int +} +if (!function_exists('PHPUnit\\Framework\\directoryExists')) { + function directoryExists() : DirectoryExists { - return $this->numExecutedLines; + return \PHPUnit\Framework\Assert::directoryExists(...func_get_args()); } - public function numberOfExecutableBranches() : int +} +if (!function_exists('PHPUnit\\Framework\\fileExists')) { + function fileExists() : FileExists { - return $this->numExecutableBranches; + return \PHPUnit\Framework\Assert::fileExists(...func_get_args()); } - public function numberOfExecutedBranches() : int +} +if (!function_exists('PHPUnit\\Framework\\greaterThan')) { + function greaterThan($value) : GreaterThan { - return $this->numExecutedBranches; + return \PHPUnit\Framework\Assert::greaterThan(...func_get_args()); } - public function numberOfExecutablePaths() : int +} +if (!function_exists('PHPUnit\\Framework\\greaterThanOrEqual')) { + function greaterThanOrEqual($value) : LogicalOr { - return $this->numExecutablePaths; + return \PHPUnit\Framework\Assert::greaterThanOrEqual(...func_get_args()); } - public function numberOfExecutedPaths() : int +} +if (!function_exists('PHPUnit\\Framework\\classHasAttribute')) { + function classHasAttribute(string $attributeName) : ClassHasAttribute { - return $this->numExecutedPaths; + return \PHPUnit\Framework\Assert::classHasAttribute(...func_get_args()); } - public function numberOfClasses() : int +} +if (!function_exists('PHPUnit\\Framework\\classHasStaticAttribute')) { + function classHasStaticAttribute(string $attributeName) : ClassHasStaticAttribute { - if ($this->numClasses === null) { - $this->numClasses = 0; - foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { - $this->numClasses++; - continue 2; - } - } - } - } - return $this->numClasses; + return \PHPUnit\Framework\Assert::classHasStaticAttribute(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\objectHasAttribute')) { + function objectHasAttribute($attributeName) : ObjectHasAttribute + { + return \PHPUnit\Framework\Assert::objectHasAttribute(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\identicalTo')) { + function identicalTo($value) : IsIdentical + { + return \PHPUnit\Framework\Assert::identicalTo(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\isInstanceOf')) { + function isInstanceOf(string $className) : IsInstanceOf + { + return \PHPUnit\Framework\Assert::isInstanceOf(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\isType')) { + function isType(string $type) : IsType + { + return \PHPUnit\Framework\Assert::isType(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\lessThan')) { + function lessThan($value) : LessThan + { + return \PHPUnit\Framework\Assert::lessThan(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\lessThanOrEqual')) { + function lessThanOrEqual($value) : LogicalOr + { + return \PHPUnit\Framework\Assert::lessThanOrEqual(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\matchesRegularExpression')) { + function matchesRegularExpression(string $pattern) : RegularExpression + { + return \PHPUnit\Framework\Assert::matchesRegularExpression(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\matches')) { + function matches(string $string) : StringMatchesFormatDescription + { + return \PHPUnit\Framework\Assert::matches(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\stringStartsWith')) { + function stringStartsWith($prefix) : StringStartsWith + { + return \PHPUnit\Framework\Assert::stringStartsWith(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\stringContains')) { + function stringContains(string $string, bool $case = \true) : StringContains + { + return \PHPUnit\Framework\Assert::stringContains(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\stringEndsWith')) { + function stringEndsWith(string $suffix) : StringEndsWith + { + return \PHPUnit\Framework\Assert::stringEndsWith(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\countOf')) { + function countOf(int $count) : Count + { + return \PHPUnit\Framework\Assert::countOf(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\objectEquals')) { + function objectEquals(object $object, string $method = 'equals') : ObjectEquals + { + return \PHPUnit\Framework\Assert::objectEquals(...func_get_args()); + } +} +if (!function_exists('PHPUnit\\Framework\\any')) { + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + function any() : AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher(); + } +} +if (!function_exists('PHPUnit\\Framework\\never')) { + /** + * Returns a matcher that matches when the method is never executed. + */ + function never() : InvokedCountMatcher + { + return new InvokedCountMatcher(0); } - public function numberOfTestedClasses() : int +} +if (!function_exists('PHPUnit\\Framework\\atLeast')) { + /** + * Returns a matcher that matches when the method is executed + * at least N times. + */ + function atLeast(int $requiredInvocations) : InvokedAtLeastCountMatcher { - return $this->numTestedClasses; + return new InvokedAtLeastCountMatcher($requiredInvocations); } - public function numberOfTraits() : int +} +if (!function_exists('PHPUnit\\Framework\\atLeastOnce')) { + /** + * Returns a matcher that matches when the method is executed at least once. + */ + function atLeastOnce() : InvokedAtLeastOnceMatcher { - if ($this->numTraits === null) { - $this->numTraits = 0; - foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { - $this->numTraits++; - continue 2; - } - } - } - } - return $this->numTraits; + return new InvokedAtLeastOnceMatcher(); } - public function numberOfTestedTraits() : int +} +if (!function_exists('PHPUnit\\Framework\\once')) { + /** + * Returns a matcher that matches when the method is executed exactly once. + */ + function once() : InvokedCountMatcher { - return $this->numTestedTraits; + return new InvokedCountMatcher(1); } - public function numberOfMethods() : int +} +if (!function_exists('PHPUnit\\Framework\\exactly')) { + /** + * Returns a matcher that matches when the method is executed + * exactly $count times. + */ + function exactly(int $count) : InvokedCountMatcher { - if ($this->numMethods === null) { - $this->numMethods = 0; - foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { - $this->numMethods++; - } - } - } - foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { - $this->numMethods++; - } - } - } - } - return $this->numMethods; + return new InvokedCountMatcher($count); } - public function numberOfTestedMethods() : int +} +if (!function_exists('PHPUnit\\Framework\\atMost')) { + /** + * Returns a matcher that matches when the method is executed + * at most N times. + */ + function atMost(int $allowedInvocations) : InvokedAtMostCountMatcher { - if ($this->numTestedMethods === null) { - $this->numTestedMethods = 0; - foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0 && $method['coverage'] === 100) { - $this->numTestedMethods++; - } - } - } - foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0 && $method['coverage'] === 100) { - $this->numTestedMethods++; - } - } - } - } - return $this->numTestedMethods; + return new InvokedAtMostCountMatcher($allowedInvocations); } - public function numberOfFunctions() : int +} +if (!function_exists('PHPUnit\\Framework\\at')) { + /** + * Returns a matcher that matches when the method is executed + * at the given index. + */ + function at(int $index) : InvokedAtIndexMatcher { - return count($this->functions); + return new InvokedAtIndexMatcher($index); } - public function numberOfTestedFunctions() : int +} +if (!function_exists('PHPUnit\\Framework\\returnValue')) { + function returnValue($value) : ReturnStub { - if ($this->numTestedFunctions === null) { - $this->numTestedFunctions = 0; - foreach ($this->functions as $function) { - if ($function['executableLines'] > 0 && $function['coverage'] === 100) { - $this->numTestedFunctions++; - } - } - } - return $this->numTestedFunctions; + return new ReturnStub($value); } - private function calculateStatistics(array $classes, array $traits, array $functions) : void +} +if (!function_exists('PHPUnit\\Framework\\returnValueMap')) { + function returnValueMap(array $valueMap) : ReturnValueMapStub { - foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) { - $this->codeUnitsByLine[$lineNumber] = []; - } - $this->processClasses($classes); - $this->processTraits($traits); - $this->processFunctions($functions); - foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) { - if (isset($this->lineCoverageData[$lineNumber])) { - foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executableLines']++; - } - unset($codeUnit); - $this->numExecutableLines++; - if (count($this->lineCoverageData[$lineNumber]) > 0) { - foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executedLines']++; - } - unset($codeUnit); - $this->numExecutedLines++; - } - } - } - foreach ($this->traits as &$trait) { - foreach ($trait['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] ? $method['executedLines'] / $method['executableLines'] * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] ? $method['executedBranches'] / $method['executableBranches'] * 100 : 0; - $methodPathCoverage = $method['executablePaths'] ? $method['executedPaths'] / $method['executablePaths'] * 100 : 0; - $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); - $trait['ccn'] += $method['ccn']; - } - unset($method); - $traitLineCoverage = $trait['executableLines'] ? $trait['executedLines'] / $trait['executableLines'] * 100 : 100; - $traitBranchCoverage = $trait['executableBranches'] ? $trait['executedBranches'] / $trait['executableBranches'] * 100 : 0; - $traitPathCoverage = $trait['executablePaths'] ? $trait['executedPaths'] / $trait['executablePaths'] * 100 : 0; - $trait['coverage'] = $traitBranchCoverage ?: $traitLineCoverage; - $trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage ?: $traitLineCoverage))->asString(); - if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) { - $this->numTestedClasses++; - } - } - unset($trait); - foreach ($this->classes as &$class) { - foreach ($class['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] ? $method['executedLines'] / $method['executableLines'] * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] ? $method['executedBranches'] / $method['executableBranches'] * 100 : 0; - $methodPathCoverage = $method['executablePaths'] ? $method['executedPaths'] / $method['executablePaths'] * 100 : 0; - $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); - $class['ccn'] += $method['ccn']; - } - unset($method); - $classLineCoverage = $class['executableLines'] ? $class['executedLines'] / $class['executableLines'] * 100 : 100; - $classBranchCoverage = $class['executableBranches'] ? $class['executedBranches'] / $class['executableBranches'] * 100 : 0; - $classPathCoverage = $class['executablePaths'] ? $class['executedPaths'] / $class['executablePaths'] * 100 : 0; - $class['coverage'] = $classBranchCoverage ?: $classLineCoverage; - $class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage ?: $classLineCoverage))->asString(); - if ($class['executableLines'] > 0 && $class['coverage'] === 100) { - $this->numTestedClasses++; - } - } - unset($class); - foreach ($this->functions as &$function) { - $functionLineCoverage = $function['executableLines'] ? $function['executedLines'] / $function['executableLines'] * 100 : 100; - $functionBranchCoverage = $function['executableBranches'] ? $function['executedBranches'] / $function['executableBranches'] * 100 : 0; - $functionPathCoverage = $function['executablePaths'] ? $function['executedPaths'] / $function['executablePaths'] * 100 : 0; - $function['coverage'] = $functionBranchCoverage ?: $functionLineCoverage; - $function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage ?: $functionLineCoverage))->asString(); - if ($function['coverage'] === 100) { - $this->numTestedFunctions++; - } - } + return new ReturnValueMapStub($valueMap); } - private function processClasses(array $classes) : void +} +if (!function_exists('PHPUnit\\Framework\\returnArgument')) { + function returnArgument(int $argumentIndex) : ReturnArgumentStub { - $link = $this->id() . '.html#'; - foreach ($classes as $className => $class) { - $this->classes[$className] = ['className' => $className, 'namespace' => $class['namespace'], 'methods' => [], 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'link' => $link . $class['startLine']]; - foreach ($class['methods'] as $methodName => $method) { - $methodData = $this->newMethod($className, $methodName, $method, $link); - $this->classes[$className]['methods'][$methodName] = $methodData; - $this->classes[$className]['executableBranches'] += $methodData['executableBranches']; - $this->classes[$className]['executedBranches'] += $methodData['executedBranches']; - $this->classes[$className]['executablePaths'] += $methodData['executablePaths']; - $this->classes[$className]['executedPaths'] += $methodData['executedPaths']; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; - foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { - $this->codeUnitsByLine[$lineNumber] = [&$this->classes[$className], &$this->classes[$className]['methods'][$methodName]]; - } - } - } + return new ReturnArgumentStub($argumentIndex); } - private function processTraits(array $traits) : void +} +if (!function_exists('PHPUnit\\Framework\\returnCallback')) { + function returnCallback($callback) : ReturnCallbackStub { - $link = $this->id() . '.html#'; - foreach ($traits as $traitName => $trait) { - $this->traits[$traitName] = ['traitName' => $traitName, 'namespace' => $trait['namespace'], 'methods' => [], 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'link' => $link . $trait['startLine']]; - foreach ($trait['methods'] as $methodName => $method) { - $methodData = $this->newMethod($traitName, $methodName, $method, $link); - $this->traits[$traitName]['methods'][$methodName] = $methodData; - $this->traits[$traitName]['executableBranches'] += $methodData['executableBranches']; - $this->traits[$traitName]['executedBranches'] += $methodData['executedBranches']; - $this->traits[$traitName]['executablePaths'] += $methodData['executablePaths']; - $this->traits[$traitName]['executedPaths'] += $methodData['executedPaths']; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; - foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { - $this->codeUnitsByLine[$lineNumber] = [&$this->traits[$traitName], &$this->traits[$traitName]['methods'][$methodName]]; - } - } - } + return new ReturnCallbackStub($callback); } - private function processFunctions(array $functions) : void +} +if (!function_exists('PHPUnit\\Framework\\returnSelf')) { + /** + * Returns the current object. + * + * This method is useful when mocking a fluent interface. + */ + function returnSelf() : ReturnSelfStub { - $link = $this->id() . '.html#'; - foreach ($functions as $functionName => $function) { - $this->functions[$functionName] = ['functionName' => $functionName, 'namespace' => $function['namespace'], 'signature' => $function['signature'], 'startLine' => $function['startLine'], 'endLine' => $function['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $function['startLine']]; - foreach (range($function['startLine'], $function['endLine']) as $lineNumber) { - $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; - } - if (isset($this->functionCoverageData[$functionName]['branches'])) { - $this->functions[$functionName]['executableBranches'] = count($this->functionCoverageData[$functionName]['branches']); - $this->functions[$functionName]['executedBranches'] = count(array_filter($this->functionCoverageData[$functionName]['branches'], static function (array $branch) { - return (bool) $branch['hit']; - })); - } - if (isset($this->functionCoverageData[$functionName]['paths'])) { - $this->functions[$functionName]['executablePaths'] = count($this->functionCoverageData[$functionName]['paths']); - $this->functions[$functionName]['executedPaths'] = count(array_filter($this->functionCoverageData[$functionName]['paths'], static function (array $path) { - return (bool) $path['hit']; - })); - } - $this->numExecutableBranches += $this->functions[$functionName]['executableBranches']; - $this->numExecutedBranches += $this->functions[$functionName]['executedBranches']; - $this->numExecutablePaths += $this->functions[$functionName]['executablePaths']; - $this->numExecutedPaths += $this->functions[$functionName]['executedPaths']; - } + return new ReturnSelfStub(); } - private function newMethod(string $className, string $methodName, array $method, string $link) : array +} +if (!function_exists('PHPUnit\\Framework\\throwException')) { + function throwException(Throwable $exception) : ExceptionStub { - $methodData = ['methodName' => $methodName, 'visibility' => $method['visibility'], 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, 'executedBranches' => 0, 'executablePaths' => 0, 'executedPaths' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine']]; - $key = $className . '->' . $methodName; - if (isset($this->functionCoverageData[$key]['branches'])) { - $methodData['executableBranches'] = count($this->functionCoverageData[$key]['branches']); - $methodData['executedBranches'] = count(array_filter($this->functionCoverageData[$key]['branches'], static function (array $branch) { - return (bool) $branch['hit']; - })); - } - if (isset($this->functionCoverageData[$key]['paths'])) { - $methodData['executablePaths'] = count($this->functionCoverageData[$key]['paths']); - $methodData['executedPaths'] = count(array_filter($this->functionCoverageData[$key]['paths'], static function (array $path) { - return (bool) $path['hit']; - })); - } - return $methodData; + return new ExceptionStub($exception); + } +} +if (!function_exists('PHPUnit\\Framework\\onConsecutiveCalls')) { + function onConsecutiveCalls() : ConsecutiveCallsStub + { + $args = func_get_args(); + return new ConsecutiveCallsStub($args); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; +namespace PHPUnit\Framework\Constraint; -use const DIRECTORY_SEPARATOR; -use function array_shift; -use function basename; -use function count; -use function dirname; -use function explode; -use function implode; -use function is_file; -use function str_replace; -use function strpos; -use function substr; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; -use PHPUnit\SebastianBergmann\CodeCoverage\StaticAnalysis\CoveredFileAnalyser; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class Builder +final class IsFalse extends \PHPUnit\Framework\Constraint\Constraint { /** - * @var CoveredFileAnalyser + * Returns a string representation of the constraint. */ - private $coveredFileAnalyser; - public function __construct(CoveredFileAnalyser $coveredFileAnalyser) - { - $this->coveredFileAnalyser = $coveredFileAnalyser; - } - public function build(CodeCoverage $coverage) : Directory + public function toString() : string { - $data = clone $coverage->getData(); - // clone because path munging is destructive to the original data - $commonPath = $this->reducePaths($data); - $root = new Directory($commonPath, null); - $this->addItems($root, $this->buildDirectoryStructure($data), $coverage->getTests()); - return $root; + return 'is false'; } - private function addItems(Directory $root, array $items, array $tests) : void + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - foreach ($items as $key => $value) { - $key = (string) $key; - if (substr($key, -2) === '/f') { - $key = substr($key, 0, -2); - $filename = $root->pathAsString() . \DIRECTORY_SEPARATOR . $key; - if (is_file($filename)) { - $root->addFile(new File($key, $root, $value['lineCoverage'], $value['functionCoverage'], $tests, $this->coveredFileAnalyser->classesIn($filename), $this->coveredFileAnalyser->traitsIn($filename), $this->coveredFileAnalyser->functionsIn($filename), $this->coveredFileAnalyser->linesOfCodeFor($filename))); - } - } else { - $child = $root->addDirectory($key); - $this->addItems($child, $value, $tests); - } - } + return $other === \false; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsTrue extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Builds an array representation of the directory structure. - * - * For instance, - * - * - * Array - * ( - * [Money.php] => Array - * ( - * ... - * ) - * - * [MoneyBag.php] => Array - * ( - * ... - * ) - * ) - * - * - * is transformed into - * - * - * Array - * ( - * [.] => Array - * ( - * [Money.php] => Array - * ( - * ... - * ) - * - * [MoneyBag.php] => Array - * ( - * ... - * ) - * ) - * ) - * + * Returns a string representation of the constraint. */ - private function buildDirectoryStructure(ProcessedCodeCoverageData $data) : array + public function toString() : string { - $result = []; - foreach ($data->coveredFiles() as $originalPath) { - $path = explode(\DIRECTORY_SEPARATOR, $originalPath); - $pointer =& $result; - $max = count($path); - for ($i = 0; $i < $max; $i++) { - $type = ''; - if ($i === $max - 1) { - $type = '/f'; - } - $pointer =& $pointer[$path[$i] . $type]; - } - $pointer = ['lineCoverage' => $data->lineCoverage()[$originalPath] ?? [], 'functionCoverage' => $data->functionCoverage()[$originalPath] ?? []]; - } - return $result; + return 'is true'; } /** - * Reduces the paths by cutting the longest common start path. - * - * For instance, - * - * - * Array - * ( - * [/home/sb/Money/Money.php] => Array - * ( - * ... - * ) - * - * [/home/sb/Money/MoneyBag.php] => Array - * ( - * ... - * ) - * ) - * - * - * is reduced to - * - * - * Array - * ( - * [Money.php] => Array - * ( - * ... - * ) + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * [MoneyBag.php] => Array - * ( - * ... - * ) - * ) - * + * @param mixed $other value or object to evaluate */ - private function reducePaths(ProcessedCodeCoverageData $coverage) : string + protected function matches($other) : bool { - if (empty($coverage->coveredFiles())) { - return '.'; - } - $commonPath = ''; - $paths = $coverage->coveredFiles(); - if (count($paths) === 1) { - $commonPath = dirname($paths[0]) . \DIRECTORY_SEPARATOR; - $coverage->renameFile($paths[0], basename($paths[0])); - return $commonPath; - } - $max = count($paths); - for ($i = 0; $i < $max; $i++) { - // strip phar:// prefixes - if (strpos($paths[$i], 'phar://') === 0) { - $paths[$i] = substr($paths[$i], 7); - $paths[$i] = str_replace('/', \DIRECTORY_SEPARATOR, $paths[$i]); - } - $paths[$i] = explode(\DIRECTORY_SEPARATOR, $paths[$i]); - if (empty($paths[$i][0])) { - $paths[$i][0] = \DIRECTORY_SEPARATOR; - } - } - $done = \false; - $max = count($paths); - while (!$done) { - for ($i = 0; $i < $max - 1; $i++) { - if (!isset($paths[$i][0]) || !isset($paths[$i + 1][0]) || $paths[$i][0] !== $paths[$i + 1][0]) { - $done = \true; - break; - } - } - if (!$done) { - $commonPath .= $paths[0][0]; - if ($paths[0][0] !== \DIRECTORY_SEPARATOR) { - $commonPath .= \DIRECTORY_SEPARATOR; - } - for ($i = 0; $i < $max; $i++) { - array_shift($paths[$i]); - } - } - } - $original = $coverage->coveredFiles(); - $max = count($original); - for ($i = 0; $i < $max; $i++) { - $coverage->renameFile($original[$i], implode(\DIRECTORY_SEPARATOR, $paths[$i])); - } - return substr($commonPath, 0, -1); + return $other === \true; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @psalm-template CallbackInput of mixed + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Callback extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * @var callable + * + * @psalm-var callable(CallbackInput $input): bool + */ + private $callback; + /** @psalm-param callable(CallbackInput $input): bool $callback */ + public function __construct(callable $callback) + { + $this->callback = $callback; + } + /** + * Returns a string representation of the constraint. + */ + public function toString() : string + { + return 'is accepted by specified callback'; + } + /** + * Evaluates the constraint for parameter $value. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + * + * @psalm-param CallbackInput $other + */ + protected function matches($other) : bool + { + return ($this->callback)($other); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; +namespace PHPUnit\Framework\Constraint; use function count; -use RecursiveIterator; +use function is_array; +use function iterator_count; +use function sprintf; +use Countable; +use EmptyIterator; +use Generator; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\Exception; +use Traversable; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class Iterator implements RecursiveIterator +class Count extends \PHPUnit\Framework\Constraint\Constraint { /** * @var int */ - private $position; - /** - * @var AbstractNode[] - */ - private $nodes; - public function __construct(Directory $node) + private $expectedCount; + public function __construct(int $expected) { - $this->nodes = $node->children(); + $this->expectedCount = $expected; + } + public function toString() : string + { + return sprintf('count matches %d', $this->expectedCount); } /** - * Rewinds the Iterator to the first element. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @throws Exception */ - public function rewind() : void + protected function matches($other) : bool { - $this->position = 0; + return $this->expectedCount === $this->getCountOf($other); } /** - * Checks if there is a current element after calls to rewind() or next(). + * @throws Exception */ - public function valid() : bool + protected function getCountOf($other) : ?int { - return $this->position < count($this->nodes); + if ($other instanceof Countable || is_array($other)) { + return count($other); + } + if ($other instanceof EmptyIterator) { + return 0; + } + if ($other instanceof Traversable) { + while ($other instanceof IteratorAggregate) { + try { + $other = $other->getIterator(); + } catch (\Exception $e) { + throw new Exception($e->getMessage(), $e->getCode(), $e); + } + } + $iterator = $other; + if ($iterator instanceof Generator) { + return $this->getCountOfGenerator($iterator); + } + if (!$iterator instanceof Iterator) { + return iterator_count($iterator); + } + $key = $iterator->key(); + $count = iterator_count($iterator); + // Manually rewind $iterator to previous key, since iterator_count + // moves pointer. + if ($key !== null) { + $iterator->rewind(); + while ($iterator->valid() && $key !== $iterator->key()) { + $iterator->next(); + } + } + return $count; + } + return null; } /** - * Returns the key of the current element. + * Returns the total number of iterations from a generator. + * This will fully exhaust the generator. */ - public function key() : int + protected function getCountOfGenerator(Generator $generator) : int { - return $this->position; + for ($count = 0; $generator->valid(); $generator->next()) { + $count++; + } + return $count; } /** - * Returns the current element. + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object */ - public function current() : ?AbstractNode + protected function failureDescription($other) : string { - return $this->valid() ? $this->nodes[$this->position] : null; + return sprintf('actual size %d matches expected size %d', (int) $this->getCountOf($other), $this->expectedCount); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class GreaterThan extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Moves forward to next element. + * @var float|int */ - public function next() : void + private $value; + /** + * @param float|int $value + */ + public function __construct($value) { - $this->position++; + $this->value = $value; } /** - * Returns the sub iterator for the current element. + * Returns a string representation of the constraint. * - * @return Iterator + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function getChildren() : self + public function toString() : string { - return new self($this->nodes[$this->position]); + return 'is greater than ' . $this->exporter()->export($this->value); } /** - * Checks whether the current element has children. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - public function hasChildren() : bool + protected function matches($other) : bool { - return $this->nodes[$this->position] instanceof Directory; + return $this->value < $other; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; +namespace PHPUnit\Framework\Constraint; -use function array_merge; use function count; -use IteratorAggregate; -use RecursiveIteratorIterator; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; +use function gettype; +use function sprintf; +use function strpos; +use Countable; +use EmptyIterator; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class Directory extends AbstractNode implements IteratorAggregate +final class IsEmpty extends \PHPUnit\Framework\Constraint\Constraint { /** - * @var AbstractNode[] - */ - private $children = []; - /** - * @var Directory[] - */ - private $directories = []; - /** - * @var File[] - */ - private $files = []; - /** - * @var array - */ - private $classes; - /** - * @var array - */ - private $traits; - /** - * @var array - */ - private $functions; - /** - * @var LinesOfCode - */ - private $linesOfCode; - /** - * @var int - */ - private $numFiles = -1; - /** - * @var int - */ - private $numExecutableLines = -1; - /** - * @var int - */ - private $numExecutedLines = -1; - /** - * @var int - */ - private $numExecutableBranches = -1; - /** - * @var int - */ - private $numExecutedBranches = -1; - /** - * @var int - */ - private $numExecutablePaths = -1; - /** - * @var int - */ - private $numExecutedPaths = -1; - /** - * @var int - */ - private $numClasses = -1; - /** - * @var int - */ - private $numTestedClasses = -1; - /** - * @var int - */ - private $numTraits = -1; - /** - * @var int - */ - private $numTestedTraits = -1; - /** - * @var int - */ - private $numMethods = -1; - /** - * @var int - */ - private $numTestedMethods = -1; - /** - * @var int - */ - private $numFunctions = -1; - /** - * @var int + * Returns a string representation of the constraint. */ - private $numTestedFunctions = -1; - public function count() : int - { - if ($this->numFiles === -1) { - $this->numFiles = 0; - foreach ($this->children as $child) { - $this->numFiles += count($child); - } - } - return $this->numFiles; - } - public function getIterator() : RecursiveIteratorIterator - { - return new RecursiveIteratorIterator(new Iterator($this), RecursiveIteratorIterator::SELF_FIRST); - } - public function addDirectory(string $name) : self - { - $directory = new self($name, $this); - $this->children[] = $directory; - $this->directories[] =& $this->children[count($this->children) - 1]; - return $directory; - } - public function addFile(File $file) : void - { - $this->children[] = $file; - $this->files[] =& $this->children[count($this->children) - 1]; - $this->numExecutableLines = -1; - $this->numExecutedLines = -1; - } - public function directories() : array - { - return $this->directories; - } - public function files() : array - { - return $this->files; - } - public function children() : array - { - return $this->children; - } - public function classes() : array - { - if ($this->classes === null) { - $this->classes = []; - foreach ($this->children as $child) { - $this->classes = array_merge($this->classes, $child->classes()); - } - } - return $this->classes; - } - public function traits() : array - { - if ($this->traits === null) { - $this->traits = []; - foreach ($this->children as $child) { - $this->traits = array_merge($this->traits, $child->traits()); - } - } - return $this->traits; - } - public function functions() : array - { - if ($this->functions === null) { - $this->functions = []; - foreach ($this->children as $child) { - $this->functions = array_merge($this->functions, $child->functions()); - } - } - return $this->functions; - } - public function linesOfCode() : LinesOfCode - { - if ($this->linesOfCode === null) { - $this->linesOfCode = new LinesOfCode(0, 0, 0, 0); - foreach ($this->children as $child) { - $this->linesOfCode = $this->linesOfCode->plus($child->linesOfCode()); - } - } - return $this->linesOfCode; - } - public function numberOfExecutableLines() : int - { - if ($this->numExecutableLines === -1) { - $this->numExecutableLines = 0; - foreach ($this->children as $child) { - $this->numExecutableLines += $child->numberOfExecutableLines(); - } - } - return $this->numExecutableLines; - } - public function numberOfExecutedLines() : int - { - if ($this->numExecutedLines === -1) { - $this->numExecutedLines = 0; - foreach ($this->children as $child) { - $this->numExecutedLines += $child->numberOfExecutedLines(); - } - } - return $this->numExecutedLines; - } - public function numberOfExecutableBranches() : int - { - if ($this->numExecutableBranches === -1) { - $this->numExecutableBranches = 0; - foreach ($this->children as $child) { - $this->numExecutableBranches += $child->numberOfExecutableBranches(); - } - } - return $this->numExecutableBranches; - } - public function numberOfExecutedBranches() : int - { - if ($this->numExecutedBranches === -1) { - $this->numExecutedBranches = 0; - foreach ($this->children as $child) { - $this->numExecutedBranches += $child->numberOfExecutedBranches(); - } - } - return $this->numExecutedBranches; - } - public function numberOfExecutablePaths() : int - { - if ($this->numExecutablePaths === -1) { - $this->numExecutablePaths = 0; - foreach ($this->children as $child) { - $this->numExecutablePaths += $child->numberOfExecutablePaths(); - } - } - return $this->numExecutablePaths; - } - public function numberOfExecutedPaths() : int - { - if ($this->numExecutedPaths === -1) { - $this->numExecutedPaths = 0; - foreach ($this->children as $child) { - $this->numExecutedPaths += $child->numberOfExecutedPaths(); - } - } - return $this->numExecutedPaths; - } - public function numberOfClasses() : int - { - if ($this->numClasses === -1) { - $this->numClasses = 0; - foreach ($this->children as $child) { - $this->numClasses += $child->numberOfClasses(); - } - } - return $this->numClasses; - } - public function numberOfTestedClasses() : int - { - if ($this->numTestedClasses === -1) { - $this->numTestedClasses = 0; - foreach ($this->children as $child) { - $this->numTestedClasses += $child->numberOfTestedClasses(); - } - } - return $this->numTestedClasses; - } - public function numberOfTraits() : int - { - if ($this->numTraits === -1) { - $this->numTraits = 0; - foreach ($this->children as $child) { - $this->numTraits += $child->numberOfTraits(); - } - } - return $this->numTraits; - } - public function numberOfTestedTraits() : int - { - if ($this->numTestedTraits === -1) { - $this->numTestedTraits = 0; - foreach ($this->children as $child) { - $this->numTestedTraits += $child->numberOfTestedTraits(); - } - } - return $this->numTestedTraits; - } - public function numberOfMethods() : int + public function toString() : string { - if ($this->numMethods === -1) { - $this->numMethods = 0; - foreach ($this->children as $child) { - $this->numMethods += $child->numberOfMethods(); - } - } - return $this->numMethods; + return 'is empty'; } - public function numberOfTestedMethods() : int + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - if ($this->numTestedMethods === -1) { - $this->numTestedMethods = 0; - foreach ($this->children as $child) { - $this->numTestedMethods += $child->numberOfTestedMethods(); - } + if ($other instanceof EmptyIterator) { + return \true; } - return $this->numTestedMethods; - } - public function numberOfFunctions() : int - { - if ($this->numFunctions === -1) { - $this->numFunctions = 0; - foreach ($this->children as $child) { - $this->numFunctions += $child->numberOfFunctions(); - } + if ($other instanceof Countable) { + return count($other) === 0; } - return $this->numFunctions; + return empty($other); } - public function numberOfTestedFunctions() : int - { - if ($this->numTestedFunctions === -1) { - $this->numTestedFunctions = 0; - foreach ($this->children as $child) { - $this->numTestedFunctions += $child->numberOfTestedFunctions(); - } - } - return $this->numTestedFunctions; + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other) : string + { + $type = gettype($other); + return sprintf('%s %s %s', strpos($type, 'a') === 0 || strpos($type, 'o') === 0 ? 'an' : 'a', $type, $this->toString()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage\Node; +namespace PHPUnit\Framework\Constraint; -use const DIRECTORY_SEPARATOR; -use function array_merge; -use function str_replace; -use function substr; -use Countable; -use PHPUnit\SebastianBergmann\CodeCoverage\Percentage; -use PHPUnit\SebastianBergmann\LinesOfCode\LinesOfCode; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -abstract class AbstractNode implements Countable +final class LessThan extends \PHPUnit\Framework\Constraint\Constraint { /** - * @var string - */ - private $name; - /** - * @var string - */ - private $pathAsString; - /** - * @var array - */ - private $pathAsArray; - /** - * @var AbstractNode + * @var float|int */ - private $parent; + private $value; /** - * @var string + * @param float|int $value */ - private $id; - public function __construct(string $name, self $parent = null) - { - if (substr($name, -1) === \DIRECTORY_SEPARATOR) { - $name = substr($name, 0, -1); - } - $this->name = $name; - $this->parent = $parent; - } - public function name() : string - { - return $this->name; - } - public function id() : string - { - if ($this->id === null) { - $parent = $this->parent(); - if ($parent === null) { - $this->id = 'index'; - } else { - $parentId = $parent->id(); - if ($parentId === 'index') { - $this->id = str_replace(':', '_', $this->name); - } else { - $this->id = $parentId . '/' . $this->name; - } - } - } - return $this->id; - } - public function pathAsString() : string - { - if ($this->pathAsString === null) { - if ($this->parent === null) { - $this->pathAsString = $this->name; - } else { - $this->pathAsString = $this->parent->pathAsString() . \DIRECTORY_SEPARATOR . $this->name; - } - } - return $this->pathAsString; - } - public function pathAsArray() : array - { - if ($this->pathAsArray === null) { - if ($this->parent === null) { - $this->pathAsArray = []; - } else { - $this->pathAsArray = $this->parent->pathAsArray(); - } - $this->pathAsArray[] = $this; - } - return $this->pathAsArray; - } - public function parent() : ?self - { - return $this->parent; - } - public function percentageOfTestedClasses() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedClasses(), $this->numberOfClasses()); - } - public function percentageOfTestedTraits() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedTraits(), $this->numberOfTraits()); - } - public function percentageOfTestedClassesAndTraits() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedClassesAndTraits(), $this->numberOfClassesAndTraits()); - } - public function percentageOfTestedFunctions() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedFunctions(), $this->numberOfFunctions()); - } - public function percentageOfTestedMethods() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedMethods(), $this->numberOfMethods()); - } - public function percentageOfTestedFunctionsAndMethods() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfTestedFunctionsAndMethods(), $this->numberOfFunctionsAndMethods()); - } - public function percentageOfExecutedLines() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfExecutedLines(), $this->numberOfExecutableLines()); - } - public function percentageOfExecutedBranches() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfExecutedBranches(), $this->numberOfExecutableBranches()); - } - public function percentageOfExecutedPaths() : Percentage - { - return Percentage::fromFractionAndTotal($this->numberOfExecutedPaths(), $this->numberOfExecutablePaths()); - } - public function numberOfClassesAndTraits() : int - { - return $this->numberOfClasses() + $this->numberOfTraits(); - } - public function numberOfTestedClassesAndTraits() : int - { - return $this->numberOfTestedClasses() + $this->numberOfTestedTraits(); - } - public function classesAndTraits() : array + public function __construct($value) { - return array_merge($this->classes(), $this->traits()); + $this->value = $value; } - public function numberOfFunctionsAndMethods() : int + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString() : string { - return $this->numberOfFunctions() + $this->numberOfMethods(); + return 'is less than ' . $this->exporter()->export($this->value); } - public function numberOfTestedFunctionsAndMethods() : int + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - return $this->numberOfTestedFunctions() + $this->numberOfTestedMethods(); + return $this->value > $other; } - public abstract function classes() : array; - public abstract function traits() : array; - public abstract function functions() : array; - public abstract function linesOfCode() : LinesOfCode; - public abstract function numberOfExecutableLines() : int; - public abstract function numberOfExecutedLines() : int; - public abstract function numberOfExecutableBranches() : int; - public abstract function numberOfExecutedBranches() : int; - public abstract function numberOfExecutablePaths() : int; - public abstract function numberOfExecutedPaths() : int; - public abstract function numberOfClasses() : int; - public abstract function numberOfTestedClasses() : int; - public abstract function numberOfTraits() : int; - public abstract function numberOfTestedTraits() : int; - public abstract function numberOfMethods() : int; - public abstract function numberOfTestedMethods() : int; - public abstract function numberOfFunctions() : int; - public abstract function numberOfTestedFunctions() : int; } -php-code-coverage - -Copyright (c) 2009-2021, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\CodeCoverage; +namespace PHPUnit\Framework\Constraint; -use function is_dir; -use function mkdir; -use function sprintf; /** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class Directory +final class SameSize extends \PHPUnit\Framework\Constraint\Count { - /** - * @throws DirectoryCouldNotBeCreatedException - */ - public static function create(string $directory) : void + public function __construct(iterable $expected) { - $success = !(!is_dir($directory) && !@mkdir($directory, 0777, \true) && !is_dir($directory)); - if (!$success) { - throw new DirectoryCouldNotBeCreatedException(sprintf('Directory "%s" could not be created', $directory)); - } + parent::__construct((int) $this->getCountOf($expected)); } } importNode($element, \true); + $success = \false; + if ($this->matches($other)) { + $success = \true; + } + if ($returnResult) { + return $success; + } + if (!$success) { + $this->fail($other, $description); + } + return null; } /** - * @deprecated Only used by assertEqualXMLStructure() + * Counts the number of constraint elements. */ - public static function removeCharacterDataNodes(DOMNode $node) : void + public function count() : int { - if ($node->hasChildNodes()) { - for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { - if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { - $node->removeChild($child); - } - } + return 1; + } + protected function exporter() : Exporter + { + if ($this->exporter === null) { + $this->exporter = new Exporter(); } + return $this->exporter; } /** - * Escapes a string for the use in XML documents. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, - * and FFFF (not even as character reference). + * This method can be overridden to implement the evaluation algorithm. * - * @see https://www.w3.org/TR/xml/#charsets + * @param mixed $other value or object to evaluate + * + * @codeCoverageIgnore */ - public static function prepareString(string $string) : string + protected function matches($other) : bool { - return preg_replace('/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', htmlspecialchars(self::convertToUtf8($string), \ENT_QUOTES)); + return \false; } /** - * "Convert" a DOMElement object into a PHP variable. + * Throws an exception for the given compared value and test description. + * + * @param mixed $other evaluated value or object + * @param string $description Additional information about the test + * @param ComparisonFailure $comparisonFailure + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-return never-return */ - public static function xmlToVariable(DOMElement $element) + protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) : void { - $variable = null; - switch ($element->tagName) { - case 'array': - $variable = []; - foreach ($element->childNodes as $entry) { - if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { - continue; - } - $item = $entry->childNodes->item(0); - if ($item instanceof DOMText) { - $item = $entry->childNodes->item(1); - } - $value = self::xmlToVariable($item); - if ($entry->hasAttribute('key')) { - $variable[(string) $entry->getAttribute('key')] = $value; - } else { - $variable[] = $value; - } - } - break; - case 'object': - $className = $element->getAttribute('class'); - if ($element->hasChildNodes()) { - $arguments = $element->childNodes->item(0)->childNodes; - $constructorArgs = []; - foreach ($arguments as $argument) { - if ($argument instanceof DOMElement) { - $constructorArgs[] = self::xmlToVariable($argument); - } - } - try { - assert(class_exists($className)); - $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Util\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } else { - $variable = new $className(); - } - break; - case 'boolean': - $variable = $element->textContent === 'true'; - break; - case 'integer': - case 'double': - case 'string': - $variable = $element->textContent; - settype($variable, $element->tagName); - break; + $failureDescription = sprintf('Failed asserting that %s.', $this->failureDescription($other)); + $additionalFailureDescription = $this->additionalFailureDescription($other); + if ($additionalFailureDescription) { + $failureDescription .= "\n" . $additionalFailureDescription; } - return $variable; + if (!empty($description)) { + $failureDescription = $description . "\n" . $failureDescription; + } + throw new ExpectationFailedException($failureDescription, $comparisonFailure); } - private static function convertToUtf8(string $string) : string + /** + * Return additional failure description where needed. + * + * The function can be overridden to provide additional failure + * information like a diff + * + * @param mixed $other evaluated value or object + */ + protected function additionalFailureDescription($other) : string { - if (!self::isUtf8($string)) { - $string = mb_convert_encoding($string, 'UTF-8'); + return ''; + } + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * To provide additional failure information additionalFailureDescription + * can be used. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other) : string + { + return $this->exporter()->export($other) . ' ' . $this->toString(); + } + /** + * Returns a custom string representation of the constraint object when it + * appears in context of an $operator expression. + * + * The purpose of this method is to provide meaningful descriptive string + * in context of operators such as LogicalNot. Native PHPUnit constraints + * are supported out of the box by LogicalNot, but externally developed + * ones had no way to provide correct strings in this context. + * + * The method shall return empty string, when it does not handle + * customization by itself. + * + * @param Operator $operator the $operator of the expression + * @param mixed $role role of $this constraint in the $operator expression + */ + protected function toStringInContext(\PHPUnit\Framework\Constraint\Operator $operator, $role) : string + { + return ''; + } + /** + * Returns the description of the failure when this constraint appears in + * context of an $operator expression. + * + * The purpose of this method is to provide meaningful failure description + * in context of operators such as LogicalNot. Native PHPUnit constraints + * are supported out of the box by LogicalNot, but externally developed + * ones had no way to provide correct messages in this context. + * + * The method shall return empty string, when it does not handle + * customization by itself. + * + * @param Operator $operator the $operator of the expression + * @param mixed $role role of $this constraint in the $operator expression + * @param mixed $other evaluated value or object + */ + protected function failureDescriptionInContext(\PHPUnit\Framework\Constraint\Operator $operator, $role, $other) : string + { + $string = $this->toStringInContext($operator, $role); + if ($string === '') { + return ''; } - return $string; + return $this->exporter()->export($other) . ' ' . $string; } - private static function isUtf8(string $string) : bool + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. + * + * Returns $this for terminal constraints and for operators that start + * non-reducible sub-expression, or the nearest descendant of $this that + * starts a non-reducible sub-expression. + * + * A constraint expression may be modelled as a tree with non-terminal + * nodes (operators) and terminal nodes. For example: + * + * LogicalOr (operator, non-terminal) + * + LogicalAnd (operator, non-terminal) + * | + IsType('int') (terminal) + * | + GreaterThan(10) (terminal) + * + LogicalNot (operator, non-terminal) + * + IsType('array') (terminal) + * + * A degenerate sub-expression is a part of the tree, that effectively does + * not contribute to the evaluation of the expression it appears in. An example + * of degenerate sub-expression is a BinaryOperator constructed with single + * operand or nested BinaryOperators, each with single operand. An + * expression involving a degenerate sub-expression is equivalent to a + * reduced expression with the degenerate sub-expression removed, for example + * + * LogicalAnd (operator) + * + LogicalOr (degenerate operator) + * | + LogicalAnd (degenerate operator) + * | + IsType('int') (terminal) + * + GreaterThan(10) (terminal) + * + * is equivalent to + * + * LogicalAnd (operator) + * + IsType('int') (terminal) + * + GreaterThan(10) (terminal) + * + * because the subexpression + * + * + LogicalOr + * + LogicalAnd + * + - + * + * is degenerate. Calling reduce() on the LogicalOr object above, as well + * as on LogicalAnd, shall return the IsType('int') instance. + * + * Other specific reductions can be implemented, for example cascade of + * LogicalNot operators + * + * + LogicalNot + * + LogicalNot + * +LogicalNot + * + IsTrue + * + * can be reduced to + * + * LogicalNot + * + IsTrue + */ + protected function reduce() : self { - $length = strlen($string); - for ($i = 0; $i < $length; $i++) { - if (ord($string[$i]) < 0x80) { - $n = 0; - } elseif ((ord($string[$i]) & 0xe0) === 0xc0) { - $n = 1; - } elseif ((ord($string[$i]) & 0xf0) === 0xe0) { - $n = 2; - } elseif ((ord($string[$i]) & 0xf0) === 0xf0) { - $n = 3; - } else { - return \false; - } - for ($j = 0; $j < $n; $j++) { - if (++$i === $length || (ord($string[$i]) & 0xc0) !== 0x80) { - return \false; - } - } - } - return \true; + return $this; } } value = $value; + $this->delta = $delta; + $this->canonicalize = $canonicalize; + $this->ignoreCase = $ignoreCase; } /** - * @param string[] $files + * Evaluates the constraint for parameter $other. * - * @throws Exception + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + * + * @return bool */ - public static function processIncludedFilesAsString(array $files) : string - { - $excludeList = new \PHPUnit\Util\ExcludeList(); - $prefix = \false; - $result = ''; - if (defined('__PHPUNIT_PHAR__')) { - $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/'; - } - // Do not process bootstrap script - unset($files[0]); - foreach (array_reverse($files) as $file) { - if (!empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], \true)) { - continue; - } - if ($prefix !== \false && strpos($file, $prefix) === 0) { - continue; - } - // Skip virtual file system protocols - if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { - continue; - } - if (!$excludeList->isExcluded($file) && is_file($file)) { - $result = 'require_once \'' . $file . "';\n" . $result; - } - } - return $result; - } - public static function getIniSettingsAsString() : string + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - $result = ''; - foreach (ini_get_all(null, \false) as $key => $value) { - $result .= sprintf('@ini_set(%s, %s);' . "\n", self::exportVariable($key), self::exportVariable((string) $value)); + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return \true; } - return $result; - } - public static function getConstantsAsString() : string - { - $constants = get_defined_constants(\true); - $result = ''; - if (isset($constants['user'])) { - foreach ($constants['user'] as $name => $value) { - $result .= sprintf('if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, self::exportVariable($value)); + $comparatorFactory = ComparatorFactory::getInstance(); + try { + $comparator = $comparatorFactory->getComparatorFor($this->value, $other); + $comparator->assertEquals($this->value, $other, $this->delta, $this->canonicalize, $this->ignoreCase); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return \false; } + throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); } - return $result; + return \true; } - public static function getGlobalsAsString() : string + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString() : string { - $result = ''; - foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { - if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { - foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { - if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { - continue; - } - $result .= sprintf('$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, self::exportVariable($GLOBALS[$superGlobalArray][$key])); - } - } - } - $excludeList = self::SUPER_GLOBAL_ARRAYS; - $excludeList[] = 'GLOBALS'; - foreach (array_keys($GLOBALS) as $key) { - if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $excludeList, \true)) { - $result .= sprintf('$GLOBALS[\'%s\'] = %s;' . "\n", $key, self::exportVariable($GLOBALS[$key])); + $delta = ''; + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== \false) { + return 'is equal to '; } + return sprintf("is equal to '%s'", $this->value); } - return $result; - } - private static function exportVariable($variable) : string - { - if (is_scalar($variable) || $variable === null || is_array($variable) && self::arrayOnlyContainsScalars($variable)) { - return var_export($variable, \true); - } - return 'unserialize(' . var_export(serialize($variable), \true) . ')'; - } - private static function arrayOnlyContainsScalars(array $array) : bool - { - $result = \true; - foreach ($array as $element) { - if (is_array($element)) { - $result = self::arrayOnlyContainsScalars($element); - } elseif (!is_scalar($element) && $element !== null) { - $result = \false; - } - if (!$result) { - break; - } + if ($this->delta != 0) { + $delta = sprintf(' with delta <%F>', $this->delta); } - return $result; + return sprintf('is equal to %s%s', $this->exporter()->export($this->value), $delta); } } value = $value; + } + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool + { + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return \true; + } + $comparatorFactory = ComparatorFactory::getInstance(); + try { + $comparator = $comparatorFactory->getComparatorFor($this->value, $other); + $comparator->assertEquals($this->value, $other, 0.0, \true, \false); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return \false; + } + throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); + } + return \true; + } + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString() : string + { + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== \false) { + return 'is equal to '; + } + return sprintf("is equal to '%s'", $this->value); + } + return sprintf('is equal to %s', $this->exporter()->export($this->value)); + } } document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = \true; - $this->root = $this->document->createElement('testsuites'); - $this->document->appendChild($this->root); - parent::__construct($out); - $this->reportRiskyTests = $reportRiskyTests; - } - /** - * Flush buffer and close output. - */ - public function flush() : void - { - $this->write($this->getXML()); - parent::flush(); - } - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time) : void - { - $this->doAddFault($test, $t, 'error'); - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time) : void - { - $this->doAddFault($test, $e, 'warning'); - $this->testSuiteWarnings[$this->testSuiteLevel]++; - } - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void - { - $this->doAddFault($test, $e, 'failure'); - $this->testSuiteFailures[$this->testSuiteLevel]++; - } - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void - { - $this->doAddSkipped(); - } - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void - { - if (!$this->reportRiskyTests) { - return; - } - $this->doAddFault($test, $t, 'error'); - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void - { - $this->doAddSkipped(); - } - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite) : void - { - $testSuite = $this->document->createElement('testsuite'); - $testSuite->setAttribute('name', $suite->getName()); - if (class_exists($suite->getName(), \false)) { - try { - $class = new ReflectionClass($suite->getName()); - $testSuite->setAttribute('file', $class->getFileName()); - } catch (ReflectionException $e) { - } - } - if ($this->testSuiteLevel > 0) { - $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); - } else { - $this->root->appendChild($testSuite); - } - $this->testSuiteLevel++; - $this->testSuites[$this->testSuiteLevel] = $testSuite; - $this->testSuiteTests[$this->testSuiteLevel] = 0; - $this->testSuiteAssertions[$this->testSuiteLevel] = 0; - $this->testSuiteErrors[$this->testSuiteLevel] = 0; - $this->testSuiteWarnings[$this->testSuiteLevel] = 0; - $this->testSuiteFailures[$this->testSuiteLevel] = 0; - $this->testSuiteSkipped[$this->testSuiteLevel] = 0; - $this->testSuiteTimes[$this->testSuiteLevel] = 0; - } - /** - * A testsuite ended. + * @var mixed */ - public function endTestSuite(TestSuite $suite) : void + private $value; + public function __construct($value) { - $this->testSuites[$this->testSuiteLevel]->setAttribute('tests', (string) $this->testSuiteTests[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('assertions', (string) $this->testSuiteAssertions[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('errors', (string) $this->testSuiteErrors[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('warnings', (string) $this->testSuiteWarnings[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('failures', (string) $this->testSuiteFailures[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('skipped', (string) $this->testSuiteSkipped[$this->testSuiteLevel]); - $this->testSuites[$this->testSuiteLevel]->setAttribute('time', sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])); - if ($this->testSuiteLevel > 1) { - $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; - $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; - $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; - $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; - $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; - $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; - $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; - } - $this->testSuiteLevel--; + $this->value = $value; } /** - * A test started. + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException */ - public function startTest(Test $test) : void + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - $usesDataprovider = \false; - if (method_exists($test, 'usesDataProvider')) { - $usesDataprovider = $test->usesDataProvider(); - } - $testCase = $this->document->createElement('testcase'); - $testCase->setAttribute('name', $test->getName()); - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $methodName = $test->getName(!$usesDataprovider); - if ($class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return \true; + } + $comparatorFactory = ComparatorFactory::getInstance(); + try { + $comparator = $comparatorFactory->getComparatorFor($this->value, $other); + $comparator->assertEquals($this->value, $other, 0.0, \false, \true); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return \false; } - // @codeCoverageIgnoreEnd - $testCase->setAttribute('class', $class->getName()); - $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); - $testCase->setAttribute('file', $class->getFileName()); - $testCase->setAttribute('line', (string) $method->getStartLine()); + throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); } - $this->currentTestCase = $testCase; + return \true; } /** - * A test ended. + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function endTest(Test $test, float $time) : void + public function toString() : string { - $numAssertions = 0; - if (method_exists($test, 'getNumAssertions')) { - $numAssertions = $test->getNumAssertions(); - } - $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; - $this->currentTestCase->setAttribute('assertions', (string) $numAssertions); - $this->currentTestCase->setAttribute('time', sprintf('%F', $time)); - $this->testSuites[$this->testSuiteLevel]->appendChild($this->currentTestCase); - $this->testSuiteTests[$this->testSuiteLevel]++; - $this->testSuiteTimes[$this->testSuiteLevel] += $time; - $testOutput = ''; - if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { - $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; - } - if (!empty($testOutput)) { - $systemOut = $this->document->createElement('system-out', Xml::prepareString($testOutput)); - $this->currentTestCase->appendChild($systemOut); + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== \false) { + return 'is equal to '; + } + return sprintf("is equal to '%s'", $this->value); } - $this->currentTestCase = null; + return sprintf('is equal to %s', $this->exporter()->export($this->value)); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEqualWithDelta extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Returns the XML as a string. + * @var mixed */ - public function getXML() : string + private $value; + /** + * @var float + */ + private $delta; + public function __construct($value, float $delta) { - return $this->document->saveXML(); + $this->value = $value; + $this->delta = $delta; } - private function doAddFault(Test $test, Throwable $t, string $type) : void + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - if ($this->currentTestCase === null) { - return; - } - if ($test instanceof SelfDescribing) { - $buffer = $test->toString() . "\n"; - } else { - $buffer = ''; + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return \true; } - $buffer .= trim(TestFailure::exceptionToString($t) . "\n" . Filter::getFilteredStacktrace($t)); - $fault = $this->document->createElement($type, Xml::prepareString($buffer)); - if ($t instanceof ExceptionWrapper) { - $fault->setAttribute('type', $t->getClassName()); - } else { - $fault->setAttribute('type', get_class($t)); + $comparatorFactory = ComparatorFactory::getInstance(); + try { + $comparator = $comparatorFactory->getComparatorFor($this->value, $other); + $comparator->assertEquals($this->value, $other, $this->delta); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return \false; + } + throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); } - $this->currentTestCase->appendChild($fault); + return \true; } - private function doAddSkipped() : void + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString() : string { - if ($this->currentTestCase === null) { - return; - } - $skipped = $this->document->createElement('skipped'); - $this->currentTestCase->appendChild($skipped); - $this->testSuiteSkipped[$this->testSuiteLevel]++; + return sprintf('is equal to %s with delta <%F>>', $this->exporter()->export($this->value), $this->delta); } } printHeader($result); - $this->printFooter($result); + $this->className = $className; } /** - * An error occurred. + * Returns a string representation of the constraint. */ - public function addError(Test $test, Throwable $t, float $time) : void + public function toString() : string { - $this->printEvent('testFailed', ['name' => $test->getName(), 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time)]); + return sprintf('exception of type "%s"', $this->className); } /** - * A warning occurred. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - public function addWarning(Test $test, Warning $e, float $time) : void + protected function matches($other) : bool { - $this->write(self::getMessage($e) . \PHP_EOL); + return $other instanceof $this->className; } /** - * A failure occurred. + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + protected function failureDescription($other) : string { - $parameters = ['name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), 'duration' => self::toMilliseconds($time)]; - if ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - if ($comparisonFailure instanceof ComparisonFailure) { - $expectedString = $comparisonFailure->getExpectedAsString(); - if ($expectedString === null || empty($expectedString)) { - $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); - } - $actualString = $comparisonFailure->getActualAsString(); - if ($actualString === null || empty($actualString)) { - $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); - } - if ($actualString !== null && $expectedString !== null) { - $parameters['type'] = 'comparisonFailure'; - $parameters['actual'] = $actualString; - $parameters['expected'] = $expectedString; - } + if ($other !== null) { + $message = ''; + if ($other instanceof Throwable) { + $message = '. Message was: "' . $other->getMessage() . '" at' . "\n" . Filter::getFilteredStacktrace($other); } + return sprintf('exception of type "%s" matches expected exception "%s"%s', get_class($other), $this->className, $message); } - $this->printEvent('testFailed', $parameters); + return sprintf('exception of type "%s" is thrown', $this->className); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionCode extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Incomplete test. + * @var int|string */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void - { - $this->printIgnoredTest($test->getName(), $t, $time); - } + private $expectedCode; /** - * Risky test. + * @param int|string $expected */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void + public function __construct($expected) { - $this->addError($test, $t, $time); + $this->expectedCode = $expected; + } + public function toString() : string + { + return 'exception code is '; } /** - * Skipped test. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param Throwable $other */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void + protected function matches($other) : bool { - $testName = $test->getName(); - if ($this->startedTestName !== $testName) { - $this->startTest($test); - $this->printIgnoredTest($testName, $t, $time); - $this->endTest($test, $time); - } else { - $this->printIgnoredTest($testName, $t, $time); - } + return (string) $other->getCode() === (string) $this->expectedCode; } - public function printIgnoredTest(string $testName, Throwable $t, float $time) : void + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other) : string { - $this->printEvent('testIgnored', ['name' => $testName, 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time)]); + return sprintf('%s is equal to expected exception code %s', $this->exporter()->export($other->getCode()), $this->exporter()->export($this->expectedCode)); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function strpos; +use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessage extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * A testsuite started. + * @var string */ - public function startTestSuite(TestSuite $suite) : void + private $expectedMessage; + public function __construct(string $expected) { - if (stripos(ini_get('disable_functions'), 'getmypid') === \false) { - $this->flowId = getmypid(); - } else { - $this->flowId = \false; - } - if (!$this->isSummaryTestCountPrinted) { - $this->isSummaryTestCountPrinted = \true; - $this->printEvent('testCount', ['count' => count($suite)]); - } - $suiteName = $suite->getName(); - if (empty($suiteName)) { - return; - } - $parameters = ['name' => $suiteName]; - if (class_exists($suiteName, \false)) { - $fileName = self::getFileName($suiteName); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - } else { - $split = explode('::', $suiteName); - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $fileName = self::getFileName($split[0]); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - $parameters['name'] = $split[1]; - } + $this->expectedMessage = $expected; + } + public function toString() : string + { + if ($this->expectedMessage === '') { + return 'exception message is empty'; } - $this->printEvent('testSuiteStarted', $parameters); + return 'exception message contains '; } /** - * A testsuite ended. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param Throwable $other */ - public function endTestSuite(TestSuite $suite) : void + protected function matches($other) : bool { - $suiteName = $suite->getName(); - if (empty($suiteName)) { - return; - } - $parameters = ['name' => $suiteName]; - if (!class_exists($suiteName, \false)) { - $split = explode('::', $suiteName); - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $parameters['name'] = $split[1]; - } + if ($this->expectedMessage === '') { + return $other->getMessage() === ''; } - $this->printEvent('testSuiteFinished', $parameters); + return strpos((string) $other->getMessage(), $this->expectedMessage) !== \false; } /** - * A test started. + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object */ - public function startTest(Test $test) : void + protected function failureDescription($other) : string { - $testName = $test->getName(); - $this->startedTestName = $testName; - $params = ['name' => $testName]; - if ($test instanceof TestCase) { - $className = get_class($test); - $fileName = self::getFileName($className); - $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; + if ($this->expectedMessage === '') { + return sprintf("exception message is empty but is '%s'", $other->getMessage()); } - $this->printEvent('testStarted', $params); + return sprintf("exception message '%s' contains '%s'", $other->getMessage(), $this->expectedMessage); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use Exception; +use PHPUnit\Util\RegularExpression as RegularExpressionUtil; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageRegularExpression extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * A test ended. + * @var string */ - public function endTest(Test $test, float $time) : void - { - parent::endTest($test, $time); - $this->printEvent('testFinished', ['name' => $test->getName(), 'duration' => self::toMilliseconds($time)]); - } - protected function writeProgress(string $progress) : void - { - } - private function printEvent(string $eventName, array $params = []) : void + private $expectedMessageRegExp; + public function __construct(string $expected) { - $this->write("\n##teamcity[{$eventName}"); - if ($this->flowId) { - $params['flowId'] = $this->flowId; - } - foreach ($params as $key => $value) { - $escapedValue = self::escapeValue((string) $value); - $this->write(" {$key}='{$escapedValue}'"); - } - $this->write("]\n"); + $this->expectedMessageRegExp = $expected; } - private static function getMessage(Throwable $t) : string + public function toString() : string { - $message = ''; - if ($t instanceof ExceptionWrapper) { - if ($t->getClassName() !== '') { - $message .= $t->getClassName(); - } - if ($message !== '' && $t->getMessage() !== '') { - $message .= ' : '; - } - } - return $message . $t->getMessage(); + return 'exception message matches '; } - private static function getDetails(Throwable $t) : string + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param \PHPUnit\Framework\Exception $other + * + * @throws \PHPUnit\Framework\Exception + * @throws Exception + */ + protected function matches($other) : bool { - $stackTrace = Filter::getFilteredStacktrace($t); - $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); - while ($previous) { - $stackTrace .= "\nCaused by\n" . TestFailure::exceptionToString($previous) . "\n" . Filter::getFilteredStacktrace($previous); - $previous = $previous instanceof ExceptionWrapper ? $previous->getPreviousWrapped() : $previous->getPrevious(); + $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); + if ($match === \false) { + throw new \PHPUnit\Framework\Exception("Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'"); } - return ' ' . str_replace("\n", "\n ", $stackTrace); + return $match === 1; } - private static function getPrimitiveValueAsString($value) : ?string + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other) : string { - if ($value === null) { - return 'null'; - } - if (is_bool($value)) { - return $value ? 'true' : 'false'; - } - if (is_scalar($value)) { - return print_r($value, \true); - } - return null; + return sprintf("exception message '%s' matches '%s'", $other->getMessage(), $this->expectedMessageRegExp); } - private static function escapeValue(string $text) : string +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_dir; +use function sprintf; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class DirectoryExists extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - return str_replace(['|', "'", "\n", "\r", ']', '['], ['||', "|'", '|n', '|r', '|]', '|['], $text); + return 'directory exists'; } /** - * @param string $className + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - private static function getFileName($className) : string + protected function matches($other) : bool { - try { - return (new ReflectionClass($className))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return is_dir($other); } /** - * @param float $time microseconds + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object */ - private static function toMilliseconds(float $time) : int + protected function failureDescription($other) : string { - return (int) round($time * 1000); + return sprintf('directory "%s" exists', $other); } } getExcludedDirectories(); + return file_exists($other); } /** - * @throws Exception + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object */ - public function isBlacklisted(string $file) : bool + protected function failureDescription($other) : string { - return (new \PHPUnit\Util\ExcludeList())->isExcluded($file); + return sprintf('file "%s" exists', $other); } } getSyntheticTrace(); - $eFile = $t->getSyntheticFile(); - $eLine = $t->getSyntheticLine(); - } elseif ($t instanceof Exception) { - $eTrace = $t->getSerializableTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); - } else { - if ($t->getPrevious()) { - $t = $t->getPrevious(); - } - $eTrace = $t->getTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); - } - if (!self::frameExists($eTrace, $eFile, $eLine)) { - array_unshift($eTrace, ['file' => $eFile, 'line' => $eLine]); - } - $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? __PHPUNIT_PHAR_ROOT__ : \false; - $excludeList = new \PHPUnit\Util\ExcludeList(); - foreach ($eTrace as $frame) { - if (self::shouldPrintFrame($frame, $prefix, $excludeList)) { - $filteredStacktrace .= sprintf("%s:%s\n", $frame['file'], $frame['line'] ?? '?'); - } - } - return $filteredStacktrace; - } - private static function shouldPrintFrame(array $frame, $prefix, \PHPUnit\Util\ExcludeList $excludeList) : bool + public function toString() : string { - if (!isset($frame['file'])) { - return \false; - } - $file = $frame['file']; - $fileIsNotPrefixed = $prefix === \false || strpos($file, $prefix) !== 0; - // @see https://github.com/sebastianbergmann/phpunit/issues/4033 - if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { - $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); - } else { - $script = ''; - } - return is_file($file) && self::fileIsExcluded($file, $excludeList) && $fileIsNotPrefixed && $file !== $script; + return 'is readable'; } - private static function fileIsExcluded(string $file, \PHPUnit\Util\ExcludeList $excludeList) : bool + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - return (empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], \true)) && !$excludeList->isExcluded($file); + return is_readable($other); } - private static function frameExists(array $trace, string $file, int $line) : bool + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other) : string { - foreach ($trace as $frame) { - if (isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line) { - return \true; - } - } - return \false; + return sprintf('"%s" is readable', $other); } } getName()]; - } - if ($test instanceof SelfDescribing) { - return ['', $test->toString()]; - } - return ['', get_class($test)]; + return is_writable($other); } - public static function describeAsString(\PHPUnit\Framework\Test $test) : string + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other) : string { - if ($test instanceof SelfDescribing) { - return $test->toString(); - } - return get_class($test); + return sprintf('"%s" is writable', $other); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\ExpectationFailedException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsAnything extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * @throws CodeCoverageException + * Evaluates the constraint for parameter $other. * - * @return array|bool - * @psalm-param class-string $className + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException */ - public static function getLinesToBeCovered(string $className, string $methodName) + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - if (!self::shouldCoversAnnotationBeUsed($annotations)) { - return \false; - } - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); + return $returnResult ? \true : null; } /** - * Returns lines of code specified with the @uses annotation. - * - * @throws CodeCoverageException - * @psalm-param class-string $className + * Returns a string representation of the constraint. */ - public static function getLinesToBeUsed(string $className, string $methodName) : array + public function toString() : string { - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); + return 'is anything'; } - public static function requiresCodeCoverageDataCollection(TestCase $test) : bool + /** + * Counts the number of constraint elements. + */ + public function count() : int { - $annotations = self::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); - // If there is no @covers annotation but a @coversNothing annotation on - // the test method then code coverage data does not need to be collected - if (isset($annotations['method']['coversNothing'])) { - return \false; - } - // If there is at least one @covers annotation then - // code coverage data needs to be collected - if (isset($annotations['method']['covers'])) { - return \true; - } - // If there is no @covers annotation but a @coversNothing annotation - // then code coverage data does not need to be collected - if (isset($annotations['class']['coversNothing'])) { - return \false; - } - // If there is no @coversNothing annotation then - // code coverage data may be collected - return \true; + return 0; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function is_array; +use function is_object; +use function is_string; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsIdentical extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * @throws Exception - * @psalm-param class-string $className + * @var mixed */ - public static function getRequirements(string $className, string $methodName) : array + private $value; + public function __construct($value) { - return self::mergeArraysRecursively(Registry::getInstance()->forClassName($className)->requirements(), Registry::getInstance()->forMethod($className, $methodName)->requirements()); + $this->value = $value; } /** - * Returns the missing requirements for a test. + * Evaluates the constraint for parameter $other. * - * @throws Exception - * @throws Warning - * @psalm-param class-string $className + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException */ - public static function getMissingRequirements(string $className, string $methodName) : array + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - $required = self::getRequirements($className, $methodName); - $missing = []; - $hint = null; - if (!empty($required['PHP'])) { - $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); - if (!version_compare(\PHP_VERSION, $required['PHP']['version'], $operator->asString())) { - $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); - $hint = 'PHP'; - } - } elseif (!empty($required['PHP_constraint'])) { - $version = new \PHPUnit\PharIo\Version\Version(self::sanitizeVersionNumber(\PHP_VERSION)); - if (!$required['PHP_constraint']['constraint']->complies($version)) { - $missing[] = sprintf('PHP version does not match the required constraint %s.', $required['PHP_constraint']['constraint']->asString()); - $hint = 'PHP_constraint'; - } - } - if (!empty($required['PHPUnit'])) { - $phpunitVersion = Version::id(); - $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); - if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { - $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); - $hint = $hint ?? 'PHPUnit'; - } - } elseif (!empty($required['PHPUnit_constraint'])) { - $phpunitVersion = new \PHPUnit\PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); - if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { - $missing[] = sprintf('PHPUnit version does not match the required constraint %s.', $required['PHPUnit_constraint']['constraint']->asString()); - $hint = $hint ?? 'PHPUnit_constraint'; - } - } - if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem())->getFamily()) { - $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); - $hint = $hint ?? 'OSFAMILY'; - } - if (!empty($required['OS'])) { - $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); - if (!preg_match($requiredOsPattern, \PHP_OS)) { - $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); - $hint = $hint ?? 'OS'; - } - } - if (!empty($required['functions'])) { - foreach ($required['functions'] as $function) { - $pieces = explode('::', $function); - if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { - continue; - } - if (function_exists($function)) { - continue; - } - $missing[] = sprintf('Function %s is required.', $function); - $hint = $hint ?? 'function_' . $function; - } - } - if (!empty($required['setting'])) { - foreach ($required['setting'] as $setting => $value) { - if (ini_get($setting) !== $value) { - $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); - $hint = $hint ?? '__SETTING_' . $setting; - } - } + $success = $this->value === $other; + if ($returnResult) { + return $success; } - if (!empty($required['extensions'])) { - foreach ($required['extensions'] as $extension) { - if (isset($required['extension_versions'][$extension])) { - continue; - } - if (!extension_loaded($extension)) { - $missing[] = sprintf('Extension %s is required.', $extension); - $hint = $hint ?? 'extension_' . $extension; - } + if (!$success) { + $f = null; + // if both values are strings, make sure a diff is generated + if (is_string($this->value) && is_string($other)) { + $f = new ComparisonFailure($this->value, $other, sprintf("'%s'", $this->value), sprintf("'%s'", $other)); } - } - if (!empty($required['extension_versions'])) { - foreach ($required['extension_versions'] as $extension => $req) { - $actualVersion = phpversion($extension); - $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); - if ($actualVersion === \false || !version_compare($actualVersion, $req['version'], $operator->asString())) { - $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); - $hint = $hint ?? 'extension_' . $extension; - } + // if both values are array, make sure a diff is generated + if (is_array($this->value) && is_array($other)) { + $f = new ComparisonFailure($this->value, $other, $this->exporter()->export($this->value), $this->exporter()->export($other)); } + $this->fail($other, $description, $f); } - if ($hint && isset($required['__OFFSET'])) { - array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); - array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); - } - return $missing; + return null; } /** - * Returns the provided data for a method. + * Returns a string representation of the constraint. * - * @throws Exception - * @psalm-param class-string $className + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function getProvidedData(string $className, string $methodName) : ?array + public function toString() : string { - return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); + if (is_object($this->value)) { + return 'is identical to an object of class "' . get_class($this->value) . '"'; + } + return 'is identical to ' . $this->exporter()->export($this->value); } /** - * @psalm-param class-string $className + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function parseTestMethodAnnotations(string $className, ?string $methodName = '') : array + protected function failureDescription($other) : string { - $registry = Registry::getInstance(); - if ($methodName !== null) { - try { - return ['method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), 'class' => $registry->forClassName($className)->symbolAnnotations()]; - } catch (\PHPUnit\Util\Exception $methodNotFound) { - // ignored - } + if (is_object($this->value) && is_object($other)) { + return 'two variables reference the same object'; } - return ['method' => null, 'class' => $registry->forClassName($className)->symbolAnnotations()]; + if (is_string($this->value) && is_string($other)) { + return 'two strings are identical'; + } + if (is_array($this->value) && is_array($other)) { + return 'two arrays are identical'; + } + return parent::failureDescription($other); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function json_decode; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Json; +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class JsonMatches extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * @psalm-param class-string $className + * @var string */ - public static function getInlineAnnotations(string $className, string $methodName) : array + private $value; + public function __construct(string $value) { - return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); + $this->value = $value; } - /** @psalm-param class-string $className */ - public static function getBackupSettings(string $className, string $methodName) : array + /** + * Returns a string representation of the object. + */ + public function toString() : string { - return ['backupGlobals' => self::getBooleanAnnotationSetting($className, $methodName, 'backupGlobals'), 'backupStaticAttributes' => self::getBooleanAnnotationSetting($className, $methodName, 'backupStaticAttributes')]; + return sprintf('matches JSON string "%s"', $this->value); } /** - * @psalm-param class-string $className + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @return ExecutionOrderDependency[] + * This method can be overridden to implement the evaluation algorithm. + * + * @param mixed $other value or object to evaluate */ - public static function getDependencies(string $className, string $methodName) : array + protected function matches($other) : bool { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - $dependsAnnotations = $annotations['class']['depends'] ?? []; - if (isset($annotations['method']['depends'])) { - $dependsAnnotations = array_merge($dependsAnnotations, $annotations['method']['depends']); + [$error, $recodedOther] = Json::canonicalize($other); + if ($error) { + return \false; } - // Normalize dependency name to className::methodName - $dependencies = []; - foreach ($dependsAnnotations as $value) { - $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value); + [$error, $recodedValue] = Json::canonicalize($this->value); + if ($error) { + return \false; } - return array_unique($dependencies); + return $recodedOther == $recodedValue; } - /** @psalm-param class-string $className */ - public static function getGroups(string $className, ?string $methodName = '') : array + /** + * Throws an exception for the given compared value and test description. + * + * @param mixed $other evaluated value or object + * @param string $description Additional information about the test + * @param ComparisonFailure $comparisonFailure + * + * @throws \PHPUnit\Framework\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-return never-return + */ + protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) : void { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - $groups = []; - if (isset($annotations['method']['author'])) { - $groups[] = $annotations['method']['author']; - } elseif (isset($annotations['class']['author'])) { - $groups[] = $annotations['class']['author']; - } - if (isset($annotations['class']['group'])) { - $groups[] = $annotations['class']['group']; - } - if (isset($annotations['method']['group'])) { - $groups[] = $annotations['method']['group']; - } - if (isset($annotations['class']['ticket'])) { - $groups[] = $annotations['class']['ticket']; - } - if (isset($annotations['method']['ticket'])) { - $groups[] = $annotations['method']['ticket']; - } - foreach (['method', 'class'] as $element) { - foreach (['small', 'medium', 'large'] as $size) { - if (isset($annotations[$element][$size])) { - $groups[] = [$size]; - break 2; - } - } - } - foreach (['method', 'class'] as $element) { - if (isset($annotations[$element]['covers'])) { - foreach ($annotations[$element]['covers'] as $coversTarget) { - $groups[] = ['__phpunit_covers_' . self::canonicalizeName($coversTarget)]; - } + if ($comparisonFailure === null) { + [$error, $recodedOther] = Json::canonicalize($other); + if ($error) { + parent::fail($other, $description); } - if (isset($annotations[$element]['uses'])) { - foreach ($annotations[$element]['uses'] as $usesTarget) { - $groups[] = ['__phpunit_uses_' . self::canonicalizeName($usesTarget)]; - } + [$error, $recodedValue] = Json::canonicalize($this->value); + if ($error) { + parent::fail($other, $description); } + $comparisonFailure = new ComparisonFailure(json_decode($this->value), json_decode($other), Json::prettify($recodedValue), Json::prettify($recodedOther), \false, 'Failed asserting that two json values are equal.'); } - return array_unique(array_merge([], ...$groups)); + parent::fail($other, $description, $comparisonFailure); } - /** @psalm-param class-string $className */ - public static function getSize(string $className, ?string $methodName) : int +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const JSON_ERROR_CTRL_CHAR; +use const JSON_ERROR_DEPTH; +use const JSON_ERROR_NONE; +use const JSON_ERROR_STATE_MISMATCH; +use const JSON_ERROR_SYNTAX; +use const JSON_ERROR_UTF8; +use function strtolower; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class JsonMatchesErrorMessageProvider +{ + /** + * Translates JSON error to a human readable string. + */ + public static function determineJsonError(string $error, string $prefix = '') : ?string { - $groups = array_flip(self::getGroups($className, $methodName)); - if (isset($groups['large'])) { - return self::LARGE; - } - if (isset($groups['medium'])) { - return self::MEDIUM; - } - if (isset($groups['small'])) { - return self::SMALL; + switch ($error) { + case JSON_ERROR_NONE: + return null; + case JSON_ERROR_DEPTH: + return $prefix . 'Maximum stack depth exceeded'; + case JSON_ERROR_STATE_MISMATCH: + return $prefix . 'Underflow or the modes mismatch'; + case JSON_ERROR_CTRL_CHAR: + return $prefix . 'Unexpected control character found'; + case JSON_ERROR_SYNTAX: + return $prefix . 'Syntax error, malformed JSON'; + case JSON_ERROR_UTF8: + return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; + default: + return $prefix . 'Unknown error'; } - return self::UNKNOWN; - } - /** @psalm-param class-string $className */ - public static function getProcessIsolationSettings(string $className, string $methodName) : bool - { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); } - /** @psalm-param class-string $className */ - public static function getClassProcessIsolationSettings(string $className, string $methodName) : bool + /** + * Translates a given type to a human readable message prefix. + */ + public static function translateTypeToPrefix(string $type) : string { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - return isset($annotations['class']['runClassInSeparateProcess']); + switch (strtolower($type)) { + case 'expected': + $prefix = 'Expected value JSON decode error - '; + break; + case 'actual': + $prefix = 'Actual value JSON decode error - '; + break; + default: + $prefix = ''; + break; + } + return $prefix; } - /** @psalm-param class-string $className */ - public static function getPreserveGlobalStateSettings(string $className, string $methodName) : ?bool +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_finite; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsFinite extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - return self::getBooleanAnnotationSetting($className, $methodName, 'preserveGlobalState'); + return 'is finite'; } - /** @psalm-param class-string $className */ - public static function getHookMethods(string $className) : array + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - if (!class_exists($className, \false)) { - return self::emptyHookMethodsArray(); - } - if (!isset(self::$hookMethods[$className])) { - self::$hookMethods[$className] = self::emptyHookMethodsArray(); - try { - foreach ((new ReflectionClass($className))->getMethods() as $method) { - if ($method->getDeclaringClass()->getName() === Assert::class) { - continue; - } - if ($method->getDeclaringClass()->getName() === TestCase::class) { - continue; - } - $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); - if ($method->isStatic()) { - if ($docBlock->isHookToBeExecutedBeforeClass()) { - array_unshift(self::$hookMethods[$className]['beforeClass'], $method->getName()); - } - if ($docBlock->isHookToBeExecutedAfterClass()) { - self::$hookMethods[$className]['afterClass'][] = $method->getName(); - } - } - if ($docBlock->isToBeExecutedBeforeTest()) { - array_unshift(self::$hookMethods[$className]['before'], $method->getName()); - } - if ($docBlock->isToBeExecutedAsPreCondition()) { - array_unshift(self::$hookMethods[$className]['preCondition'], $method->getName()); - } - if ($docBlock->isToBeExecutedAsPostCondition()) { - self::$hookMethods[$className]['postCondition'][] = $method->getName(); - } - if ($docBlock->isToBeExecutedAfterTest()) { - self::$hookMethods[$className]['after'][] = $method->getName(); - } - } - } catch (ReflectionException $e) { - } - } - return self::$hookMethods[$className]; + return is_finite($other); } - public static function isTestMethod(ReflectionMethod $method) : bool +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_infinite; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsInfinite extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - if (!$method->isPublic()) { - return \false; - } - if (strpos($method->getName(), 'test') === 0) { - return \true; - } - return array_key_exists('test', Registry::getInstance()->forMethod($method->getDeclaringClass()->getName(), $method->getName())->symbolAnnotations()); + return 'is infinite'; } /** - * @throws CodeCoverageException - * @psalm-param class-string $className + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode) : array + protected function matches($other) : bool { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - $classShortcut = null; - if (!empty($annotations['class'][$mode . 'DefaultClass'])) { - if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { - throw new CodeCoverageException(sprintf('More than one @%sClass annotation in class or interface "%s".', $mode, $className)); - } - $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; - } - $list = $annotations['class'][$mode] ?? []; - if (isset($annotations['method'][$mode])) { - $list = array_merge($list, $annotations['method'][$mode]); - } - $codeUnits = CodeUnitCollection::fromArray([]); - $mapper = new Mapper(); - foreach (array_unique($list) as $element) { - if ($classShortcut && strncmp($element, '::', 2) === 0) { - $element = $classShortcut . $element; - } - $element = preg_replace('/[\\s()]+$/', '', $element); - $element = explode(' ', $element); - $element = $element[0]; - if ($mode === 'covers' && interface_exists($element)) { - throw new InvalidCoversTargetException(sprintf('Trying to @cover interface "%s".', $element)); - } - try { - $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element)); - } catch (InvalidCodeUnitException $e) { - throw new InvalidCoversTargetException(sprintf('"@%s %s" is invalid', $mode, $element), (int) $e->getCode(), $e); - } - } - return $mapper->codeUnitsToSourceLines($codeUnits); + return is_infinite($other); } - private static function emptyHookMethodsArray() : array +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_nan; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsNan extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - return ['beforeClass' => ['setUpBeforeClass'], 'before' => ['setUp'], 'preCondition' => ['assertPreConditions'], 'postCondition' => ['assertPostConditions'], 'after' => ['tearDown'], 'afterClass' => ['tearDownAfterClass']]; + return 'is nan'; } - /** @psalm-param class-string $className */ - private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName) : ?bool + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - $annotations = self::parseTestMethodAnnotations($className, $methodName); - if (isset($annotations['method'][$settingName])) { - if ($annotations['method'][$settingName][0] === 'enabled') { - return \true; - } - if ($annotations['method'][$settingName][0] === 'disabled') { - return \false; - } - } - if (isset($annotations['class'][$settingName])) { - if ($annotations['class'][$settingName][0] === 'enabled') { - return \true; - } - if ($annotations['class'][$settingName][0] === 'disabled') { - return \false; - } - } - return null; + return is_nan($other); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function is_object; +use function sprintf; +use PHPUnit\Framework\Exception; +use ReflectionClass; +use ReflectionException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +class ClassHasAttribute extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Trims any extensions from version string that follows after - * the .[.] format. + * @var string */ - private static function sanitizeVersionNumber(string $version) + private $attributeName; + public function __construct(string $attributeName) { - return preg_replace('/^(\\d+\\.\\d+(?:.\\d+)?).*$/', '$1', $version); + $this->attributeName = $attributeName; } - private static function shouldCoversAnnotationBeUsed(array $annotations) : bool + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - if (isset($annotations['method']['coversNothing'])) { - return \false; - } - if (isset($annotations['method']['covers'])) { - return \true; - } - if (isset($annotations['class']['coversNothing'])) { - return \false; - } - return \true; + return sprintf('has attribute "%s"', $this->attributeName); } /** - * Merge two arrays together. - * - * If an integer key exists in both arrays and preserveNumericKeys is false, the value - * from the second array will be appended to the first array. If both values are arrays, they - * are merged together, else the value of the second array overwrites the one of the first array. - * - * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php - * - * Zend Framework (http://framework.zend.com/) - * - * @see http://github.com/zendframework/zf2 for the canonical source repository + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License + * @param mixed $other value or object to evaluate */ - private static function mergeArraysRecursively(array $a, array $b) : array + protected function matches($other) : bool { - foreach ($b as $key => $value) { - if (array_key_exists($key, $a)) { - if (is_int($key)) { - $a[] = $value; - } elseif (is_array($value) && is_array($a[$key])) { - $a[$key] = self::mergeArraysRecursively($a[$key], $value); - } else { - $a[$key] = $value; - } - } else { - $a[$key] = $value; - } + try { + return (new ReflectionClass($other))->hasProperty($this->attributeName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } - return $a; + // @codeCoverageIgnoreEnd + } + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other) : string + { + return sprintf('%sclass "%s" %s', is_object($other) ? 'object of ' : '', is_object($other) ? get_class($other) : $other, $this->toString()); } - private static function canonicalizeName(string $name) : string + protected function attributeName() : string { - return strtolower(trim($name, '\\')); + return $this->attributeName; } } attributeName()); } /** - * Loads a PHP sourcefile. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - public static function load(string $filename) : void + protected function matches($other) : bool { - $oldVariableNames = array_keys(get_defined_vars()); - /** - * @noinspection PhpIncludeInspection - * @psalm-suppress UnresolvableInclude - */ - include_once $filename; - $newVariables = get_defined_vars(); - foreach (array_diff(array_keys($newVariables), $oldVariableNames) as $variableName) { - if ($variableName !== 'oldVariableNames') { - $GLOBALS[$variableName] = $newVariables[$variableName]; + try { + $class = new ReflectionClass($other); + if ($class->hasProperty($this->attributeName())) { + return $class->getProperty($this->attributeName())->isStatic(); } + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } - } - /** - * @see https://github.com/sebastianbergmann/phpunit/pull/2751 - */ - private static function isReadable(string $filename) : bool - { - return @fopen($filename, 'r') !== \false; + // @codeCoverageIgnoreEnd + return \false; } } openMemory(); - $writer->setIndent(\true); - $writer->startDocument(); - $writer->startElement('tests'); - $currentTestCase = null; - foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { - if ($test instanceof TestCase) { - if (get_class($test) !== $currentTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - } - $writer->startElement('testCaseClass'); - $writer->writeAttribute('name', get_class($test)); - $currentTestCase = get_class($test); - } - $writer->startElement('testCaseMethod'); - $writer->writeAttribute('name', $test->getName(\false)); - $writer->writeAttribute('groups', implode(',', $test->getGroups())); - if (!empty($test->getDataSetAsString(\false))) { - $writer->writeAttribute('dataSet', str_replace(' with data set ', '', $test->getDataSetAsString(\false))); - } - $writer->endElement(); - } elseif ($test instanceof PhptTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - $currentTestCase = null; - } - $writer->startElement('phptFile'); - $writer->writeAttribute('path', $test->getName()); - $writer->endElement(); - } + $this->expected = $object; + $this->method = $method; + } + public function toString() : string + { + return 'two objects are equal'; + } + /** + * @throws ActualValueIsNotAnObjectException + * @throws ComparisonMethodDoesNotAcceptParameterTypeException + * @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException + * @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException + * @throws ComparisonMethodDoesNotDeclareParameterTypeException + * @throws ComparisonMethodDoesNotExistException + */ + protected function matches($other) : bool + { + if (!is_object($other)) { + throw new ActualValueIsNotAnObjectException(); } - if ($currentTestCase !== null) { - $writer->endElement(); + $object = new ReflectionObject($other); + if (!$object->hasMethod($this->method)) { + throw new ComparisonMethodDoesNotExistException(get_class($other), $this->method); } - $writer->endElement(); - return $writer->outputMemory(); + /** @noinspection PhpUnhandledExceptionInspection */ + $method = $object->getMethod($this->method); + if (!$method->hasReturnType()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + } + $returnType = $method->getReturnType(); + if (!$returnType instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + } + if ($returnType->allowsNull()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + } + if ($returnType->getName() !== 'bool') { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + } + if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) { + throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException(get_class($other), $this->method); + } + $parameter = $method->getParameters()[0]; + if (!$parameter->hasType()) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException(get_class($other), $this->method); + } + $type = $parameter->getType(); + if (!$type instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException(get_class($other), $this->method); + } + $typeName = $type->getName(); + if ($typeName === 'self') { + $typeName = get_class($other); + } + if (!$this->expected instanceof $typeName) { + throw new ComparisonMethodDoesNotAcceptParameterTypeException(get_class($other), $this->method, get_class($this->expected)); + } + return $other->{$this->method}($this->expected); + } + protected function failureDescription($other) : string + { + return $this->toString(); } } getItems($filter)); - $files = implode(",\n", $files); - return <<directories() as $directory) { - $path = realpath($directory->path()); - if (is_string($path)) { - $files[] = sprintf(addslashes('%s' . \DIRECTORY_SEPARATOR), $path); - } - } - foreach ($filter->files() as $file) { - $files[] = $file->path(); - } - return $files; + return (new ReflectionObject($other))->hasProperty($this->attributeName()); } } constraints = $constraints; + return $constraint; + } + /** + * @param mixed[] $constraints + */ + public function setConstraints(array $constraints) : void + { + $this->constraints = array_map(function ($constraint) : \PHPUnit\Framework\Constraint\Constraint { + return $this->checkConstraint($constraint); + }, array_values($constraints)); + } + /** + * Returns the number of operands (constraints). + */ + public final function arity() : int + { + return count($this->constraints); + } + /** + * Returns a string representation of the constraint. + */ + public function toString() : string + { + $reduced = $this->reduce(); + if ($reduced !== $this) { + return $reduced->toString(); + } + $text = ''; + foreach ($this->constraints as $key => $constraint) { + $constraint = $constraint->reduce(); + $text .= $this->constraintToString($constraint, $key); + } + return $text; + } + /** + * Counts the number of constraint elements. + */ + public function count() : int + { + $count = 0; + foreach ($this->constraints as $constraint) { + $count += count($constraint); + } + return $count; + } + /** + * Returns the nested constraints. + */ + protected final function constraints() : array + { + return $this->constraints; + } + /** + * Returns true if the $constraint needs to be wrapped with braces. + */ + protected final function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool + { + return $this->arity() > 1 && parent::constraintNeedsParentheses($constraint); + } + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. * - * - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php - * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php + * See Constraint::reduce() for more. */ - public static function classNameToFilename(string $className) : string + protected function reduce() : \PHPUnit\Framework\Constraint\Constraint { - return str_replace(['_', '\\'], \DIRECTORY_SEPARATOR, $className) . '.php'; + if ($this->arity() === 1 && $this->constraints[0] instanceof \PHPUnit\Framework\Constraint\Operator) { + return $this->constraints[0]->reduce(); + } + return parent::reduce(); } - public static function createDirectory(string $directory) : bool + /** + * Returns string representation of given operand in context of this operator. + * + * @param Constraint $constraint operand constraint + * @param int $position position of $constraint in this expression + */ + private function constraintToString(\PHPUnit\Framework\Constraint\Constraint $constraint, int $position) : string { - return !(!is_dir($directory) && !@mkdir($directory, 0777, \true) && !is_dir($directory)); + $prefix = ''; + if ($position > 0) { + $prefix = ' ' . $this->operator() . ' '; + } + if ($this->constraintNeedsParentheses($constraint)) { + return $prefix . '( ' . $constraint->toString() . ' )'; + } + $string = $constraint->toStringInContext($this, $position); + if ($string === '') { + $string = $constraint->toString(); + } + return $prefix . $string; } } '|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + * Returns the name of this operator. */ - private $operator; - public function __construct(string $operator) + public function operator() : string { - $this->ensureOperatorIsValid($operator); - $this->operator = $operator; + return 'and'; } /** - * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php */ - public function asString() : string + public function precedence() : int { - return $this->operator; + return 22; } /** - * @throws Exception + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @psalm-assert '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator + * @param mixed $other value or object to evaluate */ - private function ensureOperatorIsValid(string $operator) : void + protected function matches($other) : bool { - if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], \true)) { - throw new \PHPUnit\Util\Exception(sprintf('"%s" is not a valid version_compare() operator', $operator)); + foreach ($this->constraints() as $constraint) { + if (!$constraint->evaluate($other, '', \true)) { + return \false; + } } + return [] !== $this->constraints(); } } - */ - private const WHITESPACE_MAP = [' ' => '·', "\t" => '⇥']; - /** - * @var array - */ - private const WHITESPACE_EOL_MAP = [' ' => '·', "\t" => '⇥', "\n" => '↵', "\r" => '⟵']; - /** - * @var array - */ - private static $ansiCodes = ['reset' => '0', 'bold' => '1', 'dim' => '2', 'dim-reset' => '22', 'underlined' => '4', 'fg-default' => '39', 'fg-black' => '30', 'fg-red' => '31', 'fg-green' => '32', 'fg-yellow' => '33', 'fg-blue' => '34', 'fg-magenta' => '35', 'fg-cyan' => '36', 'fg-white' => '37', 'bg-default' => '49', 'bg-black' => '40', 'bg-red' => '41', 'bg-green' => '42', 'bg-yellow' => '43', 'bg-blue' => '44', 'bg-magenta' => '45', 'bg-cyan' => '46', 'bg-white' => '47']; - public static function colorize(string $color, string $buffer) : string + public static function negate(string $string) : string { - if (trim($buffer) === '') { - return $buffer; - } - $codes = array_map('\\trim', explode(',', $color)); - $styles = []; - foreach ($codes as $code) { - if (isset(self::$ansiCodes[$code])) { - $styles[] = self::$ansiCodes[$code] ?? ''; - } - } - if (empty($styles)) { - return $buffer; + $positives = ['contains ', 'exists', 'has ', 'is ', 'are ', 'matches ', 'starts with ', 'ends with ', 'reference ', 'not not ']; + $negatives = ['does not contain ', 'does not exist', 'does not have ', 'is not ', 'are not ', 'does not match ', 'starts not with ', 'ends not with ', 'don\'t reference ', 'not ']; + preg_match('/(\'[\\w\\W]*\')([\\w\\W]*)("[\\w\\W]*")/i', $string, $matches); + $positives = array_map(static function (string $s) { + return '/\\b' . preg_quote($s, '/') . '/'; + }, $positives); + if (count($matches) > 0) { + $nonInput = $matches[2]; + $negatedString = preg_replace('/' . preg_quote($nonInput, '/') . '/', preg_replace($positives, $negatives, $nonInput), $string); + } else { + $negatedString = preg_replace($positives, $negatives, $string); } - return self::optimizeColor(sprintf("\33[%sm", implode(';', $styles)) . $buffer . "\33[0m"); + return $negatedString; } - public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = \false) : string + /** + * Returns the name of this operator. + */ + public function operator() : string { - if ($prevPath === null) { - $prevPath = ''; - } - $path = explode(\DIRECTORY_SEPARATOR, $path); - $prevPath = explode(\DIRECTORY_SEPARATOR, $prevPath); - for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { - if ($path[$i] == $prevPath[$i]) { - $path[$i] = self::dim($path[$i]); - } - } - if ($colorizeFilename) { - $last = count($path) - 1; - $path[$last] = preg_replace_callback('/([\\-_\\.]+|phpt$)/', static function ($matches) { - return self::dim($matches[0]); - }, $path[$last]); - } - return self::optimizeColor(implode(self::dim(\DIRECTORY_SEPARATOR), $path)); + return 'not'; } - public static function dim(string $buffer) : string + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + public function precedence() : int { - if (trim($buffer) === '') { - return $buffer; - } - return "\33[2m{$buffer}\33[22m"; + return 5; + } + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool + { + return !$this->constraint()->evaluate($other, '', \true); } - public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = \false) : string + /** + * Applies additional transformation to strings returned by toString() or + * failureDescription(). + */ + protected function transformString(string $string) : string { - $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; - return preg_replace_callback('/\\s+/', static function ($matches) use($replaceMap) { - return self::dim(strtr($matches[0], $replaceMap)); - }, $buffer); + return self::negate($string); } - private static function optimizeColor(string $buffer) : string + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. + * + * See Constraint::reduce() for more. + */ + protected function reduce() : \PHPUnit\Framework\Constraint\Constraint { - $patterns = ["/\33\\[22m\33\\[2m/" => '', "/\33\\[([^m]*)m\33\\[([1-9][0-9;]*)m/" => "\33[\$1;\$2m", "/(\33\\[[^m]*m)+(\33\\[0m)/" => '$2']; - return preg_replace(array_keys($patterns), array_values($patterns), $buffer); + $constraint = $this->constraint(); + if ($constraint instanceof self) { + return $constraint->constraint()->reduce(); + } + return parent::reduce(); } } getIterator()) as $test) { - if ($test instanceof TestCase) { - $name = sprintf('%s::%s', get_class($test), str_replace(' with data set ', '', $test->getName())); - } elseif ($test instanceof PhptTestCase) { - $name = $test->getName(); - } else { - continue; + return 'or'; + } + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + public function precedence() : int + { + return 24; + } + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + public function matches($other) : bool + { + foreach ($this->constraints() as $constraint) { + if ($constraint->evaluate($other, '', \true)) { + return \true; } - $buffer .= sprintf(' - %s' . \PHP_EOL, $name); } - return $buffer; + return \false; } } constraints(); + $initial = array_shift($constraints); + if ($initial === null) { + return \false; + } + return array_reduce($constraints, static function (bool $matches, \PHPUnit\Framework\Constraint\Constraint $constraint) use($other) : bool { + return $matches xor $constraint->evaluate($other, '', \true); + }, $initial->evaluate($other, '', \true)); } } convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - } - public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine) : bool - { - /* - * Do not raise an exception when the error suppression operator (@) was used. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3739 - */ - if (!($errorNumber & error_reporting())) { - return \false; - } - switch ($errorNumber) { - case \E_NOTICE: - case \E_USER_NOTICE: - case \E_STRICT: - if (!$this->convertNoticesToExceptions) { - return \false; - } - throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); - case \E_WARNING: - case \E_USER_WARNING: - if (!$this->convertWarningsToExceptions) { - return \false; - } - throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); - case \E_DEPRECATED: - case \E_USER_DEPRECATED: - if (!$this->convertDeprecationsToExceptions) { - return \false; - } - throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); - default: - if (!$this->convertErrorsToExceptions) { - return \false; - } - throw new Error($errorString, $errorNumber, $errorFile, $errorLine); - } - } - public function register() : void + protected function checkConstraint($constraint) : \PHPUnit\Framework\Constraint\Constraint { - if ($this->registered) { - return; - } - $oldErrorHandler = set_error_handler($this); - if ($oldErrorHandler !== null) { - restore_error_handler(); - return; + if (!$constraint instanceof \PHPUnit\Framework\Constraint\Constraint) { + return new \PHPUnit\Framework\Constraint\IsEqual($constraint); } - $this->registered = \true; + return $constraint; } - public function unregister() : void + /** + * Returns true if the $constraint needs to be wrapped with braces. + */ + protected function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool { - if (!$this->registered) { - return; - } - restore_error_handler(); + return $constraint instanceof self && $constraint->arity() > 1 && $this->precedence() <= $constraint->precedence(); } } {driverMethod}($filter), - $filter - ); - - if ({cachesStaticAnalysis}) { - $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); - } - - $result->setCodeCoverage($codeCoverage); + /** + * @var Constraint + */ + private $constraint; + /** + * @param Constraint|mixed $constraint + */ + public function __construct($constraint) + { + $this->constraint = $this->checkConstraint($constraint); } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); - \assert($test instanceof TestCase); - - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(true); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); + /** + * Returns the number of operands (constraints). + */ + public function arity() : int + { + return 1; } - - ini_set('xdebug.scream', '0'); - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = @stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); + /** + * Returns a string representation of the constraint. + */ + public function toString() : string + { + $reduced = $this->reduce(); + if ($reduced !== $this) { + return $reduced->toString(); + } + $constraint = $this->constraint->reduce(); + if ($this->constraintNeedsParentheses($constraint)) { + return $this->operator() . '( ' . $constraint->toString() . ' )'; + } + $string = $constraint->toStringInContext($this, 0); + if ($string === '') { + return $this->transformString($constraint->toString()); } + return $string; } - - print serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); -} - -__phpunit_run_isolated_test(); -constraint); } - - $result = new PHPUnit\Framework\TestResult; - - if ({collectCodeCoverageInformation}) { - $filter = unserialize('{codeCoverageFilter}'); - - $codeCoverage = new CodeCoverage( - (new Selector)->{driverMethod}($filter), - $filter - ); - - if ({cachesStaticAnalysis}) { - $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other) : string + { + $reduced = $this->reduce(); + if ($reduced !== $this) { + return $reduced->failureDescription($other); } - - $result->setCodeCoverage($codeCoverage); + $constraint = $this->constraint->reduce(); + if ($this->constraintNeedsParentheses($constraint)) { + return $this->operator() . '( ' . $constraint->failureDescription($other) . ' )'; + } + $string = $constraint->failureDescriptionInContext($this, 0, $other); + if ($string === '') { + return $this->transformString($constraint->failureDescription($other)); + } + return $string; } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(TRUE); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); + /** + * Transforms string returned by the memeber constraint's toString() or + * failureDescription() such that it reflects constraint's participation in + * this expression. + * + * The method may be overwritten in a subclass to apply default + * transformation in case the operand constraint does not provide its own + * custom strings via toStringInContext() or failureDescriptionInContext(). + * + * @param string $string the string to be transformed + */ + protected function transformString(string $string) : string + { + return $string; } - - ini_set('xdebug.scream', '0'); - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = @stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); - } + /** + * Provides access to $this->constraint for subclasses. + */ + protected final function constraint() : \PHPUnit\Framework\Constraint\Constraint + { + return $this->constraint; + } + /** + * Returns true if the $constraint needs to be wrapped with parentheses. + */ + protected function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool + { + $constraint = $constraint->reduce(); + return $constraint instanceof self || parent::constraintNeedsParentheses($constraint); } - - print serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); } - -__phpunit_run_isolated_test(); {driverMethod}($filter), - $filter - ); +declare (strict_types=1); +/* + * This file is part of PHPUnit. + * + * (c) Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; - if ({codeCoverageCacheDirectory}) { - $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); +use function json_decode; +use function json_last_error; +use function sprintf; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsJson extends \PHPUnit\Framework\Constraint\Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString() : string + { + return 'is valid JSON'; } - - $coverage->start(__FILE__); -} - -register_shutdown_function( - function() use ($coverage) { - $output = null; - - if ($coverage) { - $output = $coverage->stop(); + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool + { + if ($other === '') { + return \false; } - - file_put_contents('{coverageFile}', serialize($output)); + json_decode($other); + if (json_last_error()) { + return \false; + } + return \true; } -); - -ob_end_clean(); - -require '{job}'; + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other) : string + { + if ($other === '') { + return 'an empty string is valid JSON'; + } + json_decode($other); + $error = (string) \PHPUnit\Framework\Constraint\JsonMatchesErrorMessageProvider::determineJsonError((string) json_last_error()); + return sprintf('%s is valid JSON (%s)', $this->exporter()->shortenedExport($other), $error); + } +} pattern = $pattern; } /** - * @throws Exception + * Returns a string representation of the constraint. */ - protected function getHandles() : array + public function toString() : string { - if (\false === ($stdout_handle = tmpfile())) { - throw new Exception('A temporary file could not be created; verify that your TEMP environment variable is writable'); - } - return [1 => $stdout_handle]; + return sprintf('matches PCRE pattern "%s"', $this->pattern); } - protected function useTemporaryFile() : bool + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - return \true; + return preg_match($this->pattern, $other) > 0; } } stdin || $this->useTemporaryFile()) { - if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || file_put_contents($this->tempFile, $job) === \false) { - throw new Exception('Unable to write temporary file'); - } - $job = $this->stdin; - } - return $this->runProcess($job, $settings); + $this->string = $string; + $this->ignoreCase = $ignoreCase; } /** - * Returns an array of file handles to be used in place of pipes. + * Returns a string representation of the constraint. */ - protected function getHandles() : array + public function toString() : string { - return []; + if ($this->ignoreCase) { + $string = mb_strtolower($this->string, 'UTF-8'); + } else { + $string = $this->string; + } + return sprintf('contains "%s"', $string); } /** - * Handles creating the child process and returning the STDOUT and STDERR. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @throws Exception + * @param mixed $other value or object to evaluate */ - protected function runProcess(string $job, array $settings) : array + protected function matches($other) : bool { - $handles = $this->getHandles(); - $env = null; - if ($this->env) { - $env = $_SERVER ?? []; - unset($env['argv'], $env['argc']); - $env = array_merge($env, $this->env); - foreach ($env as $envKey => $envVar) { - if (is_array($envVar)) { - unset($env[$envKey]); - } - } - } - $pipeSpec = [0 => $handles[0] ?? ['pipe', 'r'], 1 => $handles[1] ?? ['pipe', 'w'], 2 => $handles[2] ?? ['pipe', 'w']]; - $process = proc_open($this->getCommand($settings, $this->tempFile), $pipeSpec, $pipes, null, $env); - if (!is_resource($process)) { - throw new Exception('Unable to spawn worker process'); - } - if ($job) { - $this->process($pipes[0], $job); - } - fclose($pipes[0]); - $stderr = $stdout = ''; - if ($this->timeout) { - unset($pipes[0]); - while (\true) { - $r = $pipes; - $w = null; - $e = null; - $n = @stream_select($r, $w, $e, $this->timeout); - if ($n === \false) { - break; - } - if ($n === 0) { - proc_terminate($process, 9); - throw new Exception(sprintf('Job execution aborted after %d seconds', $this->timeout)); - } - if ($n > 0) { - foreach ($r as $pipe) { - $pipeOffset = 0; - foreach ($pipes as $i => $origPipe) { - if ($pipe === $origPipe) { - $pipeOffset = $i; - break; - } - } - if (!$pipeOffset) { - break; - } - $line = fread($pipe, 8192); - if ($line === '' || $line === \false) { - fclose($pipes[$pipeOffset]); - unset($pipes[$pipeOffset]); - } elseif ($pipeOffset === 1) { - $stdout .= $line; - } else { - $stderr .= $line; - } - } - if (empty($pipes)) { - break; - } - } - } - } else { - if (isset($pipes[1])) { - $stdout = stream_get_contents($pipes[1]); - fclose($pipes[1]); - } - if (isset($pipes[2])) { - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[2]); - } - } - if (isset($handles[1])) { - rewind($handles[1]); - $stdout = stream_get_contents($handles[1]); - fclose($handles[1]); + if ('' === $this->string) { + return \true; } - if (isset($handles[2])) { - rewind($handles[2]); - $stderr = stream_get_contents($handles[2]); - fclose($handles[2]); + if ($this->ignoreCase) { + /* + * We must use the multi byte safe version so we can accurately compare non latin upper characters with + * their lowercase equivalents. + */ + return mb_stripos($other, $this->string, 0, 'UTF-8') !== \false; } - proc_close($process); - $this->cleanup(); - return ['stdout' => $stdout, 'stderr' => $stderr]; + /* + * Use the non multi byte safe functions to see if the string is contained in $other. + * + * This function is very fast and we don't care about the character position in the string. + * + * Additionally, we want this method to be binary safe so we can check if some binary data is in other binary + * data. + */ + return strpos($other, $this->string) !== \false; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function strlen; +use function substr; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringEndsWith extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * @param resource $pipe + * @var string */ - protected function process($pipe, string $job) : void + private $suffix; + public function __construct(string $suffix) { - fwrite($pipe, $job); + $this->suffix = $suffix; } - protected function cleanup() : void + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - if ($this->tempFile) { - unlink($this->tempFile); - } + return 'ends with "' . $this->suffix . '"'; } - protected function useTemporaryFile() : bool + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool { - return \false; + return substr($other, 0 - strlen($this->suffix)) === $this->suffix; } } - */ - protected $env = []; + private $string; + public function __construct(string $string) + { + parent::__construct($this->createPatternFromFormat($this->convertNewlines($string))); + $this->string = $string; + } /** - * @var int + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - protected $timeout = 0; - public static function factory() : self + protected function matches($other) : bool { - if (\DIRECTORY_SEPARATOR === '\\') { - return new \PHPUnit\Util\PHP\WindowsPhpProcess(); - } - return new \PHPUnit\Util\PHP\DefaultPhpProcess(); + return parent::matches($this->convertNewlines($other)); } - public function __construct() + protected function failureDescription($other) : string { - $this->runtime = new Runtime(); + return 'string matches format description'; } - /** - * Defines if should use STDERR redirection or not. - * - * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT. - */ - public function setUseStderrRedirection(bool $stderrRedirection) : void + protected function additionalFailureDescription($other) : string + { + $from = explode("\n", $this->string); + $to = explode("\n", $this->convertNewlines($other)); + foreach ($from as $index => $line) { + if (isset($to[$index]) && $line !== $to[$index]) { + $line = $this->createPatternFromFormat($line); + if (preg_match($line, $to[$index]) > 0) { + $from[$index] = $to[$index]; + } + } + } + $this->string = implode("\n", $from); + $other = implode("\n", $to); + return (new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")))->diff($this->string, $other); + } + private function createPatternFromFormat(string $string) : string { - $this->stderrRedirection = $stderrRedirection; + $string = strtr(preg_quote($string, '/'), ['%%' => '%', '%e' => '\\' . DIRECTORY_SEPARATOR, '%s' => '[^\\r\\n]+', '%S' => '[^\\r\\n]*', '%a' => '.+', '%A' => '.*', '%w' => '\\s*', '%i' => '[+-]?\\d+', '%d' => '\\d+', '%x' => '[0-9a-fA-F]+', '%f' => '[+-]?\\.?\\d+\\.?\\d*(?:[Ee][+-]?\\d+)?', '%c' => '.']); + return '/^' . $string . '$/s'; } - /** - * Returns TRUE if uses STDERR redirection or FALSE if not. - */ - public function useStderrRedirection() : bool + private function convertNewlines(string $text) : string { - return $this->stderrRedirection; + return preg_replace('/\\r\\n/', "\n", $text); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function strlen; +use function strpos; +use PHPUnit\Framework\InvalidArgumentException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringStartsWith extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Sets the input string to be sent via STDIN. + * @var string */ - public function setStdin(string $stdin) : void + private $prefix; + public function __construct(string $prefix) { - $this->stdin = $stdin; + if (strlen($prefix) === 0) { + throw InvalidArgumentException::create(1, 'non-empty string'); + } + $this->prefix = $prefix; } /** - * Returns the input string to be sent via STDIN. + * Returns a string representation of the constraint. */ - public function getStdin() : string + public function toString() : string { - return $this->stdin; + return 'starts with "' . $this->prefix . '"'; } /** - * Sets the string of arguments to pass to the php job. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - public function setArgs(string $args) : void + protected function matches($other) : bool { - $this->args = $args; + return strpos((string) $other, $this->prefix) === 0; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_key_exists; +use function is_array; +use ArrayAccess; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ArrayHasKey extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Returns the string of arguments to pass to the php job. + * @var int|string */ - public function getArgs() : string + private $key; + /** + * @param int|string $key + */ + public function __construct($key) { - return $this->args; + $this->key = $key; } /** - * Sets the array of environment variables to start the child process with. + * Returns a string representation of the constraint. * - * @param array $env + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function setEnv(array $env) : void + public function toString() : string { - $this->env = $env; + return 'has the key ' . $this->exporter()->export($this->key); } /** - * Returns the array of environment variables to start the child process with. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate */ - public function getEnv() : array + protected function matches($other) : bool { - return $this->env; + if (is_array($other)) { + return array_key_exists($this->key, $other); + } + if ($other instanceof ArrayAccess) { + return $other->offsetExists($this->key); + } + return \false; } /** - * Sets the amount of seconds to wait before timing out. + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function setTimeout(int $timeout) : void + protected function failureDescription($other) : string { - $this->timeout = $timeout; + return 'an array ' . $this->toString(); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_array; +use function sprintf; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class TraversableContains extends \PHPUnit\Framework\Constraint\Constraint +{ /** - * Returns the amount of seconds to wait before timing out. + * @var mixed */ - public function getTimeout() : int + private $value; + public function __construct($value) { - return $this->timeout; + $this->value = $value; } /** - * Runs a single test in a separate PHP process. + * Returns a string representation of the constraint. * * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function runTestJob(string $job, Test $test, TestResult $result) : void + public function toString() : string { - $result->startTest($test); - $_result = $this->runJob($job); - $this->processChildResult($test, $result, $_result['stdout'], $_result['stderr']); + return 'contains ' . $this->exporter()->export($this->value); } /** - * Returns the command based into the configurations. + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function getCommand(array $settings, string $file = null) : string + protected function failureDescription($other) : string { - $command = $this->runtime->getBinary(); - if ($this->runtime->hasPCOV()) { - $settings = array_merge($settings, $this->runtime->getCurrentSettings(array_keys(ini_get_all('pcov')))); - } elseif ($this->runtime->hasXdebug()) { - $settings = array_merge($settings, $this->runtime->getCurrentSettings(array_keys(ini_get_all('xdebug')))); - } - $command .= $this->settingsToParameters($settings); - if (\PHP_SAPI === 'phpdbg') { - $command .= ' -qrr'; - if (!$file) { - $command .= 's='; - } - } - if ($file) { - $command .= ' ' . escapeshellarg($file); - } - if ($this->args) { - if (!$file) { - $command .= ' --'; - } - $command .= ' ' . $this->args; - } - if ($this->stderrRedirection) { - $command .= ' 2>&1'; - } - return $command; + return sprintf('%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString()); } - /** - * Runs a single job (PHP code) using a separate PHP process. - */ - public abstract function runJob(string $job, array $settings = []) : array; - protected function settingsToParameters(array $settings) : string + protected function value() { - $buffer = ''; - foreach ($settings as $setting) { - $buffer .= ' -d ' . escapeshellarg($setting); - } - return $buffer; + return $this->value; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use SplObjectStorage; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TraversableContainsEqual extends \PHPUnit\Framework\Constraint\TraversableContains +{ /** - * Processes the TestResult object from an isolated process. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param mixed $other value or object to evaluate */ - private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr) : void + protected function matches($other) : bool { - $time = 0; - if (!empty($stderr)) { - $result->addError($test, new Exception(trim($stderr)), $time); - } else { - set_error_handler( - /** - * @throws ErrorException - */ - static function ($errno, $errstr, $errfile, $errline) : void { - throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); - } - ); - try { - if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { - $stdout = substr($stdout, 19); - } - $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); - restore_error_handler(); - if ($childResult === \false) { - $result->addFailure($test, new AssertionFailedError('Test was run in child process and ended unexpectedly'), $time); - } - } catch (ErrorException $e) { - restore_error_handler(); - $childResult = \false; - $result->addError($test, new Exception(trim($stdout), 0, $e), $time); - } - if ($childResult !== \false) { - if (!empty($childResult['output'])) { - $output = $childResult['output']; - } - /* @var TestCase $test */ - $test->setResult($childResult['testResult']); - $test->addToAssertionCount($childResult['numAssertions']); - $childResult = $childResult['result']; - assert($childResult instanceof TestResult); - if ($result->getCollectCodeCoverageInformation()) { - $result->getCodeCoverage()->merge($childResult->getCodeCoverage()); - } - $time = $childResult->time(); - $notImplemented = $childResult->notImplemented(); - $risky = $childResult->risky(); - $skipped = $childResult->skipped(); - $errors = $childResult->errors(); - $warnings = $childResult->warnings(); - $failures = $childResult->failures(); - if (!empty($notImplemented)) { - $result->addError($test, $this->getException($notImplemented[0]), $time); - } elseif (!empty($risky)) { - $result->addError($test, $this->getException($risky[0]), $time); - } elseif (!empty($skipped)) { - $result->addError($test, $this->getException($skipped[0]), $time); - } elseif (!empty($errors)) { - $result->addError($test, $this->getException($errors[0]), $time); - } elseif (!empty($warnings)) { - $result->addWarning($test, $this->getException($warnings[0]), $time); - } elseif (!empty($failures)) { - $result->addFailure($test, $this->getException($failures[0]), $time); - } - } + if ($other instanceof SplObjectStorage) { + return $other->contains($this->value()); } - $result->endTest($test, $time); - if (!empty($output)) { - print $output; + foreach ($other as $element) { + /* @noinspection TypeUnsafeComparisonInspection */ + if ($this->value() == $element) { + return \true; + } } + return \false; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use SplObjectStorage; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TraversableContainsIdentical extends \PHPUnit\Framework\Constraint\TraversableContains +{ /** - * Gets the thrown exception from a PHPUnit\Framework\TestFailure. + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. * - * @see https://github.com/sebastianbergmann/phpunit/issues/74 + * @param mixed $other value or object to evaluate */ - private function getException(TestFailure $error) : Exception + protected function matches($other) : bool { - $exception = $error->thrownException(); - if ($exception instanceof __PHP_Incomplete_Class) { - $exceptionArray = []; - foreach ((array) $exception as $key => $value) { - $key = substr($key, strrpos($key, "\0") + 1); - $exceptionArray[$key] = $value; + if ($other instanceof SplObjectStorage) { + return $other->contains($this->value()); + } + foreach ($other as $element) { + if ($this->value() === $element) { + return \true; } - $exception = new SyntheticError(sprintf('%s: %s', $exceptionArray['_PHP_Incomplete_Class_Name'], $exceptionArray['message']), $exceptionArray['code'], $exceptionArray['file'], $exceptionArray['line'], $exceptionArray['trace']); } - return $exception; + return \false; } } stream = $out; - return; - } - if (!is_string($out)) { - return; - } - if (strpos($out, 'socket://') === 0) { - $tmp = explode(':', str_replace('socket://', '', $out)); - if (count($tmp) !== 2) { - throw new \PHPUnit\Util\Exception(sprintf('"%s" does not match "socket://hostname:port" format', $out)); - } - $this->stream = fsockopen($tmp[0], (int) $tmp[1]); - return; - } - if (strpos($out, 'php://') === \false && !\PHPUnit\Util\Filesystem::createDirectory(dirname($out))) { - throw new \PHPUnit\Util\Exception(sprintf('Directory "%s" was not created', dirname($out))); + if ($isNativeType) { + $this->constraint = new \PHPUnit\Framework\Constraint\IsType($type); + } else { + $this->constraint = new \PHPUnit\Framework\Constraint\IsInstanceOf($type); } - $this->stream = fopen($out, 'wb'); - $this->isPhpStream = strncmp($out, 'php://', 6) !== 0; + $this->type = $type; } - public function write(string $buffer) : void + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @param mixed|Traversable $other + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool { - if ($this->stream) { - assert(is_resource($this->stream)); - fwrite($this->stream, $buffer); - } else { - if (\PHP_SAPI !== 'cli' && \PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer, \ENT_COMPAT | \ENT_SUBSTITUTE); + $success = \true; + foreach ($other as $item) { + if (!$this->constraint->evaluate($item, '', \true)) { + $success = \false; + break; } - print $buffer; } + if ($returnResult) { + return $success; + } + if (!$success) { + $this->fail($other, $description); + } + return null; } - public function flush() : void + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - if ($this->stream && $this->isPhpStream) { - assert(is_resource($this->stream)); - fclose($this->stream); - } + return 'contains only values of type "' . $this->type . '"'; } } className = $className; + } + /** + * Returns a string representation of the constraint. + */ + public function toString() : string + { + return sprintf('is instance of %s "%s"', $this->getType(), $this->className); + } + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool + { + return $other instanceof $this->className; + } + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other) : string + { + return sprintf('%s is an instance of %s "%s"', $this->exporter()->shortenedExport($other), $this->getType(), $this->className); } - public static function isCloneable(object $object) : bool + private function getType() : string { try { - $clone = clone $object; - } catch (Throwable $t) { - return \false; + $reflection = new ReflectionClass($this->className); + if ($reflection->isInterface()) { + return 'interface'; + } + } catch (ReflectionException $e) { } - return $clone instanceof $object; + return 'class'; } } 0) { - $json = (array) $json; - } else { - return; - } - } - ksort($json); - foreach ($json as $key => &$value) { - self::recursiveSort($value); - } + return $other === null; } } PHP(?:Unit)?)\\s+(?P[<>=!]{0,2})\\s*(?P[\\d\\.-]+(dev|(RC|alpha|beta)[\\d\\.])?)[ \\t]*\\r?$/m'; - private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\\s+(?PPHP(?:Unit)?)\\s+(?P[\\d\\t \\-.|~^]+)[ \\t]*\\r?$/m'; - private const REGEX_REQUIRES_OS = '/@requires\\s+(?POS(?:FAMILY)?)\\s+(?P.+?)[ \\t]*\\r?$/m'; - private const REGEX_REQUIRES_SETTING = '/@requires\\s+(?Psetting)\\s+(?P([^ ]+?))\\s*(?P[\\w\\.-]+[\\w\\.]?)?[ \\t]*\\r?$/m'; - private const REGEX_REQUIRES = '/@requires\\s+(?Pfunction|extension)\\s+(?P([^\\s<>=!]+))\\s*(?P[<>=!]{0,2})\\s*(?P[\\d\\.-]+[\\d\\.]?)?[ \\t]*\\r?$/m'; - private const REGEX_TEST_WITH = '/@testWith\\s+/'; - /** @var string */ - private $docComment; - /** @var bool */ - private $isMethod; - /** @var array> pre-parsed annotations indexed by name and occurrence index */ - private $symbolAnnotations; + public const TYPE_ARRAY = 'array'; /** - * @var null|array - * - * @psalm-var null|(array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * >) + * @var string */ - private $parsedRequirements; - /** @var int */ - private $startLine; - /** @var int */ - private $endLine; - /** @var string */ - private $fileName; - /** @var string */ - private $name; + public const TYPE_BOOL = 'bool'; /** * @var string - * - * @psalm-var class-string */ - private $className; - public static function ofClass(ReflectionClass $class) : self - { - $className = $class->getName(); - return new self((string) $class->getDocComment(), \false, self::extractAnnotationsFromReflector($class), $class->getStartLine(), $class->getEndLine(), $class->getFileName(), $className, $className); - } + public const TYPE_FLOAT = 'float'; /** - * @psalm-param class-string $classNameInHierarchy + * @var string */ - public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy) : self - { - return new self((string) $method->getDocComment(), \true, self::extractAnnotationsFromReflector($method), $method->getStartLine(), $method->getEndLine(), $method->getFileName(), $method->getName(), $classNameInHierarchy); - } + public const TYPE_INT = 'int'; /** - * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized. - * - * @param array> $symbolAnnotations - * - * @psalm-param class-string $className + * @var string */ - private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) - { - $this->docComment = $docComment; - $this->isMethod = $isMethod; - $this->symbolAnnotations = $symbolAnnotations; - $this->startLine = $startLine; - $this->endLine = $endLine; - $this->fileName = $fileName; - $this->name = $name; - $this->className = $className; - } + public const TYPE_NULL = 'null'; /** - * @psalm-return array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * > - * - * @throws Warning if the requirements version constraint is not well-formed + * @var string */ - public function requirements() : array - { - if ($this->parsedRequirements !== null) { - return $this->parsedRequirements; - } - $offset = $this->startLine; - $requires = []; - $recordedSettings = []; - $extensionVersions = []; - $recordedOffsets = ['__FILE' => realpath($this->fileName)]; - // Trim docblock markers, split it into lines and rewind offset to start of docblock - $lines = preg_replace(['#^/\\*{2}#', '#\\*/$#'], '', preg_split('/\\r\\n|\\r|\\n/', $this->docComment)); - $offset -= count($lines); - foreach ($lines as $line) { - if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { - $requires[$matches['name']] = $matches['value']; - $recordedOffsets[$matches['name']] = $offset; - } - if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { - $requires[$matches['name']] = ['version' => $matches['version'], 'operator' => $matches['operator']]; - $recordedOffsets[$matches['name']] = $offset; - } - if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { - if (!empty($requires[$matches['name']])) { - $offset++; - continue; - } - try { - $versionConstraintParser = new VersionConstraintParser(); - $requires[$matches['name'] . '_constraint'] = ['constraint' => $versionConstraintParser->parse(trim($matches['constraint']))]; - $recordedOffsets[$matches['name'] . '_constraint'] = $offset; - } catch (\PHPUnit\PharIo\Version\Exception $e) { - throw new Warning($e->getMessage(), $e->getCode(), $e); - } - } - if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { - $recordedSettings[$matches['setting']] = $matches['value']; - $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; - } - if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { - $name = $matches['name'] . 's'; - if (!isset($requires[$name])) { - $requires[$name] = []; - } - $requires[$name][] = $matches['value']; - $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; - if ($name === 'extensions' && !empty($matches['version'])) { - $extensionVersions[$matches['value']] = ['version' => $matches['version'], 'operator' => $matches['operator']]; - } - } - $offset++; - } - return $this->parsedRequirements = array_merge($requires, ['__OFFSET' => $recordedOffsets], array_filter(['setting' => $recordedSettings, 'extension_versions' => $extensionVersions])); - } + public const TYPE_NUMERIC = 'numeric'; /** - * Returns the provided data for a method. - * - * @throws Exception + * @var string */ - public function getProvidedData() : ?array - { - /** @noinspection SuspiciousBinaryOperationInspection */ - $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); - if ($data === null) { - return null; - } - if ($data === []) { - throw new SkippedTestError(); - } - foreach ($data as $key => $value) { - if (!is_array($value)) { - throw new InvalidDataSetException(sprintf('Data set %s is invalid.', is_int($key) ? '#' . $key : '"' . $key . '"')); - } - } - return $data; - } + public const TYPE_OBJECT = 'object'; /** - * @psalm-return array + * @var string */ - public function getInlineAnnotations() : array - { - $code = file($this->fileName); - $lineNumber = $this->startLine; - $startLine = $this->startLine - 1; - $endLine = $this->endLine - 1; - $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); - $annotations = []; - foreach ($codeLines as $line) { - if (preg_match('#/\\*\\*?\\s*@(?P[A-Za-z_-]+)(?:[ \\t]+(?P.*?))?[ \\t]*\\r?\\*/$#m', $line, $matches)) { - $annotations[strtolower($matches['name'])] = ['line' => $lineNumber, 'value' => $matches['value']]; - } - $lineNumber++; - } - return $annotations; - } - public function symbolAnnotations() : array - { - return $this->symbolAnnotations; - } - public function isHookToBeExecutedBeforeClass() : bool - { - return $this->isMethod && \false !== strpos($this->docComment, '@beforeClass'); - } - public function isHookToBeExecutedAfterClass() : bool - { - return $this->isMethod && \false !== strpos($this->docComment, '@afterClass'); - } - public function isToBeExecutedBeforeTest() : bool - { - return 1 === preg_match('/@before\\b/', $this->docComment); - } - public function isToBeExecutedAfterTest() : bool - { - return 1 === preg_match('/@after\\b/', $this->docComment); - } - public function isToBeExecutedAsPreCondition() : bool - { - return 1 === preg_match('/@preCondition\\b/', $this->docComment); - } - public function isToBeExecutedAsPostCondition() : bool - { - return 1 === preg_match('/@postCondition\\b/', $this->docComment); - } - private function getDataFromDataProviderAnnotation(string $docComment) : ?array - { - $methodName = null; - $className = $this->className; - if ($this->isMethod) { - $methodName = $this->name; - } - if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { - return null; - } - $result = []; - foreach ($matches[1] as $match) { - $dataProviderMethodNameNamespace = explode('\\', $match); - $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); - $dataProviderMethodName = array_pop($leaf); - if (empty($dataProviderMethodNameNamespace)) { - $dataProviderMethodNameNamespace = ''; - } else { - $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; - } - if (empty($leaf)) { - $dataProviderClassName = $className; - } else { - /** @psalm-var class-string $dataProviderClassName */ - $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); - } - try { - $dataProviderClass = new ReflectionClass($dataProviderClassName); - $dataProviderMethod = $dataProviderClass->getMethod($dataProviderMethodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - // @codeCoverageIgnoreEnd - } - if ($dataProviderMethod->isStatic()) { - $object = null; - } else { - $object = $dataProviderClass->newInstance(); - } - if ($dataProviderMethod->getNumberOfParameters() === 0) { - $data = $dataProviderMethod->invoke($object); - } else { - $data = $dataProviderMethod->invoke($object, $methodName); - } - if ($data instanceof Traversable) { - $origData = $data; - $data = []; - foreach ($origData as $key => $value) { - if (is_int($key)) { - $data[] = $value; - } elseif (array_key_exists($key, $data)) { - throw new InvalidDataProviderException(sprintf('The key "%s" has already been defined in the data provider "%s".', $key, $match)); - } else { - $data[$key] = $value; - } - } - } - if (is_array($data)) { - $result = array_merge($result, $data); - } - } - return $result; - } + public const TYPE_RESOURCE = 'resource'; /** - * @throws Exception + * @var string */ - private function getDataFromTestWithAnnotation(string $docComment) : ?array + public const TYPE_CLOSED_RESOURCE = 'resource (closed)'; + /** + * @var string + */ + public const TYPE_STRING = 'string'; + /** + * @var string + */ + public const TYPE_SCALAR = 'scalar'; + /** + * @var string + */ + public const TYPE_CALLABLE = 'callable'; + /** + * @var string + */ + public const TYPE_ITERABLE = 'iterable'; + /** + * @var array + */ + private const KNOWN_TYPES = ['array' => \true, 'boolean' => \true, 'bool' => \true, 'double' => \true, 'float' => \true, 'integer' => \true, 'int' => \true, 'null' => \true, 'numeric' => \true, 'object' => \true, 'real' => \true, 'resource' => \true, 'resource (closed)' => \true, 'string' => \true, 'scalar' => \true, 'callable' => \true, 'iterable' => \true]; + /** + * @var string + */ + private $type; + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(string $type) { - $docComment = $this->cleanUpMultiLineAnnotation($docComment); - if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, \PREG_OFFSET_CAPTURE)) { - return null; - } - $offset = strlen($matches[0][0]) + $matches[0][1]; - $annotationContent = substr($docComment, $offset); - $data = []; - foreach (explode("\n", $annotationContent) as $candidateRow) { - $candidateRow = trim($candidateRow); - if ($candidateRow[0] !== '[') { - break; - } - $dataSet = json_decode($candidateRow, \true); - if (json_last_error() !== \JSON_ERROR_NONE) { - throw new Exception('The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg()); - } - $data[] = $dataSet; - } - if (!$data) { - throw new Exception('The data set for the @testWith annotation cannot be parsed.'); + if (!isset(self::KNOWN_TYPES[$type])) { + throw new \PHPUnit\Framework\Exception(sprintf('Type specified for PHPUnit\\Framework\\Constraint\\IsType <%s> ' . 'is not a valid type.', $type)); } - return $data; - } - private function cleanUpMultiLineAnnotation(string $docComment) : string - { - //removing initial ' * ' for docComment - $docComment = str_replace("\r\n", "\n", $docComment); - $docComment = preg_replace('/' . '\\n' . '\\s*' . '\\*' . '\\s?' . '/', "\n", $docComment); - $docComment = (string) substr($docComment, 0, -1); - return rtrim($docComment, "\n"); + $this->type = $type; } - /** @return array> */ - private static function parseDocBlock(string $docBlock) : array + /** + * Returns a string representation of the constraint. + */ + public function toString() : string { - // Strip away the docblock header and footer to ease parsing of one line annotations - $docBlock = (string) substr($docBlock, 3, -2); - $annotations = []; - if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \\t]+(?P.*?))?[ \\t]*\\r?$/m', $docBlock, $matches)) { - $numMatches = count($matches[0]); - for ($i = 0; $i < $numMatches; $i++) { - $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; - } - } - return $annotations; + return sprintf('is of type "%s"', $this->type); } - /** @param ReflectionClass|ReflectionFunctionAbstract $reflector */ - private static function extractAnnotationsFromReflector(Reflector $reflector) : array - { - $annotations = []; - if ($reflector instanceof ReflectionClass) { - $annotations = array_merge($annotations, ...array_map(static function (ReflectionClass $trait) : array { - return self::parseDocBlock((string) $trait->getDocComment()); - }, array_values($reflector->getTraits()))); + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other) : bool + { + switch ($this->type) { + case 'numeric': + return is_numeric($other); + case 'integer': + case 'int': + return is_int($other); + case 'double': + case 'float': + case 'real': + return is_float($other); + case 'string': + return is_string($other); + case 'boolean': + case 'bool': + return is_bool($other); + case 'null': + return null === $other; + case 'array': + return is_array($other); + case 'object': + return is_object($other); + case 'resource': + $type = gettype($other); + return $type === 'resource' || $type === 'resource (closed)'; + case 'resource (closed)': + return gettype($other) === 'resource (closed)'; + case 'scalar': + return is_scalar($other); + case 'callable': + return is_callable($other); + case 'iterable': + return is_iterable($other); + default: + return \false; } - return array_merge($annotations, self::parseDocBlock((string) $reflector->getDocComment())); } } indexed by class name */ - private $classDocBlocks = []; - /** @var array> indexed by class name and method name */ - private $methodDocBlocks = []; - public static function getInstance() : self + /** + * @var list + */ + private $dependencies = []; + /** + * @param list $dependencies + */ + public function setDependencies(array $dependencies) : void { - return self::$instance ?? (self::$instance = new self()); + $this->dependencies = $dependencies; + foreach ($this->tests as $test) { + if (!$test instanceof \PHPUnit\Framework\TestCase) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreStart + } + $test->setDependencies($dependencies); + } } - private function __construct() + /** + * @return list + */ + public function provides() : array { + if ($this->providedTests === null) { + $this->providedTests = [new \PHPUnit\Framework\ExecutionOrderDependency($this->getName())]; + } + return $this->providedTests; } /** - * @throws Exception - * @psalm-param class-string $class + * @return list */ - public function forClassName(string $class) : \PHPUnit\Util\Annotation\DocBlock + public function requires() : array { - if (array_key_exists($class, $this->classDocBlocks)) { - return $this->classDocBlocks[$class]; - } - try { - $reflection = new ReflectionClass($class); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - return $this->classDocBlocks[$class] = \PHPUnit\Util\Annotation\DocBlock::ofClass($reflection); + // A DataProviderTestSuite does not have to traverse its child tests + // as these are inherited and cannot reference dataProvider rows directly + return $this->dependencies; } /** - * @throws Exception - * @psalm-param class-string $classInHierarchy + * Returns the size of the each test created using the data provider(s). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function forMethod(string $classInHierarchy, string $method) : \PHPUnit\Util\Annotation\DocBlock + public function getSize() : int { - if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { - return $this->methodDocBlocks[$classInHierarchy][$method]; - } - try { - $reflection = new ReflectionMethod($classInHierarchy, $method); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - return $this->methodDocBlocks[$classInHierarchy][$method] = \PHPUnit\Util\Annotation\DocBlock::ofMethod($reflection, $classInHierarchy); + [$className, $methodName] = explode('::', $this->getName()); + return TestUtil::getSize($className, $methodName); } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +use PHPUnit\Framework\Exception; +/** + * @internal + */ +class Error extends Exception +{ + public function __construct(string $message, int $code, string $file, int $line, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + $this->file = $file; + $this->line = $line; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +/** + * @internal + */ +final class Notice extends \PHPUnit\Framework\Error\Error +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +/** + * @internal + */ +final class Warning extends \PHPUnit\Framework\Error\Error +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorTestCase extends \PHPUnit\Framework\TestCase { /** - * @var array + * @var bool */ - private const EXCLUDED_CLASS_NAMES = [ - // composer - ClassLoader::class => 1, - // doctrine/instantiator - Instantiator::class => 1, - // myclabs/deepcopy - DeepCopy::class => 1, - // nikic/php-parser - Parser::class => 1, - // phar-io/manifest - Manifest::class => 1, - // phar-io/version - PharIoVersion::class => 1, - // phpdocumentor/reflection-common - Project::class => 1, - // phpdocumentor/reflection-docblock - DocBlock::class => 1, - // phpdocumentor/type-resolver - Type::class => 1, - // phpspec/prophecy - Prophet::class => 1, - // phpunit/phpunit - TestCase::class => 2, - // phpunit/php-code-coverage - CodeCoverage::class => 1, - // phpunit/php-file-iterator - FileIteratorFacade::class => 1, - // phpunit/php-invoker - Invoker::class => 1, - // phpunit/php-text-template - Template::class => 1, - // phpunit/php-timer - Timer::class => 1, - // sebastian/cli-parser - CliParser::class => 1, - // sebastian/code-unit - CodeUnit::class => 1, - // sebastian/code-unit-reverse-lookup - Wizard::class => 1, - // sebastian/comparator - Comparator::class => 1, - // sebastian/complexity - Calculator::class => 1, - // sebastian/diff - Diff::class => 1, - // sebastian/environment - Runtime::class => 1, - // sebastian/exporter - Exporter::class => 1, - // sebastian/global-state - Snapshot::class => 1, - // sebastian/lines-of-code - Counter::class => 1, - // sebastian/object-enumerator - Enumerator::class => 1, - // sebastian/recursion-context - Context::class => 1, - // sebastian/resource-operations - ResourceOperations::class => 1, - // sebastian/type - TypeName::class => 1, - // sebastian/version - Version::class => 1, - // symfony/polyfill-ctype - Ctype::class => 1, - // theseer/tokenizer - Tokenizer::class => 1, - // webmozart/assert - Assert::class => 1, - ]; + protected $backupGlobals = \false; /** - * @var string[] + * @var bool */ - private static $directories; - public static function addDirectory(string $directory) : void - { - if (!is_dir($directory)) { - throw new \PHPUnit\Util\Exception(sprintf('"%s" is not a directory', $directory)); - } - self::$directories[] = realpath($directory); - } + protected $backupStaticAttributes = \false; /** - * @throws Exception - * - * @return string[] + * @var bool */ - public function getExcludedDirectories() : array + protected $runTestInSeparateProcess = \false; + /** + * @var string + */ + private $message; + public function __construct(string $message = '') { - $this->initialize(); - return self::$directories; + $this->message = $message; + parent::__construct('Error'); + } + public function getMessage() : string + { + return $this->message; } /** - * @throws Exception + * Returns a string representation of the test case. */ - public function isExcluded(string $file) : bool + public function toString() : string { - if (defined('PHPUNIT_TESTSUITE')) { - return \false; - } - $this->initialize(); - foreach (self::$directories as $directory) { - if (strpos($file, $directory) === 0) { - return \true; - } - } - return \false; + return 'Error'; } /** * @throws Exception + * + * @psalm-return never-return */ - private function initialize() : void + protected function runTest() : void { - if (self::$directories === null) { - self::$directories = []; - foreach (self::EXCLUDED_CLASS_NAMES as $className => $parent) { - if (!class_exists($className)) { - continue; - } - try { - $directory = (new ReflectionClass($className))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Util\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - for ($i = 0; $i < $parent; $i++) { - $directory = dirname($directory); - } - self::$directories[] = $directory; - } - // Hide process isolation workaround on Windows. - if (\DIRECTORY_SEPARATOR === '\\') { - // tempnam() prefix is limited to first 3 chars. - // @see https://php.net/manual/en/function.tempnam.php - self::$directories[] = sys_get_temp_dir() . '\\PHP'; - } - } + throw new \PHPUnit\Framework\Error($this->message); } } path() . 'phpunit.xsd'; - } else { - $filename = $this->path() . 'schema/' . $version . '.xsd'; - } - if (!is_file($filename)) { - throw new \PHPUnit\Util\Xml\Exception(sprintf('Schema for PHPUnit %s is not available', $version)); - } - return $filename; + parent::__construct('Actual value is not an object', 0, null); } - private function path() : string + public function __toString() : string { - if (defined('__PHPUNIT_PHAR_ROOT__')) { - return __PHPUNIT_PHAR_ROOT__ . '/'; - } - return __DIR__ . '/../../../'; + return $this->getMessage() . PHP_EOL; } } schemaValidateSource(file_get_contents($xsdFilename)); - $errors = libxml_get_errors(); - libxml_clear_errors(); - libxml_use_internal_errors($originalErrorHandling); - return \PHPUnit\Util\Xml\ValidationResult::fromArray($errors); + return $this->getMessage(); } } load($contents, $isHtml, $filename, $xinclude, $strict); - } - /** - * @throws Exception - */ - public function load(string $actual, bool $isHtml = \false, string $filename = '', bool $xinclude = \false, bool $strict = \false) : DOMDocument - { - if ($actual === '') { - throw new \PHPUnit\Util\Xml\Exception('Could not load XML from empty string'); - } - // Required for XInclude on Windows. - if ($xinclude) { - $cwd = getcwd(); - @chdir(dirname($filename)); - } - $document = new DOMDocument(); - $document->preserveWhiteSpace = \false; - $internal = libxml_use_internal_errors(\true); - $message = ''; - $reporting = error_reporting(0); - if ($filename !== '') { - // Required for XInclude - $document->documentURI = $filename; - } - if ($isHtml) { - $loaded = $document->loadHTML($actual); - } else { - $loaded = $document->loadXML($actual); - } - if (!$isHtml && $xinclude) { - $document->xinclude(); - } - foreach (libxml_get_errors() as $error) { - $message .= "\n" . $error->message; - } - libxml_use_internal_errors($internal); - error_reporting($reporting); - if (isset($cwd)) { - @chdir($cwd); - } - if ($loaded === \false || $strict && $message !== '') { - if ($filename !== '') { - throw new \PHPUnit\Util\Xml\Exception(sprintf('Could not load "%s".%s', $filename, $message !== '' ? "\n" . $message : '')); - } - if ($message === '') { - $message = 'Could not load XML for unknown reason'; - } - throw new \PHPUnit\Util\Xml\Exception($message); - } - return $document; - } } > - */ - private $validationErrors = []; - /** - * @psalm-param array $errors - */ - public static function fromArray(array $errors) : self + public function __construct(string $className, string $methodName, string $type) { - $validationErrors = []; - foreach ($errors as $error) { - if (!isset($validationErrors[$error->line])) { - $validationErrors[$error->line] = []; - } - $validationErrors[$error->line][] = trim($error->message); - } - return new self($validationErrors); + parent::__construct(sprintf('%s is not an accepted argument type for comparison method %s::%s().', $type, $className, $methodName), 0, null); } - private function __construct(array $validationErrors) + public function __toString() : string { - $this->validationErrors = $validationErrors; + return $this->getMessage() . PHP_EOL; } - public function hasValidationErrors() : bool +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends \PHPUnit\Framework\Exception +{ + public function __construct(string $className, string $methodName) { - return !empty($this->validationErrors); + parent::__construct(sprintf('Comparison method %s::%s() does not declare bool return type.', $className, $methodName), 0, null); } - public function asString() : string + public function __toString() : string { - $buffer = ''; - foreach ($this->validationErrors as $line => $validationErrorsOnLine) { - $buffer .= sprintf(\PHP_EOL . ' Line %d:' . \PHP_EOL, $line); - foreach ($validationErrorsOnLine as $validationError) { - $buffer .= sprintf(' - %s' . \PHP_EOL, $validationError); - } - } - return $buffer; + return $this->getMessage() . PHP_EOL; } } getMessage() . PHP_EOL; + } } getMessage() . PHP_EOL; + } } getMessage() . PHP_EOL; } } loadFile($filename, \false, \true, \true); - foreach (['9.2', '8.5'] as $candidate) { - $schema = (new \PHPUnit\Util\Xml\SchemaFinder())->find($candidate); - if (!(new \PHPUnit\Util\Xml\Validator())->validate($document, $schema)->hasValidationErrors()) { - return new \PHPUnit\Util\Xml\SuccessfulSchemaDetectionResult($candidate); - } - } - return new \PHPUnit\Util\Xml\FailedSchemaDetectionResult(); - } } version = $version; - } - public function detected() : bool - { - return \true; - } - public function version() : string + public function toString() : string { - return $this->version; + return $this->getMessage(); } } nodes[] = $node; + parent::__construct($message, $code, $previous); + $this->serializableTrace = $this->getTrace(); + foreach (array_keys($this->serializableTrace) as $key) { + unset($this->serializableTrace[$key]['args']); } - return $snapshot; } - public function count() : int + public function __toString() : string { - return \count($this->nodes); + $string = \PHPUnit\Framework\TestFailure::exceptionToString($this); + if ($trace = Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; + } + return $string; } - public function getIterator() : ArrayIterator + public function __sleep() : array { - return new ArrayIterator($this->nodes); + return array_keys(get_object_vars($this)); + } + /** + * Returns the serializable trace (without 'args'). + */ + public function getSerializableTrace() : array + { + return $this->serializableTrace; } } write($this->currentTestClassPrettified . "\n"); - } /** - * Handler for 'on test' event. + * @var ComparisonFailure */ - protected function onTest(string $name, bool $success = \true) : void + protected $comparisonFailure; + public function __construct(string $message, ComparisonFailure $comparisonFailure = null, Exception $previous = null) { - if ($success) { - $this->write(' [x] '); - } else { - $this->write(' [ ] '); - } - $this->write($name . "\n"); + $this->comparisonFailure = $comparisonFailure; + parent::__construct($message, 0, $previous); } - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name) : void + public function getComparisonFailure() : ?ComparisonFailure { - $this->write("\n"); + return $this->comparisonFailure; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function debug_backtrace; use function in_array; -use function is_bool; -use function is_float; -use function is_int; -use function is_numeric; -use function is_object; -use function is_scalar; -use function is_string; -use function mb_strtolower; -use function ord; -use function preg_quote; -use function preg_replace; -use function range; +use function lcfirst; use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function strtoupper; -use function substr; -use function trim; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\Test; -use ReflectionException; -use ReflectionMethod; -use ReflectionObject; -use PHPUnit\SebastianBergmann\Exporter\Exporter; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class NamePrettifier +final class InvalidArgumentException extends \PHPUnit\Framework\Exception { - /** - * @var string[] - */ - private $strings = []; - /** - * @var bool - */ - private $useColor; - public function __construct(bool $useColor = \false) - { - $this->useColor = $useColor; - } - /** - * Prettifies the name of a test class. - * - * @psalm-param class-string $className - */ - public function prettifyTestClass(string $className) : string - { - try { - $annotations = Test::parseTestMethodAnnotations($className); - if (isset($annotations['class']['testdox'][0])) { - return $annotations['class']['testdox'][0]; - } - } catch (UtilException $e) { - // ignore, determine className by parsing the provided name - } - $parts = explode('\\', $className); - $className = array_pop($parts); - if (substr($className, -1 * strlen('Test')) === 'Test') { - $className = substr($className, 0, strlen($className) - strlen('Test')); - } - if (strpos($className, 'Tests') === 0) { - $className = substr($className, strlen('Tests')); - } elseif (strpos($className, 'Test') === 0) { - $className = substr($className, strlen('Test')); - } - if (empty($className)) { - $className = 'UnnamedTests'; - } - if (!empty($parts)) { - $parts[] = $className; - $fullyQualifiedName = implode('\\', $parts); - } else { - $fullyQualifiedName = $className; - } - $result = ''; - $wasLowerCase = \false; - foreach (range(0, strlen($className) - 1) as $i) { - $isLowerCase = mb_strtolower($className[$i], 'UTF-8') === $className[$i]; - if ($wasLowerCase && !$isLowerCase) { - $result .= ' '; - } - $result .= $className[$i]; - if ($isLowerCase) { - $wasLowerCase = \true; - } else { - $wasLowerCase = \false; - } - } - if ($fullyQualifiedName !== $className) { - return $result . ' (' . $fullyQualifiedName . ')'; - } - return $result; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function prettifyTestCase(TestCase $test) : string - { - $annotations = Test::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); - $annotationWithPlaceholders = \false; - $callback = static function (string $variable) : string { - return sprintf('/%s(?=\\b)/', preg_quote($variable, '/')); - }; - if (isset($annotations['method']['testdox'][0])) { - $result = $annotations['method']['testdox'][0]; - if (strpos($result, '$') !== \false) { - $annotation = $annotations['method']['testdox'][0]; - $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); - $variables = array_map($callback, array_keys($providedData)); - $result = trim(preg_replace($variables, $providedData, $annotation)); - $annotationWithPlaceholders = \true; - } - } else { - $result = $this->prettifyTestMethod($test->getName(\false)); - } - if (!$annotationWithPlaceholders && $test->usesDataProvider()) { - $result .= $this->prettifyDataSet($test); - } - return $result; - } - public function prettifyDataSet(TestCase $test) : string - { - if (!$this->useColor) { - return $test->getDataSetAsString(\false); - } - if (is_int($test->dataName())) { - $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); - } else { - $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); - } - return $data; - } - /** - * Prettifies the name of a test method. - */ - public function prettifyTestMethod(string $name) : string - { - $buffer = ''; - if ($name === '') { - return $buffer; - } - $string = (string) preg_replace('#\\d+$#', '', $name, -1, $count); - if (in_array($string, $this->strings, \true)) { - $name = $string; - } elseif ($count === 0) { - $this->strings[] = $string; - } - if (strpos($name, 'test_') === 0) { - $name = substr($name, 5); - } elseif (strpos($name, 'test') === 0) { - $name = substr($name, 4); - } - if ($name === '') { - return $buffer; - } - $name[0] = strtoupper($name[0]); - if (strpos($name, '_') !== \false) { - return trim(str_replace('_', ' ', $name)); - } - $wasNumeric = \false; - foreach (range(0, strlen($name) - 1) as $i) { - if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { - $buffer .= ' ' . strtolower($name[$i]); - } else { - $isNumeric = is_numeric($name[$i]); - if (!$wasNumeric && $isNumeric) { - $buffer .= ' '; - $wasNumeric = \true; - } - if ($wasNumeric && !$isNumeric) { - $wasNumeric = \false; - } - $buffer .= $name[$i]; - } - } - return $buffer; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test) : array + public static function create(int $argument, string $type) : self { - try { - $reflector = new ReflectionMethod(get_class($test), $test->getName(\false)); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $providedData = []; - $providedDataValues = array_values($test->getProvidedData()); - $i = 0; - $providedData['$_dataName'] = $test->dataName(); - foreach ($reflector->getParameters() as $parameter) { - if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { - try { - $providedDataValues[$i] = $parameter->getDefaultValue(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } - $value = $providedDataValues[$i++] ?? null; - if (is_object($value)) { - $reflector = new ReflectionObject($value); - if ($reflector->hasMethod('__toString')) { - $value = (string) $value; - } else { - $value = get_class($value); - } - } - if (!is_scalar($value)) { - $value = gettype($value); - } - if (is_bool($value) || is_int($value) || is_float($value)) { - $value = (new Exporter())->export($value); - } - if (is_string($value) && $value === '') { - if ($this->useColor) { - $value = Color::colorize('dim,underlined', 'empty'); - } else { - $value = "''"; - } - } - $providedData['$' . $parameter->getName()] = $value; - } - if ($this->useColor) { - $providedData = array_map(static function ($value) { - return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, \true)); - }, $providedData); + $stack = debug_backtrace(); + $function = $stack[1]['function']; + if (isset($stack[1]['class'])) { + $function = sprintf('%s::%s', $stack[1]['class'], $stack[1]['function']); } - return $providedData; + return new self(sprintf('Argument #%d of %s() must be %s %s', $argument, $function, in_array(lcfirst($type)[0], ['a', 'e', 'i', 'o', 'u'], \true) ? 'an' : 'a', $type)); + } + private function __construct(string $message = '', int $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDataProviderException extends \PHPUnit\Framework\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MissingCoversAnnotationException extends \PHPUnit\Framework\RiskyTestError +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoChildTestSuiteException extends \PHPUnit\Framework\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class OutputError extends \PHPUnit\Framework\AssertionFailedError +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PHPTAssertionFailedError extends \PHPUnit\Framework\SyntheticError { /** - * The default Testdox left margin for messages is a vertical line. - */ - private const PREFIX_SIMPLE = ['default' => '│', 'start' => '│', 'message' => '│', 'diff' => '│', 'trace' => '│', 'last' => '│']; - /** - * Colored Testdox use box-drawing for a more textured map of the message. - */ - private const PREFIX_DECORATED = ['default' => '│', 'start' => '┐', 'message' => '├', 'diff' => '┊', 'trace' => '╵', 'last' => '┴']; - private const SPINNER_ICONS = [" \33[36m◐\33[0m running tests", " \33[36m◓\33[0m running tests", " \33[36m◑\33[0m running tests", " \33[36m◒\33[0m running tests"]; - private const STATUS_STYLES = [BaseTestRunner::STATUS_PASSED => ['symbol' => '✔', 'color' => 'fg-green'], BaseTestRunner::STATUS_ERROR => ['symbol' => '✘', 'color' => 'fg-yellow', 'message' => 'bg-yellow,fg-black'], BaseTestRunner::STATUS_FAILURE => ['symbol' => '✘', 'color' => 'fg-red', 'message' => 'bg-red,fg-white'], BaseTestRunner::STATUS_SKIPPED => ['symbol' => '↩', 'color' => 'fg-cyan', 'message' => 'fg-cyan'], BaseTestRunner::STATUS_RISKY => ['symbol' => '☢', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_INCOMPLETE => ['symbol' => '∅', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_WARNING => ['symbol' => '⚠', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_UNKNOWN => ['symbol' => '?', 'color' => 'fg-blue', 'message' => 'fg-white,bg-blue']]; - /** - * @var int[] - */ - private $nonSuccessfulTestResults = []; - /** - * @var Timer - */ - private $timer; - /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws \PHPUnit\Framework\Exception - */ - public function __construct($out = null, bool $verbose = \false, string $colors = self::COLOR_DEFAULT, bool $debug = \false, $numberOfColumns = 80, bool $reverse = \false) - { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - $this->timer = new Timer(); - $this->timer->start(); - } - public function printResult(TestResult $result) : void - { - $this->printHeader($result); - $this->printNonSuccessfulTestsSummary($result->count()); - $this->printFooter($result); - } - protected function printHeader(TestResult $result) : void - { - $this->write("\n" . (new ResourceUsageFormatter())->resourceUsage($this->timer->stop()) . "\n\n"); - } - protected function formatClassName(Test $test) : string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestClass(get_class($test)); - } - return get_class($test); - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose) : void - { - if ($status !== BaseTestRunner::STATUS_PASSED) { - $this->nonSuccessfulTestResults[] = $this->testIndex; - } - parent::registerTestResult($test, $t, $status, $time, $verbose); - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @var string */ - protected function formatTestName(Test $test) : string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestCase($test); - } - return parent::formatTestName($test); - } - protected function writeTestResult(array $prevResult, array $result) : void - { - // spacer line for new suite headers and after verbose messages - if ($prevResult['testName'] !== '' && (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { - $this->write(\PHP_EOL); - } - // suite header - if ($prevResult['className'] !== $result['className']) { - $this->write($this->colorizeTextBox('underlined', $result['className']) . \PHP_EOL); - } - // test result line - if ($this->colors && $result['className'] === PhptTestCase::class) { - $testName = Color::colorizePath($result['testName'], $prevResult['testName'], \true); - } else { - $testName = $result['testMethod']; - } - $style = self::STATUS_STYLES[$result['status']]; - $line = sprintf(' %s %s%s' . \PHP_EOL, $this->colorizeTextBox($style['color'], $style['symbol']), $testName, $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : ''); - $this->write($line); - // additional information when verbose - $this->write($result['message']); - } - protected function formatThrowable(Throwable $t, ?int $status = null) : string - { - return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); - } - protected function colorizeMessageAndDiff(string $style, string $buffer) : array - { - $lines = $buffer ? array_map('\\rtrim', explode(\PHP_EOL, $buffer)) : []; - $message = []; - $diff = []; - $insideDiff = \false; - foreach ($lines as $line) { - if ($line === '--- Expected') { - $insideDiff = \true; - } - if (!$insideDiff) { - $message[] = $line; - } else { - if (strpos($line, '-') === 0) { - $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, \true)); - } elseif (strpos($line, '+') === 0) { - $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, \true)); - } elseif ($line === '@@ @@') { - $line = Color::colorize('fg-cyan', $line); - } - $diff[] = $line; - } - } - $diff = implode(\PHP_EOL, $diff); - if (!empty($message)) { - $message = $this->colorizeTextBox($style, implode(\PHP_EOL, $message)); - } - return [$message, $diff]; - } - protected function formatStacktrace(Throwable $t) : string - { - $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t); - if (!$this->colors) { - return $trace; - } - $lines = []; - $prevPath = ''; - foreach (explode(\PHP_EOL, $trace) as $line) { - if (preg_match('/^(.*):(\\d+)$/', $line, $matches)) { - $lines[] = Color::colorizePath($matches[1], $prevPath) . Color::dim(':') . Color::colorize('fg-blue', $matches[2]) . "\n"; - $prevPath = $matches[1]; - } else { - $lines[] = $line; - $prevPath = ''; - } - } - return implode('', $lines); - } - protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null) : string - { - $message = $this->formatThrowable($t, $result['status']); - $diff = ''; - if (!($this->verbose || $result['verbose'])) { - return ''; - } - if ($message && $this->colors) { - $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; - [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); - } - if ($prefix === null || !$this->colors) { - $prefix = self::PREFIX_SIMPLE; - } - if ($this->colors) { - $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; - $prefix = array_map(static function ($p) use($color) { - return Color::colorize($color, $p); - }, self::PREFIX_DECORATED); - } - $trace = $this->formatStacktrace($t); - $out = $this->prefixLines($prefix['start'], \PHP_EOL) . \PHP_EOL; - if ($message) { - $out .= $this->prefixLines($prefix['message'], $message . \PHP_EOL) . \PHP_EOL; - } - if ($diff) { - $out .= $this->prefixLines($prefix['diff'], $diff . \PHP_EOL) . \PHP_EOL; - } - if ($trace) { - if ($message || $diff) { - $out .= $this->prefixLines($prefix['default'], \PHP_EOL) . \PHP_EOL; - } - $out .= $this->prefixLines($prefix['trace'], $trace . \PHP_EOL) . \PHP_EOL; - } - $out .= $this->prefixLines($prefix['last'], \PHP_EOL) . \PHP_EOL; - return $out; - } - protected function drawSpinner() : void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write(self::SPINNER_ICONS[$id]); - } - } - protected function undrawSpinner() : void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write("\33[1K\33[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); - } - } - private function formatRuntime(float $time, string $color = '') : string + private $diff; + public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) { - if (!$this->colors) { - return sprintf('[%.2f ms]', $time * 1000); - } - if ($time > 1) { - $color = 'fg-magenta'; - } - return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); + parent::__construct($message, $code, $file, $line, $trace); + $this->diff = $diff; } - private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests) : void + public function getDiff() : string { - if (empty($this->nonSuccessfulTestResults)) { - return; - } - if (count($this->nonSuccessfulTestResults) / $numberOfExecutedTests >= 0.7) { - return; - } - $this->write("Summary of non-successful tests:\n\n"); - $prevResult = $this->getEmptyTestResult(); - foreach ($this->nonSuccessfulTestResults as $testIndex) { - $result = $this->testResults[$testIndex]; - $this->writeTestResult($prevResult, $result); - $prevResult = $result; - } + return $this->diff; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestError extends \PHPUnit\Framework\AssertionFailedError implements \PHPUnit\Framework\SkippedTest +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestSuiteError extends \PHPUnit\Framework\AssertionFailedError implements \PHPUnit\Framework\SkippedTest +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class SyntheticError extends \PHPUnit\Framework\AssertionFailedError { /** - * @var NamePrettifier - */ - protected $prettifier; - /** + * The synthetic file. + * * @var string */ - protected $testClass = ''; - /** - * @var int - */ - protected $testStatus; - /** - * @var array - */ - protected $tests = []; - /** - * @var int - */ - protected $successful = 0; - /** - * @var int - */ - protected $warned = 0; - /** - * @var int - */ - protected $failed = 0; - /** - * @var int - */ - protected $risky = 0; - /** - * @var int - */ - protected $skipped = 0; + protected $syntheticFile = ''; /** + * The synthetic line number. + * * @var int */ - protected $incomplete = 0; - /** - * @var null|string - */ - protected $currentTestClassPrettified; - /** - * @var null|string - */ - protected $currentTestMethodPrettified; - /** - * @var array - */ - private $groups; - /** - * @var array - */ - private $excludeGroups; + protected $syntheticLine = 0; /** - * @param resource $out + * The synthetic trace. * - * @throws \PHPUnit\Framework\Exception - */ - public function __construct($out = null, array $groups = [], array $excludeGroups = []) - { - parent::__construct($out); - $this->groups = $groups; - $this->excludeGroups = $excludeGroups; - $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier(); - $this->startRun(); - } - /** - * Flush buffer and close output. - */ - public function flush() : void - { - $this->doEndClass(); - $this->endRun(); - parent::flush(); - } - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time) : void - { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_ERROR; - $this->failed++; - } - /** - * A warning occurred. + * @var array */ - public function addWarning(Test $test, Warning $e, float $time) : void + protected $syntheticTrace = []; + public function __construct(string $message, int $code, string $file, int $line, array $trace) { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_WARNING; - $this->warned++; - } - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + parent::__construct($message, $code); + $this->syntheticFile = $file; + $this->syntheticLine = $line; + $this->syntheticTrace = $trace; + } + public function getSyntheticFile() : string { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_FAILURE; - $this->failed++; + return $this->syntheticFile; } - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void + public function getSyntheticLine() : int { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; - $this->incomplete++; + return $this->syntheticLine; } - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void + public function getSyntheticTrace() : array { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_RISKY; - $this->risky++; + return $this->syntheticTrace; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SyntheticSkippedError extends \PHPUnit\Framework\SyntheticError implements \PHPUnit\Framework\SkippedTest +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnintentionallyCoveredCodeError extends \PHPUnit\Framework\RiskyTestError +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Warning extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\SelfDescribing +{ /** - * Skipped test. + * Wrapper for getMessage() which is declared as final. */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void + public function toString() : string { - if (!$this->isOfInterest($test)) { - return; - } - $this->testStatus = BaseTestRunner::STATUS_SKIPPED; - $this->skipped++; + return $this->getMessage(); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_VERSION_ID; +use function array_keys; +use function get_class; +use function spl_object_hash; +use PHPUnit\Util\Filter; +use Throwable; +use WeakReference; +/** + * Wraps Exceptions thrown by code under test. + * + * Re-instantiates Exceptions thrown by user-space code to retain their original + * class names, properties, and stack traces (but without arguments). + * + * Unlike PHPUnit\Framework\Exception, the complete stack of previous Exceptions + * is processed. + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionWrapper extends \PHPUnit\Framework\Exception +{ /** - * A testsuite started. + * @var string */ - public function startTestSuite(TestSuite $suite) : void - { - } + protected $className; /** - * A testsuite ended. + * @var null|ExceptionWrapper */ - public function endTestSuite(TestSuite $suite) : void - { - } + protected $previous; /** - * A test started. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @var null|WeakReference */ - public function startTest(Test $test) : void + private $originalException; + public function __construct(Throwable $t) { - if (!$this->isOfInterest($test)) { - return; - } - $class = get_class($test); - if ($this->testClass !== $class) { - if ($this->testClass !== '') { - $this->doEndClass(); - } - $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); - $this->testClass = $class; - $this->tests = []; - $this->startClass($class); - } - if ($test instanceof TestCase) { - $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); - } - $this->testStatus = BaseTestRunner::STATUS_PASSED; + // PDOException::getCode() is a string. + // @see https://php.net/manual/en/class.pdoexception.php#95812 + parent::__construct($t->getMessage(), (int) $t->getCode()); + $this->setOriginalException($t); } - /** - * A test ended. - */ - public function endTest(Test $test, float $time) : void + public function __toString() : string { - if (!$this->isOfInterest($test)) { - return; + $string = \PHPUnit\Framework\TestFailure::exceptionToString($this); + if ($trace = Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; } - $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; - $this->currentTestClassPrettified = null; - $this->currentTestMethodPrettified = null; + if ($this->previous) { + $string .= "\nCaused by\n" . $this->previous; + } + return $string; } - protected function doEndClass() : void + public function getClassName() : string { - foreach ($this->tests as $test) { - $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); - } - $this->endClass($this->testClass); + return $this->className; } - /** - * Handler for 'start run' event. - */ - protected function startRun() : void + public function getPreviousWrapped() : ?self { + return $this->previous; } - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name) : void + public function setClassName(string $className) : void { + $this->className = $className; } - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = \true) : void + public function setOriginalException(Throwable $t) : void { + $this->originalException($t); + $this->className = get_class($t); + $this->file = $t->getFile(); + $this->line = $t->getLine(); + $this->serializableTrace = $t->getTrace(); + foreach (array_keys($this->serializableTrace) as $key) { + unset($this->serializableTrace[$key]['args']); + } + if ($t->getPrevious()) { + $this->previous = new self($t->getPrevious()); + } } - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name) : void + public function getOriginalException() : ?Throwable { + return $this->originalException(); } /** - * Handler for 'end run' event. + * Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents, + * which can be quite big, from being garbage-collected, thus blocking memory until shutdown. + * + * Approach works both for var_dump() and var_export() and print_r(). */ - protected function endRun() : void - { - } - private function isOfInterest(Test $test) : bool + private function originalException(Throwable $exceptionToStore = null) : ?Throwable { - if (!$test instanceof TestCase) { - return \false; - } - if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { - return \false; - } - if (!empty($this->groups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->groups, \true)) { - return \true; - } + // drop once PHP 7.3 support is removed + if (PHP_VERSION_ID < 70400) { + static $originalExceptions; + $instanceId = spl_object_hash($this); + if ($exceptionToStore) { + $originalExceptions[$instanceId] = $exceptionToStore; } - return \false; + return $originalExceptions[$instanceId] ?? null; } - if (!empty($this->excludeGroups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->excludeGroups, \true)) { - return \false; - } - } - return \true; + if ($exceptionToStore) { + $this->originalException = WeakReference::create($exceptionToStore); } - return \true; + return $this->originalException !== null ? $this->originalException->get() : null; } } document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = \true; - $this->root = $this->document->createElement('tests'); - $this->document->appendChild($this->root); - $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier(); - parent::__construct($out); - } - /** - * Flush buffer and close output. - */ - public function flush() : void - { - $this->write($this->document->saveXML()); - parent::flush(); - } - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time) : void - { - $this->exception = $t; - } - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time) : void - { - } - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void - { - $this->exception = $e; - } - /** - * Incomplete test. + * @var string */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void - { - } + private $className = ''; /** - * Risky test. + * @var string */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void - { - } + private $methodName = ''; /** - * Skipped test. + * @var bool */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void - { - } + private $useShallowClone = \false; /** - * A test suite started. + * @var bool */ - public function startTestSuite(TestSuite $suite) : void + private $useDeepClone = \false; + public static function createFromDependsAnnotation(string $className, string $annotation) : self { + // Split clone option and target + $parts = explode(' ', trim($annotation), 2); + if (count($parts) === 1) { + $cloneOption = ''; + $target = $parts[0]; + } else { + $cloneOption = $parts[0]; + $target = $parts[1]; + } + // Prefix provided class for targets assumed to be in scope + if ($target !== '' && strpos($target, '::') === \false) { + $target = $className . '::' . $target; + } + return new self($target, null, $cloneOption); } /** - * A test suite ended. + * @psalm-param list $dependencies + * + * @psalm-return list */ - public function endTestSuite(TestSuite $suite) : void + public static function filterInvalid(array $dependencies) : array { + return array_values(array_filter($dependencies, static function (self $d) { + return $d->isValid(); + })); } /** - * A test started. + * @psalm-param list $existing + * @psalm-param list $additional + * + * @psalm-return list */ - public function startTest(Test $test) : void + public static function mergeUnique(array $existing, array $additional) : array { - $this->exception = null; + $existingTargets = array_map(static function ($dependency) { + return $dependency->getTarget(); + }, $existing); + foreach ($additional as $dependency) { + if (in_array($dependency->getTarget(), $existingTargets, \true)) { + continue; + } + $existingTargets[] = $dependency->getTarget(); + $existing[] = $dependency; + } + return $existing; } /** - * A test ended. + * @psalm-param list $left + * @psalm-param list $right * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @psalm-return list */ - public function endTest(Test $test, float $time) : void + public static function diff(array $left, array $right) : array { - if (!$test instanceof TestCase || $test instanceof WarningTestCase) { - return; + if ($right === []) { + return $left; } - $groups = array_filter($test->getGroups(), static function ($group) { - return !($group === 'small' || $group === 'medium' || $group === 'large' || strpos($group, '__phpunit_') === 0); - }); - $testNode = $this->document->createElement('test'); - $testNode->setAttribute('className', get_class($test)); - $testNode->setAttribute('methodName', $test->getName()); - $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); - $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); - $testNode->setAttribute('status', (string) $test->getStatus()); - $testNode->setAttribute('time', (string) $time); - $testNode->setAttribute('size', (string) $test->getSize()); - $testNode->setAttribute('groups', implode(',', $groups)); - foreach ($groups as $group) { - $groupNode = $this->document->createElement('group'); - $groupNode->setAttribute('name', $group); - $testNode->appendChild($groupNode); + if ($left === []) { + return []; } - $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); - foreach (['class', 'method'] as $type) { - foreach ($annotations[$type] as $annotation => $values) { - if ($annotation !== 'covers' && $annotation !== 'uses') { - continue; - } - foreach ($values as $value) { - $coversNode = $this->document->createElement($annotation); - $coversNode->setAttribute('target', $value); - $testNode->appendChild($coversNode); - } + $diff = []; + $rightTargets = array_map(static function ($dependency) { + return $dependency->getTarget(); + }, $right); + foreach ($left as $dependency) { + if (in_array($dependency->getTarget(), $rightTargets, \true)) { + continue; } + $diff[] = $dependency; } - foreach ($test->doubledTypes() as $doubledType) { - $testDoubleNode = $this->document->createElement('testDouble'); - $testDoubleNode->setAttribute('type', $doubledType); - $testNode->appendChild($testDoubleNode); + return $diff; + } + public function __construct(string $classOrCallableName, ?string $methodName = null, ?string $option = null) + { + if ($classOrCallableName === '') { + return; } - $inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(get_class($test), $test->getName(\false)); - if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { - $testNode->setAttribute('given', $inlineAnnotations['given']['value']); - $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); - $testNode->setAttribute('when', $inlineAnnotations['when']['value']); - $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); - $testNode->setAttribute('then', $inlineAnnotations['then']['value']); - $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); + if (strpos($classOrCallableName, '::') !== \false) { + [$this->className, $this->methodName] = explode('::', $classOrCallableName); + } else { + $this->className = $classOrCallableName; + $this->methodName = !empty($methodName) ? $methodName : 'class'; } - if ($this->exception !== null) { - if ($this->exception instanceof Exception) { - $steps = $this->exception->getSerializableTrace(); - } else { - $steps = $this->exception->getTrace(); - } - try { - $file = (new ReflectionClass($test))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - foreach ($steps as $step) { - if (isset($step['file']) && $step['file'] === $file) { - $testNode->setAttribute('exceptionLine', (string) $step['line']); - break; - } - } - $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); + if ($option === 'clone') { + $this->useDeepClone = \true; + } elseif ($option === 'shallowClone') { + $this->useShallowClone = \true; } - $this->root->appendChild($testNode); + } + public function __toString() : string + { + return $this->getTarget(); + } + public function isValid() : bool + { + // Invalid dependencies can be declared and are skipped by the runner + return $this->className !== '' && $this->methodName !== ''; + } + public function useShallowClone() : bool + { + return $this->useShallowClone; + } + public function useDeepClone() : bool + { + return $this->useDeepClone; + } + public function targetIsClass() : bool + { + return $this->methodName === 'class'; + } + public function getTarget() : string + { + return $this->isValid() ? $this->className . '::' . $this->methodName : ''; + } + public function getTargetClassName() : string + { + return $this->className; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncompleteTestCase extends \PHPUnit\Framework\TestCase { /** - * @var NamePrettifier + * @var bool */ - protected $prettifier; + protected $backupGlobals = \false; /** - * @var int The number of test results received from the TestRunner + * @var bool */ - protected $testIndex = 0; + protected $backupStaticAttributes = \false; /** - * @var int The number of test results already sent to the output + * @var bool */ - protected $testFlushIndex = 0; + protected $runTestInSeparateProcess = \false; /** - * @var array Buffer for test results + * @var string */ - protected $testResults = []; + private $message; + public function __construct(string $className, string $methodName, string $message = '') + { + parent::__construct($className . '::' . $methodName); + $this->message = $message; + } + public function getMessage() : string + { + return $this->message; + } /** - * @var array Lookup table for testname to testResults[index] + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected $testNameResultIndex = []; + public function toString() : string + { + return $this->getName(); + } /** - * @var bool + * @throws Exception */ - protected $enableOutputBuffer = \false; + protected function runTest() : void + { + $this->markTestIncomplete($this->message); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidParameterGroupException extends \PHPUnit\Framework\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +/** + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Api +{ /** - * @var array array + * @var ConfigurableMethod[] */ - protected $originalExecutionOrder = []; + private static $__phpunit_configurableMethods; /** - * @var int + * @var object */ - protected $spinState = 0; + private $__phpunit_originalObject; /** * @var bool */ - protected $showProgress = \true; + private $__phpunit_returnValueGeneration = \true; /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws \PHPUnit\Framework\Exception + * @var InvocationHandler */ - public function __construct($out = null, bool $verbose = \false, string $colors = self::COLOR_DEFAULT, bool $debug = \false, $numberOfColumns = 80, bool $reverse = \false) + private $__phpunit_invocationMocker; + /** @noinspection MagicMethodsValidityInspection */ + public static function __phpunit_initConfigurableMethods(\PHPUnit\Framework\MockObject\ConfigurableMethod ...$configurableMethods) : void { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier($this->colors); + if (isset(static::$__phpunit_configurableMethods)) { + throw new \PHPUnit\Framework\MockObject\ConfigurableMethodsAlreadyInitializedException('Configurable methods is already initialized and can not be reinitialized'); + } + static::$__phpunit_configurableMethods = $configurableMethods; } - public function setOriginalExecutionOrder(array $order) : void + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_setOriginalObject($originalObject) : void { - $this->originalExecutionOrder = $order; - $this->enableOutputBuffer = !empty($order); + $this->__phpunit_originalObject = $originalObject; } - public function setShowProgressAnimation(bool $showProgress) : void + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration) : void { - $this->showProgress = $showProgress; + $this->__phpunit_returnValueGeneration = $returnValueGeneration; } - public function printResult(TestResult $result) : void + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_getInvocationHandler() : \PHPUnit\Framework\MockObject\InvocationHandler { + if ($this->__phpunit_invocationMocker === null) { + $this->__phpunit_invocationMocker = new \PHPUnit\Framework\MockObject\InvocationHandler(static::$__phpunit_configurableMethods, $this->__phpunit_returnValueGeneration); + } + return $this->__phpunit_invocationMocker; } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function endTest(Test $test, float $time) : void + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_hasMatchers() : bool { - if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { - return; - } - if ($this->testHasPassed()) { - $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, \false); - } - if ($test instanceof TestCase || $test instanceof PhptTestCase) { - $this->testIndex++; + return $this->__phpunit_getInvocationHandler()->hasMatchers(); + } + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_verify(bool $unsetInvocationMocker = \true) : void + { + $this->__phpunit_getInvocationHandler()->verify(); + if ($unsetInvocationMocker) { + $this->__phpunit_invocationMocker = null; } - parent::endTest($test, $time); } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addError(Test $test, Throwable $t, float $time) : void + public function expects(InvocationOrder $matcher) : InvocationMockerBuilder { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, \true); + return $this->__phpunit_getInvocationHandler()->expects($matcher); } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addWarning(Test $test, Warning $e, float $time) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function call_user_func_array; +use function func_get_args; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +/** + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Method +{ + public function method() { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, \true); + $expects = $this->expects(new AnyInvokedCount()); + return call_user_func_array([$expects, 'method'], func_get_args()); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Identity +{ /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Sets the identification of the expectation to $id. + * + * @note The identifier is unique per mock object. + * + * @param string $id unique identification of expectation */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void - { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, \true); - } + public function id($id); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +use function array_map; +use function array_merge; +use function count; +use function in_array; +use function is_string; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\ConfigurableMethod; +use PHPUnit\Framework\MockObject\IncompatibleReturnValueException; +use PHPUnit\Framework\MockObject\InvocationHandler; +use PHPUnit\Framework\MockObject\Matcher; +use PHPUnit\Framework\MockObject\MatcherAlreadyRegisteredException; +use PHPUnit\Framework\MockObject\MethodCannotBeConfiguredException; +use PHPUnit\Framework\MockObject\MethodNameAlreadyConfiguredException; +use PHPUnit\Framework\MockObject\MethodNameNotConfiguredException; +use PHPUnit\Framework\MockObject\MethodParametersAlreadyConfiguredException; +use PHPUnit\Framework\MockObject\Rule; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; +use PHPUnit\Framework\MockObject\Stub\Exception; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback; +use PHPUnit\Framework\MockObject\Stub\ReturnReference; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationMocker implements \PHPUnit\Framework\MockObject\Builder\InvocationStubber, \PHPUnit\Framework\MockObject\Builder\MethodNameMatch +{ /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @var InvocationHandler */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, \false); - } + private $invocationHandler; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @var Matcher */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, \false); - } + private $matcher; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @var ConfigurableMethod[] */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void + private $configurableMethods; + public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, \false); + $this->invocationHandler = $handler; + $this->matcher = $matcher; + $this->configurableMethods = $configurableMethods; } - public function writeProgress(string $progress) : void + /** + * @throws MatcherAlreadyRegisteredException + * + * @return $this + */ + public function id($id) : self { - $this->flushOutputBuffer(); + $this->invocationHandler->registerMatcher($id, $this->matcher); + return $this; } - public function flush() : void + /** + * @return $this + */ + public function will(Stub $stub) : \PHPUnit\Framework\MockObject\Builder\Identity { - $this->flushOutputBuffer(\true); + $this->matcher->setStub($stub); + return $this; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param mixed $value + * @param mixed[] $nextValues + * + * @throws IncompatibleReturnValueException */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose) : void + public function willReturn($value, ...$nextValues) : self { - $testName = $test instanceof Reorderable ? $test->sortId() : $test->getName(); - $result = ['className' => $this->formatClassName($test), 'testName' => $testName, 'testMethod' => $this->formatTestName($test), 'message' => '', 'status' => $status, 'time' => $time, 'verbose' => $verbose]; - if ($t !== null) { - $result['message'] = $this->formatTestResultMessage($t, $result); + if (count($nextValues) === 0) { + $this->ensureTypeOfReturnValues([$value]); + $stub = $value instanceof Stub ? $value : new ReturnStub($value); + } else { + $values = array_merge([$value], $nextValues); + $this->ensureTypeOfReturnValues($values); + $stub = new ConsecutiveCalls($values); } - $this->testResults[$this->testIndex] = $result; - $this->testNameResultIndex[$testName] = $this->testIndex; + return $this->will($stub); } - protected function formatTestName(Test $test) : string + public function willReturnReference(&$reference) : self { - return method_exists($test, 'getName') ? $test->getName() : ''; + $stub = new ReturnReference($reference); + return $this->will($stub); } - protected function formatClassName(Test $test) : string + public function willReturnMap(array $valueMap) : self { - return get_class($test); + $stub = new ReturnValueMap($valueMap); + return $this->will($stub); } - protected function testHasPassed() : bool + public function willReturnArgument($argumentIndex) : self { - if (!isset($this->testResults[$this->testIndex]['status'])) { - return \true; - } - if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { - return \true; - } - return \false; + $stub = new ReturnArgument($argumentIndex); + return $this->will($stub); } - protected function flushOutputBuffer(bool $forceFlush = \false) : void + public function willReturnCallback($callback) : self { - if ($this->testFlushIndex === $this->testIndex) { - return; - } - if ($this->testFlushIndex > 0) { - if ($this->enableOutputBuffer && isset($this->originalExecutionOrder[$this->testFlushIndex - 1])) { - $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); - } else { - $prevResult = $this->testResults[$this->testFlushIndex - 1]; - } - } else { - $prevResult = $this->getEmptyTestResult(); - } - if (!$this->enableOutputBuffer) { - $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); - } else { - do { - $flushed = \false; - if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { - $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); - } else { - // This test(name) cannot found in original execution order, - // flush result to output stream right away - $result = $this->testResults[$this->testFlushIndex]; - } - if (!empty($result)) { - $this->hideSpinner(); - $this->writeTestResult($prevResult, $result); - $this->testFlushIndex++; - $prevResult = $result; - $flushed = \true; - } else { - $this->showSpinner(); - } - } while ($flushed && $this->testFlushIndex < $this->testIndex); - } + $stub = new ReturnCallback($callback); + return $this->will($stub); } - protected function showSpinner() : void + public function willReturnSelf() : self { - if (!$this->showProgress) { - return; - } - if ($this->spinState) { - $this->undrawSpinner(); - } - $this->spinState++; - $this->drawSpinner(); + $stub = new ReturnSelf(); + return $this->will($stub); } - protected function hideSpinner() : void + public function willReturnOnConsecutiveCalls(...$values) : self { - if (!$this->showProgress) { - return; - } - if ($this->spinState) { - $this->undrawSpinner(); - } - $this->spinState = 0; + $stub = new ConsecutiveCalls($values); + return $this->will($stub); } - protected function drawSpinner() : void + public function willThrowException(Throwable $exception) : self { - // optional for CLI printers: show the user a 'buffering output' spinner + $stub = new Exception($exception); + return $this->will($stub); } - protected function undrawSpinner() : void + /** + * @return $this + */ + public function after($id) : self { - // remove the spinner from the current line + $this->matcher->setAfterMatchBuilderId($id); + return $this; } - protected function writeTestResult(array $prevResult, array $result) : void + /** + * @param mixed[] $arguments + * + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function with(...$arguments) : self { + $this->ensureParametersCanBeConfigured(); + $this->matcher->setParametersRule(new Rule\Parameters($arguments)); + return $this; } - protected function getEmptyTestResult() : array + /** + * @param array ...$arguments + * + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function withConsecutive(...$arguments) : self + { + $this->ensureParametersCanBeConfigured(); + $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); + return $this; + } + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function withAnyParameters() : self { - return ['className' => '', 'testName' => '', 'message' => '', 'failed' => '', 'verbose' => '']; + $this->ensureParametersCanBeConfigured(); + $this->matcher->setParametersRule(new Rule\AnyParameters()); + return $this; } - protected function getTestResultByName(?string $testName) : array + /** + * @param Constraint|string $constraint + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws MethodCannotBeConfiguredException + * @throws MethodNameAlreadyConfiguredException + * + * @return $this + */ + public function method($constraint) : self { - if (isset($this->testNameResultIndex[$testName])) { - return $this->testResults[$this->testNameResultIndex[$testName]]; + if ($this->matcher->hasMethodNameRule()) { + throw new MethodNameAlreadyConfiguredException(); } - return []; + $configurableMethodNames = array_map(static function (ConfigurableMethod $configurable) { + return strtolower($configurable->getName()); + }, $this->configurableMethods); + if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, \true)) { + throw new MethodCannotBeConfiguredException($constraint); + } + $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); + return $this; } - protected function formatThrowable(Throwable $t, ?int $status = null) : string + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + */ + private function ensureParametersCanBeConfigured() : void { - $message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); - if ($message) { - $message .= \PHP_EOL . \PHP_EOL . $this->formatStacktrace($t); - } else { - $message = $this->formatStacktrace($t); + if (!$this->matcher->hasMethodNameRule()) { + throw new MethodNameNotConfiguredException(); + } + if ($this->matcher->hasParametersRule()) { + throw new MethodParametersAlreadyConfiguredException(); } - return $message; } - protected function formatStacktrace(Throwable $t) : string + private function getConfiguredMethod() : ?ConfigurableMethod { - return \PHPUnit\Util\Filter::getFilteredStacktrace($t); + $configuredMethod = null; + foreach ($this->configurableMethods as $configurableMethod) { + if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { + if ($configuredMethod !== null) { + return null; + } + $configuredMethod = $configurableMethod; + } + } + return $configuredMethod; } - protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│') : string + /** + * @throws IncompatibleReturnValueException + */ + private function ensureTypeOfReturnValues(array $values) : void { - $message = $this->formatThrowable($t, $result['status']); - if ($message === '') { - return ''; + $configuredMethod = $this->getConfiguredMethod(); + if ($configuredMethod === null) { + return; } - if (!($this->verbose || $result['verbose'])) { - return ''; + foreach ($values as $value) { + if (!$configuredMethod->mayReturn($value)) { + throw new IncompatibleReturnValueException($configuredMethod, $value); + } } - return $this->prefixLines($prefix, $message); - } - protected function prefixLines(string $prefix, string $message) : string - { - $message = trim($message); - return implode(\PHP_EOL, array_map(static function (string $text) use($prefix) { - return ' ' . $prefix . ($text ? ' ' . $text : ''); - }, preg_split('/\\r\\n|\\r|\\n/', $message))); } } - - - - Test Documentation - - - -EOT; - /** - * @var string - */ - private const CLASS_HEADER = <<<'EOT' - -

          %s

          -
            - -EOT; - /** - * @var string - */ - private const CLASS_FOOTER = <<<'EOT' -
          -EOT; - /** - * @var string - */ - private const PAGE_FOOTER = <<<'EOT' - - - -EOT; - public function printResult(TestResult $result) : void - { - } - /** - * Handler for 'start run' event. + * @param mixed $reference + * + * @return self */ - protected function startRun() : void - { - $this->write(self::PAGE_HEADER); - } + public function willReturnReference(&$reference); /** - * Handler for 'start class' event. + * @param array> $valueMap + * + * @return self */ - protected function startClass(string $name) : void - { - $this->write(sprintf(self::CLASS_HEADER, $name, $this->currentTestClassPrettified)); - } + public function willReturnMap(array $valueMap); /** - * Handler for 'on test' event. + * @param int $argumentIndex + * + * @return self */ - protected function onTest(string $name, bool $success = \true) : void - { - $this->write(sprintf("
        • %s %s
        • \n", $success ? '#555753' : '#ef2929', $success ? '✓' : '❌', $name)); - } + public function willReturnArgument($argumentIndex); /** - * Handler for 'end class' event. + * @param callable $callback + * + * @return self */ - protected function endClass(string $name) : void - { - $this->write(self::CLASS_FOOTER); - } + public function willReturnCallback($callback); + /** @return self */ + public function willReturnSelf(); /** - * Handler for 'end run' event. + * @param mixed $values + * + * @return self */ - protected function endRun() : void - { - $this->write(self::PAGE_FOOTER); - } + public function willReturnOnConsecutiveCalls(...$values); + /** @return self */ + public function willThrowException(Throwable $exception); } exceptionFor($suiteClassName, $suiteClassFile); - } - } - if (!class_exists($suiteClassName, \false)) { - // this block will handle namespaced classes - $offset = 0 - strlen($suiteClassName); - foreach ($loadedClasses as $loadedClass) { - if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0) { - $suiteClassName = $loadedClass; - break; - } - } - } - if (!class_exists($suiteClassName, \false)) { - throw $this->exceptionFor($suiteClassName, $suiteClassFile); - } - try { - $class = new ReflectionClass($suiteClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Runner\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($class->isSubclassOf(TestCase::class) && !$class->isAbstract()) { - return $class; - } - if ($class->hasMethod('suite')) { - try { - $method = $class->getMethod('suite'); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Runner\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { - return $class; - } - } - throw $this->exceptionFor($suiteClassName, $suiteClassFile); - } - public function reload(ReflectionClass $aClass) : ReflectionClass - { - return $aClass; - } - private function exceptionFor(string $className, string $filename) : \PHPUnit\Runner\Exception - { - return new \PHPUnit\Runner\Exception(sprintf("Class '%s' could not be found in '%s'.", $className, $filename)); - } + public function method($constraint); } 6, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE => 5, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING => 4, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE => 3, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY => 2, \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED => 1, \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN => 0]; - private const SIZE_SORT_WEIGHT = [TestUtil::SMALL => 1, TestUtil::MEDIUM => 2, TestUtil::LARGE => 3, TestUtil::UNKNOWN => 4]; - /** - * @var array Associative array of (string => DEFECT_SORT_WEIGHT) elements - */ - private $defectSortOrder = []; - /** - * @var TestResultCache - */ - private $cache; - /** - * @var array A list of normalized names of tests before reordering - */ - private $originalExecutionOrder = []; - /** - * @var array A list of normalized names of tests affected by reordering - */ - private $executionOrder = []; - public function __construct(?\PHPUnit\Runner\TestResultCache $cache = null) - { - $this->cache = $cache ?? new \PHPUnit\Runner\NullTestResultCache(); - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @param string $id the identification of the expectation that should + * occur before this one + * + * @return Stub */ - public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = \true) : void - { - $allowedOrders = [self::ORDER_DEFAULT, self::ORDER_REVERSED, self::ORDER_RANDOMIZED, self::ORDER_DURATION, self::ORDER_SIZE]; - if (!in_array($order, $allowedOrders, \true)) { - throw new \PHPUnit\Runner\Exception('$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]'); - } - $allowedOrderDefects = [self::ORDER_DEFAULT, self::ORDER_DEFECTS_FIRST]; - if (!in_array($orderDefects, $allowedOrderDefects, \true)) { - throw new \PHPUnit\Runner\Exception('$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST'); - } - if ($isRootTestSuite) { - $this->originalExecutionOrder = $this->calculateTestExecutionOrder($suite); - } - if ($suite instanceof TestSuite) { - foreach ($suite as $_suite) { - $this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects, \false); - } - if ($orderDefects === self::ORDER_DEFECTS_FIRST) { - $this->addSuiteToDefectSortOrder($suite); - } - $this->sort($suite, $order, $resolveDependencies, $orderDefects); - } - if ($isRootTestSuite) { - $this->executionOrder = $this->calculateTestExecutionOrder($suite); - } - } - public function getOriginalExecutionOrder() : array - { - return $this->originalExecutionOrder; - } - public function getExecutionOrder() : array - { - return $this->executionOrder; - } - private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects) : void - { - if (empty($suite->tests())) { - return; - } - if ($order === self::ORDER_REVERSED) { - $suite->setTests($this->reverse($suite->tests())); - } elseif ($order === self::ORDER_RANDOMIZED) { - $suite->setTests($this->randomize($suite->tests())); - } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { - $suite->setTests($this->sortByDuration($suite->tests())); - } elseif ($order === self::ORDER_SIZE) { - $suite->setTests($this->sortBySize($suite->tests())); - } - if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { - $suite->setTests($this->sortDefectsFirst($suite->tests())); - } - if ($resolveDependencies && !$suite instanceof DataProviderTestSuite) { - /** @var TestCase[] $tests */ - $tests = $suite->tests(); - $suite->setTests($this->resolveDependencies($tests)); - } - } + public function after($id); /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Sets the parameters to match for, each parameter to this function will + * be part of match. To perform specific matches or constraints create a + * new PHPUnit\Framework\Constraint\Constraint and use it for the parameter. + * If the parameter value is not a constraint it will use the + * PHPUnit\Framework\Constraint\IsEqual for the value. + * + * Some examples: + * + * // match first parameter with value 2 + * $b->with(2); + * // match first parameter with value 'smock' and second identical to 42 + * $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42)); + * + * + * @return ParametersMatch */ - private function addSuiteToDefectSortOrder(TestSuite $suite) : void - { - $max = 0; - foreach ($suite->tests() as $test) { - if (!$test instanceof Reorderable) { - continue; - } - if (!isset($this->defectSortOrder[$test->sortId()])) { - $this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())]; - $max = max($max, $this->defectSortOrder[$test->sortId()]); - } - } - $this->defectSortOrder[$suite->sortId()] = $max; - } - private function reverse(array $tests) : array - { - return array_reverse($tests); - } - private function randomize(array $tests) : array - { - shuffle($tests); - return $tests; - } - private function sortDefectsFirst(array $tests) : array - { - usort( - $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpDefectPriorityAndTime($left, $right); - } - ); - return $tests; - } - private function sortByDuration(array $tests) : array - { - usort( - $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpDuration($left, $right); - } - ); - return $tests; - } - private function sortBySize(array $tests) : array - { - usort( - $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpSize($left, $right); - } - ); - return $tests; - } + public function with(...$arguments); /** - * Comparator callback function to sort tests for "reach failure as fast as possible". + * Sets a rule which allows any kind of parameters. * - * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT - * 2. when tests are equally defective, sort the fastest to the front - * 3. do not reorder successful tests + * Some examples: + * + * // match any number of parameters + * $b->withAnyParameters(); + * * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @return ParametersMatch */ - private function cmpDefectPriorityAndTime(Test $a, Test $b) : int - { - if (!($a instanceof Reorderable && $b instanceof Reorderable)) { - return 0; - } - $priorityA = $this->defectSortOrder[$a->sortId()] ?? 0; - $priorityB = $this->defectSortOrder[$b->sortId()] ?? 0; - if ($priorityB <=> $priorityA) { - // Sort defect weight descending - return $priorityB <=> $priorityA; - } - if ($priorityA || $priorityB) { - return $this->cmpDuration($a, $b); - } - // do not change execution order - return 0; - } + public function withAnyParameters(); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +use PHPUnit\Framework\MockObject\Stub\Stub as BaseStub; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Stub extends \PHPUnit\Framework\MockObject\Builder\Identity +{ /** - * Compares test duration for sorting tests by duration ascending. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Stubs the matching method with the stub object $stub. Any invocations of + * the matched method will now be handled by the stub instead. */ - private function cmpDuration(Test $a, Test $b) : int - { - if (!($a instanceof Reorderable && $b instanceof Reorderable)) { - return 0; - } - return $this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId()); - } + public function will(BaseStub $stub) : \PHPUnit\Framework\MockObject\Builder\Identity; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\SebastianBergmann\Type\Type; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurableMethod +{ /** - * Compares test size for sorting tests small->medium->large->unknown. + * @var string */ - private function cmpSize(Test $a, Test $b) : int - { - $sizeA = $a instanceof TestCase || $a instanceof DataProviderTestSuite ? $a->getSize() : TestUtil::UNKNOWN; - $sizeB = $b instanceof TestCase || $b instanceof DataProviderTestSuite ? $b->getSize() : TestUtil::UNKNOWN; - return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; - } + private $name; /** - * Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible. - * The algorithm will leave the tests in original running order when it can. - * For more details see the documentation for test dependencies. - * - * Short description of algorithm: - * 1. Pick the next Test from remaining tests to be checked for dependencies. - * 2. If the test has no dependencies: mark done, start again from the top - * 3. If the test has dependencies but none left to do: mark done, start again from the top - * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution. - * - * @param array $tests - * - * @return array + * @var Type */ - private function resolveDependencies(array $tests) : array + private $returnType; + public function __construct(string $name, Type $returnType) { - $newTestOrder = []; - $i = 0; - $provided = []; - do { - if ([] === array_diff($tests[$i]->requires(), $provided)) { - $provided = array_merge($provided, $tests[$i]->provides()); - $newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1)); - $i = 0; - } else { - $i++; - } - } while (!empty($tests) && $i < count($tests)); - return array_merge($newTestOrder, $tests); + $this->name = $name; + $this->returnType = $returnType; } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function calculateTestExecutionOrder(Test $suite) : array + public function getName() : string { - $tests = []; - if ($suite instanceof TestSuite) { - foreach ($suite->tests() as $test) { - if (!$test instanceof TestSuite && $test instanceof Reorderable) { - $tests[] = $test->sortId(); - } else { - $tests = array_merge($tests, $this->calculateTestExecutionOrder($test)); - } - } + return $this->name; + } + public function mayReturn($value) : bool + { + if ($value === null && $this->returnType->allowsNull()) { + return \true; } - return $tests; + return $this->returnType->isAssignable(Type::fromValue($value, \false)); + } + public function getReturnTypeDeclaration() : string + { + return $this->returnType->asString(); } } , notLoadedExtensions: list} - */ - public function loadPharExtensionsInDirectory(string $directory) : array + public function __construct(string $type, string $methodName) { - $loadedExtensions = []; - $notLoadedExtensions = []; - foreach ((new FileIteratorFacade())->getFilesAsArray($directory, '.phar') as $file) { - if (!\is_file('phar://' . $file . '/manifest.xml')) { - $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; - continue; - } - try { - $applicationName = new ApplicationName('phpunit/phpunit'); - $version = new PharIoVersion(Version::series()); - $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); - if (!$manifest->isExtensionFor($applicationName)) { - $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; - continue; - } - if (!$manifest->isExtensionFor($applicationName, $version)) { - $notLoadedExtensions[] = $file . ' is not compatible with this version of PHPUnit'; - continue; - } - } catch (ManifestException $e) { - $notLoadedExtensions[] = $file . ': ' . $e->getMessage(); - continue; - } - /** - * @noinspection PhpIncludeInspection - * @psalm-suppress UnresolvableInclude - */ - require $file; - $loadedExtensions[] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); - } - return ['loadedExtensions' => $loadedExtensions, 'notLoadedExtensions' => $notLoadedExtensions]; + parent::__construct(sprintf('Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s". Use addMethods() for methods that do not exist in the class', $methodName, $type)); } } createInstance($extensionConfiguration); - if (!$extension instanceof Hook) { - throw new Exception(sprintf('Class "%s" does not implement a PHPUnit\\Runner\\Hook interface', $extensionConfiguration->className())); - } - $runner->addExtension($extension); + parent::__construct(sprintf('Class "%s" already exists', $className)); } - /** - * @throws Exception - * - * @deprecated - */ - public function createTestListenerInstance(Extension $listenerConfiguration) : TestListener +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsFinalException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ + public function __construct(string $className) { - $listener = $this->createInstance($listenerConfiguration); - if (!$listener instanceof TestListener) { - throw new Exception(sprintf('Class "%s" does not implement the PHPUnit\\Framework\\TestListener interface', $listenerConfiguration->className())); - } - return $listener; + parent::__construct(sprintf('Class "%s" is declared "final" and cannot be doubled', $className)); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurableMethodsAlreadyInitializedException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_diff_assoc; +use function array_unique; +use function implode; +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DuplicateMethodException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ /** - * @throws Exception + * @psalm-param list $methods */ - private function createInstance(Extension $extensionConfiguration) : object + public function __construct(array $methods) { - $this->ensureClassExists($extensionConfiguration); - try { - $reflector = new ReflectionClass($extensionConfiguration->className()); - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - if (!$extensionConfiguration->hasArguments()) { - return $reflector->newInstance(); - } - return $reflector->newInstanceArgs($extensionConfiguration->arguments()); + parent::__construct(sprintf('Cannot double using a method list that contains duplicates: "%s" (duplicate: "%s")', implode(', ', $methods), implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))))); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function get_class; +use function gettype; +use function is_object; +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncompatibleReturnValueException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ /** - * @throws Exception + * @param mixed $value */ - private function ensureClassExists(Extension $extensionConfiguration) : void + public function __construct(\PHPUnit\Framework\MockObject\ConfigurableMethod $method, $value) { - if (class_exists($extensionConfiguration->className(), \false)) { - return; - } - if ($extensionConfiguration->hasSourceFile()) { - /** - * @noinspection PhpIncludeInspection - * @psalm-suppress UnresolvableInclude - */ - require_once $extensionConfiguration->sourceFile(); - } - if (!class_exists($extensionConfiguration->className())) { - throw new Exception(sprintf('Class "%s" does not exist', $extensionConfiguration->className())); - } + parent::__construct(sprintf('Method %s may not return value of type %s, its declared return type is "%s"', $method->getName(), is_object($value) ? get_class($value) : gettype($value), $method->getReturnTypeDeclaration())); } } groupTests, \true); + parent::__construct(sprintf('Cannot double method with invalid name "%s"', $method)); } } groupTests, \true); + parent::__construct(sprintf('No builder found for match builder identification <%s>', $id)); } } - */ - private $filters = []; - /** - * @param array|string $args - * - * @throws Exception - */ - public function addFilter(ReflectionClass $filter, $args) : void + public function __construct(string $id) { - if (!$filter->isSubclassOf(RecursiveFilterIterator::class)) { - throw new Exception(sprintf('Class "%s" does not extend RecursiveFilterIterator', $filter->name)); - } - $this->filters[] = [$filter, $args]; + parent::__construct(sprintf('Matcher with id <%s> is already registered', $id)); } - public function factory(Iterator $iterator, TestSuite $suite) : FilterIterator +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodCannotBeConfiguredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ + public function __construct(string $method) { - foreach ($this->filters as $filter) { - [$class, $args] = $filter; - $iterator = $class->newInstance($iterator, $args, $suite); - } - assert($iterator instanceof FilterIterator); - return $iterator; + parent::__construct(sprintf('Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', $method)); } } getGroupDetails() as $group => $tests) { - if (in_array((string) $group, $groups, \true)) { - $testHashes = array_map('spl_object_hash', $tests); - $this->groupTests = array_merge($this->groupTests, $testHashes); - } - } + parent::__construct('Method name is already configured'); } - public function accept() : bool - { - $test = $this->getInnerIterator()->current(); - if ($test instanceof TestSuite) { - return \true; - } - return $this->doAccept(spl_object_hash($test)); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameNotConfiguredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +{ + public function __construct() + { + parent::__construct('Method name is not configured'); } - protected abstract function doAccept(string $hash); } setFilter($filter); - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function accept() : bool - { - $test = $this->getInnerIterator()->current(); - if ($test instanceof TestSuite) { - return \true; - } - $tmp = \PHPUnit\Util\Test::describe($test); - if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { - $name = $test->getMessage(); - } elseif ($tmp[0] !== '') { - $name = implode('::', $tmp); - } else { - $name = $tmp[1]; - } - $accepted = @preg_match($this->filter, $name, $matches); - if ($accepted && isset($this->filterMax)) { - $set = end($matches); - $accepted = $set >= $this->filterMin && $set <= $this->filterMax; - } - return (bool) $accepted; - } - /** - * @throws Exception - */ - private function setFilter(string $filter) : void + public function __construct() { - if (RegularExpression::safeMatch($filter, '') === \false) { - // Handles: - // * testAssertEqualsSucceeds#4 - // * testAssertEqualsSucceeds#4-8 - if (preg_match('/^(.*?)#(\\d+)(?:-(\\d+))?$/', $filter, $matches)) { - if (isset($matches[3]) && $matches[2] < $matches[3]) { - $filter = sprintf('%s.*with data set #(\\d+)$', $matches[1]); - $this->filterMin = (int) $matches[2]; - $this->filterMax = (int) $matches[3]; - } else { - $filter = sprintf('%s.*with data set #%s$', $matches[1], $matches[2]); - } - } elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { - $filter = sprintf('%s.*with data set "%s"$', $matches[1], $matches[2]); - } - // Escape delimiters in regular expression. Do NOT use preg_quote, - // to keep magic characters. - $filter = sprintf('/%s/i', str_replace('/', '\\/', $filter)); - } - $this->filter = $filter; + parent::__construct('Method parameters already configured'); } } getVersion(); - } - return self::$version; - } - public static function series() : string - { - if (strpos(self::id(), '-')) { - $version = explode('-', self::id())[0]; - } else { - $version = self::id(); - } - return implode('.', array_slice(explode('.', $version), 0, 2)); - } - public static function getVersionString() : string + public function __construct() { - return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; + parent::__construct('Proxying to original methods requires invoking the original constructor'); } } - */ - private const ALLOWED_TEST_STATUSES = [\PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE, \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING]; - /** - * @var string - */ - private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; - /** - * @var string - */ - private $cacheFilename; - /** - * @psalm-var array - */ - private $defects = []; - /** - * @psalm-var array - */ - private $times = []; - public function __construct(?string $filepath = null) - { - if ($filepath !== null && is_dir($filepath)) { - $filepath .= \DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; - } - $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; - } - public function setState(string $testName, int $state) : void - { - if (!in_array($state, self::ALLOWED_TEST_STATUSES, \true)) { - return; - } - $this->defects[$testName] = $state; - } - public function getState(string $testName) : int - { - return $this->defects[$testName] ?? \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN; - } - public function setTime(string $testName, float $time) : void - { - $this->times[$testName] = $time; - } - public function getTime(string $testName) : float - { - return $this->times[$testName] ?? 0.0; - } - public function load() : void - { - if (!is_file($this->cacheFilename)) { - return; - } - $data = json_decode(file_get_contents($this->cacheFilename), \true); - if ($data === null) { - return; - } - if (!isset($data['version'])) { - return; - } - if ($data['version'] !== self::VERSION) { - return; - } - assert(isset($data['defects']) && is_array($data['defects'])); - assert(isset($data['times']) && is_array($data['times'])); - $this->defects = $data['defects']; - $this->times = $data['times']; - } - /** - * @throws Exception - */ - public function persist() : void - { - if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { - throw new \PHPUnit\Runner\Exception(\sprintf('Cannot create directory "%s" for result cache file', $this->cacheFilename)); - } - file_put_contents($this->cacheFilename, json_encode(['version' => self::VERSION, 'defects' => $this->defects, 'times' => $this->times]), \LOCK_EX); - } } getClassName(), $invocation->getMethodName())); + } } __phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + } +} +EOT; + private const MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait MockedCloneMethodWithoutReturnType +{ + public function __clone() + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + } +} +EOT; + private const UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait UnmockedCloneMethodWithVoidReturnType +{ + public function __clone(): void + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + + parent::__clone(); + } +} +EOT; + private const UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait UnmockedCloneMethodWithoutReturnType { + public function __clone() + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + + parent::__clone(); + } +} +EOT; + /** + * @var array + */ + private const EXCLUDED_METHOD_NAMES = ['__CLASS__' => \true, '__DIR__' => \true, '__FILE__' => \true, '__FUNCTION__' => \true, '__LINE__' => \true, '__METHOD__' => \true, '__NAMESPACE__' => \true, '__TRAIT__' => \true, '__clone' => \true, '__halt_compiler' => \true]; + /** + * @var array + */ + private static $cache = []; + /** + * @var Template[] + */ + private static $templates = []; + /** + * Returns a mock object for the specified class. + * + * @param null|array $methods + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \true, bool $callOriginalMethods = \false, object $proxyTarget = null, bool $allowMockingUnknownTypes = \true, bool $returnValueGeneration = \true) : \PHPUnit\Framework\MockObject\MockObject + { + if (!is_array($methods) && null !== $methods) { + throw InvalidArgumentException::create(2, 'array'); + } + if ($type === 'Traversable' || $type === '\\Traversable') { + $type = 'Iterator'; + } + if (!$allowMockingUnknownTypes && !class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { + throw new \PHPUnit\Framework\MockObject\UnknownTypeException($type); + } + if (null !== $methods) { + foreach ($methods as $method) { + if (!preg_match('~[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*~', (string) $method)) { + throw new \PHPUnit\Framework\MockObject\InvalidMethodNameException((string) $method); + } + } + if ($methods !== array_unique($methods)) { + throw new \PHPUnit\Framework\MockObject\DuplicateMethodException($methods); + } + } + if ($mockClassName !== '' && class_exists($mockClassName, \false)) { + try { + $reflector = new ReflectionClass($mockClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (!$reflector->implementsInterface(\PHPUnit\Framework\MockObject\MockObject::class)) { + throw new \PHPUnit\Framework\MockObject\ClassAlreadyExistsException($mockClassName); + } + } + if (!$callOriginalConstructor && $callOriginalMethods) { + throw new \PHPUnit\Framework\MockObject\OriginalConstructorInvocationRequiredException(); + } + $mock = $this->generate($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); + return $this->getObject($mock, $type, $callOriginalConstructor, $callAutoload, $arguments, $callOriginalMethods, $proxyTarget, $returnValueGeneration); + } + /** + * @psalm-param list $interfaces + * + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function getMockForInterfaces(array $interfaces, bool $callAutoload = \true) : \PHPUnit\Framework\MockObject\MockObject + { + if (count($interfaces) < 2) { + throw new \PHPUnit\Framework\MockObject\RuntimeException('At least two interfaces must be specified'); + } + foreach ($interfaces as $interface) { + if (!interface_exists($interface, $callAutoload)) { + throw new \PHPUnit\Framework\MockObject\UnknownTypeException($interface); + } + } + sort($interfaces); + $methods = []; + foreach ($interfaces as $interface) { + $methods = array_merge($methods, $this->getClassMethods($interface)); + } + if (count(array_unique($methods)) < count($methods)) { + throw new \PHPUnit\Framework\MockObject\RuntimeException('Interfaces must not declare the same method'); + } + $unqualifiedNames = []; + foreach ($interfaces as $interface) { + $parts = explode('\\', $interface); + $unqualifiedNames[] = array_pop($parts); + } + sort($unqualifiedNames); + do { + $intersectionName = sprintf('Intersection_%s_%s', implode('_', $unqualifiedNames), substr(md5((string) mt_rand()), 0, 8)); + } while (interface_exists($intersectionName, \false)); + $template = $this->getTemplate('intersection.tpl'); + $template->setVar(['intersection' => $intersectionName, 'interfaces' => implode(', ', $interfaces)]); + eval($template->render()); + return $this->getMock($intersectionName); + } + /** + * Returns a mock object for the specified abstract class with all abstract + * methods of the class mocked. + * + * Concrete methods to mock can be specified with the $mockedMethods parameter. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownClassException + * @throws UnknownTypeException + */ + public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = null, bool $cloneArguments = \true) : \PHPUnit\Framework\MockObject\MockObject + { + if (class_exists($originalClassName, $callAutoload) || interface_exists($originalClassName, $callAutoload)) { + try { + $reflector = new ReflectionClass($originalClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $methods = $mockedMethods; + foreach ($reflector->getMethods() as $method) { + if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], \true)) { + $methods[] = $method->getName(); + } + } + if (empty($methods)) { + $methods = null; + } + return $this->getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments); + } + throw new \PHPUnit\Framework\MockObject\UnknownClassException($originalClassName); + } /** - * @var TestHook[] + * Returns a mock object for the specified trait with all abstract methods + * of the trait mocked. Concrete methods to mock can be specified with the + * `$mockedMethods` parameter. + * + * @psalm-param trait-string $traitName + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownClassException + * @throws UnknownTraitException + * @throws UnknownTypeException */ - private $hooks = []; + public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = null, bool $cloneArguments = \true) : \PHPUnit\Framework\MockObject\MockObject + { + if (!trait_exists($traitName, $callAutoload)) { + throw new \PHPUnit\Framework\MockObject\UnknownTraitException($traitName); + } + $className = $this->generateClassName($traitName, '', 'Trait_'); + $classTemplate = $this->getTemplate('trait_class.tpl'); + $classTemplate->setVar(['prologue' => 'abstract ', 'class_name' => $className['className'], 'trait_name' => $traitName]); + $mockTrait = new \PHPUnit\Framework\MockObject\MockTrait($classTemplate->render(), $className['className']); + $mockTrait->generate(); + return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + } /** - * @var bool + * Returns an object for the specified trait. + * + * @psalm-param trait-string $traitName + * + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTraitException */ - private $lastTestWasNotSuccessful; - public function add(\PHPUnit\Runner\TestHook $hook) : void + public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = \true, bool $callOriginalConstructor = \false, array $arguments = []) : object { - $this->hooks[] = $hook; + if (!trait_exists($traitName, $callAutoload)) { + throw new \PHPUnit\Framework\MockObject\UnknownTraitException($traitName); + } + $className = $this->generateClassName($traitName, $traitClassName, 'Trait_'); + $classTemplate = $this->getTemplate('trait_class.tpl'); + $classTemplate->setVar(['prologue' => '', 'class_name' => $className['className'], 'trait_name' => $traitName]); + return $this->getObject(new \PHPUnit\Framework\MockObject\MockTrait($classTemplate->render(), $className['className']), '', $callOriginalConstructor, $callAutoload, $arguments); } - public function startTest(Test $test) : void + /** + * @throws ClassIsFinalException + * @throws ReflectionException + * @throws RuntimeException + */ + public function generate(string $type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \true, bool $callOriginalMethods = \false) : \PHPUnit\Framework\MockObject\MockClass { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\BeforeTestHook) { - $hook->executeBeforeTest(TestUtil::describeAsString($test)); - } + if ($mockClassName !== '') { + return $this->generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); } - $this->lastTestWasNotSuccessful = \false; + $key = md5($type . serialize($methods) . serialize($callOriginalClone) . serialize($cloneArguments) . serialize($callOriginalMethods)); + if (!isset(self::$cache[$key])) { + self::$cache[$key] = $this->generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); + } + return self::$cache[$key]; } - public function addError(Test $test, Throwable $t, float $time) : void + /** + * @throws RuntimeException + * @throws SoapExtensionNotAvailableException + */ + public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []) : string { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterTestErrorHook) { - $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); + if (!extension_loaded('soap')) { + throw new \PHPUnit\Framework\MockObject\SoapExtensionNotAvailableException(); + } + $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); + try { + $client = new SoapClient($wsdlFile, $options); + $_methods = array_unique($client->__getFunctions()); + unset($client); + } catch (SoapFault $e) { + throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + } + sort($_methods); + $methodTemplate = $this->getTemplate('wsdl_method.tpl'); + $methodsBuffer = ''; + foreach ($_methods as $method) { + preg_match_all('/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*\\(/', $method, $matches, PREG_OFFSET_CAPTURE); + $lastFunction = array_pop($matches[0]); + $nameStart = $lastFunction[1]; + $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; + $name = str_replace('(', '', $lastFunction[0]); + if (empty($methods) || in_array($name, $methods, \true)) { + $args = explode(',', str_replace(')', '', substr($method, $nameEnd + 1))); + foreach (range(0, count($args) - 1) as $i) { + $parameterStart = strpos($args[$i], '$'); + if (!$parameterStart) { + continue; + } + $args[$i] = substr($args[$i], $parameterStart); + } + $methodTemplate->setVar(['method_name' => $name, 'arguments' => implode(', ', $args)]); + $methodsBuffer .= $methodTemplate->render(); } } - $this->lastTestWasNotSuccessful = \true; + $optionsBuffer = '['; + foreach ($options as $key => $value) { + $optionsBuffer .= $key . ' => ' . $value; + } + $optionsBuffer .= ']'; + $classTemplate = $this->getTemplate('wsdl_class.tpl'); + $namespace = ''; + if (strpos($className, '\\') !== \false) { + $parts = explode('\\', $className); + $className = array_pop($parts); + $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; + } + $classTemplate->setVar(['namespace' => $namespace, 'class_name' => $className, 'wsdl' => $wsdlFile, 'options' => $optionsBuffer, 'methods' => $methodsBuffer]); + return $classTemplate->render(); } - public function addWarning(Test $test, Warning $e, float $time) : void + /** + * @throws ReflectionException + * + * @return string[] + */ + public function getClassMethods(string $className) : array { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterTestWarningHook) { - $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $methods = []; + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); } } - $this->lastTestWasNotSuccessful = \true; + return $methods; } - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + /** + * @throws ReflectionException + * + * @return MockMethod[] + */ + public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments) : array { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterTestFailureHook) { - $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $methods = []; + foreach ($class->getMethods() as $method) { + if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { + $methods[] = \PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); } } - $this->lastTestWasNotSuccessful = \true; + return $methods; } - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void + /** + * @throws ReflectionException + * + * @return MockMethod[] + */ + public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments) : array { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterIncompleteTestHook) { - $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } + try { + $class = new ReflectionClass($interfaceName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - $this->lastTestWasNotSuccessful = \true; + // @codeCoverageIgnoreEnd + $methods = []; + foreach ($class->getMethods() as $method) { + $methods[] = \PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, \false, $cloneArguments); + } + return $methods; } - public function addRiskyTest(Test $test, Throwable $t, float $time) : void + /** + * @psalm-param class-string $interfaceName + * + * @throws ReflectionException + * + * @return ReflectionMethod[] + */ + private function userDefinedInterfaceMethods(string $interfaceName) : array { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterRiskyTestHook) { - $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + try { + // @codeCoverageIgnoreStart + $interface = new ReflectionClass($interfaceName); + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $methods = []; + foreach ($interface->getMethods() as $method) { + if (!$method->isUserDefined()) { + continue; } + $methods[] = $method; } - $this->lastTestWasNotSuccessful = \true; + return $methods; } - public function addSkippedTest(Test $test, Throwable $t, float $time) : void + /** + * @throws ReflectionException + * @throws RuntimeException + */ + private function getObject(\PHPUnit\Framework\MockObject\MockType $mockClass, $type = '', bool $callOriginalConstructor = \false, bool $callAutoload = \false, array $arguments = [], bool $callOriginalMethods = \false, object $proxyTarget = null, bool $returnValueGeneration = \true) { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterSkippedTestHook) { - $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + $className = $mockClass->generate(); + if ($callOriginalConstructor) { + if (count($arguments) === 0) { + $object = new $className(); + } else { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $object = $class->newInstanceArgs($arguments); + } + } else { + try { + $object = (new Instantiator())->instantiate($className); + } catch (InstantiatorException $e) { + throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage()); } } - $this->lastTestWasNotSuccessful = \true; + if ($callOriginalMethods) { + if (!is_object($proxyTarget)) { + if (count($arguments) === 0) { + $proxyTarget = new $type(); + } else { + try { + $class = new ReflectionClass($type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $proxyTarget = $class->newInstanceArgs($arguments); + } + } + $object->__phpunit_setOriginalObject($proxyTarget); + } + if ($object instanceof \PHPUnit\Framework\MockObject\MockObject) { + $object->__phpunit_setReturnValueGeneration($returnValueGeneration); + } + return $object; } - public function endTest(Test $test, float $time) : void + /** + * @throws ClassIsFinalException + * @throws ReflectionException + * @throws RuntimeException + */ + private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods) : \PHPUnit\Framework\MockObject\MockClass { - if (!$this->lastTestWasNotSuccessful) { - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterSuccessfulTestHook) { - $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); + $classTemplate = $this->getTemplate('mocked_class.tpl'); + $additionalInterfaces = []; + $mockedCloneMethod = \false; + $unmockedCloneMethod = \false; + $isClass = \false; + $isInterface = \false; + $class = null; + $mockMethods = new \PHPUnit\Framework\MockObject\MockMethodSet(); + $_mockClassName = $this->generateClassName($type, $mockClassName, 'Mock_'); + if (class_exists($_mockClassName['fullClassName'], $callAutoload)) { + $isClass = \true; + } elseif (interface_exists($_mockClassName['fullClassName'], $callAutoload)) { + $isInterface = \true; + } + if (!$isClass && !$isInterface) { + $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; + if (!empty($_mockClassName['namespaceName'])) { + $prologue = 'namespace ' . $_mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; + $epilogue = "\n\n}"; + } + $mockedCloneMethod = \true; + } else { + try { + $class = new ReflectionClass($_mockClassName['fullClassName']); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($class->isFinal()) { + throw new \PHPUnit\Framework\MockObject\ClassIsFinalException($_mockClassName['fullClassName']); + } + // @see https://github.com/sebastianbergmann/phpunit/issues/2995 + if ($isInterface && $class->implementsInterface(Throwable::class)) { + $actualClassName = Exception::class; + $additionalInterfaces[] = $class->getName(); + $isInterface = \false; + try { + $class = new ReflectionClass($actualClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { + $methodName = $method->getName(); + if ($class->hasMethod($methodName)) { + try { + $classMethod = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (!$this->canMockMethod($classMethod)) { + continue; + } + } + $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments)); + } + $_mockClassName = $this->generateClassName($actualClassName, $_mockClassName['className'], 'Mock_'); + } + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + if ($isInterface && $class->implementsInterface(Traversable::class) && !$class->implementsInterface(Iterator::class) && !$class->implementsInterface(IteratorAggregate::class)) { + $additionalInterfaces[] = Iterator::class; + $mockMethods->addMethods(...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments)); + } + if ($class->hasMethod('__clone')) { + try { + $cloneMethod = $class->getMethod('__clone'); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (!$cloneMethod->isFinal()) { + if ($callOriginalClone && !$isInterface) { + $unmockedCloneMethod = \true; + } else { + $mockedCloneMethod = \true; + } } + } else { + $mockedCloneMethod = \true; } } - foreach ($this->hooks as $hook) { - if ($hook instanceof \PHPUnit\Runner\AfterTestHook) { - $hook->executeAfterTest(TestUtil::describeAsString($test), $time); + if ($isClass && $explicitMethods === []) { + $mockMethods->addMethods(...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments)); + } + if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { + $mockMethods->addMethods(...$this->mockInterfaceMethods($_mockClassName['fullClassName'], $cloneArguments)); + } + if (is_array($explicitMethods)) { + foreach ($explicitMethods as $methodName) { + if ($class !== null && $class->hasMethod($methodName)) { + try { + $method = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($this->canMockMethod($method)) { + $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments)); + } + } else { + $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromName($_mockClassName['fullClassName'], $methodName, $cloneArguments)); + } } } + $mockedMethods = ''; + $configurable = []; + foreach ($mockMethods->asArray() as $mockMethod) { + $mockedMethods .= $mockMethod->generateCode(); + $configurable[] = new \PHPUnit\Framework\MockObject\ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); + } + $method = ''; + if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { + $method = PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\Method;'; + } + $cloneTrait = ''; + if ($mockedCloneMethod) { + $cloneTrait = $this->mockedCloneMethod(); + } + if ($unmockedCloneMethod) { + $cloneTrait = $this->unmockedCloneMethod(); + } + $classTemplate->setVar(['prologue' => $prologue ?? '', 'epilogue' => $epilogue ?? '', 'class_declaration' => $this->generateMockClassDeclaration($_mockClassName, $isInterface, $additionalInterfaces), 'clone' => $cloneTrait, 'mock_class_name' => $_mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $method]); + return new \PHPUnit\Framework\MockObject\MockClass($classTemplate->render(), $_mockClassName['className'], $configurable); } - public function startTestSuite(TestSuite $suite) : void + private function generateClassName(string $type, string $className, string $prefix) : array { + if ($type[0] === '\\') { + $type = substr($type, 1); + } + $classNameParts = explode('\\', $type); + if (count($classNameParts) > 1) { + $type = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + $fullClassName = $namespaceName . '\\' . $type; + } else { + $namespaceName = ''; + $fullClassName = $type; + } + if ($className === '') { + do { + $className = $prefix . $type . '_' . substr(md5((string) mt_rand()), 0, 8); + } while (class_exists($className, \false)); + } + return ['className' => $className, 'originalClassName' => $type, 'fullClassName' => $fullClassName, 'namespaceName' => $namespaceName]; } - public function endTestSuite(TestSuite $suite) : void + private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []) : string { + $buffer = 'class '; + $additionalInterfaces[] = \PHPUnit\Framework\MockObject\MockObject::class; + $interfaces = implode(', ', $additionalInterfaces); + if ($isInterface) { + $buffer .= sprintf('%s implements %s', $mockClassName['className'], $interfaces); + if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, \true)) { + $buffer .= ', '; + if (!empty($mockClassName['namespaceName'])) { + $buffer .= $mockClassName['namespaceName'] . '\\'; + } + $buffer .= $mockClassName['originalClassName']; + } + } else { + $buffer .= sprintf('%s extends %s%s implements %s', $mockClassName['className'], !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', $mockClassName['originalClassName'], $interfaces); + } + return $buffer; + } + private function canMockMethod(ReflectionMethod $method) : bool + { + return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameExcluded($method->getName())); + } + private function isMethodNameExcluded(string $name) : bool + { + return isset(self::EXCLUDED_METHOD_NAMES[$name]); + } + /** + * @throws RuntimeException + */ + private function getTemplate(string $template) : Template + { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; + if (!isset(self::$templates[$filename])) { + try { + self::$templates[$filename] = new Template($filename); + } catch (TemplateException $e) { + throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + } + } + return self::$templates[$filename]; + } + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/4139#issuecomment-605409765 + */ + private function isConstructor(ReflectionMethod $method) : bool + { + $methodName = strtolower($method->getName()); + if ($methodName === '__construct') { + return \true; + } + if (PHP_MAJOR_VERSION >= 8) { + return \false; + } + $className = strtolower($method->getDeclaringClass()->getName()); + return $methodName === $className; + } + private function mockedCloneMethod() : string + { + if (PHP_MAJOR_VERSION >= 8) { + if (!trait_exists('\\PHPUnit\\Framework\\MockObject\\MockedCloneMethodWithVoidReturnType')) { + eval(self::MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); + } + return PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\MockedCloneMethodWithVoidReturnType;'; + } + if (!trait_exists('\\PHPUnit\\Framework\\MockObject\\MockedCloneMethodWithoutReturnType')) { + eval(self::MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); + } + return PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\MockedCloneMethodWithoutReturnType;'; + } + private function unmockedCloneMethod() : string + { + if (PHP_MAJOR_VERSION >= 8) { + if (!trait_exists('\\PHPUnit\\Framework\\MockObject\\UnmockedCloneMethodWithVoidReturnType')) { + eval(self::UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); + } + return PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\UnmockedCloneMethodWithVoidReturnType;'; + } + if (!trait_exists('\\PHPUnit\\Framework\\MockObject\\UnmockedCloneMethodWithoutReturnType')) { + eval(self::UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); + } + return PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\UnmockedCloneMethodWithoutReturnType;'; } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface AfterRiskyTestHook extends \PHPUnit\Runner\TestHook -{ - public function executeAfterRiskyTest(string $test, string $message, float $time) : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface BeforeFirstTestHook extends \PHPUnit\Runner\Hook -{ - public function executeBeforeFirstTest() : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + @trigger_error({deprecation}, E_USER_DEPRECATED); +declare(strict_types=1); -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface AfterSuccessfulTestHook extends \PHPUnit\Runner\TestHook +interface {intersection} extends {interfaces} { - public function executeAfterSuccessfulTest(string $test, float $time) : void; } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; +declare(strict_types=1); -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface TestHook extends \PHPUnit\Runner\Hook +{prologue}{class_declaration} { -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + use \PHPUnit\Framework\MockObject\Api;{method}{clone} +{mocked_methods}}{epilogue} -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface BeforeTestHook extends \PHPUnit\Runner\TestHook -{ - public function executeBeforeTest(string $test) : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface AfterTestHook extends \PHPUnit\Runner\TestHook -{ - /** - * This hook will fire after any test, regardless of the result. - * - * For more fine grained control, have a look at the other hooks - * that extend PHPUnit\Runner\Hook. - */ - public function executeAfterTest(string $test, float $time) : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} + ) + ); -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface Hook -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + {{deprecation} + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface AfterLastTestHook extends \PHPUnit\Runner\Hook -{ - public function executeAfterLastTest() : void; -} - {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); -declare (strict_types=1); -/* - * This file is part of PHPUnit. - * - * (c) Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NullTestResultCache implements \PHPUnit\Runner\TestResultCache -{ - public function setState(string $testName, int $state) : void - { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} + ) + ); } - public function getState(string $testName) : int + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} { - return \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN; + throw new \PHPUnit\Framework\MockObject\BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object'); } - public function setTime(string $testName, float $time) : void + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} { + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true + ) + ); + + return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); } - public function getTime(string $testName) : float + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} { - return 0; + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true + ) + ); + + call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); } - public function load() : void +declare(strict_types=1); + +{prologue}class {class_name} +{ + use {trait_name}; +} +declare(strict_types=1); + +{namespace}class {class_name} extends \SoapClient +{ + public function __construct($wsdl, array $options) { + parent::__construct('{wsdl}', $options); } - public function persist() : void +{methods}} + + public function {method_name}({arguments}) { } -} filename = $filename; - $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); - } + private $isReturnTypeNullable = \false; /** - * Counts the number of test cases executed by run(TestResult result). + * @var bool */ - public function count() : int - { - return 1; - } + private $proxiedCall; /** - * Runs a test and collects its result in a TestResult instance. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @var object */ - public function run(TestResult $result = null) : TestResult + private $object; + public function __construct(string $className, string $methodName, array $parameters, string $returnType, object $object, bool $cloneObjects = \false, bool $proxiedCall = \false) { - if ($result === null) { - $result = new TestResult(); - } - try { - $sections = $this->parse(); - } catch (\PHPUnit\Runner\Exception $e) { - $result->startTest($this); - $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); - $result->endTest($this, 0); - return $result; - } - $code = $this->render($sections['FILE']); - $xfail = \false; - $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); - $result->startTest($this); - if (isset($sections['INI'])) { - $settings = $this->parseIniSection($sections['INI'], $settings); - } - if (isset($sections['ENV'])) { - $env = $this->parseEnvSection($sections['ENV']); - $this->phpUtil->setEnv($env); - } - $this->phpUtil->setUseStderrRedirection(\true); - if ($result->enforcesTimeLimit()) { - $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); - } - $skip = $this->runSkip($sections, $result, $settings); - if ($skip) { - return $result; - } - if (isset($sections['XFAIL'])) { - $xfail = trim($sections['XFAIL']); - } - if (isset($sections['STDIN'])) { - $this->phpUtil->setStdin($sections['STDIN']); - } - if (isset($sections['ARGS'])) { - $this->phpUtil->setArgs($sections['ARGS']); + $this->className = $className; + $this->methodName = $methodName; + $this->parameters = $parameters; + $this->object = $object; + $this->proxiedCall = $proxiedCall; + if (strtolower($methodName) === '__tostring') { + $returnType = 'string'; } - if ($result->getCollectCodeCoverageInformation()) { - $codeCoverageCacheDirectory = null; - $pathCoverage = \false; - $codeCoverage = $result->getCodeCoverage(); - if ($codeCoverage) { - if ($codeCoverage->cachesStaticAnalysis()) { - $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); - } - $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); - } - $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory); + if (strpos($returnType, '?') === 0) { + $returnType = substr($returnType, 1); + $this->isReturnTypeNullable = \true; } - $timer = new Timer(); - $timer->start(); - $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); - $time = $timer->stop()->asSeconds(); - $this->output = $jobResult['stdout'] ?? ''; - if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { - $codeCoverage->append($coverage, $this, \true, [], []); + $this->returnType = $returnType; + if (!$cloneObjects) { + return; } - try { - $this->assertPhptExpectation($sections, $this->output); - } catch (AssertionFailedError $e) { - $failure = $e; - if ($xfail !== \false) { - $failure = new IncompleteTestError($xfail, 0, $e); - } elseif ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - if ($comparisonFailure) { - $diff = $comparisonFailure->getDiff(); - } else { - $diff = $e->getMessage(); - } - $hint = $this->getLocationHintFromDiff($diff, $sections); - $trace = array_merge($hint, debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); - $failure = new PHPTAssertionFailedError($e->getMessage(), 0, $trace[0]['file'], $trace[0]['line'], $trace, $comparisonFailure ? $diff : ''); + foreach ($this->parameters as $key => $value) { + if (is_object($value)) { + $this->parameters[$key] = Cloner::clone($value); } - $result->addFailure($this, $failure, $time); - } catch (Throwable $t) { - $result->addError($this, $t, $time); - } - if ($xfail !== \false && $result->allCompletelyImplemented()) { - $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); } - $this->runClean($sections, $result->getCollectCodeCoverageInformation()); - $result->endTest($this, $time); - return $result; - } - /** - * Returns the name of the test case. - */ - public function getName() : string - { - return $this->toString(); } - /** - * Returns a string representation of the test case. - */ - public function toString() : string + public function getClassName() : string { - return $this->filename; + return $this->className; } - public function usesDataProvider() : bool + public function getMethodName() : string { - return \false; + return $this->methodName; } - public function getNumAssertions() : int + public function getParameters() : array { - return 1; + return $this->parameters; } - public function getActualOutput() : string + /** + * @throws RuntimeException + * + * @return mixed Mocked return value + */ + public function generateReturnValue() { - return $this->output; + if ($this->isReturnTypeNullable || $this->proxiedCall) { + return null; + } + $intersection = \false; + $union = \false; + $unionContainsIntersections = \false; + if (strpos($this->returnType, '|') !== \false) { + $types = explode('|', $this->returnType); + $union = \true; + if (strpos($this->returnType, '(') !== \false) { + $unionContainsIntersections = \true; + } + } elseif (strpos($this->returnType, '&') !== \false) { + $types = explode('&', $this->returnType); + $intersection = \true; + } else { + $types = [$this->returnType]; + } + $types = array_map('strtolower', $types); + if (!$intersection && !$unionContainsIntersections) { + if (in_array('', $types, \true) || in_array('null', $types, \true) || in_array('mixed', $types, \true) || in_array('void', $types, \true)) { + return null; + } + if (in_array('true', $types, \true)) { + return \true; + } + if (in_array('false', $types, \true) || in_array('bool', $types, \true)) { + return \false; + } + if (in_array('float', $types, \true)) { + return 0.0; + } + if (in_array('int', $types, \true)) { + return 0; + } + if (in_array('string', $types, \true)) { + return ''; + } + if (in_array('array', $types, \true)) { + return []; + } + if (in_array('static', $types, \true)) { + try { + return (new Instantiator())->instantiate(get_class($this->object)); + } catch (Throwable $t) { + throw new \PHPUnit\Framework\MockObject\RuntimeException($t->getMessage(), (int) $t->getCode(), $t); + } + } + if (in_array('object', $types, \true)) { + return new stdClass(); + } + if (in_array('callable', $types, \true) || in_array('closure', $types, \true)) { + return static function () : void { + }; + } + if (in_array('traversable', $types, \true) || in_array('generator', $types, \true) || in_array('iterable', $types, \true)) { + $generator = static function () : \Generator { + yield from []; + }; + return $generator(); + } + if (!$union) { + try { + return (new \PHPUnit\Framework\MockObject\Generator())->getMock($this->returnType, [], [], '', \false); + } catch (Throwable $t) { + if ($t instanceof \PHPUnit\Framework\MockObject\Exception) { + throw $t; + } + throw new \PHPUnit\Framework\MockObject\RuntimeException($t->getMessage(), (int) $t->getCode(), $t); + } + } + } + if ($intersection && $this->onlyInterfaces($types)) { + try { + return (new \PHPUnit\Framework\MockObject\Generator())->getMockForInterfaces($types); + } catch (Throwable $t) { + throw new \PHPUnit\Framework\MockObject\RuntimeException(sprintf('Return value for %s::%s() cannot be generated: %s', $this->className, $this->methodName, $t->getMessage()), (int) $t->getCode()); + } + } + $reason = ''; + if ($union) { + $reason = ' because the declared return type is a union'; + } elseif ($intersection) { + $reason = ' because the declared return type is an intersection'; + } + throw new \PHPUnit\Framework\MockObject\RuntimeException(sprintf('Return value for %s::%s() cannot be generated%s, please configure a return value for this method', $this->className, $this->methodName, $reason)); } - public function hasOutput() : bool + public function toString() : string { - return !empty($this->output); + $exporter = new Exporter(); + return sprintf('%s::%s(%s)%s', $this->className, $this->methodName, implode(', ', array_map([$exporter, 'shortenedExport'], $this->parameters)), $this->returnType ? sprintf(': %s', $this->returnType) : ''); } - public function sortId() : string + public function getObject() : object { - return $this->filename; + return $this->object; } /** - * @return list + * @psalm-param non-empty-list $types */ - public function provides() : array + private function onlyInterfaces(array $types) : bool { - return []; + foreach ($types as $type) { + if (!interface_exists($type)) { + return \false; + } + } + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function strtolower; +use Exception; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationHandler +{ /** - * @return list + * @var Matcher[] */ - public function requires() : array - { - return []; - } + private $matchers = []; /** - * Parse --INI-- section key value pairs and return as array. - * - * @param array|string $content + * @var Matcher[] */ - private function parseIniSection($content, array $ini = []) : array + private $matcherMap = []; + /** + * @var ConfigurableMethod[] + */ + private $configurableMethods; + /** + * @var bool + */ + private $returnValueGeneration; + /** + * @var Throwable + */ + private $deferredError; + public function __construct(array $configurableMethods, bool $returnValueGeneration) { - if (is_string($content)) { - $content = explode("\n", trim($content)); - } - foreach ($content as $setting) { - if (strpos($setting, '=') === \false) { - continue; - } - $setting = explode('=', $setting, 2); - $name = trim($setting[0]); - $value = trim($setting[1]); - if ($name === 'extension' || $name === 'zend_extension') { - if (!isset($ini[$name])) { - $ini[$name] = []; - } - $ini[$name][] = $value; - continue; - } - $ini[$name] = $value; - } - return $ini; + $this->configurableMethods = $configurableMethods; + $this->returnValueGeneration = $returnValueGeneration; } - private function parseEnvSection(string $content) : array + public function hasMatchers() : bool { - $env = []; - foreach (explode("\n", trim($content)) as $e) { - $e = explode('=', trim($e), 2); - if (!empty($e[0]) && isset($e[1])) { - $env[$e[0]] = $e[1]; + foreach ($this->matchers as $matcher) { + if ($matcher->hasMatchers()) { + return \true; } } - return $env; + return \false; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * Looks up the match builder with identification $id and returns it. + * + * @param string $id The identification of the match builder */ - private function assertPhptExpectation(array $sections, string $output) : void + public function lookupMatcher(string $id) : ?\PHPUnit\Framework\MockObject\Matcher { - $assertions = ['EXPECT' => 'assertEquals', 'EXPECTF' => 'assertStringMatchesFormat', 'EXPECTREGEX' => 'assertMatchesRegularExpression']; - $actual = preg_replace('/\\r\\n/', "\n", trim($output)); - foreach ($assertions as $sectionName => $sectionAssertion) { - if (isset($sections[$sectionName])) { - $sectionContent = preg_replace('/\\r\\n/', "\n", trim($sections[$sectionName])); - $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; - if ($expected === '') { - throw new \PHPUnit\Runner\Exception('No PHPT expectation found'); - } - Assert::$sectionAssertion($expected, $actual); - return; - } + if (isset($this->matcherMap[$id])) { + return $this->matcherMap[$id]; } - throw new \PHPUnit\Runner\Exception('No PHPT assertion found'); + return null; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Registers a matcher with the identification $id. The matcher can later be + * looked up using lookupMatcher() to figure out if it has been invoked. + * + * @param string $id The identification of the matcher + * @param Matcher $matcher The builder which is being registered + * + * @throws MatcherAlreadyRegisteredException */ - private function runSkip(array &$sections, TestResult $result, array $settings) : bool + public function registerMatcher(string $id, \PHPUnit\Framework\MockObject\Matcher $matcher) : void { - if (!isset($sections['SKIPIF'])) { - return \false; - } - $skipif = $this->render($sections['SKIPIF']); - $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); - if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { - $message = ''; - if (preg_match('/^\\s*skip\\s*(.+)\\s*/i', $jobResult['stdout'], $skipMatch)) { - $message = substr($skipMatch[1], 2); - } - $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); - $trace = array_merge($hint, debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); - $result->addFailure($this, new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), 0); - $result->endTest($this, 0); - return \true; + if (isset($this->matcherMap[$id])) { + throw new \PHPUnit\Framework\MockObject\MatcherAlreadyRegisteredException($id); } - return \false; + $this->matcherMap[$id] = $matcher; } - private function runClean(array &$sections, bool $collectCoverage) : void + public function expects(InvocationOrder $rule) : InvocationMocker { - $this->phpUtil->setStdin(''); - $this->phpUtil->setArgs(''); - if (isset($sections['CLEAN'])) { - $cleanCode = $this->render($sections['CLEAN']); - $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); - } + $matcher = new \PHPUnit\Framework\MockObject\Matcher($rule); + $this->addMatcher($matcher); + return new InvocationMocker($this, $matcher, ...$this->configurableMethods); } /** * @throws Exception + * @throws RuntimeException */ - private function parse() : array + public function invoke(\PHPUnit\Framework\MockObject\Invocation $invocation) { - $sections = []; - $section = ''; - $unsupportedSections = ['CGI', 'COOKIE', 'DEFLATE_POST', 'EXPECTHEADERS', 'EXTENSIONS', 'GET', 'GZIP_POST', 'HEADERS', 'PHPDBG', 'POST', 'POST_RAW', 'PUT', 'REDIRECTTEST', 'REQUEST']; - $lineNr = 0; - foreach (file($this->filename) as $line) { - $lineNr++; - if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { - $section = $result[1]; - $sections[$section] = ''; - $sections[$section . '_offset'] = $lineNr; - continue; - } - if (empty($section)) { - throw new \PHPUnit\Runner\Exception('Invalid PHPT file: empty section header'); + $exception = null; + $hasReturnValue = \false; + $returnValue = null; + foreach ($this->matchers as $match) { + try { + if ($match->matches($invocation)) { + $value = $match->invoked($invocation); + if (!$hasReturnValue) { + $returnValue = $value; + $hasReturnValue = \true; + } + } + } catch (Exception $e) { + $exception = $e; } - $sections[$section] .= $line; } - if (isset($sections['FILEEOF'])) { - $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); - unset($sections['FILEEOF']); + if ($exception !== null) { + throw $exception; } - $this->parseExternal($sections); - if (!$this->validate($sections)) { - throw new \PHPUnit\Runner\Exception('Invalid PHPT file'); + if ($hasReturnValue) { + return $returnValue; } - foreach ($unsupportedSections as $section) { - if (isset($sections[$section])) { - throw new \PHPUnit\Runner\Exception("PHPUnit does not support PHPT {$section} sections"); + if (!$this->returnValueGeneration) { + $exception = new \PHPUnit\Framework\MockObject\ReturnValueNotConfiguredException($invocation); + if (strtolower($invocation->getMethodName()) === '__tostring') { + $this->deferredError = $exception; + return ''; } + throw $exception; } - return $sections; + return $invocation->generateReturnValue(); } - /** - * @throws Exception - */ - private function parseExternal(array &$sections) : void + public function matches(\PHPUnit\Framework\MockObject\Invocation $invocation) : bool { - $allowSections = ['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX']; - $testDirectory = dirname($this->filename) . \DIRECTORY_SEPARATOR; - foreach ($allowSections as $section) { - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFilename = trim($sections[$section . '_EXTERNAL']); - if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) { - throw new \PHPUnit\Runner\Exception(sprintf('Could not load --%s-- %s for PHPT file', $section . '_EXTERNAL', $testDirectory . $externalFilename)); - } - $sections[$section] = file_get_contents($testDirectory . $externalFilename); + foreach ($this->matchers as $matcher) { + if (!$matcher->matches($invocation)) { + return \false; } } + return \true; } - private function validate(array &$sections) : bool + /** + * @throws Throwable + */ + public function verify() : void { - $requiredSections = ['FILE', ['EXPECT', 'EXPECTF', 'EXPECTREGEX']]; - foreach ($requiredSections as $section) { - if (is_array($section)) { - $foundSection = \false; - foreach ($section as $anySection) { - if (isset($sections[$anySection])) { - $foundSection = \true; - break; - } - } - if (!$foundSection) { - return \false; - } - continue; - } - if (!isset($sections[$section])) { - return \false; - } + foreach ($this->matchers as $matcher) { + $matcher->verify(); + } + if ($this->deferredError) { + throw $this->deferredError; } - return \true; } - private function render(string $code) : string + private function addMatcher(\PHPUnit\Framework\MockObject\Matcher $matcher) : void { - return str_replace(['__DIR__', '__FILE__'], ["'" . dirname($this->filename) . "'", "'" . $this->filename . "'"], $code); + $this->matchers[] = $matcher; } - private function getCoverageFiles() : array +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function assert; +use function implode; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Rule\AnyParameters; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use PHPUnit\Framework\MockObject\Rule\MethodName; +use PHPUnit\Framework\MockObject\Rule\ParametersRule; +use PHPUnit\Framework\MockObject\Stub\Stub; +use PHPUnit\Framework\TestFailure; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Matcher +{ + /** + * @var InvocationOrder + */ + private $invocationRule; + /** + * @var mixed + */ + private $afterMatchBuilderId; + /** + * @var bool + */ + private $afterMatchBuilderIsInvoked = \false; + /** + * @var MethodName + */ + private $methodNameRule; + /** + * @var ParametersRule + */ + private $parametersRule; + /** + * @var Stub + */ + private $stub; + public function __construct(InvocationOrder $rule) { - $baseDir = dirname(realpath($this->filename)) . \DIRECTORY_SEPARATOR; - $basename = basename($this->filename, 'phpt'); - return ['coverage' => $baseDir . $basename . 'coverage', 'job' => $baseDir . $basename . 'php']; + $this->invocationRule = $rule; } - private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory) : void + public function hasMatchers() : bool { - $files = $this->getCoverageFiles(); - $template = new Template(__DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl'); - $composerAutoload = '\'\''; - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, \true); + return !$this->invocationRule instanceof AnyInvokedCount; + } + public function hasMethodNameRule() : bool + { + return $this->methodNameRule !== null; + } + public function getMethodNameRule() : MethodName + { + return $this->methodNameRule; + } + public function setMethodNameRule(MethodName $rule) : void + { + $this->methodNameRule = $rule; + } + public function hasParametersRule() : bool + { + return $this->parametersRule !== null; + } + public function setParametersRule(ParametersRule $rule) : void + { + $this->parametersRule = $rule; + } + public function setStub(Stub $stub) : void + { + $this->stub = $stub; + } + public function setAfterMatchBuilderId(string $id) : void + { + $this->afterMatchBuilderId = $id; + } + /** + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function invoked(\PHPUnit\Framework\MockObject\Invocation $invocation) + { + if ($this->methodNameRule === null) { + throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); } - $phar = '\'\''; - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, \true); + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->getObject()->__phpunit_getInvocationHandler()->lookupMatcher($this->afterMatchBuilderId); + if (!$matcher) { + throw new \PHPUnit\Framework\MockObject\MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + assert($matcher instanceof self); + if ($matcher->invocationRule->hasBeenInvoked()) { + $this->afterMatchBuilderIsInvoked = \true; + } } - $globals = ''; - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], \true) . ";\n"; + $this->invocationRule->invoked($invocation); + try { + if ($this->parametersRule !== null) { + $this->parametersRule->apply($invocation); + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage()), $e->getComparisonFailure()); } - if ($codeCoverageCacheDirectory === null) { - $codeCoverageCacheDirectory = 'null'; - } else { - $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; + if ($this->stub) { + return $this->stub->invoke($invocation); } - $template->setVar(['composerAutoload' => $composerAutoload, 'phar' => $phar, 'globals' => $globals, 'job' => $files['job'], 'coverageFile' => $files['coverage'], 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory]); - file_put_contents($files['job'], $job); - $job = $template->render(); + return $invocation->generateReturnValue(); } - private function cleanupForCoverage() : RawCodeCoverageData + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function matches(\PHPUnit\Framework\MockObject\Invocation $invocation) : bool { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - $files = $this->getCoverageFiles(); - if (is_file($files['coverage'])) { - $buffer = @file_get_contents($files['coverage']); - if ($buffer !== \false) { - $coverage = @unserialize($buffer); - if ($coverage === \false) { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - } + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->getObject()->__phpunit_getInvocationHandler()->lookupMatcher($this->afterMatchBuilderId); + if (!$matcher) { + throw new \PHPUnit\Framework\MockObject\MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + assert($matcher instanceof self); + if (!$matcher->invocationRule->hasBeenInvoked()) { + return \false; } } - foreach ($files as $file) { - @unlink($file); + if ($this->methodNameRule === null) { + throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); } - return $coverage; - } - private function stringifyIni(array $ini) : array - { - $settings = []; - foreach ($ini as $key => $value) { - if (is_array($value)) { - foreach ($value as $val) { - $settings[] = $key . '=' . $val; - } - continue; + if (!$this->invocationRule->matches($invocation)) { + return \false; + } + try { + if (!$this->methodNameRule->matches($invocation)) { + return \false; } - $settings[] = $key . '=' . $value; + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage()), $e->getComparisonFailure()); } - return $settings; + return \true; } - private function getLocationHintFromDiff(string $message, array $sections) : array + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws MethodNameNotConfiguredException + */ + public function verify() : void { - $needle = ''; - $previousLine = ''; - $block = 'message'; - foreach (preg_split('/\\r\\n|\\r|\\n/', $message) as $line) { - $line = trim($line); - if ($block === 'message' && $line === '--- Expected') { - $block = 'expected'; - } - if ($block === 'expected' && $line === '@@ @@') { - $block = 'diff'; - } - if ($block === 'diff') { - if (strpos($line, '+') === 0) { - $needle = $this->getCleanDiffLine($previousLine); - break; - } - if (strpos($line, '-') === 0) { - $needle = $this->getCleanDiffLine($line); - break; - } + if ($this->methodNameRule === null) { + throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); + } + try { + $this->invocationRule->verify(); + if ($this->parametersRule === null) { + $this->parametersRule = new AnyParameters(); } - if (!empty($line)) { - $previousLine = $line; + $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; + $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); + $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; + if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { + $this->parametersRule->verify(); } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s.\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), TestFailure::exceptionToString($e))); } - return $this->getLocationHint($needle, $sections); - } - private function getCleanDiffLine(string $line) : string - { - if (preg_match('/^[\\-+]([\'\\"]?)(.*)\\1$/', $line, $matches)) { - $line = $matches[2]; - } - return $line; } - private function getLocationHint(string $needle, array $sections, ?string $sectionName = null) : array + public function toString() : string { - $needle = trim($needle); - if (empty($needle)) { - return [['file' => realpath($this->filename), 'line' => 1]]; + $list = []; + if ($this->invocationRule !== null) { + $list[] = $this->invocationRule->toString(); } - if ($sectionName) { - $search = [$sectionName]; - } else { - $search = [ - // 'FILE', - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ]; + if ($this->methodNameRule !== null) { + $list[] = 'where ' . $this->methodNameRule->toString(); } - $sectionOffset = null; - foreach ($search as $section) { - if (!isset($sections[$section])) { - continue; - } - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFile = trim($sections[$section . '_EXTERNAL']); - return [['file' => realpath(dirname($this->filename) . \DIRECTORY_SEPARATOR . $externalFile), 'line' => 1], ['file' => realpath($this->filename), 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1]]; - } - $sectionOffset = $sections[$section . '_offset'] ?? 0; - $offset = $sectionOffset + 1; - foreach (preg_split('/\\r\\n|\\r|\\n/', $sections[$section]) as $line) { - if (strpos($line, $needle) !== \false) { - return [['file' => realpath($this->filename), 'line' => $offset]]; - } - $offset++; - } + if ($this->parametersRule !== null) { + $list[] = 'and ' . $this->parametersRule->toString(); } - if ($sectionName) { - // String not found in specified section, show user the start of the named section - return [['file' => realpath($this->filename), 'line' => $sectionOffset]]; + if ($this->afterMatchBuilderId !== null) { + $list[] = 'after ' . $this->afterMatchBuilderId; } - // No section specified, show user start of code - return [['file' => realpath($this->filename), 'line' => 1]]; + if ($this->stub !== null) { + $list[] = 'will ' . $this->stub->toString(); + } + return implode(' ', $list); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function is_string; +use function sprintf; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameConstraint extends Constraint +{ /** - * @psalm-return list + * @var string */ - private function settings(bool $collectCoverage) : array + private $methodName; + public function __construct(string $methodName) { - $settings = ['allow_url_fopen=1', 'auto_append_file=', 'auto_prepend_file=', 'disable_functions=', 'display_errors=1', 'docref_ext=.html', 'docref_root=', 'error_append_string=', 'error_prepend_string=', 'error_reporting=-1', 'html_errors=0', 'log_errors=0', 'open_basedir=', 'output_buffering=Off', 'output_handler=', 'report_memleaks=0', 'report_zend_debug=0']; - if (extension_loaded('pcov')) { - if ($collectCoverage) { - $settings[] = 'pcov.enabled=1'; - } else { - $settings[] = 'pcov.enabled=0'; - } - } - if (extension_loaded('xdebug')) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - if ($collectCoverage) { - $settings[] = 'xdebug.mode=coverage'; - } else { - $settings[] = 'xdebug.mode=off'; - } - } else { - $settings[] = 'xdebug.default_enable=0'; - if ($collectCoverage) { - $settings[] = 'xdebug.coverage_enable=1'; - } - } + $this->methodName = $methodName; + } + public function toString() : string + { + return sprintf('is "%s"', $this->methodName); + } + protected function matches($other) : bool + { + if (!is_string($other)) { + return \false; } - return $settings; + return strtolower($this->methodName) === strtolower($other); } } |string|string[] $type + */ + public function __construct(TestCase $testCase, $type) { - return new \PHPUnit\Runner\StandardTestSuiteLoader(); + $this->testCase = $testCase; + $this->type = $type; + $this->generator = new \PHPUnit\Framework\MockObject\Generator(); } /** - * Returns the Test corresponding to the given suite. - * This is a template method, subclasses override - * the runFailed() and clearStatus() methods. + * Creates a mock object using a fluent interface. * - * @param string|string[] $suffixes + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException * - * @throws Exception + * @psalm-return MockObject&MockedType */ - public function getTest(string $suiteClassFile, $suffixes = '') : ?TestSuite + public function getMock() : \PHPUnit\Framework\MockObject\MockObject { - if (is_dir($suiteClassFile)) { - /** @var string[] $files */ - $files = (new FileIteratorFacade())->getFilesAsArray($suiteClassFile, $suffixes); - $suite = new TestSuite($suiteClassFile); - $suite->addTestFiles($files); - return $suite; + $object = $this->generator->getMock($this->type, !$this->emptyMethodsArray ? $this->methods : null, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->cloneArguments, $this->callOriginalMethods, $this->proxyTarget, $this->allowMockingUnknownTypes, $this->returnValueGeneration); + $this->testCase->registerMockObject($object); + return $object; + } + /** + * Creates a mock object for an abstract class using a fluent interface. + * + * @psalm-return MockObject&MockedType + * + * @throws \PHPUnit\Framework\Exception + * @throws ReflectionException + * @throws RuntimeException + */ + public function getMockForAbstractClass() : \PHPUnit\Framework\MockObject\MockObject + { + $object = $this->generator->getMockForAbstractClass($this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments); + $this->testCase->registerMockObject($object); + return $object; + } + /** + * Creates a mock object for a trait using a fluent interface. + * + * @psalm-return MockObject&MockedType + * + * @throws \PHPUnit\Framework\Exception + * @throws ReflectionException + * @throws RuntimeException + */ + public function getMockForTrait() : \PHPUnit\Framework\MockObject\MockObject + { + $object = $this->generator->getMockForTrait($this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments); + $this->testCase->registerMockObject($object); + return $object; + } + /** + * Specifies the subset of methods to mock. Default is to mock none of them. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 + * + * @return $this + */ + public function setMethods(?array $methods = null) : self + { + if ($methods === null) { + $this->methods = $methods; + } else { + $this->methods = array_merge($this->methods ?? [], $methods); } - if (is_file($suiteClassFile) && substr($suiteClassFile, -5, 5) === '.phpt') { - $suite = new TestSuite(); - $suite->addTestFile($suiteClassFile); - return $suite; + return $this; + } + /** + * Specifies the subset of methods to mock, requiring each to exist in the class. + * + * @param string[] $methods + * + * @throws CannotUseOnlyMethodsException + * @throws ReflectionException + * + * @return $this + */ + public function onlyMethods(array $methods) : self + { + if (empty($methods)) { + $this->emptyMethodsArray = \true; + return $this; } try { - $testClass = $this->loadSuiteClass($suiteClassFile); - } catch (\PHPUnit\Exception $e) { - $this->runFailed($e->getMessage()); - return null; + $reflector = new ReflectionClass($this->type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + foreach ($methods as $method) { + if (!$reflector->hasMethod($method)) { + throw new \PHPUnit\Framework\MockObject\CannotUseOnlyMethodsException($this->type, $method); + } + } + $this->methods = array_merge($this->methods ?? [], $methods); + return $this; + } + /** + * Specifies methods that don't exist in the class which you want to mock. + * + * @param string[] $methods + * + * @throws CannotUseAddMethodsException + * @throws ReflectionException + * @throws RuntimeException + * + * @return $this + */ + public function addMethods(array $methods) : self + { + if (empty($methods)) { + $this->emptyMethodsArray = \true; + return $this; } try { - $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); - if (!$suiteMethod->isStatic()) { - $this->runFailed('suite() method must be static.'); - return null; + $reflector = new ReflectionClass($this->type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + foreach ($methods as $method) { + if ($reflector->hasMethod($method)) { + throw new \PHPUnit\Framework\MockObject\CannotUseAddMethodsException($this->type, $method); } - $test = $suiteMethod->invoke(null, $testClass->getName()); - } catch (ReflectionException $e) { - $test = new TestSuite($testClass); } - $this->clearStatus(); - return $test; + $this->methods = array_merge($this->methods ?? [], $methods); + return $this; } /** - * Returns the loaded ReflectionClass for a suite name. + * Specifies the subset of methods to not mock. Default is to mock all of them. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 + * + * @throws ReflectionException */ - protected function loadSuiteClass(string $suiteClassFile) : ReflectionClass + public function setMethodsExcept(array $methods = []) : self { - return $this->getLoader()->load($suiteClassFile); + return $this->setMethods(array_diff($this->generator->getClassMethods($this->type), $methods)); } /** - * Clears the status message. + * Specifies the arguments for the constructor. + * + * @return $this */ - protected function clearStatus() : void + public function setConstructorArgs(array $args) : self + { + $this->constructorArgs = $args; + return $this; + } + /** + * Specifies the name for the mock class. + * + * @return $this + */ + public function setMockClassName(string $name) : self + { + $this->mockClassName = $name; + return $this; + } + /** + * Disables the invocation of the original constructor. + * + * @return $this + */ + public function disableOriginalConstructor() : self + { + $this->originalConstructor = \false; + return $this; + } + /** + * Enables the invocation of the original constructor. + * + * @return $this + */ + public function enableOriginalConstructor() : self + { + $this->originalConstructor = \true; + return $this; + } + /** + * Disables the invocation of the original clone constructor. + * + * @return $this + */ + public function disableOriginalClone() : self + { + $this->originalClone = \false; + return $this; + } + /** + * Enables the invocation of the original clone constructor. + * + * @return $this + */ + public function enableOriginalClone() : self + { + $this->originalClone = \true; + return $this; + } + /** + * Disables the use of class autoloading while creating the mock object. + * + * @return $this + */ + public function disableAutoload() : self + { + $this->autoload = \false; + return $this; + } + /** + * Enables the use of class autoloading while creating the mock object. + * + * @return $this + */ + public function enableAutoload() : self + { + $this->autoload = \true; + return $this; + } + /** + * Disables the cloning of arguments passed to mocked methods. + * + * @return $this + */ + public function disableArgumentCloning() : self + { + $this->cloneArguments = \false; + return $this; + } + /** + * Enables the cloning of arguments passed to mocked methods. + * + * @return $this + */ + public function enableArgumentCloning() : self + { + $this->cloneArguments = \true; + return $this; + } + /** + * Enables the invocation of the original methods. + * + * @return $this + */ + public function enableProxyingToOriginalMethods() : self + { + $this->callOriginalMethods = \true; + return $this; + } + /** + * Disables the invocation of the original methods. + * + * @return $this + */ + public function disableProxyingToOriginalMethods() : self + { + $this->callOriginalMethods = \false; + $this->proxyTarget = null; + return $this; + } + /** + * Sets the proxy target. + * + * @return $this + */ + public function setProxyTarget(object $object) : self + { + $this->proxyTarget = $object; + return $this; + } + /** + * @return $this + */ + public function allowMockingUnknownTypes() : self + { + $this->allowMockingUnknownTypes = \true; + return $this; + } + /** + * @return $this + */ + public function disallowMockingUnknownTypes() : self + { + $this->allowMockingUnknownTypes = \false; + return $this; + } + /** + * @return $this + */ + public function enableAutoReturnValueGeneration() : self + { + $this->returnValueGeneration = \true; + return $this; + } + /** + * @return $this + */ + public function disableAutoReturnValueGeneration() : self { + $this->returnValueGeneration = \false; + return $this; } - /** - * Override to define how to handle a failed loading of - * a test suite. - */ - protected abstract function runFailed(string $message) : void; } cache = $cache; - } - public function flush() : void - { - $this->cache->persist(); - } - public function executeAfterSuccessfulTest(string $test, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - } - public function executeAfterIncompleteTest(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE); - } - public function executeAfterRiskyTest(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY); - } - public function executeAfterSkippedTest(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED); - } - public function executeAfterTestError(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR); - } - public function executeAfterTestFailure(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE); - } - public function executeAfterTestWarning(string $test, string $message, float $time) : void - { - $testName = $this->getTestName($test); - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING); - } - public function executeAfterLastTest() : void + private $classCode; + /** + * @var class-string + */ + private $mockName; + /** + * @var ConfigurableMethod[] + */ + private $configurableMethods; + /** + * @psalm-param class-string $mockName + */ + public function __construct(string $classCode, string $mockName, array $configurableMethods) { - $this->flush(); + $this->classCode = $classCode; + $this->mockName = $mockName; + $this->configurableMethods = $configurableMethods; } /** - * @param string $test A long description format of the current test - * - * @return string The test name without TestSuiteClassName:: and @dataprovider details + * @psalm-return class-string */ - private function getTestName(string $test) : string + public function generate() : string { - $matches = []; - if (preg_match('/^(?\\S+::\\S+)(?:(? with data set (?:#\\d+|"[^"]+"))\\s\\()?/', $test, $matches)) { - $test = $matches['name'] . ($matches['dataname'] ?? ''); + if (!class_exists($this->mockName, \false)) { + eval($this->classCode); + call_user_func([$this->mockName, '__phpunit_initConfigurableMethods'], ...$this->configurableMethods); } - return $test; + return $this->mockName; + } + public function getClassCode() : string + { + return $this->classCode; } } groups = $groups->asArray(); + if ($method->isPrivate()) { + $modifier = 'private'; + } elseif ($method->isProtected()) { + $modifier = 'protected'; + } else { + $modifier = 'public'; + } + if ($method->isStatic()) { + $modifier .= ' static'; + } + if ($method->returnsReference()) { + $reference = '&'; + } else { + $reference = ''; + } + $docComment = $method->getDocComment(); + if (is_string($docComment) && preg_match('#\\*[ \\t]*+@deprecated[ \\t]*+(.*?)\\r?+\\n[ \\t]*+\\*(?:[ \\t]*+@|/$)#s', $docComment, $deprecation)) { + $deprecation = trim(preg_replace('#[ \\t]*\\r?\\n[ \\t]*+\\*[ \\t]*+#', ' ', $deprecation[1])); + } else { + $deprecation = null; + } + return new self($method->getDeclaringClass()->getName(), $method->getName(), $cloneArguments, $modifier, self::getMethodParametersForDeclaration($method), self::getMethodParametersForCall($method), (new ReflectionMapper())->fromReturnType($method), $reference, $callOriginalMethod, $method->isStatic(), $deprecation); } - public function count() : int + public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments) : self { - return iterator_count($this); + return new self($fullClassName, $methodName, $cloneArguments, 'public', '', '', new UnknownType(), '', \false, \false, null); } - public function rewind() : void + public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) { - $this->position = 0; + $this->className = $className; + $this->methodName = $methodName; + $this->cloneArguments = $cloneArguments; + $this->modifier = $modifier; + $this->argumentsForDeclaration = $argumentsForDeclaration; + $this->argumentsForCall = $argumentsForCall; + $this->returnType = $returnType; + $this->reference = $reference; + $this->callOriginalMethod = $callOriginalMethod; + $this->static = $static; + $this->deprecation = $deprecation; } - public function valid() : bool + public function getName() : string { - return $this->position < count($this->groups); + return $this->methodName; } - public function key() : int + /** + * @throws RuntimeException + */ + public function generateCode() : string { - return $this->position; + if ($this->static) { + $templateFile = 'mocked_static_method.tpl'; + } elseif ($this->returnType->isNever() || $this->returnType->isVoid()) { + $templateFile = sprintf('%s_method_never_or_void.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked'); + } else { + $templateFile = sprintf('%s_method.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked'); + } + $deprecation = $this->deprecation; + if (null !== $this->deprecation) { + $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; + $deprecationTemplate = $this->getTemplate('deprecation.tpl'); + $deprecationTemplate->setVar(['deprecation' => var_export($deprecation, \true)]); + $deprecation = $deprecationTemplate->render(); + } + $template = $this->getTemplate($templateFile); + $template->setVar(['arguments_decl' => $this->argumentsForDeclaration, 'arguments_call' => $this->argumentsForCall, 'return_declaration' => !empty($this->returnType->asString()) ? ': ' . $this->returnType->asString() : '', 'return_type' => $this->returnType->asString(), 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, 'class_name' => $this->className, 'method_name' => $this->methodName, 'modifier' => $this->modifier, 'reference' => $this->reference, 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', 'deprecation' => $deprecation]); + return $template->render(); } - public function current() : \PHPUnit\TextUI\XmlConfiguration\Group + public function getReturnType() : Type { - return $this->groups[$this->position]; + return $this->returnType; } - public function next() : void + /** + * @throws RuntimeException + */ + private function getTemplate(string $template) : Template { - $this->position++; + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; + if (!isset(self::$templates[$filename])) { + try { + self::$templates[$filename] = new Template($filename); + } catch (TemplateException $e) { + throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + } + } + return self::$templates[$filename]; + } + /** + * Returns the parameters of a function or method. + * + * @throws RuntimeException + */ + private static function getMethodParametersForDeclaration(ReflectionMethod $method) : string + { + $parameters = []; + $types = (new ReflectionMapper())->fromParameterTypes($method); + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + $default = ''; + $reference = ''; + $typeDeclaration = ''; + if (!$types[$i]->type()->isUnknown()) { + $typeDeclaration = $types[$i]->type()->asString() . ' '; + } + if ($parameter->isPassedByReference()) { + $reference = '&'; + } + if ($parameter->isVariadic()) { + $name = '...' . $name; + } elseif ($parameter->isDefaultValueAvailable()) { + $default = ' = ' . self::exportDefaultValue($parameter); + } elseif ($parameter->isOptional()) { + $default = ' = null'; + } + $parameters[] = $typeDeclaration . $reference . $name . $default; + } + return implode(', ', $parameters); + } + /** + * Returns the parameters of a function or method. + * + * @throws ReflectionException + */ + private static function getMethodParametersForCall(ReflectionMethod $method) : string + { + $parameters = []; + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + if ($parameter->isVariadic()) { + continue; + } + if ($parameter->isPassedByReference()) { + $parameters[] = '&' . $name; + } else { + $parameters[] = $name; + } + } + return implode(', ', $parameters); + } + /** + * @throws ReflectionException + */ + private static function exportDefaultValue(ReflectionParameter $parameter) : string + { + try { + $defaultValue = $parameter->getDefaultValue(); + if (!is_object($defaultValue)) { + return (string) var_export($defaultValue, \true); + } + $parameterAsString = $parameter->__toString(); + return (string) explode(' = ', substr(substr($parameterAsString, strpos($parameterAsString, ' ') + strlen(' ')), 0, -2))[1]; + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd } } groups = $groups; + foreach ($methods as $method) { + $this->methods[strtolower($method->getName())] = $method; + } } /** - * @return Group[] + * @return MockMethod[] */ public function asArray() : array { - return $this->groups; - } - /** - * @return string[] - */ - public function asArrayOfStrings() : array - { - $result = []; - foreach ($this->groups as $group) { - $result[] = $group->name(); - } - return $result; - } - public function isEmpty() : bool - { - return empty($this->groups); + return array_values($this->methods); } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\GroupCollectionIterator + public function hasMethod(string $methodName) : bool { - return new \PHPUnit\TextUI\XmlConfiguration\GroupCollectionIterator($this); + return array_key_exists(strtolower($methodName), $this->methods); } } include = $include; - $this->exclude = $exclude; - } - public function hasInclude() : bool - { - return !$this->include->isEmpty(); - } - public function include() : \PHPUnit\TextUI\XmlConfiguration\GroupCollection - { - return $this->include; - } - public function hasExclude() : bool - { - return !$this->exclude->isEmpty(); - } - public function exclude() : \PHPUnit\TextUI\XmlConfiguration\GroupCollection - { - return $this->exclude; - } + public function __phpunit_setOriginalObject($originalObject) : void; + public function __phpunit_verify(bool $unsetInvocationMocker = \true) : void; + public function expects(InvocationOrder $invocationRule) : BuilderInvocationMocker; } name = $name; + $this->classCode = $classCode; + $this->mockName = $mockName; } - public function name() : string + /** + * @psalm-return class-string + */ + public function generate() : string { - return $this->name; + if (!class_exists($this->mockName, \false)) { + eval($this->classCode); + } + return $this->mockName; + } + public function getClassCode() : string + { + return $this->classCode; } } - - - - {tests_directory} - - - - - - {src_directory} - - - - -EOT; - public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory) : string - { - return str_replace(['{phpunit_version}', '{bootstrap_script}', '{tests_directory}', '{src_directory}', '{cache_directory}'], [$phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory, $cacheDirectory], self::TEMPLATE); - } + public function generate() : string; } target = $target; + return 'invoked zero or more times'; } - public function target() : File + public function verify() : void + { + } + public function matches(BaseInvocation $invocation) : bool + { + return \true; + } + protected function invokedDo(BaseInvocation $invocation) : void { - return $this->target; } } target = $target; + return 'with any parameters'; } - public function target() : File + public function apply(BaseInvocation $invocation) : void + { + } + public function verify() : void { - return $this->target; } } junit = $junit; - $this->text = $text; - $this->teamCity = $teamCity; - $this->testDoxHtml = $testDoxHtml; - $this->testDoxText = $testDoxText; - $this->testDoxXml = $testDoxXml; - } - public function hasJunit() : bool - { - return $this->junit !== null; - } - public function junit() : \PHPUnit\TextUI\XmlConfiguration\Logging\Junit + public function __construct(array $parameterGroups) { - if ($this->junit === null) { - throw new Exception('Logger "JUnit XML" is not configured'); + foreach ($parameterGroups as $index => $parameters) { + if (!is_iterable($parameters)) { + throw new InvalidParameterGroupException(sprintf('Parameter group #%d must be an array or Traversable, got %s', $index, gettype($parameters))); + } + foreach ($parameters as $parameter) { + if (!$parameter instanceof Constraint) { + $parameter = new IsEqual($parameter); + } + $this->parameterGroups[$index][] = $parameter; + } } - return $this->junit; - } - public function hasText() : bool - { - return $this->text !== null; } - public function text() : \PHPUnit\TextUI\XmlConfiguration\Logging\Text + public function toString() : string { - if ($this->text === null) { - throw new Exception('Logger "Text" is not configured'); - } - return $this->text; + return 'with consecutive parameters'; } - public function hasTeamCity() : bool + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function apply(BaseInvocation $invocation) : void { - return $this->teamCity !== null; + $this->invocations[] = $invocation; + $callIndex = count($this->invocations) - 1; + $this->verifyInvocation($invocation, $callIndex); } - public function teamCity() : \PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function verify() : void { - if ($this->teamCity === null) { - throw new Exception('Logger "Team City" is not configured'); + foreach ($this->invocations as $callIndex => $invocation) { + $this->verifyInvocation($invocation, $callIndex); } - return $this->teamCity; - } - public function hasTestDoxHtml() : bool - { - return $this->testDoxHtml !== null; } - public function testDoxHtml() : TestDoxHtml + /** + * Verify a single invocation. + * + * @param int $callIndex + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + private function verifyInvocation(BaseInvocation $invocation, $callIndex) : void { - if ($this->testDoxHtml === null) { - throw new Exception('Logger "TestDox HTML" is not configured'); + if (!isset($this->parameterGroups[$callIndex])) { + // no parameter assertion for this call index + return; } - return $this->testDoxHtml; - } - public function hasTestDoxText() : bool - { - return $this->testDoxText !== null; - } - public function testDoxText() : TestDoxText - { - if ($this->testDoxText === null) { - throw new Exception('Logger "TestDox Text" is not configured'); + $parameters = $this->parameterGroups[$callIndex]; + if (count($invocation->getParameters()) < count($parameters)) { + throw new ExpectationFailedException(sprintf('Parameter count for invocation %s is too low.', $invocation->toString())); } - return $this->testDoxText; - } - public function hasTestDoxXml() : bool - { - return $this->testDoxXml !== null; - } - public function testDoxXml() : TestDoxXml - { - if ($this->testDoxXml === null) { - throw new Exception('Logger "TestDox XML" is not configured'); + foreach ($parameters as $i => $parameter) { + $parameter->evaluate($invocation->getParameters()[$i], sprintf('Parameter %s for invocation #%d %s does not match expected ' . 'value.', $i, $callIndex, $invocation->toString())); } - return $this->testDoxXml; } } target = $target; + return count($this->invocations); } - public function target() : File + public function hasBeenInvoked() : bool { - return $this->target; + return count($this->invocations) > 0; + } + public final function invoked(BaseInvocation $invocation) + { + $this->invocations[] = $invocation; + return $this->invokedDo($invocation); } + public abstract function matches(BaseInvocation $invocation) : bool; + protected abstract function invokedDo(BaseInvocation $invocation); } target = $target; + $this->sequenceIndex = $sequenceIndex; } - public function target() : File + public function toString() : string + { + return 'invoked at sequence index ' . $this->sequenceIndex; + } + public function matches(BaseInvocation $invocation) : bool + { + $this->currentIndex++; + return $this->currentIndex == $this->sequenceIndex; + } + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify() : void + { + if ($this->currentIndex < $this->sequenceIndex) { + throw new ExpectationFailedException(sprintf('The expected invocation at index %s was never reached.', $this->sequenceIndex)); + } + } + protected function invokedDo(BaseInvocation $invocation) : void { - return $this->target; } } target = $target; + $this->requiredInvocations = $requiredInvocations; } - public function target() : File + public function toString() : string + { + return 'invoked at least ' . $this->requiredInvocations . ' times'; + } + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify() : void + { + $count = $this->getInvocationCount(); + if ($count < $this->requiredInvocations) { + throw new ExpectationFailedException('Expected invocation at least ' . $this->requiredInvocations . ' times but it occurred ' . $count . ' time(s).'); + } + } + public function matches(BaseInvocation $invocation) : bool + { + return \true; + } + protected function invokedDo(BaseInvocation $invocation) : void { - return $this->target; } } target = $target; + $count = $this->getInvocationCount(); + if ($count < 1) { + throw new ExpectationFailedException('Expected invocation at least once but it never occurred.'); + } } - public function target() : File + public function matches(BaseInvocation $invocation) : bool + { + return \true; + } + protected function invokedDo(BaseInvocation $invocation) : void { - return $this->target; } } directories = $directories->asArray(); - } - public function count() : int - { - return iterator_count($this); - } - public function rewind() : void + public function __construct($allowedInvocations) { - $this->position = 0; + $this->allowedInvocations = $allowedInvocations; } - public function valid() : bool + public function toString() : string { - return $this->position < count($this->directories); + return 'invoked at most ' . $this->allowedInvocations . ' times'; } - public function key() : int + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify() : void { - return $this->position; + $count = $this->getInvocationCount(); + if ($count > $this->allowedInvocations) { + throw new ExpectationFailedException('Expected invocation at most ' . $this->allowedInvocations . ' times but it occurred ' . $count . ' time(s).'); + } } - public function current() : \PHPUnit\TextUI\XmlConfiguration\Directory + public function matches(BaseInvocation $invocation) : bool { - return $this->directories[$this->position]; + return \true; } - public function next() : void + protected function invokedDo(BaseInvocation $invocation) : void { - $this->position++; } } expectedCount = $expectedCount; } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\Directory ...$directories) + public function isNever() : bool { - $this->directories = $directories; + return $this->expectedCount === 0; } - /** - * @return Directory[] - */ - public function asArray() : array + public function toString() : string { - return $this->directories; + return 'invoked ' . $this->expectedCount . ' time(s)'; } - public function count() : int + public function matches(BaseInvocation $invocation) : bool { - return count($this->directories); + return \true; } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\DirectoryCollectionIterator + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify() : void { - return new \PHPUnit\TextUI\XmlConfiguration\DirectoryCollectionIterator($this); + $count = $this->getInvocationCount(); + if ($count !== $this->expectedCount) { + throw new ExpectationFailedException(sprintf('Method was expected to be called %d times, ' . 'actually called %d times.', $this->expectedCount, $count)); + } } - public function isEmpty() : bool + /** + * @throws ExpectationFailedException + */ + protected function invokedDo(BaseInvocation $invocation) : void { - return $this->count() === 0; + $count = $this->getInvocationCount(); + if ($count > $this->expectedCount) { + $message = $invocation->toString() . ' '; + switch ($this->expectedCount) { + case 0: + $message .= 'was not expected to be called.'; + break; + case 1: + $message .= 'was not expected to be called more than once.'; + break; + default: + $message .= sprintf('was not expected to be called more than %d times.', $this->expectedCount); + } + throw new ExpectationFailedException($message); + } } } path = $path; + if (is_string($constraint)) { + $constraint = new MethodNameConstraint($constraint); + } + if (!$constraint instanceof Constraint) { + throw InvalidArgumentException::create(1, 'PHPUnit\\Framework\\Constraint\\Constraint object or string'); + } + $this->constraint = $constraint; } - public function path() : string + public function toString() : string + { + return 'method name ' . $this->constraint->toString(); + } + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function matches(BaseInvocation $invocation) : bool + { + return $this->matchesName($invocation->getMethodName()); + } + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function matchesName(string $methodName) : bool { - return $this->path; + return (bool) $this->constraint->evaluate($methodName, '', \true); } } files = $files->asArray(); - } - public function count() : int + private $invocation; + /** + * @var bool|ExpectationFailedException + */ + private $parameterVerificationResult; + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(array $parameters) { - return iterator_count($this); + foreach ($parameters as $parameter) { + if (!$parameter instanceof Constraint) { + $parameter = new IsEqual($parameter); + } + $this->parameters[] = $parameter; + } } - public function rewind() : void + public function toString() : string { - $this->position = 0; + $text = 'with parameter'; + foreach ($this->parameters as $index => $parameter) { + if ($index > 0) { + $text .= ' and'; + } + $text .= ' ' . $index . ' ' . $parameter->toString(); + } + return $text; } - public function valid() : bool + /** + * @throws Exception + */ + public function apply(BaseInvocation $invocation) : void { - return $this->position < count($this->files); + $this->invocation = $invocation; + $this->parameterVerificationResult = null; + try { + $this->parameterVerificationResult = $this->doVerify(); + } catch (ExpectationFailedException $e) { + $this->parameterVerificationResult = $e; + throw $this->parameterVerificationResult; + } } - public function key() : int + /** + * Checks if the invocation $invocation matches the current rules. If it + * does the rule will get the invoked() method called which should check + * if an expectation is met. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function verify() : void { - return $this->position; + $this->doVerify(); } - public function current() : \PHPUnit\TextUI\XmlConfiguration\File + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + private function doVerify() : bool { - return $this->files[$this->position]; + if (isset($this->parameterVerificationResult)) { + return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); + } + if ($this->invocation === null) { + throw new ExpectationFailedException('Mocked method does not exist.'); + } + if (count($this->invocation->getParameters()) < count($this->parameters)) { + $message = 'Parameter count for invocation %s is too low.'; + // The user called `->with($this->anything())`, but may have meant + // `->withAnyParameters()`. + // + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 + if (count($this->parameters) === 1 && get_class($this->parameters[0]) === IsAnything::class) { + $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; + } + throw new ExpectationFailedException(sprintf($message, $this->invocation->toString())); + } + foreach ($this->parameters as $i => $parameter) { + $parameter->evaluate($this->invocation->getParameters()[$i], sprintf('Parameter %s for invocation %s does not match expected ' . 'value.', $i, $this->invocation->toString())); + } + return \true; } - public function next() : void + /** + * @throws ExpectationFailedException + */ + private function guardAgainstDuplicateEvaluationOfParameterConstraints() : bool { - $this->position++; + if ($this->parameterVerificationResult instanceof ExpectationFailedException) { + throw $this->parameterVerificationResult; + } + return (bool) $this->parameterVerificationResult; } } files = $files; - } - /** - * @return File[] + * @throws ExpectationFailedException if the invocation violates the rule */ - public function asArray() : array - { - return $this->files; - } - public function count() : int - { - return count($this->files); - } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\FileCollectionIterator - { - return new \PHPUnit\TextUI\XmlConfiguration\FileCollectionIterator($this); - } - public function isEmpty() : bool - { - return $this->count() === 0; - } + public function apply(BaseInvocation $invocation) : void; + public function verify() : void; } path = $path; - } - public function path() : string - { - return $this->path; - } + public function __phpunit_getInvocationHandler() : \PHPUnit\Framework\MockObject\InvocationHandler; + public function __phpunit_hasMatchers() : bool; + public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration) : void; } loadFile($filename, \false, \true, \true); - } catch (XmlException $e) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - $xpath = new DOMXPath($document); - try { - $xsdFilename = (new SchemaFinder())->find(Version::series()); - } catch (XmlException $e) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - return new \PHPUnit\TextUI\XmlConfiguration\Configuration($filename, (new Validator())->validate($document, $xsdFilename), $this->extensions($filename, $xpath), $this->codeCoverage($filename, $xpath, $document), $this->groups($xpath), $this->testdoxGroups($xpath), $this->listeners($filename, $xpath), $this->logging($filename, $xpath), $this->php($filename, $xpath), $this->phpunit($filename, $document), $this->testSuite($filename, $xpath)); - } - public function logging(string $filename, DOMXPath $xpath) : Logging - { - if ($xpath->query('logging/log')->length !== 0) { - return $this->legacyLogging($filename, $xpath); - } - $junit = null; - $element = $this->element($xpath, 'logging/junit'); - if ($element) { - $junit = new Junit(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $text = null; - $element = $this->element($xpath, 'logging/text'); - if ($element) { - $text = new Text(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $teamCity = null; - $element = $this->element($xpath, 'logging/teamcity'); - if ($element) { - $teamCity = new TeamCity(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $testDoxHtml = null; - $element = $this->element($xpath, 'logging/testdoxHtml'); - if ($element) { - $testDoxHtml = new TestDoxHtml(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $testDoxText = null; - $element = $this->element($xpath, 'logging/testdoxText'); - if ($element) { - $testDoxText = new TestDoxText(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $testDoxXml = null; - $element = $this->element($xpath, 'logging/testdoxXml'); - if ($element) { - $testDoxXml = new TestDoxXml(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - return new Logging($junit, $text, $teamCity, $testDoxHtml, $testDoxText, $testDoxXml); - } - public function legacyLogging(string $filename, DOMXPath $xpath) : Logging - { - $junit = null; - $teamCity = null; - $testDoxHtml = null; - $testDoxText = null; - $testDoxXml = null; - $text = null; - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - if (!$target) { - continue; - } - $target = $this->toAbsolutePath($filename, $target); - switch ($type) { - case 'plain': - $text = new Text(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'junit': - $junit = new Junit(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'teamcity': - $teamCity = new TeamCity(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'testdox-html': - $testDoxHtml = new TestDoxHtml(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'testdox-text': - $testDoxText = new TestDoxText(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'testdox-xml': - $testDoxXml = new TestDoxXml(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - } - } - return new Logging($junit, $text, $teamCity, $testDoxHtml, $testDoxText, $testDoxXml); - } - private function extensions(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection - { - $extensions = []; - foreach ($xpath->query('extensions/extension') as $extension) { - assert($extension instanceof DOMElement); - $extensions[] = $this->getElementConfigurationParameters($filename, $extension); - } - return \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection::fromArray($extensions); - } - private function getElementConfigurationParameters(string $filename, DOMElement $element) : \PHPUnit\TextUI\XmlConfiguration\Extension + private $stack; + /** + * @var mixed + */ + private $value; + public function __construct(array $stack) { - /** @psalm-var class-string $class */ - $class = (string) $element->getAttribute('class'); - $file = ''; - $arguments = $this->getConfigurationArguments($filename, $element->childNodes); - if ($element->getAttribute('file')) { - $file = $this->toAbsolutePath($filename, (string) $element->getAttribute('file'), \true); - } - return new \PHPUnit\TextUI\XmlConfiguration\Extension($class, $file, $arguments); + $this->stack = $stack; } - private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = \false) : string + public function invoke(Invocation $invocation) { - $path = trim($path); - if (strpos($path, '/') === 0) { - return $path; - } - // Matches the following on Windows: - // - \\NetworkComputer\Path - // - \\.\D: - // - \\.\c: - // - C:\Windows - // - C:\windows - // - C:/windows - // - c:/windows - if (defined('PHP_WINDOWS_VERSION_BUILD') && ($path[0] === '\\' || strlen($path) >= 3 && preg_match('#^[A-Z]\\:[/\\\\]#i', substr($path, 0, 3)))) { - return $path; - } - if (strpos($path, '://') !== \false) { - return $path; - } - $file = dirname($filename) . \DIRECTORY_SEPARATOR . $path; - if ($useIncludePath && !is_file($file)) { - $includePathFile = stream_resolve_include_path($path); - if ($includePathFile) { - $file = $includePathFile; - } + $this->value = array_shift($this->stack); + if ($this->value instanceof \PHPUnit\Framework\MockObject\Stub\Stub) { + $this->value = $this->value->invoke($invocation); } - return $file; + return $this->value; } - private function getConfigurationArguments(string $filename, DOMNodeList $nodes) : array + public function toString() : string { - $arguments = []; - if ($nodes->length === 0) { - return $arguments; - } - foreach ($nodes as $node) { - if (!$node instanceof DOMElement) { - continue; - } - if ($node->tagName !== 'arguments') { - continue; - } - foreach ($node->childNodes as $argument) { - if (!$argument instanceof DOMElement) { - continue; - } - if ($argument->tagName === 'file' || $argument->tagName === 'directory') { - $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); - } else { - $arguments[] = Xml::xmlToVariable($argument); - } - } - } - return $arguments; + $exporter = new Exporter(); + return sprintf('return user-specified value %s', $exporter->export($this->value)); } - private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document) : CodeCoverage +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\SebastianBergmann\Exporter\Exporter; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception implements \PHPUnit\Framework\MockObject\Stub\Stub +{ + private $exception; + public function __construct(Throwable $exception) { - if ($xpath->query('filter/whitelist')->length !== 0) { - return $this->legacyCodeCoverage($filename, $xpath, $document); - } - $cacheDirectory = null; - $pathCoverage = \false; - $includeUncoveredFiles = \true; - $processUncoveredFiles = \false; - $ignoreDeprecatedCodeUnits = \false; - $disableCodeCoverageIgnore = \false; - $element = $this->element($xpath, 'coverage'); - if ($element) { - $cacheDirectory = $this->getStringAttribute($element, 'cacheDirectory'); - if ($cacheDirectory !== null) { - $cacheDirectory = new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, $cacheDirectory)); - } - $pathCoverage = $this->getBooleanAttribute($element, 'pathCoverage', \false); - $includeUncoveredFiles = $this->getBooleanAttribute($element, 'includeUncoveredFiles', \true); - $processUncoveredFiles = $this->getBooleanAttribute($element, 'processUncoveredFiles', \false); - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute($element, 'ignoreDeprecatedCodeUnits', \false); - $disableCodeCoverageIgnore = $this->getBooleanAttribute($element, 'disableCodeCoverageIgnore', \false); - } - $clover = null; - $element = $this->element($xpath, 'coverage/report/clover'); - if ($element) { - $clover = new Clover(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $cobertura = null; - $element = $this->element($xpath, 'coverage/report/cobertura'); - if ($element) { - $cobertura = new Cobertura(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $crap4j = null; - $element = $this->element($xpath, 'coverage/report/crap4j'); - if ($element) { - $crap4j = new Crap4j(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile'))), $this->getIntegerAttribute($element, 'threshold', 30)); - } - $html = null; - $element = $this->element($xpath, 'coverage/report/html'); - if ($element) { - $html = new CodeCoverageHtml(new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputDirectory'))), $this->getIntegerAttribute($element, 'lowUpperBound', 50), $this->getIntegerAttribute($element, 'highLowerBound', 90)); - } - $php = null; - $element = $this->element($xpath, 'coverage/report/php'); - if ($element) { - $php = new CodeCoveragePhp(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); - } - $text = null; - $element = $this->element($xpath, 'coverage/report/text'); - if ($element) { - $text = new CodeCoverageText(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile'))), $this->getBooleanAttribute($element, 'showUncoveredFiles', \false), $this->getBooleanAttribute($element, 'showOnlySummary', \false)); - } - $xml = null; - $element = $this->element($xpath, 'coverage/report/xml'); - if ($element) { - $xml = new CodeCoverageXml(new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputDirectory')))); - } - return new CodeCoverage($cacheDirectory, $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'), $this->readFilterFiles($filename, $xpath, 'coverage/include/file'), $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'), $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'), $pathCoverage, $includeUncoveredFiles, $processUncoveredFiles, $ignoreDeprecatedCodeUnits, $disableCodeCoverageIgnore, $clover, $cobertura, $crap4j, $html, $php, $text, $xml); + $this->exception = $exception; } /** - * @deprecated + * @throws Throwable */ - private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document) : CodeCoverage + public function invoke(Invocation $invocation) : void { - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute($document->documentElement, 'ignoreDeprecatedCodeUnitsFromCodeCoverage', \false); - $disableCodeCoverageIgnore = $this->getBooleanAttribute($document->documentElement, 'disableCodeCoverageIgnore', \false); - $includeUncoveredFiles = \true; - $processUncoveredFiles = \false; - $element = $this->element($xpath, 'filter/whitelist'); - if ($element) { - if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) { - $includeUncoveredFiles = (bool) $this->getBoolean((string) $element->getAttribute('addUncoveredFilesFromWhitelist'), \true); - } - if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) { - $processUncoveredFiles = (bool) $this->getBoolean((string) $element->getAttribute('processUncoveredFilesFromWhitelist'), \false); - } - } - $clover = null; - $cobertura = null; - $crap4j = null; - $html = null; - $php = null; - $text = null; - $xml = null; - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - if (!$target) { - continue; - } - $target = $this->toAbsolutePath($filename, $target); - switch ($type) { - case 'coverage-clover': - $clover = new Clover(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'coverage-cobertura': - $cobertura = new Cobertura(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'coverage-crap4j': - $crap4j = new Crap4j(new \PHPUnit\TextUI\XmlConfiguration\File($target), $this->getIntegerAttribute($log, 'threshold', 30)); - break; - case 'coverage-html': - $html = new CodeCoverageHtml(new \PHPUnit\TextUI\XmlConfiguration\Directory($target), $this->getIntegerAttribute($log, 'lowUpperBound', 50), $this->getIntegerAttribute($log, 'highLowerBound', 90)); - break; - case 'coverage-php': - $php = new CodeCoveragePhp(new \PHPUnit\TextUI\XmlConfiguration\File($target)); - break; - case 'coverage-text': - $text = new CodeCoverageText(new \PHPUnit\TextUI\XmlConfiguration\File($target), $this->getBooleanAttribute($log, 'showUncoveredFiles', \false), $this->getBooleanAttribute($log, 'showOnlySummary', \false)); - break; - case 'coverage-xml': - $xml = new CodeCoverageXml(new \PHPUnit\TextUI\XmlConfiguration\Directory($target)); - break; - } - } - return new CodeCoverage(null, $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), \false, $includeUncoveredFiles, $processUncoveredFiles, $ignoreDeprecatedCodeUnits, $disableCodeCoverageIgnore, $clover, $cobertura, $crap4j, $html, $php, $text, $xml); + throw $this->exception; + } + public function toString() : string + { + $exporter = new Exporter(); + return sprintf('raise user-specified exception %s', $exporter->export($this->exception)); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnArgument implements \PHPUnit\Framework\MockObject\Stub\Stub +{ /** - * If $value is 'false' or 'true', this returns the value that $value represents. - * Otherwise, returns $default, which may be a string in rare cases. - * - * @see \PHPUnit\TextUI\XmlConfigurationTest::testPHPConfigurationIsReadCorrectly - * - * @param bool|string $default - * - * @return bool|string + * @var int */ - private function getBoolean(string $value, $default) + private $argumentIndex; + public function __construct($argumentIndex) { - if (strtolower($value) === 'false') { - return \false; - } - if (strtolower($value) === 'true') { - return \true; - } - return $default; + $this->argumentIndex = $argumentIndex; } - private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query) : FilterDirectoryCollection + public function invoke(Invocation $invocation) { - $directories = []; - foreach ($xpath->query($query) as $directoryNode) { - assert($directoryNode instanceof DOMElement); - $directoryPath = (string) $directoryNode->textContent; - if (!$directoryPath) { - continue; - } - $directories[] = new FilterDirectory($this->toAbsolutePath($filename, $directoryPath), $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT'); + if (isset($invocation->getParameters()[$this->argumentIndex])) { + return $invocation->getParameters()[$this->argumentIndex]; } - return FilterDirectoryCollection::fromArray($directories); } - private function readFilterFiles(string $filename, DOMXPath $xpath, string $query) : \PHPUnit\TextUI\XmlConfiguration\FileCollection + public function toString() : string { - $files = []; - foreach ($xpath->query($query) as $file) { - $filePath = (string) $file->textContent; - if ($filePath) { - $files[] = new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, $filePath)); - } - } - return \PHPUnit\TextUI\XmlConfiguration\FileCollection::fromArray($files); + return sprintf('return argument #%d', $this->argumentIndex); } - private function groups(DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Groups +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function call_user_func_array; +use function get_class; +use function is_array; +use function is_object; +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnCallback implements \PHPUnit\Framework\MockObject\Stub\Stub +{ + private $callback; + public function __construct($callback) { - return $this->parseGroupConfiguration($xpath, 'groups'); + $this->callback = $callback; } - private function testdoxGroups(DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Groups + public function invoke(Invocation $invocation) { - return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); + return call_user_func_array($this->callback, $invocation->getParameters()); } - private function parseGroupConfiguration(DOMXPath $xpath, string $root) : \PHPUnit\TextUI\XmlConfiguration\Groups + public function toString() : string { - $include = []; - $exclude = []; - foreach ($xpath->query($root . '/include/group') as $group) { - $include[] = new \PHPUnit\TextUI\XmlConfiguration\Group((string) $group->textContent); - } - foreach ($xpath->query($root . '/exclude/group') as $group) { - $exclude[] = new \PHPUnit\TextUI\XmlConfiguration\Group((string) $group->textContent); + if (is_array($this->callback)) { + if (is_object($this->callback[0])) { + $class = get_class($this->callback[0]); + $type = '->'; + } else { + $class = $this->callback[0]; + $type = '::'; + } + return sprintf('return result of user defined callback %s%s%s() with the ' . 'passed arguments', $class, $type, $this->callback[1]); } - return new \PHPUnit\TextUI\XmlConfiguration\Groups(\PHPUnit\TextUI\XmlConfiguration\GroupCollection::fromArray($include), \PHPUnit\TextUI\XmlConfiguration\GroupCollection::fromArray($exclude)); + return 'return result of user defined callback ' . $this->callback . ' with the passed arguments'; } - private function listeners(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\SebastianBergmann\Exporter\Exporter; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnReference implements \PHPUnit\Framework\MockObject\Stub\Stub +{ + /** + * @var mixed + */ + private $reference; + public function __construct(&$reference) { - $listeners = []; - foreach ($xpath->query('listeners/listener') as $listener) { - assert($listener instanceof DOMElement); - $listeners[] = $this->getElementConfigurationParameters($filename, $listener); - } - return \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection::fromArray($listeners); + $this->reference =& $reference; } - private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default) : bool + public function invoke(Invocation $invocation) { - if (!$element->hasAttribute($attribute)) { - return $default; - } - return (bool) $this->getBoolean((string) $element->getAttribute($attribute), \false); + return $this->reference; } - private function getIntegerAttribute(DOMElement $element, string $attribute, int $default) : int + public function toString() : string { - if (!$element->hasAttribute($attribute)) { - return $default; - } - return $this->getInteger((string) $element->getAttribute($attribute), $default); + $exporter = new Exporter(); + return sprintf('return user-specified reference %s', $exporter->export($this->reference)); } - private function getStringAttribute(DOMElement $element, string $attribute) : ?string +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnSelf implements \PHPUnit\Framework\MockObject\Stub\Stub +{ + /** + * @throws RuntimeException + */ + public function invoke(Invocation $invocation) { - if (!$element->hasAttribute($attribute)) { - return null; - } - return (string) $element->getAttribute($attribute); + return $invocation->getObject(); } - private function getInteger(string $value, int $default) : int + public function toString() : string { - if (is_numeric($value)) { - return (int) $value; - } - return $default; + return 'return the current object'; } - private function php(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Php +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\SebastianBergmann\Exporter\Exporter; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnStub implements \PHPUnit\Framework\MockObject\Stub\Stub +{ + /** + * @var mixed + */ + private $value; + public function __construct($value) { - $includePaths = []; - foreach ($xpath->query('php/includePath') as $includePath) { - $path = (string) $includePath->textContent; - if ($path) { - $includePaths[] = new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, $path)); - } - } - $iniSettings = []; - foreach ($xpath->query('php/ini') as $ini) { - assert($ini instanceof DOMElement); - $iniSettings[] = new \PHPUnit\TextUI\XmlConfiguration\IniSetting((string) $ini->getAttribute('name'), (string) $ini->getAttribute('value')); - } - $constants = []; - foreach ($xpath->query('php/const') as $const) { - assert($const instanceof DOMElement); - $value = (string) $const->getAttribute('value'); - $constants[] = new \PHPUnit\TextUI\XmlConfiguration\Constant((string) $const->getAttribute('name'), $this->getBoolean($value, $value)); - } - $variables = ['var' => [], 'env' => [], 'post' => [], 'get' => [], 'cookie' => [], 'server' => [], 'files' => [], 'request' => []]; - foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { - foreach ($xpath->query('php/' . $array) as $var) { - assert($var instanceof DOMElement); - $name = (string) $var->getAttribute('name'); - $value = (string) $var->getAttribute('value'); - $force = \false; - $verbatim = \false; - if ($var->hasAttribute('force')) { - $force = (bool) $this->getBoolean($var->getAttribute('force'), \false); - } - if ($var->hasAttribute('verbatim')) { - $verbatim = $this->getBoolean($var->getAttribute('verbatim'), \false); - } - if (!$verbatim) { - $value = $this->getBoolean($value, $value); - } - $variables[$array][] = new \PHPUnit\TextUI\XmlConfiguration\Variable($name, $value, $force); - } - } - return new \PHPUnit\TextUI\XmlConfiguration\Php(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection::fromArray($includePaths), \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection::fromArray($iniSettings), \PHPUnit\TextUI\XmlConfiguration\ConstantCollection::fromArray($constants), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['var']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['env']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['post']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['get']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['cookie']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['server']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['files']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['request'])); + $this->value = $value; } - private function phpunit(string $filename, DOMDocument $document) : \PHPUnit\TextUI\XmlConfiguration\PHPUnit + public function invoke(Invocation $invocation) { - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = \false; - $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', \true); - if ($document->documentElement->hasAttribute('executionOrder')) { - foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = \false; - $resolveDependencies = \true; - break; - case 'depends': - $resolveDependencies = \true; - break; - case 'no-depends': - $resolveDependencies = \false; - break; - case 'defects': - $defectsFirst = \true; - break; - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - break; - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - break; - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - break; - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - break; - } - } - } - $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); - $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', \false); - $conflictBetweenPrinterClassAndTestdox = \false; - if ($testdox) { - if ($printerClass !== null) { - $conflictBetweenPrinterClassAndTestdox = \true; - } - $printerClass = CliTestDoxPrinter::class; - } - $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); - if ($cacheResultFile !== null) { - $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); - } - $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); - if ($bootstrap !== null) { - $bootstrap = $this->toAbsolutePath($filename, $bootstrap); - } - $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); - if ($extensionsDirectory !== null) { - $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); - } - $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); - if ($testSuiteLoaderFile !== null) { - $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); - } - $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); - if ($printerFile !== null) { - $printerFile = $this->toAbsolutePath($filename, $printerFile); - } - return new \PHPUnit\TextUI\XmlConfiguration\PHPUnit($this->getBooleanAttribute($document->documentElement, 'cacheResult', \true), $cacheResultFile, $this->getColumns($document), $this->getColors($document), $this->getBooleanAttribute($document->documentElement, 'stderr', \false), $this->getBooleanAttribute($document->documentElement, 'noInteraction', \false), $this->getBooleanAttribute($document->documentElement, 'verbose', \false), $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', \false), $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', \false), $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', \false), $bootstrap, $this->getBooleanAttribute($document->documentElement, 'processIsolation', \false), $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', \false), $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', \false), $this->getBooleanAttribute($document->documentElement, 'failOnRisky', \false), $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', \false), $this->getBooleanAttribute($document->documentElement, 'failOnWarning', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnError', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', \false), $extensionsDirectory, $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), $testSuiteLoaderFile, $printerClass, $printerFile, $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', \true), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', \false), $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', \false), $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), $executionOrder, $resolveDependencies, $defectsFirst, $this->getBooleanAttribute($document->documentElement, 'backupGlobals', \false), $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', \false), $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', \false), $conflictBetweenPrinterClassAndTestdox); + return $this->value; } - private function getColors(DOMDocument $document) : string + public function toString() : string { - $colors = DefaultResultPrinter::COLOR_DEFAULT; - if ($document->documentElement->hasAttribute('colors')) { - /* only allow boolean for compatibility with previous versions - 'always' only allowed from command line */ - if ($this->getBoolean($document->documentElement->getAttribute('colors'), \false)) { - $colors = DefaultResultPrinter::COLOR_AUTO; - } else { - $colors = DefaultResultPrinter::COLOR_NEVER; - } - } - return $colors; + $exporter = new Exporter(); + return sprintf('return user-specified value %s', $exporter->export($this->value)); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_pop; +use function count; +use function is_array; +use PHPUnit\Framework\MockObject\Invocation; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueMap implements \PHPUnit\Framework\MockObject\Stub\Stub +{ /** - * @return int|string + * @var array */ - private function getColumns(DOMDocument $document) - { - $columns = 80; - if ($document->documentElement->hasAttribute('columns')) { - $columns = (string) $document->documentElement->getAttribute('columns'); - if ($columns !== 'max') { - $columns = $this->getInteger($columns, 80); - } - } - return $columns; - } - private function testSuite(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection + private $valueMap; + public function __construct(array $valueMap) { - $testSuites = []; - foreach ($this->getTestSuiteElements($xpath) as $element) { - $exclude = []; - foreach ($element->getElementsByTagName('exclude') as $excludeNode) { - $excludeFile = (string) $excludeNode->textContent; - if ($excludeFile) { - $exclude[] = new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, $excludeFile)); - } - } - $directories = []; - foreach ($element->getElementsByTagName('directory') as $directoryNode) { - assert($directoryNode instanceof DOMElement); - $directory = (string) $directoryNode->textContent; - if (empty($directory)) { - continue; - } - $prefix = ''; - if ($directoryNode->hasAttribute('prefix')) { - $prefix = (string) $directoryNode->getAttribute('prefix'); - } - $suffix = 'Test.php'; - if ($directoryNode->hasAttribute('suffix')) { - $suffix = (string) $directoryNode->getAttribute('suffix'); - } - $phpVersion = \PHP_VERSION; - if ($directoryNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); - } - $phpVersionOperator = new VersionComparisonOperator('>='); - if ($directoryNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); - } - $directories[] = new \PHPUnit\TextUI\XmlConfiguration\TestDirectory($this->toAbsolutePath($filename, $directory), $prefix, $suffix, $phpVersion, $phpVersionOperator); - } - $files = []; - foreach ($element->getElementsByTagName('file') as $fileNode) { - assert($fileNode instanceof DOMElement); - $file = (string) $fileNode->textContent; - if (empty($file)) { - continue; - } - $phpVersion = \PHP_VERSION; - if ($fileNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $fileNode->getAttribute('phpVersion'); - } - $phpVersionOperator = new VersionComparisonOperator('>='); - if ($fileNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); - } - $files[] = new \PHPUnit\TextUI\XmlConfiguration\TestFile($this->toAbsolutePath($filename, $file), $phpVersion, $phpVersionOperator); - } - $testSuites[] = new TestSuiteConfiguration((string) $element->getAttribute('name'), \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection::fromArray($directories), \PHPUnit\TextUI\XmlConfiguration\TestFileCollection::fromArray($files), \PHPUnit\TextUI\XmlConfiguration\FileCollection::fromArray($exclude)); - } - return \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection::fromArray($testSuites); + $this->valueMap = $valueMap; } - /** - * @return DOMElement[] - */ - private function getTestSuiteElements(DOMXPath $xpath) : array + public function invoke(Invocation $invocation) { - /** @var DOMElement[] $elements */ - $elements = []; - $testSuiteNodes = $xpath->query('testsuites/testsuite'); - if ($testSuiteNodes->length === 0) { - $testSuiteNodes = $xpath->query('testsuite'); - } - if ($testSuiteNodes->length === 1) { - $element = $testSuiteNodes->item(0); - assert($element instanceof DOMElement); - $elements[] = $element; - } else { - foreach ($testSuiteNodes as $testSuiteNode) { - assert($testSuiteNode instanceof DOMElement); - $elements[] = $testSuiteNode; + $parameterCount = count($invocation->getParameters()); + foreach ($this->valueMap as $map) { + if (!is_array($map) || $parameterCount !== count($map) - 1) { + continue; + } + $return = array_pop($map); + if ($invocation->getParameters() === $map) { + return $return; } } - return $elements; } - private function element(DOMXPath $xpath, string $element) : ?DOMElement + public function toString() : string { - $nodes = $xpath->query($element); - if ($nodes->length === 1) { - $node = $nodes->item(0); - assert($node instanceof DOMElement); - return $node; - } - return null; + return 'return value from a map'; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\ExpectationFailedException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Verifiable +{ /** - * @var CodeCoverage + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException */ - private $codeCoverage; + public function verify() : void; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Reorderable +{ + public function sortId() : string; /** - * @var Groups + * @return list */ - private $groups; + public function provides() : array; /** - * @var Groups + * @return list */ - private $testdoxGroups; + public function requires() : array; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface SelfDescribing +{ /** - * @var ExtensionCollection + * Returns a string representation of the object. */ - private $listeners; + public function toString() : string; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedTest extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestCase extends \PHPUnit\Framework\TestCase +{ /** - * @var Logging + * @var bool */ - private $logging; + protected $backupGlobals = \false; /** - * @var Php + * @var bool */ - private $php; + protected $backupStaticAttributes = \false; /** - * @var PHPUnit + * @var bool */ - private $phpunit; + protected $runTestInSeparateProcess = \false; /** - * @var TestSuiteCollection + * @var string */ - private $testSuite; - public function __construct(string $filename, ValidationResult $validationResult, \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $extensions, CodeCoverage $codeCoverage, \PHPUnit\TextUI\XmlConfiguration\Groups $groups, \PHPUnit\TextUI\XmlConfiguration\Groups $testdoxGroups, \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $listeners, Logging $logging, \PHPUnit\TextUI\XmlConfiguration\Php $php, \PHPUnit\TextUI\XmlConfiguration\PHPUnit $phpunit, \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection $testSuite) - { - $this->filename = $filename; - $this->validationResult = $validationResult; - $this->extensions = $extensions; - $this->codeCoverage = $codeCoverage; - $this->groups = $groups; - $this->testdoxGroups = $testdoxGroups; - $this->listeners = $listeners; - $this->logging = $logging; - $this->php = $php; - $this->phpunit = $phpunit; - $this->testSuite = $testSuite; - } - public function filename() : string - { - return $this->filename; - } - public function hasValidationErrors() : bool - { - return $this->validationResult->hasValidationErrors(); - } - public function validationErrors() : string - { - return $this->validationResult->asString(); - } - public function extensions() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection + private $message; + public function __construct(string $className, string $methodName, string $message = '') { - return $this->extensions; + parent::__construct($className . '::' . $methodName); + $this->message = $message; } - public function codeCoverage() : CodeCoverage + public function getMessage() : string { - return $this->codeCoverage; + return $this->message; } - public function groups() : \PHPUnit\TextUI\XmlConfiguration\Groups + /** + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString() : string { - return $this->groups; + return $this->getName(); } - public function testdoxGroups() : \PHPUnit\TextUI\XmlConfiguration\Groups + /** + * @throws Exception + */ + protected function runTest() : void { - return $this->testdoxGroups; + $this->markTestSkipped($this->message); } - public function listeners() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Countable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Test extends Countable +{ + /** + * Runs a test and collects its result in a TestResult instance. + */ + public function run(\PHPUnit\Framework\TestResult $result = null) : \PHPUnit\Framework\TestResult; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function count; +use function get_class; +use function sprintf; +use function trim; +use PHPUnit\Util\Filter; +use PHPUnit\Util\InvalidDataSetException; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestBuilder +{ + public function build(ReflectionClass $theClass, string $methodName) : \PHPUnit\Framework\Test { - return $this->listeners; + $className = $theClass->getName(); + if (!$theClass->isInstantiable()) { + return new \PHPUnit\Framework\ErrorTestCase(sprintf('Cannot instantiate class "%s".', $className)); + } + $backupSettings = TestUtil::getBackupSettings($className, $methodName); + $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings($className, $methodName); + $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings($className, $methodName); + $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings($className, $methodName); + $constructor = $theClass->getConstructor(); + if ($constructor === null) { + throw new \PHPUnit\Framework\Exception('No valid test provided.'); + } + $parameters = $constructor->getParameters(); + // TestCase() or TestCase($name) + if (count($parameters) < 2) { + $test = $this->buildTestWithoutData($className); + } else { + try { + $data = TestUtil::getProvidedData($className, $methodName); + } catch (\PHPUnit\Framework\IncompleteTestError $e) { + $message = sprintf("Test for %s::%s marked incomplete by data provider\n%s", $className, $methodName, $this->throwableToString($e)); + $data = new \PHPUnit\Framework\IncompleteTestCase($className, $methodName, $message); + } catch (\PHPUnit\Framework\SkippedTestError $e) { + $message = sprintf("Test for %s::%s skipped by data provider\n%s", $className, $methodName, $this->throwableToString($e)); + $data = new \PHPUnit\Framework\SkippedTestCase($className, $methodName, $message); + } catch (Throwable $t) { + $message = sprintf("The data provider specified for %s::%s is invalid.\n%s", $className, $methodName, $this->throwableToString($t)); + $data = new \PHPUnit\Framework\ErrorTestCase($message); + } + // Test method with @dataProvider. + if (isset($data)) { + $test = $this->buildDataProviderTestSuite($methodName, $className, $data, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); + } else { + $test = $this->buildTestWithoutData($className); + } + } + if ($test instanceof \PHPUnit\Framework\TestCase) { + $test->setName($methodName); + $this->configureTestCase($test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); + } + return $test; } - public function logging() : Logging + /** @psalm-param class-string $className */ + private function buildTestWithoutData(string $className) { - return $this->logging; + return new $className(); } - public function php() : \PHPUnit\TextUI\XmlConfiguration\Php + /** @psalm-param class-string $className */ + private function buildDataProviderTestSuite(string $methodName, string $className, $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings) : \PHPUnit\Framework\DataProviderTestSuite { - return $this->php; + $dataProviderTestSuite = new \PHPUnit\Framework\DataProviderTestSuite($className . '::' . $methodName); + $groups = TestUtil::getGroups($className, $methodName); + if ($data instanceof \PHPUnit\Framework\ErrorTestCase || $data instanceof \PHPUnit\Framework\SkippedTestCase || $data instanceof \PHPUnit\Framework\IncompleteTestCase) { + $dataProviderTestSuite->addTest($data, $groups); + } else { + foreach ($data as $_dataName => $_data) { + $_test = new $className($methodName, $_data, $_dataName); + assert($_test instanceof \PHPUnit\Framework\TestCase); + $this->configureTestCase($_test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); + $dataProviderTestSuite->addTest($_test, $groups); + } + } + return $dataProviderTestSuite; } - public function phpunit() : \PHPUnit\TextUI\XmlConfiguration\PHPUnit + private function configureTestCase(\PHPUnit\Framework\TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings) : void { - return $this->phpunit; + if ($runTestInSeparateProcess) { + $test->setRunTestInSeparateProcess(\true); + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } + } + if ($runClassInSeparateProcess) { + $test->setRunClassInSeparateProcess(\true); + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } + } + if ($backupSettings['backupGlobals'] !== null) { + $test->setBackupGlobals($backupSettings['backupGlobals']); + } + if ($backupSettings['backupStaticAttributes'] !== null) { + $test->setBackupStaticAttributes($backupSettings['backupStaticAttributes']); + } } - public function testSuite() : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection + private function throwableToString(Throwable $t) : string { - return $this->testSuite; + $message = $t->getMessage(); + if (empty(trim($message))) { + $message = ''; + } + if ($t instanceof InvalidDataSetException) { + return sprintf("%s\n%s", $message, Filter::getFilteredStacktrace($t)); + } + return sprintf("%s: %s\n%s", get_class($t), $message, Filter::getFilteredStacktrace($t)); } } > */ - private $colors; + protected $backupStaticAttributesExcludeList = []; /** - * @var bool + * @var array> + * + * @deprecated Use $backupStaticAttributesExcludeList instead */ - private $stderr; + protected $backupStaticAttributesBlacklist = []; /** * @var bool */ - private $noInteraction; + protected $runTestInSeparateProcess; /** * @var bool */ - private $verbose; + protected $preserveGlobalState = \true; /** - * @var bool + * @var list */ - private $reverseDefectList; + protected $providedTests = []; /** * @var bool */ - private $convertDeprecationsToExceptions; + private $runClassInSeparateProcess; /** * @var bool */ - private $convertErrorsToExceptions; + private $inIsolation = \false; /** - * @var bool + * @var array */ - private $convertNoticesToExceptions; + private $data; /** - * @var bool + * @var int|string */ - private $convertWarningsToExceptions; + private $dataName; /** - * @var bool + * @var null|string */ - private $forceCoversAnnotation; + private $expectedException; /** - * @var ?string + * @var null|string */ - private $bootstrap; + private $expectedExceptionMessage; /** - * @var bool + * @var null|string */ - private $processIsolation; + private $expectedExceptionMessageRegExp; /** - * @var bool + * @var null|int|string */ - private $failOnEmptyTestSuite; + private $expectedExceptionCode; /** - * @var bool + * @var string */ - private $failOnIncomplete; + private $name = ''; /** - * @var bool + * @var list */ - private $failOnRisky; + private $dependencies = []; /** - * @var bool + * @var array */ - private $failOnSkipped; + private $dependencyInput = []; /** - * @var bool + * @var array */ - private $failOnWarning; + private $iniSettings = []; /** - * @var bool + * @var array */ - private $stopOnDefect; + private $locale = []; /** - * @var bool + * @var MockObject[] */ - private $stopOnError; + private $mockObjects = []; /** - * @var bool + * @var MockGenerator */ - private $stopOnFailure; + private $mockObjectGenerator; /** - * @var bool + * @var int */ - private $stopOnWarning; + private $status = BaseTestRunner::STATUS_UNKNOWN; /** - * @var bool + * @var string */ - private $stopOnIncomplete; + private $statusMessage = ''; /** - * @var bool + * @var int */ - private $stopOnRisky; + private $numAssertions = 0; /** - * @var bool + * @var TestResult */ - private $stopOnSkipped; + private $result; /** - * @var ?string + * @var mixed */ - private $extensionsDirectory; + private $testResult; /** - * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @var string */ - private $testSuiteLoaderClass; + private $output = ''; /** * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - private $testSuiteLoaderFile; + private $outputExpectedRegex; /** * @var ?string */ - private $printerClass; + private $outputExpectedString; /** - * @var ?string + * @var mixed */ - private $printerFile; + private $outputCallback = \false; /** * @var bool */ - private $beStrictAboutChangesToGlobalState; + private $outputBufferingActive = \false; /** - * @var bool + * @var int */ - private $beStrictAboutOutputDuringTests; + private $outputBufferingLevel; /** * @var bool */ - private $beStrictAboutResourceUsageDuringSmallTests; + private $outputRetrievedForAssertion = \false; /** - * @var bool + * @var ?Snapshot */ - private $beStrictAboutTestsThatDoNotTestAnything; + private $snapshot; /** - * @var bool + * @var \Prophecy\Prophet */ - private $beStrictAboutTodoAnnotatedTests; + private $prophet; /** * @var bool */ - private $beStrictAboutCoversAnnotation; + private $beStrictAboutChangesToGlobalState = \false; /** * @var bool */ - private $enforceTimeLimit; + private $registerMockObjectsFromTestArgumentsRecursively = \false; /** - * @var int + * @var string[] */ - private $defaultTimeLimit; + private $warnings = []; /** - * @var int + * @var string[] */ - private $timeoutForSmallTests; + private $groups = []; /** - * @var int + * @var bool */ - private $timeoutForMediumTests; + private $doesNotPerformAssertions = \false; /** - * @var int + * @var Comparator[] */ - private $timeoutForLargeTests; + private $customComparators = []; /** - * @var ?string + * @var string[] */ - private $defaultTestSuite; + private $doubledTypes = []; /** - * @var int + * Returns a matcher that matches when the method is executed + * zero or more times. */ - private $executionOrder; + public static function any() : AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher(); + } /** - * @var bool + * Returns a matcher that matches when the method is never executed. */ - private $resolveDependencies; + public static function never() : InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } /** - * @var bool + * Returns a matcher that matches when the method is executed + * at least N times. */ - private $defectsFirst; + public static function atLeast(int $requiredInvocations) : InvokedAtLeastCountMatcher + { + return new InvokedAtLeastCountMatcher($requiredInvocations); + } /** - * @var bool + * Returns a matcher that matches when the method is executed at least once. */ - private $backupGlobals; + public static function atLeastOnce() : InvokedAtLeastOnceMatcher + { + return new InvokedAtLeastOnceMatcher(); + } /** - * @var bool + * Returns a matcher that matches when the method is executed exactly once. */ - private $backupStaticAttributes; + public static function once() : InvokedCountMatcher + { + return new InvokedCountMatcher(1); + } /** - * @var bool + * Returns a matcher that matches when the method is executed + * exactly $count times. */ - private $registerMockObjectsFromTestArgumentsRecursively; + public static function exactly(int $count) : InvokedCountMatcher + { + return new InvokedCountMatcher($count); + } /** - * @var bool + * Returns a matcher that matches when the method is executed + * at most N times. */ - private $conflictBetweenPrinterClassAndTestdox; - public function __construct(bool $cacheResult, ?string $cacheResultFile, $columns, string $colors, bool $stderr, bool $noInteraction, bool $verbose, bool $reverseDefectList, bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions, bool $forceCoversAnnotation, ?string $bootstrap, bool $processIsolation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnError, bool $stopOnFailure, bool $stopOnWarning, bool $stopOnIncomplete, bool $stopOnRisky, bool $stopOnSkipped, ?string $extensionsDirectory, ?string $testSuiteLoaderClass, ?string $testSuiteLoaderFile, ?string $printerClass, ?string $printerFile, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutResourceUsageDuringSmallTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutTodoAnnotatedTests, bool $beStrictAboutCoversAnnotation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticAttributes, bool $registerMockObjectsFromTestArgumentsRecursively, bool $conflictBetweenPrinterClassAndTestdox) + public static function atMost(int $allowedInvocations) : InvokedAtMostCountMatcher { - $this->cacheResult = $cacheResult; - $this->cacheResultFile = $cacheResultFile; - $this->columns = $columns; - $this->colors = $colors; - $this->stderr = $stderr; - $this->noInteraction = $noInteraction; - $this->verbose = $verbose; - $this->reverseDefectList = $reverseDefectList; - $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - $this->forceCoversAnnotation = $forceCoversAnnotation; - $this->bootstrap = $bootstrap; - $this->processIsolation = $processIsolation; - $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; - $this->failOnIncomplete = $failOnIncomplete; - $this->failOnRisky = $failOnRisky; - $this->failOnSkipped = $failOnSkipped; - $this->failOnWarning = $failOnWarning; - $this->stopOnDefect = $stopOnDefect; - $this->stopOnError = $stopOnError; - $this->stopOnFailure = $stopOnFailure; - $this->stopOnWarning = $stopOnWarning; - $this->stopOnIncomplete = $stopOnIncomplete; - $this->stopOnRisky = $stopOnRisky; - $this->stopOnSkipped = $stopOnSkipped; - $this->extensionsDirectory = $extensionsDirectory; - $this->testSuiteLoaderClass = $testSuiteLoaderClass; - $this->testSuiteLoaderFile = $testSuiteLoaderFile; - $this->printerClass = $printerClass; - $this->printerFile = $printerFile; - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; - $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; - $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; - $this->beStrictAboutTodoAnnotatedTests = $beStrictAboutTodoAnnotatedTests; - $this->beStrictAboutCoversAnnotation = $beStrictAboutCoversAnnotation; - $this->enforceTimeLimit = $enforceTimeLimit; - $this->defaultTimeLimit = $defaultTimeLimit; - $this->timeoutForSmallTests = $timeoutForSmallTests; - $this->timeoutForMediumTests = $timeoutForMediumTests; - $this->timeoutForLargeTests = $timeoutForLargeTests; - $this->defaultTestSuite = $defaultTestSuite; - $this->executionOrder = $executionOrder; - $this->resolveDependencies = $resolveDependencies; - $this->defectsFirst = $defectsFirst; - $this->backupGlobals = $backupGlobals; - $this->backupStaticAttributes = $backupStaticAttributes; - $this->registerMockObjectsFromTestArgumentsRecursively = $registerMockObjectsFromTestArgumentsRecursively; - $this->conflictBetweenPrinterClassAndTestdox = $conflictBetweenPrinterClassAndTestdox; + return new InvokedAtMostCountMatcher($allowedInvocations); } - public function cacheResult() : bool + /** + * Returns a matcher that matches when the method is executed + * at the given index. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 + * + * @codeCoverageIgnore + */ + public static function at(int $index) : InvokedAtIndexMatcher { - return $this->cacheResult; + $stack = debug_backtrace(); + while (!empty($stack)) { + $frame = array_pop($stack); + if (isset($frame['object']) && $frame['object'] instanceof self) { + $frame['object']->addWarning('The at() matcher has been deprecated. It will be removed in PHPUnit 10. Please refactor your test to not rely on the order in which methods are invoked.'); + break; + } + } + return new InvokedAtIndexMatcher($index); + } + public static function returnValue($value) : ReturnStub + { + return new ReturnStub($value); + } + public static function returnValueMap(array $valueMap) : ReturnValueMapStub + { + return new ReturnValueMapStub($valueMap); + } + public static function returnArgument(int $argumentIndex) : ReturnArgumentStub + { + return new ReturnArgumentStub($argumentIndex); + } + public static function returnCallback($callback) : ReturnCallbackStub + { + return new ReturnCallbackStub($callback); } /** - * @psalm-assert-if-true !null $this->cacheResultFile + * Returns the current object. + * + * This method is useful when mocking a fluent interface. */ - public function hasCacheResultFile() : bool + public static function returnSelf() : ReturnSelfStub { - return $this->cacheResultFile !== null; + return new ReturnSelfStub(); + } + public static function throwException(Throwable $exception) : ExceptionStub + { + return new ExceptionStub($exception); + } + public static function onConsecutiveCalls(...$args) : ConsecutiveCallsStub + { + return new ConsecutiveCallsStub($args); } /** - * @throws Exception + * @param int|string $dataName + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function cacheResultFile() : string + public function __construct(?string $name = null, array $data = [], $dataName = '') { - if (!$this->hasCacheResultFile()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Cache result file is not configured'); + if ($name !== null) { + $this->setName($name); } - return (string) $this->cacheResultFile; + $this->data = $data; + $this->dataName = $dataName; } - public function columns() + /** + * This method is called before the first test of this test class is run. + */ + public static function setUpBeforeClass() : void { - return $this->columns; } - public function colors() : string + /** + * This method is called after the last test of this test class is run. + */ + public static function tearDownAfterClass() : void { - return $this->colors; } - public function stderr() : bool + /** + * This method is called before each test. + */ + protected function setUp() : void { - return $this->stderr; } - public function noInteraction() : bool + /** + * Performs assertions shared by all tests of a test case. + * + * This method is called between setUp() and test. + */ + protected function assertPreConditions() : void { - return $this->noInteraction; } - public function verbose() : bool + /** + * Performs assertions shared by all tests of a test case. + * + * This method is called between test and tearDown(). + */ + protected function assertPostConditions() : void { - return $this->verbose; } - public function reverseDefectList() : bool + /** + * This method is called after each test. + */ + protected function tearDown() : void { - return $this->reverseDefectList; } - public function convertDeprecationsToExceptions() : bool + /** + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + */ + public function toString() : string { - return $this->convertDeprecationsToExceptions; + try { + $class = new ReflectionClass($this); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $buffer = sprintf('%s::%s', $class->name, $this->getName(\false)); + return $buffer . $this->getDataSetAsString(); } - public function convertErrorsToExceptions() : bool + public function count() : int { - return $this->convertErrorsToExceptions; + return 1; } - public function convertNoticesToExceptions() : bool + public function getActualOutputForAssertion() : string { - return $this->convertNoticesToExceptions; + $this->outputRetrievedForAssertion = \true; + return $this->getActualOutput(); } - public function convertWarningsToExceptions() : bool + public function expectOutputRegex(string $expectedRegex) : void { - return $this->convertWarningsToExceptions; + $this->outputExpectedRegex = $expectedRegex; } - public function forceCoversAnnotation() : bool + public function expectOutputString(string $expectedString) : void { - return $this->forceCoversAnnotation; + $this->outputExpectedString = $expectedString; } /** - * @psalm-assert-if-true !null $this->bootstrap + * @psalm-param class-string<\Throwable> $exception */ - public function hasBootstrap() : bool + public function expectException(string $exception) : void { - return $this->bootstrap !== null; + // @codeCoverageIgnoreStart + switch ($exception) { + case Deprecated::class: + $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Deprecated is deprecated and will be removed in PHPUnit 10. Use expectDeprecation() instead.'); + break; + case Error::class: + $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Error is deprecated and will be removed in PHPUnit 10. Use expectError() instead.'); + break; + case Notice::class: + $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Notice is deprecated and will be removed in PHPUnit 10. Use expectNotice() instead.'); + break; + case WarningError::class: + $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Warning is deprecated and will be removed in PHPUnit 10. Use expectWarning() instead.'); + break; + } + // @codeCoverageIgnoreEnd + $this->expectedException = $exception; } /** - * @throws Exception + * @param int|string $code */ - public function bootstrap() : string + public function expectExceptionCode($code) : void { - if (!$this->hasBootstrap()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Bootstrap script is not configured'); - } - return (string) $this->bootstrap; + $this->expectedExceptionCode = $code; } - public function processIsolation() : bool + public function expectExceptionMessage(string $message) : void { - return $this->processIsolation; + $this->expectedExceptionMessage = $message; } - public function failOnEmptyTestSuite() : bool + public function expectExceptionMessageMatches(string $regularExpression) : void { - return $this->failOnEmptyTestSuite; + $this->expectedExceptionMessageRegExp = $regularExpression; } - public function failOnIncomplete() : bool + /** + * Sets up an expectation for an exception to be raised by the code under test. + * Information for expected exception class, expected exception message, and + * expected exception code are retrieved from a given Exception object. + */ + public function expectExceptionObject(\Exception $exception) : void { - return $this->failOnIncomplete; + $this->expectException(get_class($exception)); + $this->expectExceptionMessage($exception->getMessage()); + $this->expectExceptionCode($exception->getCode()); } - public function failOnRisky() : bool + public function expectNotToPerformAssertions() : void { - return $this->failOnRisky; + $this->doesNotPerformAssertions = \true; } - public function failOnSkipped() : bool + public function expectDeprecation() : void { - return $this->failOnSkipped; + $this->expectedException = Deprecated::class; } - public function failOnWarning() : bool + public function expectDeprecationMessage(string $message) : void { - return $this->failOnWarning; + $this->expectExceptionMessage($message); } - public function stopOnDefect() : bool + public function expectDeprecationMessageMatches(string $regularExpression) : void + { + $this->expectExceptionMessageMatches($regularExpression); + } + public function expectNotice() : void + { + $this->expectedException = Notice::class; + } + public function expectNoticeMessage(string $message) : void + { + $this->expectExceptionMessage($message); + } + public function expectNoticeMessageMatches(string $regularExpression) : void + { + $this->expectExceptionMessageMatches($regularExpression); + } + public function expectWarning() : void + { + $this->expectedException = WarningError::class; + } + public function expectWarningMessage(string $message) : void + { + $this->expectExceptionMessage($message); + } + public function expectWarningMessageMatches(string $regularExpression) : void + { + $this->expectExceptionMessageMatches($regularExpression); + } + public function expectError() : void + { + $this->expectedException = Error::class; + } + public function expectErrorMessage(string $message) : void + { + $this->expectExceptionMessage($message); + } + public function expectErrorMessageMatches(string $regularExpression) : void + { + $this->expectExceptionMessageMatches($regularExpression); + } + public function getStatus() : int { - return $this->stopOnDefect; + return $this->status; } - public function stopOnError() : bool + public function markAsRisky() : void { - return $this->stopOnError; + $this->status = BaseTestRunner::STATUS_RISKY; } - public function stopOnFailure() : bool + public function getStatusMessage() : string { - return $this->stopOnFailure; + return $this->statusMessage; } - public function stopOnWarning() : bool + public function hasFailed() : bool { - return $this->stopOnWarning; + $status = $this->getStatus(); + return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; } - public function stopOnIncomplete() : bool + /** + * Runs the test case and collects the results in a TestResult object. + * If no TestResult object is passed a new one will be created. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * @throws UtilException + */ + public function run(\PHPUnit\Framework\TestResult $result = null) : \PHPUnit\Framework\TestResult { - return $this->stopOnIncomplete; + if ($result === null) { + $result = $this->createResult(); + } + if (!$this instanceof \PHPUnit\Framework\ErrorTestCase && !$this instanceof \PHPUnit\Framework\WarningTestCase) { + $this->setTestResultObject($result); + } + if (!$this instanceof \PHPUnit\Framework\ErrorTestCase && !$this instanceof \PHPUnit\Framework\WarningTestCase && !$this instanceof \PHPUnit\Framework\SkippedTestCase && !$this->handleDependencies()) { + return $result; + } + if ($this->runInSeparateProcess()) { + $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; + try { + $class = new ReflectionClass($this); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($runEntireClass) { + $template = new Template(__DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl'); + } else { + $template = new Template(__DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl'); + } + if ($this->preserveGlobalState) { + $constants = GlobalState::getConstantsAsString(); + $globals = GlobalState::getGlobalsAsString(); + $includedFiles = GlobalState::getIncludedFilesAsString(); + $iniSettings = GlobalState::getIniSettingsAsString(); + } else { + $constants = ''; + if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], \true) . ";\n"; + } else { + $globals = ''; + } + $includedFiles = ''; + $iniSettings = ''; + } + $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; + $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; + $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; + $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; + $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; + $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; + if (defined('PHPUnit\\PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, \true); + } else { + $composerAutoload = '\'\''; + } + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(\__PHPUNIT_PHAR__, \true); + } else { + $phar = '\'\''; + } + $codeCoverage = $result->getCodeCoverage(); + $codeCoverageFilter = null; + $cachesStaticAnalysis = 'false'; + $codeCoverageCacheDirectory = null; + $driverMethod = 'forLineCoverage'; + if ($codeCoverage) { + $codeCoverageFilter = $codeCoverage->filter(); + if ($codeCoverage->collectsBranchAndPathCoverage()) { + $driverMethod = 'forLineAndPathCoverage'; + } + if ($codeCoverage->cachesStaticAnalysis()) { + $cachesStaticAnalysis = 'true'; + $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); + } + } + $data = var_export(serialize($this->data), \true); + $dataName = var_export($this->dataName, \true); + $dependencyInput = var_export(serialize($this->dependencyInput), \true); + $includePath = var_export(get_include_path(), \true); + $codeCoverageFilter = var_export(serialize($codeCoverageFilter), \true); + $codeCoverageCacheDirectory = var_export(serialize($codeCoverageCacheDirectory), \true); + // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC + // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences + $data = "'." . $data . ".'"; + $dataName = "'.(" . $dataName . ").'"; + $dependencyInput = "'." . $dependencyInput . ".'"; + $includePath = "'." . $includePath . ".'"; + $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; + $codeCoverageCacheDirectory = "'." . $codeCoverageCacheDirectory . ".'"; + $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; + $var = ['composerAutoload' => $composerAutoload, 'phar' => $phar, 'filename' => $class->getFileName(), 'className' => $class->getName(), 'collectCodeCoverageInformation' => $coverage, 'cachesStaticAnalysis' => $cachesStaticAnalysis, 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, 'driverMethod' => $driverMethod, 'data' => $data, 'dataName' => $dataName, 'dependencyInput' => $dependencyInput, 'constants' => $constants, 'globals' => $globals, 'include_path' => $includePath, 'included_files' => $includedFiles, 'iniSettings' => $iniSettings, 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, 'enforcesTimeLimit' => $enforcesTimeLimit, 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, 'codeCoverageFilter' => $codeCoverageFilter, 'configurationFilePath' => $configurationFilePath, 'name' => $this->getName(\false)]; + if (!$runEntireClass) { + $var['methodName'] = $this->name; + } + $template->setVar($var); + $php = AbstractPhpProcess::factory(); + $php->runTestJob($template->render(), $this, $result); + } else { + $result->run($this); + } + $this->result = null; + return $result; } - public function stopOnRisky() : bool + /** + * Returns a builder object to create mock objects using a fluent interface. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $className + * + * @psalm-return MockBuilder + */ + public function getMockBuilder(string $className) : MockBuilder { - return $this->stopOnRisky; + $this->recordDoubledType($className); + return new MockBuilder($this, $className); } - public function stopOnSkipped() : bool + public function registerComparator(Comparator $comparator) : void { - return $this->stopOnSkipped; + ComparatorFactory::getInstance()->register($comparator); + $this->customComparators[] = $comparator; } /** - * @psalm-assert-if-true !null $this->extensionsDirectory + * @return string[] + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasExtensionsDirectory() : bool + public function doubledTypes() : array { - return $this->extensionsDirectory !== null; + return array_unique($this->doubledTypes); } /** - * @throws Exception + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function extensionsDirectory() : string + public function getGroups() : array { - if (!$this->hasExtensionsDirectory()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Extensions directory is not configured'); - } - return (string) $this->extensionsDirectory; + return $this->groups; } /** - * @psalm-assert-if-true !null $this->testSuiteLoaderClass - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasTestSuiteLoaderClass() : bool + public function setGroups(array $groups) : void { - return $this->testSuiteLoaderClass !== null; + $this->groups = $groups; } /** - * @throws Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function testSuiteLoaderClass() : string + public function getName(bool $withDataSet = \true) : string { - if (!$this->hasTestSuiteLoaderClass()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('TestSuiteLoader class is not configured'); + if ($withDataSet) { + return $this->name . $this->getDataSetAsString(\false); } - return (string) $this->testSuiteLoaderClass; + return $this->name; } /** - * @psalm-assert-if-true !null $this->testSuiteLoaderFile + * Returns the size of the test. * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasTestSuiteLoaderFile() : bool + public function getSize() : int { - return $this->testSuiteLoaderFile !== null; + return TestUtil::getSize(static::class, $this->getName(\false)); } /** - * @throws Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function testSuiteLoaderFile() : string + public function hasSize() : bool { - if (!$this->hasTestSuiteLoaderFile()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('TestSuiteLoader sourcecode file is not configured'); - } - return (string) $this->testSuiteLoaderFile; + return $this->getSize() !== TestUtil::UNKNOWN; } /** - * @psalm-assert-if-true !null $this->printerClass + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasPrinterClass() : bool + public function isSmall() : bool { - return $this->printerClass !== null; + return $this->getSize() === TestUtil::SMALL; } /** - * @throws Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function printerClass() : string + public function isMedium() : bool { - if (!$this->hasPrinterClass()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('ResultPrinter class is not configured'); - } - return (string) $this->printerClass; + return $this->getSize() === TestUtil::MEDIUM; } /** - * @psalm-assert-if-true !null $this->printerFile + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasPrinterFile() : bool + public function isLarge() : bool { - return $this->printerFile !== null; + return $this->getSize() === TestUtil::LARGE; } /** - * @throws Exception + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function printerFile() : string + public function getActualOutput() : string { - if (!$this->hasPrinterFile()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('ResultPrinter sourcecode file is not configured'); + if (!$this->outputBufferingActive) { + return $this->output; } - return (string) $this->printerFile; - } - public function beStrictAboutChangesToGlobalState() : bool - { - return $this->beStrictAboutChangesToGlobalState; - } - public function beStrictAboutOutputDuringTests() : bool - { - return $this->beStrictAboutOutputDuringTests; - } - public function beStrictAboutResourceUsageDuringSmallTests() : bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - public function beStrictAboutTestsThatDoNotTestAnything() : bool - { - return $this->beStrictAboutTestsThatDoNotTestAnything; + return (string) ob_get_contents(); } - public function beStrictAboutTodoAnnotatedTests() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function hasOutput() : bool { - return $this->beStrictAboutTodoAnnotatedTests; + if ($this->output === '') { + return \false; + } + if ($this->hasExpectationOnOutput()) { + return \false; + } + return \true; } - public function beStrictAboutCoversAnnotation() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function doesNotPerformAssertions() : bool { - return $this->beStrictAboutCoversAnnotation; + return $this->doesNotPerformAssertions; } - public function enforceTimeLimit() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function hasExpectationOnOutput() : bool { - return $this->enforceTimeLimit; + return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; } - public function defaultTimeLimit() : int + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedException() : ?string { - return $this->defaultTimeLimit; + return $this->expectedException; } - public function timeoutForSmallTests() : int + /** + * @return null|int|string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionCode() { - return $this->timeoutForSmallTests; + return $this->expectedExceptionCode; } - public function timeoutForMediumTests() : int + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionMessage() : ?string { - return $this->timeoutForMediumTests; + return $this->expectedExceptionMessage; } - public function timeoutForLargeTests() : int + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionMessageRegExp() : ?string { - return $this->timeoutForLargeTests; + return $this->expectedExceptionMessageRegExp; } /** - * @psalm-assert-if-true !null $this->defaultTestSuite + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasDefaultTestSuite() : bool + public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag) : void { - return $this->defaultTestSuite !== null; + $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } /** - * @throws Exception + * @throws Throwable + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function defaultTestSuite() : string + public function runBare() : void { - if (!$this->hasDefaultTestSuite()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Default test suite is not configured'); + $this->numAssertions = 0; + $this->snapshotGlobalState(); + $this->startOutputBuffering(); + clearstatcache(); + $currentWorkingDirectory = getcwd(); + $hookMethods = TestUtil::getHookMethods(static::class); + $hasMetRequirements = \false; + try { + $this->checkRequirements(); + $hasMetRequirements = \true; + if ($this->inIsolation) { + foreach ($hookMethods['beforeClass'] as $method) { + $this->{$method}(); + } + } + $this->setDoesNotPerformAssertionsFromAnnotation(); + foreach ($hookMethods['before'] as $method) { + $this->{$method}(); + } + foreach ($hookMethods['preCondition'] as $method) { + $this->{$method}(); + } + $this->testResult = $this->runTest(); + $this->verifyMockObjects(); + foreach ($hookMethods['postCondition'] as $method) { + $this->{$method}(); + } + if (!empty($this->warnings)) { + throw new \PHPUnit\Framework\Warning(implode("\n", array_unique($this->warnings))); + } + $this->status = BaseTestRunner::STATUS_PASSED; + } catch (\PHPUnit\Framework\IncompleteTest $e) { + $this->status = BaseTestRunner::STATUS_INCOMPLETE; + $this->statusMessage = $e->getMessage(); + } catch (\PHPUnit\Framework\SkippedTest $e) { + $this->status = BaseTestRunner::STATUS_SKIPPED; + $this->statusMessage = $e->getMessage(); + } catch (\PHPUnit\Framework\Warning $e) { + $this->status = BaseTestRunner::STATUS_WARNING; + $this->statusMessage = $e->getMessage(); + } catch (\PHPUnit\Framework\AssertionFailedError $e) { + $this->status = BaseTestRunner::STATUS_FAILURE; + $this->statusMessage = $e->getMessage(); + } catch (PredictionException $e) { + $this->status = BaseTestRunner::STATUS_FAILURE; + $this->statusMessage = $e->getMessage(); + } catch (Throwable $_e) { + $e = $_e; + $this->status = BaseTestRunner::STATUS_ERROR; + $this->statusMessage = $_e->getMessage(); + } + $this->mockObjects = []; + $this->prophet = null; + // Tear down the fixture. An exception raised in tearDown() will be + // caught and passed on when no exception was raised before. + try { + if ($hasMetRequirements) { + foreach ($hookMethods['after'] as $method) { + $this->{$method}(); + } + if ($this->inIsolation) { + foreach ($hookMethods['afterClass'] as $method) { + $this->{$method}(); + } + } + } + } catch (Throwable $_e) { + $e = $e ?? $_e; + } + try { + $this->stopOutputBuffering(); + } catch (\PHPUnit\Framework\RiskyTestError $_e) { + $e = $e ?? $_e; + } + if (isset($_e)) { + $this->status = BaseTestRunner::STATUS_ERROR; + $this->statusMessage = $_e->getMessage(); + } + clearstatcache(); + if ($currentWorkingDirectory !== getcwd()) { + chdir($currentWorkingDirectory); + } + $this->restoreGlobalState(); + $this->unregisterCustomComparators(); + $this->cleanupIniSettings(); + $this->cleanupLocaleSettings(); + libxml_clear_errors(); + // Perform assertion on output. + if (!isset($e)) { + try { + if ($this->outputExpectedRegex !== null) { + $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); + } elseif ($this->outputExpectedString !== null) { + $this->assertEquals($this->outputExpectedString, $this->output); + } + } catch (Throwable $_e) { + $e = $_e; + } + } + // Workaround for missing "finally". + if (isset($e)) { + if ($e instanceof PredictionException) { + $e = new \PHPUnit\Framework\AssertionFailedError($e->getMessage()); + } + $this->onNotSuccessfulTest($e); } - return (string) $this->defaultTestSuite; - } - public function executionOrder() : int - { - return $this->executionOrder; - } - public function resolveDependencies() : bool - { - return $this->resolveDependencies; - } - public function defectsFirst() : bool - { - return $this->defectsFirst; } - public function backupGlobals() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setName(string $name) : void { - return $this->backupGlobals; + $this->name = $name; + if (is_callable($this->sortId(), \true)) { + $this->providedTests = [new \PHPUnit\Framework\ExecutionOrderDependency($this->sortId())]; + } } - public function backupStaticAttributes() : bool + /** + * @param list $dependencies + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setDependencies(array $dependencies) : void { - return $this->backupStaticAttributes; + $this->dependencies = $dependencies; } - public function registerMockObjectsFromTestArgumentsRecursively() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setDependencyInput(array $dependencyInput) : void { - return $this->registerMockObjectsFromTestArgumentsRecursively; + $this->dependencyInput = $dependencyInput; } - public function conflictBetweenPrinterClassAndTestdox() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState) : void { - return $this->conflictBetweenPrinterClassAndTestdox; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Extension -{ /** - * @var string - * @psalm-var class-string + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $className; + public function setBackupGlobals(?bool $backupGlobals) : void + { + if ($this->backupGlobals === null && $backupGlobals !== null) { + $this->backupGlobals = $backupGlobals; + } + } /** - * @var string + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $sourceFile; + public function setBackupStaticAttributes(?bool $backupStaticAttributes) : void + { + if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { + $this->backupStaticAttributes = $backupStaticAttributes; + } + } /** - * @var array + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $arguments; + public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess) : void + { + if ($this->runTestInSeparateProcess === null) { + $this->runTestInSeparateProcess = $runTestInSeparateProcess; + } + } /** - * @psalm-param class-string $className + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function __construct(string $className, string $sourceFile, array $arguments) + public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess) : void { - $this->className = $className; - $this->sourceFile = $sourceFile; - $this->arguments = $arguments; + if ($this->runClassInSeparateProcess === null) { + $this->runClassInSeparateProcess = $runClassInSeparateProcess; + } } /** - * @psalm-return class-string + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function className() : string + public function setPreserveGlobalState(bool $preserveGlobalState) : void { - return $this->className; + $this->preserveGlobalState = $preserveGlobalState; } - public function hasSourceFile() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setInIsolation(bool $inIsolation) : void { - return $this->sourceFile !== ''; + $this->inIsolation = $inIsolation; } - public function sourceFile() : string + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isInIsolation() : bool { - return $this->sourceFile; + return $this->inIsolation; } - public function hasArguments() : bool + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getResult() { - return !empty($this->arguments); + return $this->testResult; } - public function arguments() : array + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setResult($result) : void { - return $this->arguments; + $this->testResult = $result; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use IteratorAggregate; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class ExtensionCollection implements IteratorAggregate -{ /** - * @var Extension[] + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $extensions; + public function setOutputCallback(callable $callback) : void + { + $this->outputCallback = $callback; + } /** - * @param Extension[] $extensions + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function fromArray(array $extensions) : self + public function getTestResultObject() : ?\PHPUnit\Framework\TestResult { - return new self(...$extensions); + return $this->result; } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\Extension ...$extensions) + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setTestResultObject(\PHPUnit\Framework\TestResult $result) : void { - $this->extensions = $extensions; + $this->result = $result; } /** - * @return Extension[] + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function asArray() : array + public function registerMockObject(MockObject $mockObject) : void { - return $this->extensions; + $this->mockObjects[] = $mockObject; } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollectionIterator + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function addToAssertionCount(int $count) : void { - return new \PHPUnit\TextUI\XmlConfiguration\ExtensionCollectionIterator($this); + $this->numAssertions += $count; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExtensionCollectionIterator implements Countable, Iterator -{ /** - * @var Extension[] + * Returns the number of assertions performed by this test. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $extensions; + public function getNumAssertions() : int + { + return $this->numAssertions; + } /** - * @var int + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $position; - public function __construct(\PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $extensions) + public function usesDataProvider() : bool { - $this->extensions = $extensions->asArray(); + return !empty($this->data); } - public function count() : int + /** + * @return int|string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function dataName() { - return iterator_count($this); + return $this->dataName; } - public function rewind() : void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getDataSetAsString(bool $includeData = \true) : string { - $this->position = 0; + $buffer = ''; + if (!empty($this->data)) { + if (is_int($this->dataName)) { + $buffer .= sprintf(' with data set #%d', $this->dataName); + } else { + $buffer .= sprintf(' with data set "%s"', $this->dataName); + } + if ($includeData) { + $exporter = new Exporter(); + $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); + } + } + return $buffer; } - public function valid() : bool + /** + * Gets the data set of a TestCase. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getProvidedData() : array { - return $this->position < count($this->extensions); + return $this->data; } - public function key() : int + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function addWarning(string $warning) : void { - return $this->position; + $this->warnings[] = $warning; } - public function current() : \PHPUnit\TextUI\XmlConfiguration\Extension + public function sortId() : string { - return $this->extensions[$this->position]; + $id = $this->name; + if (strpos($id, '::') === \false) { + $id = static::class . '::' . $id; + } + if ($this->usesDataProvider()) { + $id .= $this->getDataSetAsString(\false); + } + return $id; } - public function next() : void + /** + * Returns the normalized test name as class::method. + * + * @return list + */ + public function provides() : array { - $this->position++; + return $this->providedTests; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestSuite -{ /** - * @var string + * Returns a list of normalized dependency names, class::method. + * + * This list can differ from the raw dependencies as the resolver has + * no need for the [!][shallow]clone prefix that is filtered out + * during normalization. + * + * @return list */ - private $name; + public function requires() : array + { + return $this->dependencies; + } /** - * @var TestDirectoryCollection + * Override to run the test and assert its state. + * + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @throws AssertionFailedError + * @throws Exception + * @throws ExpectationFailedException + * @throws Throwable */ - private $directories; + protected function runTest() + { + if (trim($this->name) === '') { + throw new \PHPUnit\Framework\Exception('PHPUnit\\Framework\\TestCase::$name must be a non-blank string.'); + } + $testArguments = array_merge($this->data, $this->dependencyInput); + $this->registerMockObjectsFromTestArguments($testArguments); + try { + $testResult = $this->{$this->name}(...array_values($testArguments)); + } catch (Throwable $exception) { + if (!$this->checkExceptionExpectations($exception)) { + throw $exception; + } + if ($this->expectedException !== null) { + if ($this->expectedException === Error::class) { + $this->assertThat($exception, LogicalOr::fromConstraints(new ExceptionConstraint(Error::class), new ExceptionConstraint(\Error::class))); + } else { + $this->assertThat($exception, new ExceptionConstraint($this->expectedException)); + } + } + if ($this->expectedExceptionMessage !== null) { + $this->assertThat($exception, new ExceptionMessage($this->expectedExceptionMessage)); + } + if ($this->expectedExceptionMessageRegExp !== null) { + $this->assertThat($exception, new ExceptionMessageRegularExpression($this->expectedExceptionMessageRegExp)); + } + if ($this->expectedExceptionCode !== null) { + $this->assertThat($exception, new ExceptionCode($this->expectedExceptionCode)); + } + return; + } + if ($this->expectedException !== null) { + $this->assertThat(null, new ExceptionConstraint($this->expectedException)); + } elseif ($this->expectedExceptionMessage !== null) { + $this->numAssertions++; + throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with message "%s" is thrown', $this->expectedExceptionMessage)); + } elseif ($this->expectedExceptionMessageRegExp !== null) { + $this->numAssertions++; + throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with message matching "%s" is thrown', $this->expectedExceptionMessageRegExp)); + } elseif ($this->expectedExceptionCode !== null) { + $this->numAssertions++; + throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with code "%s" is thrown', $this->expectedExceptionCode)); + } + return $testResult; + } /** - * @var TestFileCollection + * This method is a wrapper for the ini_set() function that automatically + * resets the modified php.ini setting to its original value after the + * test is run. + * + * @throws Exception */ - private $files; + protected function iniSet(string $varName, string $newValue) : void + { + $currentValue = ini_set($varName, $newValue); + if ($currentValue !== \false) { + $this->iniSettings[$varName] = $currentValue; + } else { + throw new \PHPUnit\Framework\Exception(sprintf('INI setting "%s" could not be set to "%s".', $varName, $newValue)); + } + } /** - * @var FileCollection + * This method is a wrapper for the setlocale() function that automatically + * resets the locale to its original value after the test is run. + * + * @throws Exception */ - private $exclude; - public function __construct(string $name, \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection $directories, \PHPUnit\TextUI\XmlConfiguration\TestFileCollection $files, \PHPUnit\TextUI\XmlConfiguration\FileCollection $exclude) + protected function setLocale(...$args) : void { - $this->name = $name; - $this->directories = $directories; - $this->files = $files; - $this->exclude = $exclude; + if (count($args) < 2) { + throw new \PHPUnit\Framework\Exception(); + } + [$category, $locale] = $args; + if (!in_array($category, self::LOCALE_CATEGORIES, \true)) { + throw new \PHPUnit\Framework\Exception(); + } + if (!is_array($locale) && !is_string($locale)) { + throw new \PHPUnit\Framework\Exception(); + } + $this->locale[$category] = setlocale($category, 0); + $result = setlocale(...$args); + if ($result === \false) { + throw new \PHPUnit\Framework\Exception('The locale functionality is not implemented on your platform, ' . 'the specified locale does not exist or the category name is ' . 'invalid.'); + } } - public function name() : string + /** + * Makes configurable stub for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return Stub&RealInstanceType + */ + protected function createStub(string $originalClassName) : Stub { - return $this->name; + return $this->createMockObject($originalClassName); } - public function directories() : \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection + /** + * Returns a mock object for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createMock(string $originalClassName) : MockObject { - return $this->directories; + return $this->createMockObject($originalClassName); } - public function files() : \PHPUnit\TextUI\XmlConfiguration\TestFileCollection + /** + * Returns a configured mock object for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createConfiguredMock(string $originalClassName, array $configuration) : MockObject { - return $this->files; + $o = $this->createMockObject($originalClassName); + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); + } + return $o; } - public function exclude() : \PHPUnit\TextUI\XmlConfiguration\FileCollection + /** + * Returns a partial mock object for the specified class. + * + * @param string[] $methods + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createPartialMock(string $originalClassName, array $methods) : MockObject { - return $this->exclude; + try { + $reflector = new ReflectionClass($originalClassName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $mockedMethodsThatDontExist = array_filter($methods, static function (string $method) use($reflector) { + return !$reflector->hasMethod($method); + }); + if ($mockedMethodsThatDontExist) { + $this->addWarning(sprintf('createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', implode(', ', $mockedMethodsThatDontExist), $originalClassName)); + } + return $this->getMockBuilder($originalClassName)->disableOriginalConstructor()->disableOriginalClone()->disableArgumentCloning()->disallowMockingUnknownTypes()->setMethods(empty($methods) ? null : $methods)->getMock(); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestDirectory -{ /** - * @var string + * Returns a test proxy for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType */ - private $path; + protected function createTestProxy(string $originalClassName, array $constructorArguments = []) : MockObject + { + return $this->getMockBuilder($originalClassName)->setConstructorArgs($constructorArguments)->enableProxyingToOriginalMethods()->getMock(); + } /** - * @var string + * Mocks the specified class and returns the name of the mocked class. + * + * @param null|array $methods $methods + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string|string $originalClassName + * + * @psalm-return class-string */ - private $prefix; + protected function getMockClass(string $originalClassName, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \false, bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \false) : string + { + $this->recordDoubledType($originalClassName); + $mock = $this->getMockObjectGenerator()->getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments); + return get_class($mock); + } /** - * @var string + * Returns a mock object for the specified abstract class with all abstract + * methods of the class mocked. Concrete methods are not mocked by default. + * To mock concrete methods, use the 7th parameter ($mockedMethods). + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType */ - private $suffix; + protected function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = [], bool $cloneArguments = \false) : MockObject + { + $this->recordDoubledType($originalClassName); + $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass($originalClassName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + $this->registerMockObject($mockObject); + return $mockObject; + } /** - * @var string + * Returns a mock object based on the given WSDL file. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string|string $originalClassName + * + * @psalm-return MockObject&RealInstanceType */ - private $phpVersion; + protected function getMockFromWsdl(string $wsdlFile, string $originalClassName = '', string $mockClassName = '', array $methods = [], bool $callOriginalConstructor = \true, array $options = []) : MockObject + { + $this->recordDoubledType(SoapClient::class); + if ($originalClassName === '') { + $fileName = pathinfo(basename(parse_url($wsdlFile, PHP_URL_PATH)), PATHINFO_FILENAME); + $originalClassName = preg_replace('/\\W/', '', $fileName); + } + if (!class_exists($originalClassName)) { + eval($this->getMockObjectGenerator()->generateClassFromWsdl($wsdlFile, $originalClassName, $methods, $options)); + } + $mockObject = $this->getMockObjectGenerator()->getMock($originalClassName, $methods, ['', $options], $mockClassName, $callOriginalConstructor, \false, \false); + $this->registerMockObject($mockObject); + return $mockObject; + } /** - * @var VersionComparisonOperator - */ - private $phpVersionOperator; - public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator) + * Returns a mock object for the specified trait with all abstract methods + * of the trait mocked. Concrete methods to mock can be specified with the + * `$mockedMethods` parameter. + * + * @psalm-param trait-string $traitName + */ + protected function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = [], bool $cloneArguments = \false) : MockObject { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; + $this->recordDoubledType($traitName); + $mockObject = $this->getMockObjectGenerator()->getMockForTrait($traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + $this->registerMockObject($mockObject); + return $mockObject; } - public function path() : string + /** + * Returns an object for the specified trait. + * + * @psalm-param trait-string $traitName + */ + protected function getObjectForTrait(string $traitName, array $arguments = [], string $traitClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true) : object { - return $this->path; + $this->recordDoubledType($traitName); + return $this->getMockObjectGenerator()->getObjectForTrait($traitName, $traitClassName, $callAutoload, $callOriginalConstructor, $arguments); } - public function prefix() : string + /** + * @throws \Prophecy\Exception\Doubler\ClassNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * + * @psalm-param class-string|null $classOrInterface + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4141 + */ + protected function prophesize(?string $classOrInterface = null) : ObjectProphecy { - return $this->prefix; + if (!class_exists(Prophet::class)) { + throw new \PHPUnit\Framework\Exception('This test uses TestCase::prophesize(), but phpspec/prophecy is not installed. Please run "composer require --dev phpspec/prophecy".'); + } + $this->addWarning('PHPUnit\\Framework\\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.'); + if (is_string($classOrInterface)) { + $this->recordDoubledType($classOrInterface); + } + return $this->getProphet()->prophesize($classOrInterface); } - public function suffix() : string + /** + * Creates a default TestResult object. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + protected function createResult() : \PHPUnit\Framework\TestResult { - return $this->suffix; + return new \PHPUnit\Framework\TestResult(); } - public function phpVersion() : string + /** + * This method is called when a test method did not execute successfully. + * + * @throws Throwable + */ + protected function onNotSuccessfulTest(Throwable $t) : void { - return $this->phpVersion; + throw $t; } - public function phpVersionOperator() : VersionComparisonOperator + protected function recordDoubledType(string $originalClassName) : void { - return $this->phpVersionOperator; + $this->doubledTypes[] = $originalClassName; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestSuiteCollectionIterator implements Countable, Iterator -{ /** - * @var TestSuite[] + * @throws Throwable */ - private $testSuites; + private function verifyMockObjects() : void + { + foreach ($this->mockObjects as $mockObject) { + if ($mockObject->__phpunit_hasMatchers()) { + $this->numAssertions++; + } + $mockObject->__phpunit_verify($this->shouldInvocationMockerBeReset($mockObject)); + } + if ($this->prophet !== null) { + try { + $this->prophet->checkPredictions(); + } finally { + foreach ($this->prophet->getProphecies() as $objectProphecy) { + foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { + foreach ($methodProphecies as $methodProphecy) { + /* @var MethodProphecy $methodProphecy */ + $this->numAssertions += count($methodProphecy->getCheckedPredictions()); + } + } + } + } + } + } /** - * @var int + * @throws SkippedTestError + * @throws SyntheticSkippedError + * @throws Warning */ - private $position; - public function __construct(\PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection $testSuites) + private function checkRequirements() : void { - $this->testSuites = $testSuites->asArray(); + if (!$this->name || !method_exists($this, $this->name)) { + return; + } + $missingRequirements = TestUtil::getMissingRequirements(static::class, $this->name); + if (!empty($missingRequirements)) { + $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); + } } - public function count() : int + private function handleDependencies() : bool { - return iterator_count($this); + if ([] === $this->dependencies || $this->inIsolation) { + return \true; + } + $passed = $this->result->passed(); + $passedKeys = array_keys($passed); + $numKeys = count($passedKeys); + for ($i = 0; $i < $numKeys; $i++) { + $pos = strpos($passedKeys[$i], ' with data set'); + if ($pos !== \false) { + $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); + } + } + $passedKeys = array_flip(array_unique($passedKeys)); + foreach ($this->dependencies as $dependency) { + if (!$dependency->isValid()) { + $this->markSkippedForNotSpecifyingDependency(); + return \false; + } + if ($dependency->targetIsClass()) { + $dependencyClassName = $dependency->getTargetClassName(); + if (array_search($dependencyClassName, $this->result->passedClasses(), \true) === \false) { + $this->markSkippedForMissingDependency($dependency); + return \false; + } + continue; + } + $dependencyTarget = $dependency->getTarget(); + if (!isset($passedKeys[$dependencyTarget])) { + if (!$this->isCallableTestMethod($dependencyTarget)) { + $this->markWarningForUncallableDependency($dependency); + } else { + $this->markSkippedForMissingDependency($dependency); + } + return \false; + } + if (isset($passed[$dependencyTarget])) { + if ($passed[$dependencyTarget]['size'] != \PHPUnit\Util\Test::UNKNOWN && $this->getSize() != \PHPUnit\Util\Test::UNKNOWN && $passed[$dependencyTarget]['size'] > $this->getSize()) { + $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError('This test depends on a test that is larger than itself.'), 0); + return \false; + } + if ($dependency->useDeepClone()) { + $deepCopy = new DeepCopy(); + $deepCopy->skipUncloneable(\false); + $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($passed[$dependencyTarget]['result']); + } elseif ($dependency->useShallowClone()) { + $this->dependencyInput[$dependencyTarget] = clone $passed[$dependencyTarget]['result']; + } else { + $this->dependencyInput[$dependencyTarget] = $passed[$dependencyTarget]['result']; + } + } else { + $this->dependencyInput[$dependencyTarget] = null; + } + } + return \true; } - public function rewind() : void + private function markSkippedForNotSpecifyingDependency() : void { - $this->position = 0; + $this->status = BaseTestRunner::STATUS_SKIPPED; + $this->result->startTest($this); + $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError('This method has an invalid @depends annotation.'), 0); + $this->result->endTest($this, 0); } - public function valid() : bool + private function markSkippedForMissingDependency(\PHPUnit\Framework\ExecutionOrderDependency $dependency) : void { - return $this->position < count($this->testSuites); + $this->status = BaseTestRunner::STATUS_SKIPPED; + $this->result->startTest($this); + $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError(sprintf('This test depends on "%s" to pass.', $dependency->getTarget())), 0); + $this->result->endTest($this, 0); } - public function key() : int + private function markWarningForUncallableDependency(\PHPUnit\Framework\ExecutionOrderDependency $dependency) : void { - return $this->position; + $this->status = BaseTestRunner::STATUS_WARNING; + $this->result->startTest($this); + $this->result->addWarning($this, new \PHPUnit\Framework\Warning(sprintf('This test depends on "%s" which does not exist.', $dependency->getTarget())), 0); + $this->result->endTest($this, 0); } - public function current() : \PHPUnit\TextUI\XmlConfiguration\TestSuite + /** + * Get the mock object generator, creating it if it doesn't exist. + */ + private function getMockObjectGenerator() : MockGenerator { - return $this->testSuites[$this->position]; + if ($this->mockObjectGenerator === null) { + $this->mockObjectGenerator = new MockGenerator(); + } + return $this->mockObjectGenerator; } - public function next() : void + private function startOutputBuffering() : void { - $this->position++; + ob_start(); + $this->outputBufferingActive = \true; + $this->outputBufferingLevel = ob_get_level(); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestFile -{ - /** - * @var string - */ - private $path; - /** - * @var string - */ - private $phpVersion; /** - * @var VersionComparisonOperator + * @throws RiskyTestError */ - private $phpVersionOperator; - public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator) + private function stopOutputBuffering() : void { - $this->path = $path; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; + if (ob_get_level() !== $this->outputBufferingLevel) { + while (ob_get_level() >= $this->outputBufferingLevel) { + ob_end_clean(); + } + throw new \PHPUnit\Framework\RiskyTestError('Test code or tested code did not (only) close its own output buffers'); + } + $this->output = ob_get_contents(); + if ($this->outputCallback !== \false) { + $this->output = (string) call_user_func($this->outputCallback, $this->output); + } + ob_end_clean(); + $this->outputBufferingActive = \false; + $this->outputBufferingLevel = ob_get_level(); } - public function path() : string + private function snapshotGlobalState() : void { - return $this->path; + if ($this->runTestInSeparateProcess || $this->inIsolation || !$this->backupGlobals && !$this->backupStaticAttributes) { + return; + } + $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === \true); } - public function phpVersion() : string + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws RiskyTestError + */ + private function restoreGlobalState() : void { - return $this->phpVersion; + if (!$this->snapshot instanceof Snapshot) { + return; + } + if ($this->beStrictAboutChangesToGlobalState) { + try { + $this->compareGlobalStateSnapshots($this->snapshot, $this->createGlobalStateSnapshot($this->backupGlobals === \true)); + } catch (\PHPUnit\Framework\RiskyTestError $rte) { + // Intentionally left empty + } + } + $restorer = new Restorer(); + if ($this->backupGlobals) { + $restorer->restoreGlobalVariables($this->snapshot); + } + if ($this->backupStaticAttributes) { + $restorer->restoreStaticAttributes($this->snapshot); + } + $this->snapshot = null; + if (isset($rte)) { + throw $rte; + } } - public function phpVersionOperator() : VersionComparisonOperator + private function createGlobalStateSnapshot(bool $backupGlobals) : Snapshot { - return $this->phpVersionOperator; + $excludeList = new ExcludeList(); + foreach ($this->backupGlobalsExcludeList as $globalVariable) { + $excludeList->addGlobalVariable($globalVariable); + } + if (!empty($this->backupGlobalsBlacklist)) { + $this->addWarning('PHPUnit\\Framework\\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\\Framework\\TestCase::$backupGlobalsExcludeList instead.'); + foreach ($this->backupGlobalsBlacklist as $globalVariable) { + $excludeList->addGlobalVariable($globalVariable); + } + } + if (!defined('PHPUnit\\PHPUNIT_TESTSUITE')) { + $excludeList->addClassNamePrefix('PHPUnit'); + $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\CodeCoverage'); + $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\FileIterator'); + $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Invoker'); + $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Template'); + $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Timer'); + $excludeList->addClassNamePrefix('PHPUnit\\Doctrine\\Instantiator'); + $excludeList->addClassNamePrefix('Prophecy'); + $excludeList->addStaticAttribute(ComparatorFactory::class, 'instance'); + foreach ($this->backupStaticAttributesExcludeList as $class => $attributes) { + foreach ($attributes as $attribute) { + $excludeList->addStaticAttribute($class, $attribute); + } + } + if (!empty($this->backupStaticAttributesBlacklist)) { + $this->addWarning('PHPUnit\\Framework\\TestCase::$backupStaticAttributesBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\\Framework\\TestCase::$backupStaticAttributesExcludeList instead.'); + foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { + foreach ($attributes as $attribute) { + $excludeList->addStaticAttribute($class, $attribute); + } + } + } + } + return new Snapshot($excludeList, $backupGlobals, (bool) $this->backupStaticAttributes, \false, \false, \false, \false, \false, \false, \false); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestDirectoryCollection implements Countable, IteratorAggregate -{ /** - * @var TestDirectory[] + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws RiskyTestError */ - private $directories; + private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after) : void + { + $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; + if ($backupGlobals) { + $this->compareGlobalStateSnapshotPart($before->globalVariables(), $after->globalVariables(), "--- Global variables before the test\n+++ Global variables after the test\n"); + $this->compareGlobalStateSnapshotPart($before->superGlobalVariables(), $after->superGlobalVariables(), "--- Super-global variables before the test\n+++ Super-global variables after the test\n"); + } + if ($this->backupStaticAttributes) { + $this->compareGlobalStateSnapshotPart($before->staticAttributes(), $after->staticAttributes(), "--- Static attributes before the test\n+++ Static attributes after the test\n"); + } + } /** - * @param TestDirectory[] $directories + * @throws RiskyTestError */ - public static function fromArray(array $directories) : self + private function compareGlobalStateSnapshotPart(array $before, array $after, string $header) : void { - return new self(...$directories); + if ($before != $after) { + $differ = new Differ($header); + $exporter = new Exporter(); + $diff = $differ->diff($exporter->export($before), $exporter->export($after)); + throw new \PHPUnit\Framework\RiskyTestError($diff); + } } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\TestDirectory ...$directories) + private function getProphet() : Prophet { - $this->directories = $directories; + if ($this->prophet === null) { + $this->prophet = new Prophet(); + } + return $this->prophet; } /** - * @return TestDirectory[] + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException */ - public function asArray() : array + private function shouldInvocationMockerBeReset(MockObject $mock) : bool { - return $this->directories; + $enumerator = new Enumerator(); + foreach ($enumerator->enumerate($this->dependencyInput) as $object) { + if ($mock === $object) { + return \false; + } + } + if (!is_array($this->testResult) && !is_object($this->testResult)) { + return \true; + } + return !in_array($mock, $enumerator->enumerate($this->testResult), \true); } - public function count() : int + /** + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []) : void { - return count($this->directories); + if ($this->registerMockObjectsFromTestArgumentsRecursively) { + foreach ((new Enumerator())->enumerate($testArguments) as $object) { + if ($object instanceof MockObject) { + $this->registerMockObject($object); + } + } + } else { + foreach ($testArguments as $testArgument) { + if ($testArgument instanceof MockObject) { + $testArgument = Cloner::clone($testArgument); + $this->registerMockObject($testArgument); + } elseif (is_array($testArgument) && !in_array($testArgument, $visited, \true)) { + $visited[] = $testArgument; + $this->registerMockObjectsFromTestArguments($testArgument, $visited); + } + } + } } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollectionIterator + private function setDoesNotPerformAssertionsFromAnnotation() : void { - return new \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollectionIterator($this); + $annotations = TestUtil::parseTestMethodAnnotations(static::class, $this->name); + if (isset($annotations['method']['doesNotPerformAssertions'])) { + $this->doesNotPerformAssertions = \true; + } } - public function isEmpty() : bool + private function unregisterCustomComparators() : void { - return $this->count() === 0; + $factory = ComparatorFactory::getInstance(); + foreach ($this->customComparators as $comparator) { + $factory->unregister($comparator); + } + $this->customComparators = []; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestSuiteCollection implements Countable, IteratorAggregate -{ - /** - * @var TestSuite[] - */ - private $testSuites; - /** - * @param TestSuite[] $testSuites - */ - public static function fromArray(array $testSuites) : self + private function cleanupIniSettings() : void { - return new self(...$testSuites); + foreach ($this->iniSettings as $varName => $oldValue) { + ini_set($varName, $oldValue); + } + $this->iniSettings = []; } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\TestSuite ...$testSuites) + private function cleanupLocaleSettings() : void { - $this->testSuites = $testSuites; + foreach ($this->locale as $category => $locale) { + setlocale($category, $locale); + } + $this->locale = []; } /** - * @return TestSuite[] + * @throws Exception */ - public function asArray() : array + private function checkExceptionExpectations(Throwable $throwable) : bool { - return $this->testSuites; + $result = \false; + if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { + $result = \true; + } + if ($throwable instanceof \PHPUnit\Framework\Exception) { + $result = \false; + } + if (is_string($this->expectedException)) { + try { + $reflector = new ReflectionClass($this->expectedException); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($this->expectedException === 'PHPUnit\\Framework\\Exception' || $this->expectedException === '\\PHPUnit\\Framework\\Exception' || $reflector->isSubclassOf(\PHPUnit\Framework\Exception::class)) { + $result = \true; + } + } + return $result; } - public function count() : int + private function runInSeparateProcess() : bool { - return count($this->testSuites); + return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && !$this->inIsolation && !$this instanceof PhptTestCase; } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollectionIterator + private function isCallableTestMethod(string $dependency) : bool { - return new \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollectionIterator($this); + [$className, $methodName] = explode('::', $dependency); + if (!class_exists($className)) { + return \false; + } + try { + $class = new ReflectionClass($className); + } catch (ReflectionException $e) { + return \false; + } + if (!$class->isSubclassOf(__CLASS__)) { + return \false; + } + if (!$class->hasMethod($methodName)) { + return \false; + } + try { + $method = $class->getMethod($methodName); + } catch (ReflectionException $e) { + return \false; + } + return TestUtil::isTestMethod($method); } - public function isEmpty() : bool + /** + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + private function createMockObject(string $originalClassName) : MockObject { - return $this->count() === 0; + return $this->getMockBuilder($originalClassName)->disableOriginalConstructor()->disableOriginalClone()->disableArgumentCloning()->disallowMockingUnknownTypes()->getMock(); } } files = $files->asArray(); - } - public function count() : int - { - return iterator_count($this); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return $this->position < count($this->files); - } - public function key() : int - { - return $this->position; - } - public function current() : \PHPUnit\TextUI\XmlConfiguration\TestFile + private $thrownException; + /** + * @var string + */ + private $testName; + /** + * Returns a description for an exception. + */ + public static function exceptionToString(Throwable $e) : string { - return $this->files[$this->position]; + if ($e instanceof \PHPUnit\Framework\SelfDescribing) { + $buffer = $e->toString(); + if ($e instanceof \PHPUnit\Framework\ExpectationFailedException && $e->getComparisonFailure()) { + $buffer .= $e->getComparisonFailure()->getDiff(); + } + if ($e instanceof \PHPUnit\Framework\PHPTAssertionFailedError) { + $buffer .= $e->getDiff(); + } + if (!empty($buffer)) { + $buffer = trim($buffer) . "\n"; + } + return $buffer; + } + if ($e instanceof Error) { + return $e->getMessage() . "\n"; + } + if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) { + return $e->getClassName() . ': ' . $e->getMessage() . "\n"; + } + return get_class($e) . ': ' . $e->getMessage() . "\n"; } - public function next() : void + /** + * Constructs a TestFailure with the given test and exception. + */ + public function __construct(\PHPUnit\Framework\Test $failedTest, Throwable $t) { - $this->position++; + if ($failedTest instanceof \PHPUnit\Framework\SelfDescribing) { + $this->testName = $failedTest->toString(); + } else { + $this->testName = get_class($failedTest); + } + if (!$failedTest instanceof \PHPUnit\Framework\TestCase || !$failedTest->isInIsolation()) { + $this->failedTest = $failedTest; + } + $this->thrownException = $t; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestFileCollection implements Countable, IteratorAggregate -{ /** - * @var TestFile[] + * Returns a short description of the failure. */ - private $files; + public function toString() : string + { + return sprintf('%s: %s', $this->testName, $this->thrownException->getMessage()); + } /** - * @param TestFile[] $files + * Returns a description for the thrown exception. */ - public static function fromArray(array $files) : self + public function getExceptionAsString() : string { - return new self(...$files); + return self::exceptionToString($this->thrownException); } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\TestFile ...$files) + /** + * Returns the name of the failing test (including data set, if any). + */ + public function getTestName() : string { - $this->files = $files; + return $this->testName; } /** - * @return TestFile[] + * Returns the failing test. + * + * Note: The test object is not set when the test is executed in process + * isolation. + * + * @see Exception */ - public function asArray() : array + public function failedTest() : ?\PHPUnit\Framework\Test { - return $this->files; + return $this->failedTest; } - public function count() : int + /** + * Gets the thrown exception. + */ + public function thrownException() : Throwable { - return count($this->files); + return $this->thrownException; } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestFileCollectionIterator + /** + * Returns the exception's message. + */ + public function exceptionMessage() : string { - return new \PHPUnit\TextUI\XmlConfiguration\TestFileCollectionIterator($this); + return $this->thrownException()->getMessage(); } - public function isEmpty() : bool + /** + * Returns true if the thrown exception + * is of type AssertionFailedError. + */ + public function isFailure() : bool { - return $this->count() === 0; + return $this->thrownException() instanceof \PHPUnit\Framework\AssertionFailedError; } } directories = $directories->asArray(); - } - public function count() : int - { - return iterator_count($this); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return $this->position < count($this->directories); - } - public function key() : int - { - return $this->position; - } - public function current() : \PHPUnit\TextUI\XmlConfiguration\TestDirectory - { - return $this->directories[$this->position]; - } - public function next() : void - { - $this->position++; - } + public function addError(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void; + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void; + public function addIncompleteTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; + public function addRiskyTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; + public function addSkippedTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void; + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void; + public function startTest(\PHPUnit\Framework\Test $test) : void; + public function endTest(\PHPUnit\Framework\Test $test, float $time) : void; } directories = $directories->asArray(); } - public function count() : int + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { - return iterator_count($this); } - public function rewind() : void + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { - $this->position = 0; } - public function valid() : bool + public function addIncompleteTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void { - return $this->position < count($this->directories); } - public function key() : int + public function addRiskyTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void { - return $this->position; } - public function current() : \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory + public function addSkippedTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void { - return $this->directories[$this->position]; } - public function next() : void + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void + { + } + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void + { + } + public function startTest(\PHPUnit\Framework\Test $test) : void + { + } + public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { - $this->position++; } } */ - public static function fromArray(array $directories) : self - { - return new self(...$directories); - } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory ...$directories) - { - $this->directories = $directories; - } + private $passedTestClasses = []; /** - * @return Directory[] + * @var bool */ - public function asArray() : array - { - return $this->directories; - } - public function count() : int - { - return count($this->directories); - } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollectionIterator - { - return new \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollectionIterator($this); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Directory -{ + private $currentTestSuiteFailed = \false; /** - * @var string + * @var TestFailure[] */ - private $path; + private $errors = []; /** - * @var string + * @var TestFailure[] */ - private $prefix; + private $failures = []; /** - * @var string + * @var TestFailure[] */ - private $suffix; + private $warnings = []; /** - * @var string + * @var TestFailure[] */ - private $group; - public function __construct(string $path, string $prefix, string $suffix, string $group) - { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->group = $group; - } - public function path() : string - { - return $this->path; - } - public function prefix() : string - { - return $this->prefix; - } - public function suffix() : string - { - return $this->suffix; - } - public function group() : string - { - return $this->group; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; - -use function count; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; -use PHPUnit\TextUI\XmlConfiguration\Directory; -use PHPUnit\TextUI\XmlConfiguration\Exception; -use PHPUnit\TextUI\XmlConfiguration\FileCollection; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class CodeCoverage -{ + private $notImplemented = []; + /** + * @var TestFailure[] + */ + private $risky = []; + /** + * @var TestFailure[] + */ + private $skipped = []; + /** + * @deprecated Use the `TestHook` interfaces instead + * + * @var TestListener[] + */ + private $listeners = []; + /** + * @var int + */ + private $runTests = 0; + /** + * @var float + */ + private $time = 0; + /** + * Code Coverage information. + * + * @var CodeCoverage + */ + private $codeCoverage; + /** + * @var bool + */ + private $convertDeprecationsToExceptions = \false; + /** + * @var bool + */ + private $convertErrorsToExceptions = \true; + /** + * @var bool + */ + private $convertNoticesToExceptions = \true; + /** + * @var bool + */ + private $convertWarningsToExceptions = \true; + /** + * @var bool + */ + private $stop = \false; /** - * @var ?Directory + * @var bool */ - private $cacheDirectory; + private $stopOnError = \false; /** - * @var DirectoryCollection + * @var bool */ - private $directories; + private $stopOnFailure = \false; /** - * @var FileCollection + * @var bool */ - private $files; + private $stopOnWarning = \false; /** - * @var DirectoryCollection + * @var bool */ - private $excludeDirectories; + private $beStrictAboutTestsThatDoNotTestAnything = \true; /** - * @var FileCollection + * @var bool */ - private $excludeFiles; + private $beStrictAboutOutputDuringTests = \false; /** * @var bool */ - private $pathCoverage; + private $beStrictAboutTodoAnnotatedTests = \false; /** * @var bool */ - private $includeUncoveredFiles; + private $beStrictAboutResourceUsageDuringSmallTests = \false; /** * @var bool */ - private $processUncoveredFiles; + private $enforceTimeLimit = \false; /** * @var bool */ - private $ignoreDeprecatedCodeUnits; + private $forceCoversAnnotation = \false; + /** + * @var int + */ + private $timeoutForSmallTests = 1; + /** + * @var int + */ + private $timeoutForMediumTests = 10; + /** + * @var int + */ + private $timeoutForLargeTests = 60; /** * @var bool */ - private $disableCodeCoverageIgnore; + private $stopOnRisky = \false; /** - * @var ?Clover + * @var bool */ - private $clover; + private $stopOnIncomplete = \false; /** - * @var ?Cobertura + * @var bool */ - private $cobertura; + private $stopOnSkipped = \false; /** - * @var ?Crap4j + * @var bool */ - private $crap4j; + private $lastTestFailed = \false; /** - * @var ?Html + * @var int */ - private $html; + private $defaultTimeLimit = 0; /** - * @var ?Php + * @var bool */ - private $php; + private $stopOnDefect = \false; /** - * @var ?Text + * @var bool */ - private $text; + private $registerMockObjectsFromTestArgumentsRecursively = \false; /** - * @var ?Xml + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Registers a TestListener. */ - private $xml; - public function __construct(?Directory $cacheDirectory, DirectoryCollection $directories, FileCollection $files, DirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $pathCoverage, bool $includeUncoveredFiles, bool $processUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?Php $php, ?Text $text, ?Xml $xml) + public function addListener(\PHPUnit\Framework\TestListener $listener) : void { - $this->cacheDirectory = $cacheDirectory; - $this->directories = $directories; - $this->files = $files; - $this->excludeDirectories = $excludeDirectories; - $this->excludeFiles = $excludeFiles; - $this->pathCoverage = $pathCoverage; - $this->includeUncoveredFiles = $includeUncoveredFiles; - $this->processUncoveredFiles = $processUncoveredFiles; - $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; - $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; - $this->clover = $clover; - $this->cobertura = $cobertura; - $this->crap4j = $crap4j; - $this->html = $html; - $this->php = $php; - $this->text = $text; - $this->xml = $xml; + $this->listeners[] = $listener; } /** - * @psalm-assert-if-true !null $this->cacheDirectory + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Unregisters a TestListener. */ - public function hasCacheDirectory() : bool + public function removeListener(\PHPUnit\Framework\TestListener $listener) : void { - return $this->cacheDirectory !== null; + foreach ($this->listeners as $key => $_listener) { + if ($listener === $_listener) { + unset($this->listeners[$key]); + } + } } /** - * @throws Exception + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Flushes all flushable TestListeners. */ - public function cacheDirectory() : Directory + public function flushListeners() : void { - if (!$this->hasCacheDirectory()) { - throw new Exception('No cache directory has been configured'); + foreach ($this->listeners as $listener) { + if ($listener instanceof Printer) { + $listener->flush(); + } } - return $this->cacheDirectory; } - public function hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport() : bool + /** + * Adds an error to the list of errors. + */ + public function addError(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void { - return count($this->directories) > 0 || count($this->files) > 0; + if ($t instanceof \PHPUnit\Framework\RiskyTestError) { + $this->recordRisky($test, $t); + $notifyMethod = 'addRiskyTest'; + if ($test instanceof \PHPUnit\Framework\TestCase) { + $test->markAsRisky(); + } + if ($this->stopOnRisky || $this->stopOnDefect) { + $this->stop(); + } + } elseif ($t instanceof \PHPUnit\Framework\IncompleteTest) { + $this->recordNotImplemented($test, $t); + $notifyMethod = 'addIncompleteTest'; + if ($this->stopOnIncomplete) { + $this->stop(); + } + } elseif ($t instanceof \PHPUnit\Framework\SkippedTest) { + $this->recordSkipped($test, $t); + $notifyMethod = 'addSkippedTest'; + if ($this->stopOnSkipped) { + $this->stop(); + } + } else { + $this->recordError($test, $t); + $notifyMethod = 'addError'; + if ($this->stopOnError || $this->stopOnFailure) { + $this->stop(); + } + } + // @see https://github.com/sebastianbergmann/phpunit/issues/1953 + if ($t instanceof Error) { + $t = new \PHPUnit\Framework\ExceptionWrapper($t); + } + foreach ($this->listeners as $listener) { + $listener->{$notifyMethod}($test, $t, $time); + } + $this->lastTestFailed = \true; + $this->time += $time; } - public function directories() : DirectoryCollection + /** + * Adds a warning to the list of warnings. + * The passed in exception caused the warning. + */ + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { - return $this->directories; + if ($this->stopOnWarning || $this->stopOnDefect) { + $this->stop(); + } + $this->recordWarning($test, $e); + foreach ($this->listeners as $listener) { + $listener->addWarning($test, $e, $time); + } + $this->time += $time; } - public function files() : FileCollection + /** + * Adds a failure to the list of failures. + * The passed in exception caused the failure. + */ + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { - return $this->files; + if ($e instanceof \PHPUnit\Framework\RiskyTestError || $e instanceof \PHPUnit\Framework\OutputError) { + $this->recordRisky($test, $e); + $notifyMethod = 'addRiskyTest'; + if ($test instanceof \PHPUnit\Framework\TestCase) { + $test->markAsRisky(); + } + if ($this->stopOnRisky || $this->stopOnDefect) { + $this->stop(); + } + } elseif ($e instanceof \PHPUnit\Framework\IncompleteTest) { + $this->recordNotImplemented($test, $e); + $notifyMethod = 'addIncompleteTest'; + if ($this->stopOnIncomplete) { + $this->stop(); + } + } elseif ($e instanceof \PHPUnit\Framework\SkippedTest) { + $this->recordSkipped($test, $e); + $notifyMethod = 'addSkippedTest'; + if ($this->stopOnSkipped) { + $this->stop(); + } + } else { + $this->failures[] = new \PHPUnit\Framework\TestFailure($test, $e); + $notifyMethod = 'addFailure'; + if ($this->stopOnFailure || $this->stopOnDefect) { + $this->stop(); + } + } + foreach ($this->listeners as $listener) { + $listener->{$notifyMethod}($test, $e, $time); + } + $this->lastTestFailed = \true; + $this->time += $time; } - public function excludeDirectories() : DirectoryCollection + /** + * Informs the result that a test suite will be started. + */ + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { - return $this->excludeDirectories; + $this->currentTestSuiteFailed = \false; + foreach ($this->listeners as $listener) { + $listener->startTestSuite($suite); + } } - public function excludeFiles() : FileCollection + /** + * Informs the result that a test suite was completed. + */ + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { - return $this->excludeFiles; + if (!$this->currentTestSuiteFailed) { + $this->passedTestClasses[] = $suite->getName(); + } + foreach ($this->listeners as $listener) { + $listener->endTestSuite($suite); + } } - public function pathCoverage() : bool + /** + * Informs the result that a test will be started. + */ + public function startTest(\PHPUnit\Framework\Test $test) : void { - return $this->pathCoverage; + $this->lastTestFailed = \false; + $this->runTests += count($test); + foreach ($this->listeners as $listener) { + $listener->startTest($test); + } } - public function includeUncoveredFiles() : bool + /** + * Informs the result that a test was completed. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { - return $this->includeUncoveredFiles; + foreach ($this->listeners as $listener) { + $listener->endTest($test, $time); + } + if (!$this->lastTestFailed && $test instanceof \PHPUnit\Framework\TestCase) { + $class = get_class($test); + $key = $class . '::' . $test->getName(); + $this->passed[$key] = ['result' => $test->getResult(), 'size' => TestUtil::getSize($class, $test->getName(\false))]; + $this->time += $time; + } + if ($this->lastTestFailed && $test instanceof \PHPUnit\Framework\TestCase) { + $this->currentTestSuiteFailed = \true; + } } - public function ignoreDeprecatedCodeUnits() : bool + /** + * Returns true if no risky test occurred. + */ + public function allHarmless() : bool { - return $this->ignoreDeprecatedCodeUnits; + return $this->riskyCount() === 0; } - public function disableCodeCoverageIgnore() : bool + /** + * Gets the number of risky tests. + */ + public function riskyCount() : int { - return $this->disableCodeCoverageIgnore; + return count($this->risky); } - public function processUncoveredFiles() : bool + /** + * Returns true if no incomplete test occurred. + */ + public function allCompletelyImplemented() : bool { - return $this->processUncoveredFiles; + return $this->notImplementedCount() === 0; } /** - * @psalm-assert-if-true !null $this->clover + * Gets the number of incomplete tests. */ - public function hasClover() : bool + public function notImplementedCount() : int { - return $this->clover !== null; + return count($this->notImplemented); } /** - * @throws Exception + * Returns an array of TestFailure objects for the risky tests. + * + * @return TestFailure[] */ - public function clover() : Clover + public function risky() : array { - if (!$this->hasClover()) { - throw new Exception('Code Coverage report "Clover XML" has not been configured'); + return $this->risky; + } + /** + * Returns an array of TestFailure objects for the incomplete tests. + * + * @return TestFailure[] + */ + public function notImplemented() : array + { + return $this->notImplemented; + } + /** + * Returns true if no test has been skipped. + */ + public function noneSkipped() : bool + { + return $this->skippedCount() === 0; + } + /** + * Gets the number of skipped tests. + */ + public function skippedCount() : int + { + return count($this->skipped); + } + /** + * Returns an array of TestFailure objects for the skipped tests. + * + * @return TestFailure[] + */ + public function skipped() : array + { + return $this->skipped; + } + /** + * Gets the number of detected errors. + */ + public function errorCount() : int + { + return count($this->errors); + } + /** + * Returns an array of TestFailure objects for the errors. + * + * @return TestFailure[] + */ + public function errors() : array + { + return $this->errors; + } + /** + * Gets the number of detected failures. + */ + public function failureCount() : int + { + return count($this->failures); + } + /** + * Returns an array of TestFailure objects for the failures. + * + * @return TestFailure[] + */ + public function failures() : array + { + return $this->failures; + } + /** + * Gets the number of detected warnings. + */ + public function warningCount() : int + { + return count($this->warnings); + } + /** + * Returns an array of TestFailure objects for the warnings. + * + * @return TestFailure[] + */ + public function warnings() : array + { + return $this->warnings; + } + /** + * Returns the names of the tests that have passed. + */ + public function passed() : array + { + return $this->passed; + } + /** + * Returns the names of the TestSuites that have passed. + * + * This enables @depends-annotations for TestClassName::class + */ + public function passedClasses() : array + { + return $this->passedTestClasses; + } + /** + * Returns whether code coverage information should be collected. + */ + public function getCollectCodeCoverageInformation() : bool + { + return $this->codeCoverage !== null; + } + /** + * Runs a TestCase. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * @throws UnintentionallyCoveredCodeException + */ + public function run(\PHPUnit\Framework\Test $test) : void + { + \PHPUnit\Framework\Assert::resetCount(); + $size = TestUtil::UNKNOWN; + if ($test instanceof \PHPUnit\Framework\TestCase) { + $test->setRegisterMockObjectsFromTestArgumentsRecursively($this->registerMockObjectsFromTestArgumentsRecursively); + $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); + $size = $test->getSize(); + } + $error = \false; + $failure = \false; + $warning = \false; + $incomplete = \false; + $risky = \false; + $skipped = \false; + $this->startTest($test); + if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { + $errorHandler = new ErrorHandler($this->convertDeprecationsToExceptions, $this->convertErrorsToExceptions, $this->convertNoticesToExceptions, $this->convertWarningsToExceptions); + $errorHandler->register(); + } + $collectCodeCoverage = $this->codeCoverage !== null && !$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $isAnyCoverageRequired; + if ($collectCodeCoverage) { + $this->codeCoverage->start($test); + } + $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && !$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $size === TestUtil::SMALL && function_exists('xdebug_start_function_monitor'); + if ($monitorFunctions) { + /* @noinspection ForgottenDebugOutputInspection */ + xdebug_start_function_monitor(ResourceOperations::getFunctions()); + } + $timer = new Timer(); + $timer->start(); + try { + $invoker = new Invoker(); + if (!$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $this->shouldTimeLimitBeEnforced($size) && $invoker->canInvokeWithTimeout()) { + switch ($size) { + case TestUtil::SMALL: + $_timeout = $this->timeoutForSmallTests; + break; + case TestUtil::MEDIUM: + $_timeout = $this->timeoutForMediumTests; + break; + case TestUtil::LARGE: + $_timeout = $this->timeoutForLargeTests; + break; + default: + $_timeout = $this->defaultTimeLimit; + } + $invoker->invoke([$test, 'runBare'], [], $_timeout); + } else { + $test->runBare(); + } + } catch (TimeoutException $e) { + $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError($e->getMessage()), $_timeout); + $risky = \true; + } catch (\PHPUnit\Framework\AssertionFailedError $e) { + $failure = \true; + if ($e instanceof \PHPUnit\Framework\RiskyTestError) { + $risky = \true; + } elseif ($e instanceof \PHPUnit\Framework\IncompleteTestError) { + $incomplete = \true; + } elseif ($e instanceof \PHPUnit\Framework\SkippedTestError) { + $skipped = \true; + } + } catch (AssertionError $e) { + $test->addToAssertionCount(1); + $failure = \true; + $frame = $e->getTrace()[0]; + $e = new \PHPUnit\Framework\AssertionFailedError(sprintf('%s in %s:%s', $e->getMessage(), $frame['file'] ?? $e->getFile(), $frame['line'] ?? $e->getLine()), 0, $e); + } catch (\PHPUnit\Framework\Warning $e) { + $warning = \true; + } catch (\PHPUnit\Framework\Exception $e) { + $error = \true; + } catch (Throwable $e) { + $e = new \PHPUnit\Framework\ExceptionWrapper($e); + $error = \true; + } + $time = $timer->stop()->asSeconds(); + $test->addToAssertionCount(\PHPUnit\Framework\Assert::getCount()); + if ($monitorFunctions) { + $excludeList = new ExcludeList(); + /** @noinspection ForgottenDebugOutputInspection */ + $functions = xdebug_get_monitored_functions(); + /* @noinspection ForgottenDebugOutputInspection */ + xdebug_stop_function_monitor(); + foreach ($functions as $function) { + if (!$excludeList->isExcluded($function['filename'])) { + $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf('%s() used in %s:%s', $function['function'], $function['filename'], $function['lineno'])), $time); + } + } + } + if ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() === 0) { + $risky = \true; + } + if ($this->forceCoversAnnotation && !$error && !$failure && !$warning && !$incomplete && !$skipped && !$risky) { + $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); + if (!isset($annotations['class']['covers']) && !isset($annotations['method']['covers']) && !isset($annotations['class']['coversNothing']) && !isset($annotations['method']['coversNothing'])) { + $this->addFailure($test, new \PHPUnit\Framework\MissingCoversAnnotationException('This test does not have a @covers annotation but is expected to have one'), $time); + $risky = \true; + } + } + if ($collectCodeCoverage) { + $append = !$risky && !$incomplete && !$skipped; + $linesToBeCovered = []; + $linesToBeUsed = []; + if ($append && $test instanceof \PHPUnit\Framework\TestCase) { + try { + $linesToBeCovered = TestUtil::getLinesToBeCovered(get_class($test), $test->getName(\false)); + $linesToBeUsed = TestUtil::getLinesToBeUsed(get_class($test), $test->getName(\false)); + } catch (\PHPUnit\Framework\InvalidCoversTargetException $cce) { + $this->addWarning($test, new \PHPUnit\Framework\Warning($cce->getMessage()), $time); + } + } + try { + $this->codeCoverage->stop($append, $linesToBeCovered, $linesToBeUsed); + } catch (UnintentionallyCoveredCodeException $cce) { + $unintentionallyCoveredCodeError = new \PHPUnit\Framework\UnintentionallyCoveredCodeError('This test executed code that is not listed as code to be covered or used:' . PHP_EOL . $cce->getMessage()); + } catch (OriginalCodeCoverageException $cce) { + $error = \true; + $e = $e ?? $cce; + } } - return $this->clover; + if (isset($errorHandler)) { + $errorHandler->unregister(); + unset($errorHandler); + } + if ($error) { + $this->addError($test, $e, $time); + } elseif ($failure) { + $this->addFailure($test, $e, $time); + } elseif ($warning) { + $this->addWarning($test, $e, $time); + } elseif (isset($unintentionallyCoveredCodeError)) { + $this->addFailure($test, $unintentionallyCoveredCodeError, $time); + } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && !$test->doesNotPerformAssertions() && $test->getNumAssertions() === 0) { + try { + $reflected = new ReflectionClass($test); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $name = $test->getName(\false); + if ($name && $reflected->hasMethod($name)) { + try { + $reflected = $reflected->getMethod($name); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf("This test did not perform any assertions\n\n%s:%d", $reflected->getFileName(), $reflected->getStartLine())), $time); + } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && $test->doesNotPerformAssertions() && $test->getNumAssertions() > 0) { + $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf('This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', $test->getNumAssertions())), $time); + } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { + $this->addFailure($test, new \PHPUnit\Framework\OutputError(sprintf('This test printed output: %s', $test->getActualOutput())), $time); + } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof \PHPUnit\Framework\TestCase) { + $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); + if (isset($annotations['method']['todo'])) { + $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError('Test method is annotated with @todo'), $time); + } + } + $this->endTest($test, $time); } /** - * @psalm-assert-if-true !null $this->cobertura + * Gets the number of run tests. */ - public function hasCobertura() : bool + public function count() : int { - return $this->cobertura !== null; + return $this->runTests; } /** - * @throws Exception + * Checks whether the test run should stop. */ - public function cobertura() : Cobertura + public function shouldStop() : bool { - if (!$this->hasCobertura()) { - throw new Exception('Code Coverage report "Cobertura XML" has not been configured'); - } - return $this->cobertura; + return $this->stop; } /** - * @psalm-assert-if-true !null $this->crap4j + * Marks that the test run should stop. */ - public function hasCrap4j() : bool + public function stop() : void { - return $this->crap4j !== null; + $this->stop = \true; } /** - * @throws Exception + * Returns the code coverage object. */ - public function crap4j() : Crap4j + public function getCodeCoverage() : ?CodeCoverage { - if (!$this->hasCrap4j()) { - throw new Exception('Code Coverage report "Crap4J" has not been configured'); - } - return $this->crap4j; + return $this->codeCoverage; } /** - * @psalm-assert-if-true !null $this->html + * Sets the code coverage object. */ - public function hasHtml() : bool + public function setCodeCoverage(CodeCoverage $codeCoverage) : void { - return $this->html !== null; + $this->codeCoverage = $codeCoverage; } /** - * @throws Exception + * Enables or disables the deprecation-to-exception conversion. */ - public function html() : Html + public function convertDeprecationsToExceptions(bool $flag) : void { - if (!$this->hasHtml()) { - throw new Exception('Code Coverage report "HTML" has not been configured'); - } - return $this->html; + $this->convertDeprecationsToExceptions = $flag; } /** - * @psalm-assert-if-true !null $this->php + * Returns the deprecation-to-exception conversion setting. */ - public function hasPhp() : bool + public function getConvertDeprecationsToExceptions() : bool { - return $this->php !== null; + return $this->convertDeprecationsToExceptions; } /** - * @throws Exception + * Enables or disables the error-to-exception conversion. */ - public function php() : Php + public function convertErrorsToExceptions(bool $flag) : void { - if (!$this->hasPhp()) { - throw new Exception('Code Coverage report "PHP" has not been configured'); - } - return $this->php; + $this->convertErrorsToExceptions = $flag; } /** - * @psalm-assert-if-true !null $this->text + * Returns the error-to-exception conversion setting. */ - public function hasText() : bool + public function getConvertErrorsToExceptions() : bool { - return $this->text !== null; + return $this->convertErrorsToExceptions; } /** - * @throws Exception + * Enables or disables the notice-to-exception conversion. */ - public function text() : Text + public function convertNoticesToExceptions(bool $flag) : void { - if (!$this->hasText()) { - throw new Exception('Code Coverage report "Text" has not been configured'); - } - return $this->text; + $this->convertNoticesToExceptions = $flag; } /** - * @psalm-assert-if-true !null $this->xml + * Returns the notice-to-exception conversion setting. */ - public function hasXml() : bool + public function getConvertNoticesToExceptions() : bool { - return $this->xml !== null; + return $this->convertNoticesToExceptions; } /** - * @throws Exception + * Enables or disables the warning-to-exception conversion. */ - public function xml() : Xml - { - if (!$this->hasXml()) { - throw new Exception('Code Coverage report "XML" has not been configured'); - } - return $this->xml; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; - -use PHPUnit\SebastianBergmann\CodeCoverage\Filter; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class FilterMapper -{ - public function map(Filter $filter, \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage $configuration) : void + public function convertWarningsToExceptions(bool $flag) : void { - foreach ($configuration->directories() as $directory) { - $filter->includeDirectory($directory->path(), $directory->suffix(), $directory->prefix()); - } - foreach ($configuration->files() as $file) { - $filter->includeFile($file->path()); - } - foreach ($configuration->excludeDirectories() as $directory) { - $filter->excludeDirectory($directory->path(), $directory->suffix(), $directory->prefix()); - } - foreach ($configuration->excludeFiles() as $file) { - $filter->excludeFile($file->path()); - } + $this->convertWarningsToExceptions = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Xml -{ /** - * @var Directory + * Returns the warning-to-exception conversion setting. */ - private $target; - public function __construct(Directory $target) - { - $this->target = $target; - } - public function target() : Directory + public function getConvertWarningsToExceptions() : bool { - return $this->target; + return $this->convertWarningsToExceptions; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Php -{ /** - * @var File + * Enables or disables the stopping when an error occurs. */ - private $target; - public function __construct(File $target) - { - $this->target = $target; - } - public function target() : File + public function stopOnError(bool $flag) : void { - return $this->target; + $this->stopOnError = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Html -{ - /** - * @var Directory - */ - private $target; - /** - * @var int - */ - private $lowUpperBound; /** - * @var int + * Enables or disables the stopping when a failure occurs. */ - private $highLowerBound; - public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound) - { - $this->target = $target; - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - } - public function target() : Directory - { - return $this->target; - } - public function lowUpperBound() : int - { - return $this->lowUpperBound; - } - public function highLowerBound() : int + public function stopOnFailure(bool $flag) : void { - return $this->highLowerBound; + $this->stopOnFailure = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Crap4j -{ - /** - * @var File - */ - private $target; /** - * @var int + * Enables or disables the stopping when a warning occurs. */ - private $threshold; - public function __construct(File $target, int $threshold) - { - $this->target = $target; - $this->threshold = $threshold; - } - public function target() : File + public function stopOnWarning(bool $flag) : void { - return $this->target; + $this->stopOnWarning = $flag; } - public function threshold() : int + public function beStrictAboutTestsThatDoNotTestAnything(bool $flag) : void { - return $this->threshold; + $this->beStrictAboutTestsThatDoNotTestAnything = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - /** - * @var bool - */ - private $showUncoveredFiles; - /** - * @var bool - */ - private $showOnlySummary; - public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) + public function isStrictAboutTestsThatDoNotTestAnything() : bool { - $this->target = $target; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; + return $this->beStrictAboutTestsThatDoNotTestAnything; } - public function target() : File + public function beStrictAboutOutputDuringTests(bool $flag) : void { - return $this->target; + $this->beStrictAboutOutputDuringTests = $flag; } - public function showUncoveredFiles() : bool + public function isStrictAboutOutputDuringTests() : bool { - return $this->showUncoveredFiles; + return $this->beStrictAboutOutputDuringTests; } - public function showOnlySummary() : bool + public function beStrictAboutResourceUsageDuringSmallTests(bool $flag) : void { - return $this->showOnlySummary; + $this->beStrictAboutResourceUsageDuringSmallTests = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Clover -{ - /** - * @var File - */ - private $target; - public function __construct(File $target) + public function isStrictAboutResourceUsageDuringSmallTests() : bool { - $this->target = $target; + return $this->beStrictAboutResourceUsageDuringSmallTests; } - public function target() : File + public function enforceTimeLimit(bool $flag) : void { - return $this->target; + $this->enforceTimeLimit = $flag; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Cobertura -{ - /** - * @var File - */ - private $target; - public function __construct(File $target) + public function enforcesTimeLimit() : bool { - $this->target = $target; + return $this->enforceTimeLimit; } - public function target() : File + public function beStrictAboutTodoAnnotatedTests(bool $flag) : void { - return $this->target; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use RuntimeException; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Php -{ - /** - * @var DirectoryCollection - */ - private $includePaths; - /** - * @var IniSettingCollection - */ - private $iniSettings; - /** - * @var ConstantCollection - */ - private $constants; - /** - * @var VariableCollection - */ - private $globalVariables; + $this->beStrictAboutTodoAnnotatedTests = $flag; + } + public function isStrictAboutTodoAnnotatedTests() : bool + { + return $this->beStrictAboutTodoAnnotatedTests; + } + public function forceCoversAnnotation() : void + { + $this->forceCoversAnnotation = \true; + } + public function forcesCoversAnnotation() : bool + { + return $this->forceCoversAnnotation; + } /** - * @var VariableCollection + * Enables or disables the stopping for risky tests. */ - private $envVariables; + public function stopOnRisky(bool $flag) : void + { + $this->stopOnRisky = $flag; + } /** - * @var VariableCollection + * Enables or disables the stopping for incomplete tests. */ - private $postVariables; + public function stopOnIncomplete(bool $flag) : void + { + $this->stopOnIncomplete = $flag; + } /** - * @var VariableCollection + * Enables or disables the stopping for skipped tests. */ - private $getVariables; + public function stopOnSkipped(bool $flag) : void + { + $this->stopOnSkipped = $flag; + } /** - * @var VariableCollection + * Enables or disables the stopping for defects: error, failure, warning. */ - private $cookieVariables; + public function stopOnDefect(bool $flag) : void + { + $this->stopOnDefect = $flag; + } /** - * @var VariableCollection + * Returns the time spent running the tests. */ - private $serverVariables; + public function time() : float + { + return $this->time; + } /** - * @var VariableCollection + * Returns whether the entire test was successful or not. */ - private $filesVariables; + public function wasSuccessful() : bool + { + return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); + } + public function wasSuccessfulIgnoringWarnings() : bool + { + return empty($this->errors) && empty($this->failures); + } + public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete() : bool + { + return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); + } /** - * @var VariableCollection + * Sets the default timeout for tests. */ - private $requestVariables; - public function __construct(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection $includePaths, \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings, \PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $globalVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $envVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $postVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $getVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $cookieVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $serverVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $filesVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $requestVariables) + public function setDefaultTimeLimit(int $timeout) : void { - $this->includePaths = $includePaths; - $this->iniSettings = $iniSettings; - $this->constants = $constants; - $this->globalVariables = $globalVariables; - $this->envVariables = $envVariables; - $this->postVariables = $postVariables; - $this->getVariables = $getVariables; - $this->cookieVariables = $cookieVariables; - $this->serverVariables = $serverVariables; - $this->filesVariables = $filesVariables; - $this->requestVariables = $requestVariables; + $this->defaultTimeLimit = $timeout; } - public function includePaths() : \PHPUnit\TextUI\XmlConfiguration\DirectoryCollection + /** + * Sets the timeout for small tests. + */ + public function setTimeoutForSmallTests(int $timeout) : void { - return $this->includePaths; + $this->timeoutForSmallTests = $timeout; } - public function iniSettings() : \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection + /** + * Sets the timeout for medium tests. + */ + public function setTimeoutForMediumTests(int $timeout) : void { - return $this->iniSettings; + $this->timeoutForMediumTests = $timeout; } - public function constants() : \PHPUnit\TextUI\XmlConfiguration\ConstantCollection + /** + * Sets the timeout for large tests. + */ + public function setTimeoutForLargeTests(int $timeout) : void { - return $this->constants; + $this->timeoutForLargeTests = $timeout; } - public function globalVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + /** + * Returns the set timeout for large tests. + */ + public function getTimeoutForLargeTests() : int { - return $this->globalVariables; + return $this->timeoutForLargeTests; } - public function envVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag) : void { - return $this->envVariables; + $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } - public function postVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function recordError(\PHPUnit\Framework\Test $test, Throwable $t) : void { - return $this->postVariables; + $this->errors[] = new \PHPUnit\Framework\TestFailure($test, $t); } - public function getVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function recordNotImplemented(\PHPUnit\Framework\Test $test, Throwable $t) : void { - return $this->getVariables; + $this->notImplemented[] = new \PHPUnit\Framework\TestFailure($test, $t); } - public function cookieVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function recordRisky(\PHPUnit\Framework\Test $test, Throwable $t) : void { - return $this->cookieVariables; + $this->risky[] = new \PHPUnit\Framework\TestFailure($test, $t); } - public function serverVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function recordSkipped(\PHPUnit\Framework\Test $test, Throwable $t) : void { - return $this->serverVariables; + $this->skipped[] = new \PHPUnit\Framework\TestFailure($test, $t); } - public function filesVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function recordWarning(\PHPUnit\Framework\Test $test, Throwable $t) : void { - return $this->filesVariables; + $this->warnings[] = new \PHPUnit\Framework\TestFailure($test, $t); } - public function requestVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection + private function shouldTimeLimitBeEnforced(int $size) : bool { - return $this->requestVariables; + if (!$this->enforceTimeLimit) { + return \false; + } + if (!($this->defaultTimeLimit || $size !== TestUtil::UNKNOWN)) { + return \false; + } + if (!extension_loaded('pcntl')) { + return \false; + } + if (!class_exists(Invoker::class)) { + return \false; + } + if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { + return \false; + } + return \true; } } > + */ + protected $groups = []; + /** + * The tests in the test suite. + * + * @var Test[] + */ + protected $tests = []; + /** + * The number of tests in the test suite. + * + * @var int + */ + protected $numTests = -1; + /** + * @var bool + */ + protected $testCase = \false; + /** + * @var string[] + */ + protected $foundClasses = []; + /** + * @var null|list + */ + protected $providedTests; + /** + * @var null|list + */ + protected $requiredTests; + /** + * @var bool + */ + private $beStrictAboutChangesToGlobalState; + /** + * @var Factory + */ + private $iteratorFilter; + /** + * @var int + */ + private $declaredClassesPointer; + /** + * @psalm-var array + */ + private $warnings = []; + /** + * Constructs a new TestSuite. + * + * - PHPUnit\Framework\TestSuite() constructs an empty TestSuite. + * + * - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a + * TestSuite from the given class. + * + * - PHPUnit\Framework\TestSuite(ReflectionClass, String) + * constructs a TestSuite from the given class with the given + * name. + * + * - PHPUnit\Framework\TestSuite(String) either constructs a + * TestSuite from the given class (if the passed string is the + * name of an existing class) or constructs an empty TestSuite + * with the given name. + * + * @param ReflectionClass|string $theClass + * + * @throws Exception + */ + public function __construct($theClass = '', string $name = '') { - return new self(...$variables); + if (!is_string($theClass) && !$theClass instanceof ReflectionClass) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'ReflectionClass object or string'); + } + $this->declaredClassesPointer = count(get_declared_classes()); + if (!$theClass instanceof ReflectionClass) { + if (class_exists($theClass, \true)) { + if ($name === '') { + $name = $theClass; + } + try { + $theClass = new ReflectionClass($theClass); + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } else { + $this->setName($theClass); + return; + } + } + if (!$theClass->isSubclassOf(\PHPUnit\Framework\TestCase::class)) { + $this->setName((string) $theClass); + return; + } + if ($name !== '') { + $this->setName($name); + } else { + $this->setName($theClass->getName()); + } + $constructor = $theClass->getConstructor(); + if ($constructor !== null && !$constructor->isPublic()) { + $this->addTest(new \PHPUnit\Framework\WarningTestCase(sprintf('Class "%s" has no public constructor.', $theClass->getName()))); + return; + } + foreach ((new Reflection())->publicMethodsInTestClass($theClass) as $method) { + if (!TestUtil::isTestMethod($method)) { + continue; + } + $this->addTestMethod($theClass, $method); + } + if (empty($this->tests)) { + $this->addTest(new \PHPUnit\Framework\WarningTestCase(sprintf('No tests found in class "%s".', $theClass->getName()))); + } + $this->testCase = \true; } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\Variable ...$variables) + /** + * Returns a string representation of the test suite. + */ + public function toString() : string { - $this->variables = $variables; + return $this->getName(); + } + /** + * Adds a test to the suite. + * + * @param array $groups + */ + public function addTest(\PHPUnit\Framework\Test $test, $groups = []) : void + { + try { + $class = new ReflectionClass($test); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (!$class->isAbstract()) { + $this->tests[] = $test; + $this->clearCaches(); + if ($test instanceof self && empty($groups)) { + $groups = $test->getGroups(); + } + if ($this->containsOnlyVirtualGroups($groups)) { + $groups[] = 'default'; + } + foreach ($groups as $group) { + if (!isset($this->groups[$group])) { + $this->groups[$group] = [$test]; + } else { + $this->groups[$group][] = $test; + } + } + if ($test instanceof \PHPUnit\Framework\TestCase) { + $test->setGroups($groups); + } + } + } + /** + * Adds the tests from the given class to the suite. + * + * @psalm-param object|class-string $testClass + * + * @throws Exception + */ + public function addTestSuite($testClass) : void + { + if (!(is_object($testClass) || is_string($testClass) && class_exists($testClass))) { + throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class name or object'); + } + if (!is_object($testClass)) { + try { + $testClass = new ReflectionClass($testClass); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + if ($testClass instanceof self) { + $this->addTest($testClass); + } elseif ($testClass instanceof ReflectionClass) { + $suiteMethod = \false; + if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { + try { + $method = $testClass->getMethod(BaseTestRunner::SUITE_METHODNAME); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($method->isStatic()) { + $this->addTest($method->invoke(null, $testClass->getName())); + $suiteMethod = \true; + } + } + if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(\PHPUnit\Framework\TestCase::class)) { + $this->addTest(new self($testClass)); + } + } else { + throw new \PHPUnit\Framework\Exception(); + } + } + public function addWarning(string $warning) : void + { + $this->warnings[] = $warning; + } + /** + * Wraps both addTest() and addTestSuite + * as well as the separate import statements for the user's convenience. + * + * If the named file cannot be read or there are no new tests that can be + * added, a PHPUnit\Framework\WarningTestCase will be created instead, + * leaving the current test run untouched. + * + * @throws Exception + */ + public function addTestFile(string $filename) : void + { + if (is_file($filename) && substr($filename, -5) === '.phpt') { + $this->addTest(new PhptTestCase($filename)); + $this->declaredClassesPointer = count(get_declared_classes()); + return; + } + $numTests = count($this->tests); + // The given file may contain further stub classes in addition to the + // test class itself. Figure out the actual test class. + $filename = FileLoader::checkAndLoad($filename); + $newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer); + // The diff is empty in case a parent class (with test methods) is added + // AFTER a child class that inherited from it. To account for that case, + // accumulate all discovered classes, so the parent class may be found in + // a later invocation. + if (!empty($newClasses)) { + // On the assumption that test classes are defined first in files, + // process discovered classes in approximate LIFO order, so as to + // avoid unnecessary reflection. + $this->foundClasses = array_merge($newClasses, $this->foundClasses); + $this->declaredClassesPointer = count(get_declared_classes()); + } + // The test class's name must match the filename, either in full, or as + // a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a + // PSR-1 local short name ('NameSpace\ShortName'). The comparison must be + // anchored to prevent false-positive matches (e.g., 'OtherShortName'). + $shortName = basename($filename, '.php'); + $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; + foreach ($this->foundClasses as $i => $className) { + if (preg_match($shortNameRegEx, $className)) { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($class->getFileName() == $filename) { + $newClasses = [$className]; + unset($this->foundClasses[$i]); + break; + } + } + } + foreach ($newClasses as $className) { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (dirname($class->getFileName()) === __DIR__) { + continue; + } + if (!$class->isAbstract()) { + if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { + try { + $method = $class->getMethod(BaseTestRunner::SUITE_METHODNAME); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($method->isStatic()) { + $this->addTest($method->invoke(null, $className)); + } + } elseif ($class->implementsInterface(\PHPUnit\Framework\Test::class)) { + // Do we have modern namespacing ('Foo\Bar\WhizBangTest') or old-school namespacing ('Foo_Bar_WhizBangTest')? + $isPsr0 = !$class->inNamespace() && strpos($class->getName(), '_') !== \false; + $expectedClassName = $isPsr0 ? $className : $shortName; + if (($pos = strpos($expectedClassName, '.')) !== \false) { + $expectedClassName = substr($expectedClassName, 0, $pos); + } + if ($class->getShortName() !== $expectedClassName) { + $this->addWarning(sprintf("Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'", $filename, $class->getShortName(), $expectedClassName)); + } + $this->addTestSuite($class); + } + } + } + if (count($this->tests) > ++$numTests) { + $this->addWarning(sprintf("Multiple test case classes per file is deprecated\n in %s", $filename)); + } + $this->numTests = -1; + } + /** + * Wrapper for addTestFile() that adds multiple test files. + * + * @throws Exception + */ + public function addTestFiles(iterable $fileNames) : void + { + foreach ($fileNames as $filename) { + $this->addTestFile((string) $filename); + } } /** - * @return Variable[] + * Counts the number of test cases that will be run by this test. + * + * @todo refactor usage of numTests in DefaultResultPrinter */ - public function asArray() : array - { - return $this->variables; - } public function count() : int { - return count($this->variables); - } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\VariableCollectionIterator - { - return new \PHPUnit\TextUI\XmlConfiguration\VariableCollectionIterator($this); + $this->numTests = 0; + foreach ($this as $test) { + $this->numTests += count($test); + } + return $this->numTests; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Variable -{ - /** - * @var string - */ - private $name; - /** - * @var mixed - */ - private $value; /** - * @var bool + * Returns the name of the suite. */ - private $force; - public function __construct(string $name, $value, bool $force) - { - $this->name = $name; - $this->value = $value; - $this->force = $force; - } - public function name() : string + public function getName() : string { return $this->name; } - public function value() - { - return $this->value; - } - public function force() : bool - { - return $this->force; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConstantCollectionIterator implements Countable, Iterator -{ - /** - * @var Constant[] - */ - private $constants; /** - * @var int + * Returns the test groups of the suite. + * + * @psalm-return list */ - private $position; - public function __construct(\PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants) - { - $this->constants = $constants->asArray(); - } - public function count() : int - { - return iterator_count($this); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool - { - return $this->position < count($this->constants); - } - public function key() : int - { - return $this->position; - } - public function current() : \PHPUnit\TextUI\XmlConfiguration\Constant + public function getGroups() : array { - return $this->constants[$this->position]; + return array_map(static function ($key) : string { + return (string) $key; + }, array_keys($this->groups)); } - public function next() : void + public function getGroupDetails() : array { - $this->position++; + return $this->groups; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IniSettingCollectionIterator implements Countable, Iterator -{ /** - * @var IniSetting[] - */ - private $iniSettings; - /** - * @var int + * Set tests groups of the test case. */ - private $position; - public function __construct(\PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings) - { - $this->iniSettings = $iniSettings->asArray(); - } - public function count() : int - { - return iterator_count($this); - } - public function rewind() : void - { - $this->position = 0; - } - public function valid() : bool + public function setGroupDetails(array $groups) : void { - return $this->position < count($this->iniSettings); + $this->groups = $groups; } - public function key() : int + /** + * Runs the tests and collects their result in a TestResult. + * + * @throws \PHPUnit\Framework\CodeCoverageException + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Warning + */ + public function run(\PHPUnit\Framework\TestResult $result = null) : \PHPUnit\Framework\TestResult { - return $this->position; + if ($result === null) { + $result = $this->createResult(); + } + if (count($this) === 0) { + return $result; + } + /** @psalm-var class-string $className */ + $className = $this->name; + $hookMethods = TestUtil::getHookMethods($className); + $result->startTestSuite($this); + $test = null; + if ($this->testCase && class_exists($this->name, \false)) { + try { + foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { + if (method_exists($this->name, $beforeClassMethod)) { + if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { + $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); + } + call_user_func([$this->name, $beforeClassMethod]); + } + } + } catch (\PHPUnit\Framework\SkippedTestSuiteError $error) { + foreach ($this->tests() as $test) { + $result->startTest($test); + $result->addFailure($test, $error, 0); + $result->endTest($test, 0); + } + $result->endTestSuite($this); + return $result; + } catch (Throwable $t) { + $errorAdded = \false; + foreach ($this->tests() as $test) { + if ($result->shouldStop()) { + break; + } + $result->startTest($test); + if (!$errorAdded) { + $result->addError($test, $t, 0); + $errorAdded = \true; + } else { + $result->addFailure($test, new \PHPUnit\Framework\SkippedTestError('Test skipped because of an error in hook method'), 0); + } + $result->endTest($test, 0); + } + $result->endTestSuite($this); + return $result; + } + } + foreach ($this as $test) { + if ($result->shouldStop()) { + break; + } + if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof self) { + $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); + $test->setBackupGlobals($this->backupGlobals); + $test->setBackupStaticAttributes($this->backupStaticAttributes); + $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); + } + $test->run($result); + } + if ($this->testCase && class_exists($this->name, \false)) { + foreach ($hookMethods['afterClass'] as $afterClassMethod) { + if (method_exists($this->name, $afterClassMethod)) { + try { + call_user_func([$this->name, $afterClassMethod]); + } catch (Throwable $t) { + $message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage(); + $error = new \PHPUnit\Framework\SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); + $placeholderTest = clone $test; + $placeholderTest->setName($afterClassMethod); + $result->startTest($placeholderTest); + $result->addFailure($placeholderTest, $error, 0); + $result->endTest($placeholderTest, 0); + } + } + } + } + $result->endTestSuite($this); + return $result; } - public function current() : \PHPUnit\TextUI\XmlConfiguration\IniSetting + public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess) : void { - return $this->iniSettings[$this->position]; + $this->runTestInSeparateProcess = $runTestInSeparateProcess; } - public function next() : void + public function setName(string $name) : void { - $this->position++; + $this->name = $name; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class IniSettingCollection implements Countable, IteratorAggregate -{ - /** - * @var IniSetting[] - */ - private $iniSettings; /** - * @param IniSetting[] $iniSettings + * Returns the tests as an enumeration. + * + * @return Test[] */ - public static function fromArray(array $iniSettings) : self - { - return new self(...$iniSettings); - } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\IniSetting ...$iniSettings) + public function tests() : array { - $this->iniSettings = $iniSettings; + return $this->tests; } /** - * @return IniSetting[] + * Set tests of the test suite. + * + * @param Test[] $tests */ - public function asArray() : array + public function setTests(array $tests) : void { - return $this->iniSettings; + $this->tests = $tests; } - public function count() : int + /** + * Mark the test suite as skipped. + * + * @param string $message + * + * @throws SkippedTestSuiteError + * + * @psalm-return never-return + */ + public function markTestSuiteSkipped($message = '') : void { - return count($this->iniSettings); + throw new \PHPUnit\Framework\SkippedTestSuiteError($message); } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\IniSettingCollectionIterator + /** + * @param bool $beStrictAboutChangesToGlobalState + */ + public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState) : void { - return new \PHPUnit\TextUI\XmlConfiguration\IniSettingCollectionIterator($this); + if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + } } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use const PATH_SEPARATOR; -use function constant; -use function define; -use function defined; -use function getenv; -use function implode; -use function ini_get; -use function ini_set; -use function putenv; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PhpHandler -{ - public function handle(\PHPUnit\TextUI\XmlConfiguration\Php $configuration) : void + /** + * @param bool $backupGlobals + */ + public function setBackupGlobals($backupGlobals) : void { - $this->handleIncludePaths($configuration->includePaths()); - $this->handleIniSettings($configuration->iniSettings()); - $this->handleConstants($configuration->constants()); - $this->handleGlobalVariables($configuration->globalVariables()); - $this->handleServerVariables($configuration->serverVariables()); - $this->handleEnvVariables($configuration->envVariables()); - $this->handleVariables('_POST', $configuration->postVariables()); - $this->handleVariables('_GET', $configuration->getVariables()); - $this->handleVariables('_COOKIE', $configuration->cookieVariables()); - $this->handleVariables('_FILES', $configuration->filesVariables()); - $this->handleVariables('_REQUEST', $configuration->requestVariables()); + if (null === $this->backupGlobals && is_bool($backupGlobals)) { + $this->backupGlobals = $backupGlobals; + } } - private function handleIncludePaths(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection $includePaths) : void + /** + * @param bool $backupStaticAttributes + */ + public function setBackupStaticAttributes($backupStaticAttributes) : void { - if (!$includePaths->isEmpty()) { - $includePathsAsStrings = []; - foreach ($includePaths as $includePath) { - $includePathsAsStrings[] = $includePath->path(); - } - ini_set('include_path', implode(\PATH_SEPARATOR, $includePathsAsStrings) . \PATH_SEPARATOR . ini_get('include_path')); + if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { + $this->backupStaticAttributes = $backupStaticAttributes; } } - private function handleIniSettings(\PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings) : void + /** + * Returns an iterator for this test suite. + */ + public function getIterator() : Iterator { - foreach ($iniSettings as $iniSetting) { - $value = $iniSetting->value(); - if (defined($value)) { - $value = (string) constant($value); - } - ini_set($iniSetting->name(), $value); + $iterator = new \PHPUnit\Framework\TestSuiteIterator($this); + if ($this->iteratorFilter !== null) { + $iterator = $this->iteratorFilter->factory($iterator, $this); } + return $iterator; } - private function handleConstants(\PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants) : void + public function injectFilter(Factory $filter) : void { - foreach ($constants as $constant) { - if (!defined($constant->name())) { - define($constant->name(), $constant->value()); + $this->iteratorFilter = $filter; + foreach ($this as $test) { + if ($test instanceof self) { + $test->injectFilter($filter); } } } - private function handleGlobalVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void + /** + * @psalm-return array + */ + public function warnings() : array { - foreach ($variables as $variable) { - $GLOBALS[$variable->name()] = $variable->value(); - } + return array_unique($this->warnings); } - private function handleServerVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void + /** + * @return list + */ + public function provides() : array { - foreach ($variables as $variable) { - $_SERVER[$variable->name()] = $variable->value(); + if ($this->providedTests === null) { + $this->providedTests = []; + if (is_callable($this->sortId(), \true)) { + $this->providedTests[] = new \PHPUnit\Framework\ExecutionOrderDependency($this->sortId()); + } + foreach ($this->tests as $test) { + if (!$test instanceof \PHPUnit\Framework\Reorderable) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $this->providedTests = \PHPUnit\Framework\ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides()); + } } + return $this->providedTests; } - private function handleVariables(string $target, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void + /** + * @return list + */ + public function requires() : array { - foreach ($variables as $variable) { - $GLOBALS[$target][$variable->name()] = $variable->value(); + if ($this->requiredTests === null) { + $this->requiredTests = []; + foreach ($this->tests as $test) { + if (!$test instanceof \PHPUnit\Framework\Reorderable) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $this->requiredTests = \PHPUnit\Framework\ExecutionOrderDependency::mergeUnique(\PHPUnit\Framework\ExecutionOrderDependency::filterInvalid($this->requiredTests), $test->requires()); + } + $this->requiredTests = \PHPUnit\Framework\ExecutionOrderDependency::diff($this->requiredTests, $this->provides()); } + return $this->requiredTests; } - private function handleEnvVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void + public function sortId() : string { - foreach ($variables as $variable) { - $name = $variable->name(); - $value = $variable->value(); - $force = $variable->force(); - if ($force || getenv($name) === \false) { - putenv("{$name}={$value}"); - } - $value = getenv($name); - if ($force || !isset($_ENV[$name])) { - $_ENV[$name] = $value; - } - } + return $this->getName() . '::class'; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Constant -{ /** - * @var string + * Creates a default TestResult object. */ - private $name; + protected function createResult() : \PHPUnit\Framework\TestResult + { + return new \PHPUnit\Framework\TestResult(); + } /** - * @var mixed + * @throws Exception */ - private $value; - public function __construct(string $name, $value) + protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method) : void { - $this->name = $name; - $this->value = $value; + $methodName = $method->getName(); + $test = (new \PHPUnit\Framework\TestBuilder())->build($class, $methodName); + if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof \PHPUnit\Framework\DataProviderTestSuite) { + $test->setDependencies(TestUtil::getDependencies($class->getName(), $methodName)); + } + $this->addTest($test, TestUtil::getGroups($class->getName(), $methodName)); } - public function name() : string + private function clearCaches() : void { - return $this->name; + $this->numTests = -1; + $this->providedTests = null; + $this->requiredTests = null; } - public function value() + private function containsOnlyVirtualGroups(array $groups) : bool { - return $this->value; + foreach ($groups as $group) { + if (strpos($group, '__phpunit_') !== 0) { + return \false; + } + } + return \true; } } variables = $variables->asArray(); - } - public function count() : int + private $tests; + public function __construct(\PHPUnit\Framework\TestSuite $testSuite) { - return iterator_count($this); + $this->tests = $testSuite->tests(); } public function rewind() : void { @@ -63051,20 +65654,36 @@ final class VariableCollectionIterator implements Countable, Iterator } public function valid() : bool { - return $this->position < count($this->variables); + return $this->position < count($this->tests); } public function key() : int { return $this->position; } - public function current() : \PHPUnit\TextUI\XmlConfiguration\Variable + public function current() : \PHPUnit\Framework\Test { - return $this->variables[$this->position]; + return $this->tests[$this->position]; } public function next() : void { $this->position++; } + /** + * @throws NoChildTestSuiteException + */ + public function getChildren() : self + { + if (!$this->hasChildren()) { + throw new \PHPUnit\Framework\NoChildTestSuiteException('The current item is not a TestSuite instance and therefore does not have any children.'); + } + $current = $this->current(); + assert($current instanceof \PHPUnit\Framework\TestSuite); + return new self($current); + } + public function hasChildren() : bool + { + return $this->valid() && $this->current() instanceof \PHPUnit\Framework\TestSuite; + } } message = $message; + parent::__construct('Warning'); } - private function __construct(\PHPUnit\TextUI\XmlConfiguration\Constant ...$constants) + public function getMessage() : string { - $this->constants = $constants; + return $this->message; } /** - * @return Constant[] + * Returns a string representation of the test case. */ - public function asArray() : array - { - return $this->constants; - } - public function count() : int + public function toString() : string { - return count($this->constants); + return 'Warning'; } - public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\ConstantCollectionIterator + /** + * @throws Exception + * + * @psalm-return never-return + */ + protected function runTest() : void { - return new \PHPUnit\TextUI\XmlConfiguration\ConstantCollectionIterator($this); + throw new \PHPUnit\Framework\Warning($this->message); } } name = $name; - $this->value = $value; + return new \PHPUnit\Runner\StandardTestSuiteLoader(); } - public function name() : string + /** + * Returns the Test corresponding to the given suite. + * This is a template method, subclasses override + * the runFailed() and clearStatus() methods. + * + * @param string|string[] $suffixes + * + * @throws Exception + */ + public function getTest(string $suiteClassFile, $suffixes = '') : ?TestSuite { - return $this->name; + if (is_dir($suiteClassFile)) { + /** @var string[] $files */ + $files = (new FileIteratorFacade())->getFilesAsArray($suiteClassFile, $suffixes); + $suite = new TestSuite($suiteClassFile); + $suite->addTestFiles($files); + return $suite; + } + if (is_file($suiteClassFile) && substr($suiteClassFile, -5, 5) === '.phpt') { + $suite = new TestSuite(); + $suite->addTestFile($suiteClassFile); + return $suite; + } + try { + $testClass = $this->loadSuiteClass($suiteClassFile); + } catch (\PHPUnit\Exception $e) { + $this->runFailed($e->getMessage()); + return null; + } + try { + $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); + if (!$suiteMethod->isStatic()) { + $this->runFailed('suite() method must be static.'); + return null; + } + $test = $suiteMethod->invoke(null, $testClass->getName()); + } catch (ReflectionException $e) { + $test = new TestSuite($testClass); + } + $this->clearStatus(); + return $test; } - public function value() : string + /** + * Returns the loaded ReflectionClass for a suite name. + */ + protected function loadSuiteClass(string $suiteClassFile) : ReflectionClass + { + return $this->getLoader()->load($suiteClassFile); + } + /** + * Clears the status message. + */ + protected function clearStatus() : void { - return $this->value; } + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + protected abstract function runFailed(string $message) : void; } + */ + private const ALLOWED_TEST_STATUSES = [\PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE, \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING]; + /** + * @var string + */ + private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; + /** + * @var string + */ + private $cacheFilename; + /** + * @psalm-var array + */ + private $defects = []; + /** + * @psalm-var array + */ + private $times = []; + public function __construct(?string $filepath = null) { - $origin = (new SchemaDetector())->detect($filename); - if (!$origin->detected()) { - throw new \PHPUnit\TextUI\XmlConfiguration\Exception(sprintf('"%s" is not a valid PHPUnit XML configuration file that can be migrated', $filename)); + if ($filepath !== null && is_dir($filepath)) { + $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; } - $configurationDocument = (new XmlLoader())->loadFile($filename, \false, \true, \true); - foreach ((new \PHPUnit\TextUI\XmlConfiguration\MigrationBuilder())->build($origin->version()) as $migration) { - $migration->migrate($configurationDocument); + $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; + } + public function setState(string $testName, int $state) : void + { + if (!in_array($state, self::ALLOWED_TEST_STATUSES, \true)) { + return; } - $configurationDocument->formatOutput = \true; - $configurationDocument->preserveWhiteSpace = \false; - return $configurationDocument->saveXML(); + $this->defects[$testName] = $state; + } + public function getState(string $testName) : int + { + return $this->defects[$testName] ?? \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN; + } + public function setTime(string $testName, float $time) : void + { + $this->times[$testName] = $time; + } + public function getTime(string $testName) : float + { + return $this->times[$testName] ?? 0.0; + } + public function load() : void + { + if (!is_file($this->cacheFilename)) { + return; + } + $data = json_decode(file_get_contents($this->cacheFilename), \true); + if ($data === null) { + return; + } + if (!isset($data['version'])) { + return; + } + if ($data['version'] !== self::VERSION) { + return; + } + assert(isset($data['defects']) && is_array($data['defects'])); + assert(isset($data['times']) && is_array($data['times'])); + $this->defects = $data['defects']; + $this->times = $data['times']; + } + /** + * @throws Exception + */ + public function persist() : void + { + if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { + throw new \PHPUnit\Runner\Exception(sprintf('Cannot create directory "%s" for result cache file', $this->cacheFilename)); + } + file_put_contents($this->cacheFilename, json_encode(['version' => self::VERSION, 'defects' => $this->defects, 'times' => $this->times]), LOCK_EX); } } getElementsByTagName('whitelist')->item(0); - if (!$whitelist) { - return; + $extension = $this->createInstance($extensionConfiguration); + if (!$extension instanceof Hook) { + throw new Exception(sprintf('Class "%s" does not implement a PHPUnit\\Runner\\Hook interface', $extensionConfiguration->className())); } - $coverage = $document->getElementsByTagName('coverage')->item(0); - if (!$coverage instanceof DOMElement) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + $runner->addExtension($extension); + } + /** + * @throws Exception + * + * @deprecated + */ + public function createTestListenerInstance(Extension $listenerConfiguration) : TestListener + { + $listener = $this->createInstance($listenerConfiguration); + if (!$listener instanceof TestListener) { + throw new Exception(sprintf('Class "%s" does not implement the PHPUnit\\Framework\\TestListener interface', $listenerConfiguration->className())); } - $map = ['addUncoveredFilesFromWhitelist' => 'includeUncoveredFiles', 'processUncoveredFilesFromWhitelist' => 'processUncoveredFiles']; - foreach ($map as $old => $new) { - if (!$whitelist->hasAttribute($old)) { - continue; - } - $coverage->setAttribute($new, $whitelist->getAttribute($old)); - $whitelist->removeAttribute($old); + return $listener; + } + /** + * @throws Exception + */ + private function createInstance(Extension $extensionConfiguration) : object + { + $this->ensureClassExists($extensionConfiguration); + try { + $reflector = new ReflectionClass($extensionConfiguration->className()); + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } + if (!$extensionConfiguration->hasArguments()) { + return $reflector->newInstance(); + } + return $reflector->newInstanceArgs($extensionConfiguration->arguments()); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use DOMDocument; -use DOMElement; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MoveAttributesFromRootToCoverage implements \PHPUnit\TextUI\XmlConfiguration\Migration -{ /** - * @throws MigrationException + * @throws Exception */ - public function migrate(DOMDocument $document) : void + private function ensureClassExists(Extension $extensionConfiguration) : void { - $map = ['disableCodeCoverageIgnore' => 'disableCodeCoverageIgnore', 'ignoreDeprecatedCodeUnitsFromCodeCoverage' => 'ignoreDeprecatedCodeUnits']; - $root = $document->documentElement; - $coverage = $document->getElementsByTagName('coverage')->item(0); - if (!$coverage instanceof DOMElement) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + if (class_exists($extensionConfiguration->className(), \false)) { + return; } - foreach ($map as $old => $new) { - if (!$root->hasAttribute($old)) { - continue; - } - $coverage->setAttribute($new, $root->getAttribute($old)); - $root->removeAttribute($old); + if ($extensionConfiguration->hasSourceFile()) { + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require_once $extensionConfiguration->sourceFile(); + } + if (!class_exists($extensionConfiguration->className())) { + throw new Exception(sprintf('Class "%s" does not exist', $extensionConfiguration->className())); } } } @@ -63317,20 +66107,57 @@ declare (strict_types=1); * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TextUI\XmlConfiguration; +namespace PHPUnit\Runner\Extension; -use DOMDocument; +use function is_file; +use PHPUnit\PharIo\Manifest\ApplicationName; +use PHPUnit\PharIo\Manifest\Exception as ManifestException; +use PHPUnit\PharIo\Manifest\ManifestLoader; +use PHPUnit\PharIo\Version\Version as PharIoVersion; +use PHPUnit\Runner\Version; +use PHPUnit\SebastianBergmann\FileIterator\Facade as FileIteratorFacade; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class RemoveCacheTokensAttribute implements \PHPUnit\TextUI\XmlConfiguration\Migration +final class PharLoader { - public function migrate(DOMDocument $document) : void + /** + * @psalm-return array{loadedExtensions: list, notLoadedExtensions: list} + */ + public function loadPharExtensionsInDirectory(string $directory) : array { - $root = $document->documentElement; - if ($root->hasAttribute('cacheTokens')) { - $root->removeAttribute('cacheTokens'); + $loadedExtensions = []; + $notLoadedExtensions = []; + foreach ((new FileIteratorFacade())->getFilesAsArray($directory, '.phar') as $file) { + if (!is_file('phar://' . $file . '/manifest.xml')) { + $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + continue; + } + try { + $applicationName = new ApplicationName('phpunit/phpunit'); + $version = new PharIoVersion(Version::series()); + $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); + if (!$manifest->isExtensionFor($applicationName)) { + $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + continue; + } + if (!$manifest->isExtensionFor($applicationName, $version)) { + $notLoadedExtensions[] = $file . ' is not compatible with this version of PHPUnit'; + continue; + } + } catch (ManifestException $e) { + $notLoadedExtensions[] = $file . ': ' . $e->getMessage(); + continue; + } + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require $file; + $loadedExtensions[] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); } + return ['loadedExtensions' => $loadedExtensions, 'notLoadedExtensions' => $notLoadedExtensions]; } } getElementsByTagName('logging')->item(0); - if (!$logging instanceof DOMElement) { - return; - } - $types = ['junit' => 'junit', 'teamcity' => 'teamcity', 'testdox-html' => 'testdoxHtml', 'testdox-text' => 'testdoxText', 'testdox-xml' => 'testdoxXml', 'plain' => 'text']; - $logNodes = []; - foreach ($logging->getElementsByTagName('log') as $logNode) { - if (!isset($types[$logNode->getAttribute('type')])) { - continue; - } - $logNodes[] = $logNode; - } - foreach ($logNodes as $oldNode) { - $newLogNode = $document->createElement($types[$oldNode->getAttribute('type')]); - $newLogNode->setAttribute('outputFile', $oldNode->getAttribute('target')); - $logging->replaceChild($newLogNode, $oldNode); - } + return !in_array($hash, $this->groupTests, \true); } } + */ + private $filters = []; + /** + * @param array|string $args + * + * @throws Exception + */ + public function addFilter(ReflectionClass $filter, $args) : void { - return 'coverage-clover'; + if (!$filter->isSubclassOf(RecursiveFilterIterator::class)) { + throw new Exception(sprintf('Class "%s" does not extend RecursiveFilterIterator', $filter->name)); + } + $this->filters[] = [$filter, $args]; } - protected function toReportFormat(DOMElement $logNode) : DOMElement + public function factory(Iterator $iterator, TestSuite $suite) : FilterIterator { - $clover = $logNode->ownerDocument->createElement('clover'); - $clover->setAttribute('outputFile', $logNode->getAttribute('target')); - return $clover; + foreach ($this->filters as $filter) { + [$class, $args] = $filter; + $iterator = $class->newInstance($iterator, $args, $suite); + } + assert($iterator instanceof FilterIterator); + return $iterator; } } getGroupDetails() as $group => $tests) { + if (in_array((string) $group, $groups, \true)) { + $testHashes = array_map('spl_object_hash', $tests); + $this->groupTests = array_merge($this->groupTests, $testHashes); + } + } } - protected function toReportFormat(DOMElement $logNode) : DOMElement + public function accept() : bool { - $php = $logNode->ownerDocument->createElement('php'); - $php->setAttribute('outputFile', $logNode->getAttribute('target')); - return $php; + $test = $this->getInnerIterator()->current(); + if ($test instanceof TestSuite) { + return \true; + } + return $this->doAccept(spl_object_hash($test)); } + protected abstract function doAccept(string $hash); } groupTests, \true); + } } documentElement->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation', 'https://schema.phpunit.de/9.3/phpunit.xsd'); + parent::__construct($iterator); + $this->setFilter($filter); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function accept() : bool + { + $test = $this->getInnerIterator()->current(); + if ($test instanceof TestSuite) { + return \true; + } + $tmp = \PHPUnit\Util\Test::describe($test); + if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { + $name = $test->getMessage(); + } elseif ($tmp[0] !== '') { + $name = implode('::', $tmp); + } else { + $name = $tmp[1]; + } + $accepted = @preg_match($this->filter, $name, $matches); + if ($accepted && isset($this->filterMax)) { + $set = end($matches); + $accepted = $set >= $this->filterMin && $set <= $this->filterMax; + } + return (bool) $accepted; + } + /** + * @throws Exception + */ + private function setFilter(string $filter) : void + { + if (RegularExpression::safeMatch($filter, '') === \false) { + // Handles: + // * testAssertEqualsSucceeds#4 + // * testAssertEqualsSucceeds#4-8 + if (preg_match('/^(.*?)#(\\d+)(?:-(\\d+))?$/', $filter, $matches)) { + if (isset($matches[3]) && $matches[2] < $matches[3]) { + $filter = sprintf('%s.*with data set #(\\d+)$', $matches[1]); + $this->filterMin = (int) $matches[2]; + $this->filterMax = (int) $matches[3]; + } else { + $filter = sprintf('%s.*with data set #%s$', $matches[1], $matches[2]); + } + } elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { + $filter = sprintf('%s.*with data set "%s"$', $matches[1], $matches[2]); + } + // Escape delimiters in regular expression. Do NOT use preg_quote, + // to keep magic characters. + $filter = sprintf('/%s/i', str_replace('/', '\\/', $filter)); + } + $this->filter = $filter; } } ownerDocument->createElement('crap4j'); - $crap4j->setAttribute('outputFile', $logNode->getAttribute('target')); - $this->migrateAttributes($logNode, $crap4j, ['threshold']); - return $crap4j; - } + public function executeAfterIncompleteTest(string $test, string $message, float $time) : void; } createElement('coverage'); - $document->documentElement->insertBefore($coverage, $document->documentElement->firstChild); - } + public function executeAfterLastTest() : void; } getElementsByTagName('whitelist')->item(0); - if ($whitelist instanceof DOMElement) { - $this->ensureEmpty($whitelist); - $whitelist->parentNode->removeChild($whitelist); - } - $filter = $document->getElementsByTagName('filter')->item(0); - if ($filter instanceof DOMElement) { - $this->ensureEmpty($filter); - $filter->parentNode->removeChild($filter); - } - } - /** - * @throws MigrationException - */ - private function ensureEmpty(DOMElement $element) : void - { - if ($element->attributes->length > 0) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException(sprintf('%s element has unexpected attributes', $element->nodeName)); - } - if ($element->getElementsByTagName('*')->length > 0) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException(sprintf('%s element has unexpected children', $element->nodeName)); - } - } + public function executeAfterRiskyTest(string $test, string $message, float $time) : void; } getElementsByTagName('whitelist')->item(0); - if ($whitelist === null) { - return; - } - $coverage = $document->getElementsByTagName('coverage')->item(0); - if (!$coverage instanceof DOMElement) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); - } - $include = $document->createElement('include'); - $coverage->appendChild($include); - foreach (SnapshotNodeList::fromNodeList($whitelist->childNodes) as $child) { - if (!$child instanceof DOMElement || $child->nodeName !== 'directory') { - continue; - } - $include->appendChild($child); - } - } + public function executeAfterSkippedTest(string $test, string $message, float $time) : void; } getElementsByTagName('logging')->item(0); - if (!$logging instanceof DOMElement) { - return; - } - foreach (SnapshotNodeList::fromNodeList($logging->getElementsByTagName('log')) as $logNode) { - switch ($logNode->getAttribute('type')) { - case 'json': - case 'tap': - $logging->removeChild($logNode); - } - } - } + public function executeAfterSuccessfulTest(string $test, float $time) : void; } ownerDocument->createElement('html'); - $html->setAttribute('outputDirectory', $logNode->getAttribute('target')); - $this->migrateAttributes($logNode, $html, ['lowUpperBound', 'highLowerBound']); - return $html; - } + public function executeAfterTestError(string $test, string $message, float $time) : void; } getElementsByTagName('coverage')->item(0); - if (!$coverage instanceof DOMElement) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); - } - $logNode = $this->findLogNode($document); - if ($logNode === null) { - return; - } - $reportChild = $this->toReportFormat($logNode); - $report = $coverage->getElementsByTagName('report')->item(0); - if ($report === null) { - $report = $coverage->appendChild($document->createElement('report')); - } - $report->appendChild($reportChild); - $logNode->parentNode->removeChild($logNode); - } - protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes) : void - { - foreach ($attributes as $attr) { - if (!$src->hasAttribute($attr)) { - continue; - } - $dest->setAttribute($attr, $src->getAttribute($attr)); - $src->removeAttribute($attr); - } - } - protected abstract function forType() : string; - protected abstract function toReportFormat(DOMElement $logNode) : DOMElement; - private function findLogNode(DOMDocument $document) : ?DOMElement - { - $logNode = (new DOMXPath($document))->query(sprintf('//logging/log[@type="%s"]', $this->forType()))->item(0); - if (!$logNode instanceof DOMElement) { - return null; - } - return $logNode; - } + public function executeAfterTestFailure(string $test, string $message, float $time) : void; } ownerDocument->createElement('text'); - $text->setAttribute('outputFile', $logNode->getAttribute('target')); - $this->migrateAttributes($logNode, $text, ['showUncoveredFiles', 'showOnlySummary']); - return $text; - } + /** + * This hook will fire after any test, regardless of the result. + * + * For more fine grained control, have a look at the other hooks + * that extend PHPUnit\Runner\Hook. + */ + public function executeAfterTest(string $test, float $time) : void; } getElementsByTagName('whitelist')->item(0); - if ($whitelist === null) { - return; - } - $excludeNodes = SnapshotNodeList::fromNodeList($whitelist->getElementsByTagName('exclude')); - if ($excludeNodes->count() === 0) { - return; - } - $coverage = $document->getElementsByTagName('coverage')->item(0); - if (!$coverage instanceof DOMElement) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); - } - $targetExclude = $coverage->getElementsByTagName('exclude')->item(0); - if ($targetExclude === null) { - $targetExclude = $coverage->appendChild($document->createElement('exclude')); - } - foreach ($excludeNodes as $excludeNode) { - \assert($excludeNode instanceof DOMElement); - foreach (SnapshotNodeList::fromNodeList($excludeNode->childNodes) as $child) { - if (!$child instanceof DOMElement || !\in_array($child->nodeName, ['directory', 'file'], \true)) { - continue; - } - $targetExclude->appendChild($child); - } - if ($excludeNode->getElementsByTagName('*')->count() !== 0) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Dangling child elements in exclude found.'); - } - $whitelist->removeChild($excludeNode); - } - } + public function executeAfterTestWarning(string $test, string $message, float $time) : void; } ownerDocument->createElement('xml'); - $xml->setAttribute('outputDirectory', $logNode->getAttribute('target')); - return $xml; - } + public function executeBeforeFirstTest() : void; } [\PHPUnit\TextUI\XmlConfiguration\RemoveLogTypes::class], '9.2' => [\PHPUnit\TextUI\XmlConfiguration\RemoveCacheTokensAttribute::class, \PHPUnit\TextUI\XmlConfiguration\IntroduceCoverageElement::class, \PHPUnit\TextUI\XmlConfiguration\MoveAttributesFromRootToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveAttributesFromFilterWhitelistToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveWhitelistDirectoriesToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveWhitelistExcludesToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\RemoveEmptyFilter::class, \PHPUnit\TextUI\XmlConfiguration\CoverageCloverToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageCrap4jToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageHtmlToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoveragePhpToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageTextToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageXmlToReport::class, \PHPUnit\TextUI\XmlConfiguration\ConvertLogTypes::class, \PHPUnit\TextUI\XmlConfiguration\UpdateSchemaLocationTo93::class]]; - /** - * @throws MigrationBuilderException - */ - public function build(string $fromVersion) : array - { - if (!array_key_exists($fromVersion, self::AVAILABLE_MIGRATIONS)) { - throw new \PHPUnit\TextUI\XmlConfiguration\MigrationBuilderException(sprintf('Migration from schema version %s is not supported', $fromVersion)); - } - $stack = []; - foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { - if (version_compare($version, $fromVersion, '<')) { - continue; - } - foreach ($migrations as $migration) { - $stack[] = new $migration(); - } - } - return $stack; - } + public function executeBeforeTest(string $test) : void; } [['text' => 'phpunit [options] UnitTest.php'], ['text' => 'phpunit [options] ']], 'Code Coverage Options' => [['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], ['arg' => '--coverage-cobertura ', 'desc' => 'Generate code coverage report in Cobertura XML format'], ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], ['arg' => '--coverage-cache ', 'desc' => 'Cache static analysis results'], ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage analysis'], ['arg' => '--path-coverage', 'desc' => 'Perform path coverage analysis'], ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration']], 'Logging Options' => [['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration']], 'Test Selection Options' => [['arg' => '--list-suites', 'desc' => 'List available test suites'], ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], ['arg' => '--list-groups', 'desc' => 'List available test groups'], ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--covers ', 'desc' => 'Only runs tests annotated with "@covers "'], ['arg' => '--uses ', 'desc' => 'Only runs tests annotated with "@uses "'], ['arg' => '--list-tests', 'desc' => 'List available tests'], ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt']], 'Test Execution Options' => [['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], ['spacer' => ''], ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], ['spacer' => ''], ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], ['arg' => '--fail-on-incomplete', 'desc' => 'Treat incomplete tests as failures'], ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], ['arg' => '--fail-on-skipped', 'desc' => 'Treat skipped tests as failures'], ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], ['arg' => '--debug', 'desc' => 'Display debugging information'], ['spacer' => ''], ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], ['spacer' => ''], ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], ['arg' => '--random-order-seed ', 'desc' => 'Use a specific random seed for random order'], ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file']], 'Configuration Options' => [['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], ['arg' => '--extensions ', 'desc' => 'A comma separated list of PHPUnit extensions to load'], ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], ['arg' => '--cache-result-file ', 'desc' => 'Specify result cache path and filename'], ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format']], 'Miscellaneous Options' => [['arg' => '-h|--help', 'desc' => 'Prints this usage information'], ['arg' => '--version', 'desc' => 'Prints the version and exits'], ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], ['arg' => '--check-version', 'desc' => 'Check whether PHPUnit is the latest version']]]; - /** - * @var int Number of columns required to write the longest option name to the console - */ - private $maxArgLength = 0; - /** - * @var int Number of columns left for the description field after padding and option - */ - private $maxDescLength; - /** - * @var bool Use color highlights for sections, options and parameters - */ - private $hasColor = \false; - public function __construct(?int $width = null, ?bool $withColor = null) - { - if ($width === null) { - $width = (new Console())->getNumberOfColumns(); - } - if ($withColor === null) { - $this->hasColor = (new Console())->hasColorSupport(); - } else { - $this->hasColor = $withColor; - } - foreach (self::HELP_TEXT as $options) { - foreach ($options as $option) { - if (isset($option['arg'])) { - $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); - } - } - } - $this->maxDescLength = $width - $this->maxArgLength - 4; - } - /** - * Write the help file to the CLI, adapting width and colors to the console. - */ - public function writeToConsole() : void - { - if ($this->hasColor) { - $this->writeWithColor(); - } else { - $this->writePlaintext(); - } - } - private function writePlaintext() : void - { - foreach (self::HELP_TEXT as $section => $options) { - print "{$section}:" . \PHP_EOL; - if ($section !== 'Usage') { - print \PHP_EOL; - } - foreach ($options as $option) { - if (isset($option['spacer'])) { - print \PHP_EOL; - } - if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . \PHP_EOL; - } - if (isset($option['arg'])) { - $arg = str_pad($option['arg'], $this->maxArgLength); - print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . \PHP_EOL; - } - } - print \PHP_EOL; - } - } - private function writeWithColor() : void - { - foreach (self::HELP_TEXT as $section => $options) { - print Color::colorize('fg-yellow', "{$section}:") . \PHP_EOL; - foreach ($options as $option) { - if (isset($option['spacer'])) { - print \PHP_EOL; - } - if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . \PHP_EOL; - } - if (isset($option['arg'])) { - $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); - $arg = preg_replace_callback('/(<[^>]+>)/', static function ($matches) { - return Color::colorize('fg-cyan', $matches[0]); - }, $arg); - $desc = explode(\PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, \PHP_EOL)); - print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . \PHP_EOL; - for ($i = 1; $i < count($desc); $i++) { - print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . \PHP_EOL; - } - } - } - print \PHP_EOL; - } - } } codeCoverageFilter = $filter; - $this->loader = $loader; - $this->timer = new Timer(); - } /** - * @throws \PHPUnit\Runner\Exception - * @throws \PHPUnit\TextUI\XmlConfiguration\Exception - * @throws Exception + * @var TestHook[] */ - public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = \true) : TestResult - { - if (isset($arguments['configuration'])) { - $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; - } - $this->handleConfiguration($arguments); - $warnings = array_merge($warnings, $arguments['warnings']); - if (is_int($arguments['columns']) && $arguments['columns'] < 16) { - $arguments['columns'] = 16; - $tooFewColumnsRequested = \true; - } - if (isset($arguments['bootstrap'])) { - $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; - } - if ($arguments['backupGlobals'] === \true) { - $suite->setBackupGlobals(\true); - } - if ($arguments['backupStaticAttributes'] === \true) { - $suite->setBackupStaticAttributes(\true); - } - if ($arguments['beStrictAboutChangesToGlobalState'] === \true) { - $suite->setBeStrictAboutChangesToGlobalState(\true); - } - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - mt_srand($arguments['randomOrderSeed']); - } - if ($arguments['cacheResult']) { - if (!isset($arguments['cacheResultFile'])) { - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - $cacheLocation = $arguments['configurationObject']->filename(); - } else { - $cacheLocation = $_SERVER['PHP_SELF']; - } - $arguments['cacheResultFile'] = null; - $cacheResultFile = realpath($cacheLocation); - if ($cacheResultFile !== \false) { - $arguments['cacheResultFile'] = dirname($cacheResultFile); - } - } - $cache = new DefaultTestResultCache($arguments['cacheResultFile']); - $this->addExtension(new ResultCacheExtension($cache)); - } - if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { - $cache = $cache ?? new NullTestResultCache(); - $cache->load(); - $sorter = new TestSuiteSorter($cache); - $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); - $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); - unset($sorter); - } - if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { - $_suite = new TestSuite(); - /* @noinspection PhpUnusedLocalVariableInspection */ - foreach (range(1, $arguments['repeat']) as $step) { - $_suite->addTest($suite); - } - $suite = $_suite; - unset($_suite); - } - $result = $this->createTestResult(); - $listener = new TestListenerAdapter(); - $listenerNeeded = \false; - foreach ($this->extensions as $extension) { - if ($extension instanceof TestHook) { - $listener->add($extension); - $listenerNeeded = \true; - } - } - if ($listenerNeeded) { - $result->addListener($listener); - } - unset($listener, $listenerNeeded); - if ($arguments['convertDeprecationsToExceptions']) { - $result->convertDeprecationsToExceptions(\true); - } - if (!$arguments['convertErrorsToExceptions']) { - $result->convertErrorsToExceptions(\false); - } - if (!$arguments['convertNoticesToExceptions']) { - $result->convertNoticesToExceptions(\false); - } - if (!$arguments['convertWarningsToExceptions']) { - $result->convertWarningsToExceptions(\false); - } - if ($arguments['stopOnError']) { - $result->stopOnError(\true); - } - if ($arguments['stopOnFailure']) { - $result->stopOnFailure(\true); - } - if ($arguments['stopOnWarning']) { - $result->stopOnWarning(\true); - } - if ($arguments['stopOnIncomplete']) { - $result->stopOnIncomplete(\true); - } - if ($arguments['stopOnRisky']) { - $result->stopOnRisky(\true); - } - if ($arguments['stopOnSkipped']) { - $result->stopOnSkipped(\true); - } - if ($arguments['stopOnDefect']) { - $result->stopOnDefect(\true); - } - if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { - $result->setRegisterMockObjectsFromTestArgumentsRecursively(\true); - } - if ($this->printer === null) { - if (isset($arguments['printer'])) { - if ($arguments['printer'] instanceof \PHPUnit\TextUI\ResultPrinter) { - $this->printer = $arguments['printer']; - } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], \false)) { - try { - $reflector = new ReflectionClass($arguments['printer']); - if ($reflector->implementsInterface(\PHPUnit\TextUI\ResultPrinter::class)) { - $this->printer = $this->createPrinter($arguments['printer'], $arguments); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } - } else { - $this->printer = $this->createPrinter(\PHPUnit\TextUI\DefaultResultPrinter::class, $arguments); - } - } - if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { - assert($this->printer instanceof CliTestDoxPrinter); - $this->printer->setOriginalExecutionOrder($originalExecutionOrder); - $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); - } - $this->printer->write(Version::getVersionString() . "\n"); - self::$versionStringPrinted = \true; - foreach ($arguments['listeners'] as $listener) { - $result->addListener($listener); - } - $result->addListener($this->printer); - $coverageFilterFromConfigurationFile = \false; - $coverageFilterFromOption = \false; - $codeCoverageReports = 0; - if (isset($arguments['testdoxHTMLFile'])) { - $result->addListener(new HtmlResultPrinter($arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'])); - } - if (isset($arguments['testdoxTextFile'])) { - $result->addListener(new TextResultPrinter($arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'])); - } - if (isset($arguments['testdoxXMLFile'])) { - $result->addListener(new XmlResultPrinter($arguments['testdoxXMLFile'])); - } - if (isset($arguments['teamcityLogfile'])) { - $result->addListener(new TeamCity($arguments['teamcityLogfile'])); - } - if (isset($arguments['junitLogfile'])) { - $result->addListener(new JUnit($arguments['junitLogfile'], $arguments['reportUselessTests'])); - } - if (isset($arguments['coverageClover'])) { - $codeCoverageReports++; - } - if (isset($arguments['coverageCobertura'])) { - $codeCoverageReports++; - } - if (isset($arguments['coverageCrap4J'])) { - $codeCoverageReports++; - } - if (isset($arguments['coverageHtml'])) { - $codeCoverageReports++; - } - if (isset($arguments['coveragePHP'])) { - $codeCoverageReports++; - } - if (isset($arguments['coverageText'])) { - $codeCoverageReports++; - } - if (isset($arguments['coverageXml'])) { - $codeCoverageReports++; - } - if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { - if (isset($arguments['coverageFilter'])) { - if (!is_array($arguments['coverageFilter'])) { - $coverageFilterDirectories = [$arguments['coverageFilter']]; - } else { - $coverageFilterDirectories = $arguments['coverageFilter']; - } - foreach ($coverageFilterDirectories as $coverageFilterDirectory) { - $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); - } - $coverageFilterFromOption = \true; - } - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - $coverageFilterFromConfigurationFile = \true; - (new FilterMapper())->map($this->codeCoverageFilter, $codeCoverageConfiguration); - } - } - } - if ($codeCoverageReports > 0) { - try { - if (isset($codeCoverageConfiguration) && ($codeCoverageConfiguration->pathCoverage() || isset($arguments['pathCoverage']) && $arguments['pathCoverage'] === \true)) { - $codeCoverageDriver = (new Selector())->forLineAndPathCoverage($this->codeCoverageFilter); - } else { - $codeCoverageDriver = (new Selector())->forLineCoverage($this->codeCoverageFilter); - } - $codeCoverage = new CodeCoverage($codeCoverageDriver, $this->codeCoverageFilter); - if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->hasCacheDirectory()) { - $codeCoverage->cacheStaticAnalysis($codeCoverageConfiguration->cacheDirectory()->path()); - } - if (isset($arguments['coverageCacheDirectory'])) { - $codeCoverage->cacheStaticAnalysis($arguments['coverageCacheDirectory']); - } - $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); - if ($arguments['strictCoverage']) { - $codeCoverage->enableCheckForUnintentionallyCoveredCode(); - } - if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { - if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { - $codeCoverage->ignoreDeprecatedCode(); - } else { - $codeCoverage->doNotIgnoreDeprecatedCode(); - } - } - if (isset($arguments['disableCodeCoverageIgnore'])) { - if ($arguments['disableCodeCoverageIgnore']) { - $codeCoverage->disableAnnotationsForIgnoringCode(); - } else { - $codeCoverage->enableAnnotationsForIgnoringCode(); - } - } - if (isset($arguments['configurationObject'])) { - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - if ($codeCoverageConfiguration->includeUncoveredFiles()) { - $codeCoverage->includeUncoveredFiles(); - } else { - $codeCoverage->excludeUncoveredFiles(); - } - if ($codeCoverageConfiguration->processUncoveredFiles()) { - $codeCoverage->processUncoveredFiles(); - } else { - $codeCoverage->doNotProcessUncoveredFiles(); - } - } - } - if ($this->codeCoverageFilter->isEmpty()) { - if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { - $warnings[] = 'No filter is configured, code coverage will not be processed'; - } else { - $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; - } - unset($codeCoverage); - } - } catch (CodeCoverageException $e) { - $warnings[] = $e->getMessage(); + private $hooks = []; + /** + * @var bool + */ + private $lastTestWasNotSuccessful; + public function add(\PHPUnit\Runner\TestHook $hook) : void + { + $this->hooks[] = $hook; + } + public function startTest(Test $test) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\BeforeTestHook) { + $hook->executeBeforeTest(TestUtil::describeAsString($test)); } } - if ($arguments['verbose']) { - if (\PHP_SAPI === 'phpdbg') { - $this->writeMessage('Runtime', 'PHPDBG ' . \PHP_VERSION); - } else { - $runtime = 'PHP ' . \PHP_VERSION; - if (isset($codeCoverageDriver)) { - $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); - } - $this->writeMessage('Runtime', $runtime); - } - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - $this->writeMessage('Configuration', $arguments['configurationObject']->filename()); - } - foreach ($arguments['loadedExtensions'] as $extension) { - $this->writeMessage('Extension', $extension); - } - foreach ($arguments['notLoadedExtensions'] as $extension) { - $this->writeMessage('Extension', $extension); + $this->lastTestWasNotSuccessful = \false; + } + public function addError(Test $test, Throwable $t, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterTestErrorHook) { + $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); } } - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - $this->writeMessage('Random Seed', (string) $arguments['randomOrderSeed']); + $this->lastTestWasNotSuccessful = \true; + } + public function addWarning(Test $test, Warning $e, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterTestWarningHook) { + $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); + } } - if (isset($tooFewColumnsRequested)) { - $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; + $this->lastTestWasNotSuccessful = \true; + } + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterTestFailureHook) { + $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); + } } - if ((new Runtime())->discardsComments()) { - $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; + $this->lastTestWasNotSuccessful = \true; + } + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterIncompleteTestHook) { + $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } } - if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { - $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; + $this->lastTestWasNotSuccessful = \true; + } + public function addRiskyTest(Test $test, Throwable $t, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterRiskyTestHook) { + $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } } - foreach ($warnings as $warning) { - $this->writeMessage('Warning', $warning); + $this->lastTestWasNotSuccessful = \true; + } + public function addSkippedTest(Test $test, Throwable $t, float $time) : void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterSkippedTestHook) { + $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } } - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - if ($arguments['configurationObject']->hasValidationErrors()) { - if ((new SchemaDetector())->detect($arguments['configurationObject']->filename())->detected()) { - $this->writeMessage('Warning', 'Your XML configuration validates against a deprecated schema.'); - $this->writeMessage('Suggestion', 'Migrate your XML configuration using "--migrate-configuration"!'); - } else { - $this->write("\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n"); - $this->write($arguments['configurationObject']->validationErrors()); - $this->write("\n Test results may not be as expected.\n\n"); + $this->lastTestWasNotSuccessful = \true; + } + public function endTest(Test $test, float $time) : void + { + if (!$this->lastTestWasNotSuccessful) { + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterSuccessfulTestHook) { + $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); } } } - if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { - $this->write(\PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . \PHP_EOL); - $script = (new XdebugFilterScriptGenerator())->generate($codeCoverageConfiguration); - if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { - $this->write(sprintf('Cannot write Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile'])); - exit(self::EXCEPTION_EXIT); + foreach ($this->hooks as $hook) { + if ($hook instanceof \PHPUnit\Runner\AfterTestHook) { + $hook->executeAfterTest(TestUtil::describeAsString($test), $time); } - file_put_contents($arguments['xdebugFilterFile'], $script); - $this->write(sprintf('Wrote Xdebug filter script to %s ' . \PHP_EOL . \PHP_EOL, $arguments['xdebugFilterFile'])); - exit(self::SUCCESS_EXIT); } - $this->printer->write("\n"); - if (isset($codeCoverage)) { - $result->setCodeCoverage($codeCoverage); + } + public function startTestSuite(TestSuite $suite) : void + { + } + public function endTestSuite(TestSuite $suite) : void + { + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NullTestResultCache implements \PHPUnit\Runner\TestResultCache +{ + public function setState(string $testName, int $state) : void + { + } + public function getState(string $testName) : int + { + return \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN; + } + public function setTime(string $testName, float $time) : void + { + } + public function getTime(string $testName) : float + { + return 0; + } + public function load() : void + { + } + public function persist() : void + { + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function basename; +use function debug_backtrace; +use function defined; +use function dirname; +use function explode; +use function extension_loaded; +use function file; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_file; +use function is_readable; +use function is_string; +use function ltrim; +use function phpversion; +use function preg_match; +use function preg_replace; +use function preg_split; +use function realpath; +use function rtrim; +use function sprintf; +use function str_replace; +use function strncasecmp; +use function strpos; +use function substr; +use function trim; +use function unlink; +use function unserialize; +use function var_export; +use function version_compare; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\IncompleteTestError; +use PHPUnit\Framework\PHPTAssertionFailedError; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\SkippedTestError; +use PHPUnit\Framework\SyntheticSkippedError; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestResult; +use PHPUnit\Util\PHP\AbstractPhpProcess; +use PHPUnit\SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use PHPUnit\SebastianBergmann\Template\Template; +use PHPUnit\SebastianBergmann\Timer\Timer; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptTestCase implements Reorderable, SelfDescribing, Test +{ + /** + * @var string + */ + private $filename; + /** + * @var AbstractPhpProcess + */ + private $phpUtil; + /** + * @var string + */ + private $output = ''; + /** + * Constructs a test case with the given filename. + * + * @throws Exception + */ + public function __construct(string $filename, AbstractPhpProcess $phpUtil = null) + { + if (!is_file($filename)) { + throw new \PHPUnit\Runner\Exception(sprintf('File "%s" does not exist.', $filename)); } - $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); - $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); - $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); - $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); - if ($arguments['enforceTimeLimit'] === \true && !(new Invoker())->canInvokeWithTimeout()) { - $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); + $this->filename = $filename; + $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); + } + /** + * Counts the number of test cases executed by run(TestResult result). + */ + public function count() : int + { + return 1; + } + /** + * Runs a test and collects its result in a TestResult instance. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + */ + public function run(TestResult $result = null) : TestResult + { + if ($result === null) { + $result = new TestResult(); } - $result->enforceTimeLimit($arguments['enforceTimeLimit']); - $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); - $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); - $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); - $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); - if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === \true) { - $result->forceCoversAnnotation(); + try { + $sections = $this->parse(); + } catch (\PHPUnit\Runner\Exception $e) { + $result->startTest($this); + $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); + $result->endTest($this, 0); + return $result; } - $this->processSuiteFilters($suite, $arguments); - $suite->setRunTestInSeparateProcess($arguments['processIsolation']); - foreach ($this->extensions as $extension) { - if ($extension instanceof BeforeFirstTestHook) { - $extension->executeBeforeFirstTest(); - } + $code = $this->render($sections['FILE']); + $xfail = \false; + $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); + $result->startTest($this); + if (isset($sections['INI'])) { + $settings = $this->parseIniSection($sections['INI'], $settings); } - $testSuiteWarningsPrinted = \false; - foreach ($suite->warnings() as $warning) { - $this->writeMessage('Warning', $warning); - $testSuiteWarningsPrinted = \true; + if (isset($sections['ENV'])) { + $env = $this->parseEnvSection($sections['ENV']); + $this->phpUtil->setEnv($env); } - if ($testSuiteWarningsPrinted) { - $this->write(\PHP_EOL); + $this->phpUtil->setUseStderrRedirection(\true); + if ($result->enforcesTimeLimit()) { + $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); } - $suite->run($result); - foreach ($this->extensions as $extension) { - if ($extension instanceof AfterLastTestHook) { - $extension->executeAfterLastTest(); - } + $skip = $this->runSkip($sections, $result, $settings); + if ($skip) { + return $result; } - $result->flushListeners(); - $this->printer->printResult($result); - if (isset($codeCoverage)) { - if (isset($arguments['coverageClover'])) { - $this->codeCoverageGenerationStart('Clover XML'); - try { - $writer = new CloverReport(); - $writer->process($codeCoverage, $arguments['coverageClover']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - if (isset($arguments['coverageCobertura'])) { - $this->codeCoverageGenerationStart('Cobertura XML'); - try { - $writer = new CoberturaReport(); - $writer->process($codeCoverage, $arguments['coverageCobertura']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - if (isset($arguments['coverageCrap4J'])) { - $this->codeCoverageGenerationStart('Crap4J XML'); - try { - $writer = new Crap4jReport($arguments['crap4jThreshold']); - $writer->process($codeCoverage, $arguments['coverageCrap4J']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - if (isset($arguments['coverageHtml'])) { - $this->codeCoverageGenerationStart('HTML'); - try { - $writer = new HtmlReport($arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], sprintf(' and PHPUnit %s', Version::id())); - $writer->process($codeCoverage, $arguments['coverageHtml']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - if (isset($arguments['coveragePHP'])) { - $this->codeCoverageGenerationStart('PHP'); - try { - $writer = new PhpReport(); - $writer->process($codeCoverage, $arguments['coveragePHP']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); + if (isset($sections['XFAIL'])) { + $xfail = trim($sections['XFAIL']); + } + if (isset($sections['STDIN'])) { + $this->phpUtil->setStdin($sections['STDIN']); + } + if (isset($sections['ARGS'])) { + $this->phpUtil->setArgs($sections['ARGS']); + } + if ($result->getCollectCodeCoverageInformation()) { + $codeCoverageCacheDirectory = null; + $pathCoverage = \false; + $codeCoverage = $result->getCodeCoverage(); + if ($codeCoverage) { + if ($codeCoverage->cachesStaticAnalysis()) { + $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); } + $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); } - if (isset($arguments['coverageText'])) { - if ($arguments['coverageText'] === 'php://stdout') { - $outputStream = $this->printer; - $colors = $arguments['colors'] && $arguments['colors'] !== \PHPUnit\TextUI\DefaultResultPrinter::COLOR_NEVER; + $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory); + } + $timer = new Timer(); + $timer->start(); + $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); + $time = $timer->stop()->asSeconds(); + $this->output = $jobResult['stdout'] ?? ''; + if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { + $codeCoverage->append($coverage, $this, \true, [], []); + } + try { + $this->assertPhptExpectation($sections, $this->output); + } catch (AssertionFailedError $e) { + $failure = $e; + if ($xfail !== \false) { + $failure = new IncompleteTestError($xfail, 0, $e); + } elseif ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + if ($comparisonFailure) { + $diff = $comparisonFailure->getDiff(); } else { - $outputStream = new Printer($arguments['coverageText']); - $colors = \false; - } - $processor = new TextReport($arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary']); - $outputStream->write($processor->process($codeCoverage, $colors)); - } - if (isset($arguments['coverageXml'])) { - $this->codeCoverageGenerationStart('PHPUnit XML'); - try { - $writer = new XmlReport(Version::id()); - $writer->process($codeCoverage, $arguments['coverageXml']); - $this->codeCoverageGenerationSucceeded(); - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); + $diff = $e->getMessage(); } + $hint = $this->getLocationHintFromDiff($diff, $sections); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $failure = new PHPTAssertionFailedError($e->getMessage(), 0, $trace[0]['file'], $trace[0]['line'], $trace, $comparisonFailure ? $diff : ''); } + $result->addFailure($this, $failure, $time); + } catch (Throwable $t) { + $result->addError($this, $t, $time); } - if ($exit) { - if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === \true && count($result) === 0) { - exit(self::FAILURE_EXIT); + if ($xfail !== \false && $result->allCompletelyImplemented()) { + $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); + } + $this->runClean($sections, $result->getCollectCodeCoverageInformation()); + $result->endTest($this, $time); + return $result; + } + /** + * Returns the name of the test case. + */ + public function getName() : string + { + return $this->toString(); + } + /** + * Returns a string representation of the test case. + */ + public function toString() : string + { + return $this->filename; + } + public function usesDataProvider() : bool + { + return \false; + } + public function getNumAssertions() : int + { + return 1; + } + public function getActualOutput() : string + { + return $this->output; + } + public function hasOutput() : bool + { + return !empty($this->output); + } + public function sortId() : string + { + return $this->filename; + } + /** + * @return list + */ + public function provides() : array + { + return []; + } + /** + * @return list + */ + public function requires() : array + { + return []; + } + /** + * Parse --INI-- section key value pairs and return as array. + * + * @param array|string $content + */ + private function parseIniSection($content, array $ini = []) : array + { + if (is_string($content)) { + $content = explode("\n", trim($content)); + } + foreach ($content as $setting) { + if (strpos($setting, '=') === \false) { + continue; } - if ($result->wasSuccessfulIgnoringWarnings()) { - if ($arguments['failOnRisky'] && !$result->allHarmless()) { - exit(self::FAILURE_EXIT); - } - if ($arguments['failOnWarning'] && $result->warningCount() > 0) { - exit(self::FAILURE_EXIT); - } - if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { - exit(self::FAILURE_EXIT); - } - if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { - exit(self::FAILURE_EXIT); + $setting = explode('=', $setting, 2); + $name = trim($setting[0]); + $value = trim($setting[1]); + if ($name === 'extension' || $name === 'zend_extension') { + if (!isset($ini[$name])) { + $ini[$name] = []; } - exit(self::SUCCESS_EXIT); - } - if ($result->errorCount() > 0) { - exit(self::EXCEPTION_EXIT); + $ini[$name][] = $value; + continue; } - if ($result->failureCount() > 0) { - exit(self::FAILURE_EXIT); + $ini[$name] = $value; + } + return $ini; + } + private function parseEnvSection(string $content) : array + { + $env = []; + foreach (explode("\n", trim($content)) as $e) { + $e = explode('=', trim($e), 2); + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; } } - return $result; + return $env; } /** - * Returns the loader to be used. + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException */ - public function getLoader() : TestSuiteLoader + private function assertPhptExpectation(array $sections, string $output) : void { - if ($this->loader === null) { - $this->loader = new StandardTestSuiteLoader(); + $assertions = ['EXPECT' => 'assertEquals', 'EXPECTF' => 'assertStringMatchesFormat', 'EXPECTREGEX' => 'assertMatchesRegularExpression']; + $actual = preg_replace('/\\r\\n/', "\n", trim($output)); + foreach ($assertions as $sectionName => $sectionAssertion) { + if (isset($sections[$sectionName])) { + $sectionContent = preg_replace('/\\r\\n/', "\n", trim($sections[$sectionName])); + $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; + if ($expected === '') { + throw new \PHPUnit\Runner\Exception('No PHPT expectation found'); + } + Assert::$sectionAssertion($expected, $actual); + return; + } } - return $this->loader; - } - public function addExtension(Hook $extension) : void - { - $this->extensions[] = $extension; + throw new \PHPUnit\Runner\Exception('No PHPT assertion found'); } /** - * Override to define how to handle a failed loading of - * a test suite. + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function runFailed(string $message) : void + private function runSkip(array &$sections, TestResult $result, array $settings) : bool { - $this->write($message . \PHP_EOL); - exit(self::FAILURE_EXIT); + if (!isset($sections['SKIPIF'])) { + return \false; + } + $skipif = $this->render($sections['SKIPIF']); + $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); + if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { + $message = ''; + if (preg_match('/^\\s*skip\\s*(.+)\\s*/i', $jobResult['stdout'], $skipMatch)) { + $message = substr($skipMatch[1], 2); + } + $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $result->addFailure($this, new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), 0); + $result->endTest($this, 0); + return \true; + } + return \false; } - private function createTestResult() : TestResult + private function runClean(array &$sections, bool $collectCoverage) : void { - return new TestResult(); + $this->phpUtil->setStdin(''); + $this->phpUtil->setArgs(''); + if (isset($sections['CLEAN'])) { + $cleanCode = $this->render($sections['CLEAN']); + $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); + } } - private function write(string $buffer) : void + /** + * @throws Exception + */ + private function parse() : array { - if (\PHP_SAPI !== 'cli' && \PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer); + $sections = []; + $section = ''; + $unsupportedSections = ['CGI', 'COOKIE', 'DEFLATE_POST', 'EXPECTHEADERS', 'EXTENSIONS', 'GET', 'GZIP_POST', 'HEADERS', 'PHPDBG', 'POST', 'POST_RAW', 'PUT', 'REDIRECTTEST', 'REQUEST']; + $lineNr = 0; + foreach (file($this->filename) as $line) { + $lineNr++; + if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { + $section = $result[1]; + $sections[$section] = ''; + $sections[$section . '_offset'] = $lineNr; + continue; + } + if (empty($section)) { + throw new \PHPUnit\Runner\Exception('Invalid PHPT file: empty section header'); + } + $sections[$section] .= $line; } - if ($this->printer !== null) { - $this->printer->write($buffer); - } else { - print $buffer; + if (isset($sections['FILEEOF'])) { + $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); + unset($sections['FILEEOF']); + } + $this->parseExternal($sections); + if (!$this->validate($sections)) { + throw new \PHPUnit\Runner\Exception('Invalid PHPT file'); + } + foreach ($unsupportedSections as $section) { + if (isset($sections[$section])) { + throw new \PHPUnit\Runner\Exception("PHPUnit does not support PHPT {$section} sections"); + } } + return $sections; } /** - * @throws \PHPUnit\TextUI\XmlConfiguration\Exception * @throws Exception */ - private function handleConfiguration(array &$arguments) : void + private function parseExternal(array &$sections) : void { - if (!isset($arguments['configurationObject']) && isset($arguments['configuration'])) { - $arguments['configurationObject'] = (new Loader())->load($arguments['configuration']); - } - if (!isset($arguments['warnings'])) { - $arguments['warnings'] = []; - } - $arguments['debug'] = $arguments['debug'] ?? \false; - $arguments['filter'] = $arguments['filter'] ?? \false; - $arguments['listeners'] = $arguments['listeners'] ?? []; - if (isset($arguments['configurationObject'])) { - (new PhpHandler())->handle($arguments['configurationObject']->php()); - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - if (!isset($arguments['noCoverage'])) { - if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { - $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); - } - if (!isset($arguments['coverageCobertura']) && $codeCoverageConfiguration->hasCobertura()) { - $arguments['coverageCobertura'] = $codeCoverageConfiguration->cobertura()->target()->path(); - } - if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { - $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); - if (!isset($arguments['crap4jThreshold'])) { - $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); - } - } - if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { - $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); - if (!isset($arguments['reportLowUpperBound'])) { - $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); - } - if (!isset($arguments['reportHighLowerBound'])) { - $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); - } - } - if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { - $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); - } - if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { - $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); - $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); - $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); - } - if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { - $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); + $allowSections = ['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX']; + $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; + foreach ($allowSections as $section) { + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFilename = trim($sections[$section . '_EXTERNAL']); + if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) { + throw new \PHPUnit\Runner\Exception(sprintf('Could not load --%s-- %s for PHPT file', $section . '_EXTERNAL', $testDirectory . $externalFilename)); } + $sections[$section] = file_get_contents($testDirectory . $externalFilename); } - $phpunitConfiguration = $arguments['configurationObject']->phpunit(); - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); - $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); - $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); - $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); - $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); - $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); - $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); - $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); - $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); - $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); - $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); - $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); - $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); - if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { - $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); - } - if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { - $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); - } - if (!isset($arguments['executionOrderDefects'])) { - $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; - } - if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { - $arguments['conflictBetweenPrinterClassAndTestdox'] = \true; - } - $groupCliArgs = []; - if (!empty($arguments['groups'])) { - $groupCliArgs = $arguments['groups']; - } - $groupConfiguration = $arguments['configurationObject']->groups(); - if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { - $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); - } - if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { - $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); - } - $extensionHandler = new ExtensionHandler(); - foreach ($arguments['configurationObject']->extensions() as $extension) { - $extensionHandler->registerExtension($extension, $this); - } - foreach ($arguments['configurationObject']->listeners() as $listener) { - $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); - } - unset($extensionHandler); - foreach ($arguments['unavailableExtensions'] as $extension) { - $arguments['warnings'][] = sprintf('Extension "%s" is not available', $extension); - } - $loggingConfiguration = $arguments['configurationObject']->logging(); - if (!isset($arguments['noLogging'])) { - if ($loggingConfiguration->hasText()) { - $arguments['listeners'][] = new \PHPUnit\TextUI\DefaultResultPrinter($loggingConfiguration->text()->target()->path(), \true); - } - if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { - $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); - } - if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { - $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); - } - if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { - $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); - } - if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { - $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); + } + } + private function validate(array &$sections) : bool + { + $requiredSections = ['FILE', ['EXPECT', 'EXPECTF', 'EXPECTREGEX']]; + foreach ($requiredSections as $section) { + if (is_array($section)) { + $foundSection = \false; + foreach ($section as $anySection) { + if (isset($sections[$anySection])) { + $foundSection = \true; + break; + } } - if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { - $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); + if (!$foundSection) { + return \false; } + continue; } - $testdoxGroupConfiguration = $arguments['configurationObject']->testdoxGroups(); - if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { - $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); - } - if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { - $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); + if (!isset($sections[$section])) { + return \false; } } - $extensionHandler = new ExtensionHandler(); - foreach ($arguments['extensions'] as $extension) { - $extensionHandler->registerExtension($extension, $this); - } - unset($extensionHandler); - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? \false; - $arguments['cacheResult'] = $arguments['cacheResult'] ?? \true; - $arguments['colors'] = $arguments['colors'] ?? \PHPUnit\TextUI\DefaultResultPrinter::COLOR_DEFAULT; - $arguments['columns'] = $arguments['columns'] ?? 80; - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? \false; - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? \true; - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? \true; - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? \true; - $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? \false; - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? \false; - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? \false; - $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; - $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? \false; - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? \false; - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? \false; - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? \false; - $arguments['groups'] = $arguments['groups'] ?? []; - $arguments['noInteraction'] = $arguments['noInteraction'] ?? \false; - $arguments['processIsolation'] = $arguments['processIsolation'] ?? \false; - $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? \false; - $arguments['repeat'] = $arguments['repeat'] ?? \false; - $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; - $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? \true; - $arguments['reverseList'] = $arguments['reverseList'] ?? \false; - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? \true; - $arguments['stopOnError'] = $arguments['stopOnError'] ?? \false; - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? \false; - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? \false; - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? \false; - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? \false; - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? \false; - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? \false; - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? \false; - $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; - $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; - $arguments['verbose'] = $arguments['verbose'] ?? \false; + return \true; } - private function processSuiteFilters(TestSuite $suite, array $arguments) : void + private function render(string $code) : string { - if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups']) && empty($arguments['testsCovering']) && empty($arguments['testsUsing'])) { - return; + return str_replace(['__DIR__', '__FILE__'], ["'" . dirname($this->filename) . "'", "'" . $this->filename . "'"], $code); + } + private function getCoverageFiles() : array + { + $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; + $basename = basename($this->filename, 'phpt'); + return ['coverage' => $baseDir . $basename . 'coverage', 'job' => $baseDir . $basename . 'php']; + } + private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory) : void + { + $files = $this->getCoverageFiles(); + $template = new Template(__DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl'); + $composerAutoload = '\'\''; + if (defined('PHPUnit\\PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, \true); } - $filterFactory = new Factory(); - if (!empty($arguments['excludeGroups'])) { - $filterFactory->addFilter(new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups']); + $phar = '\'\''; + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(\__PHPUNIT_PHAR__, \true); } - if (!empty($arguments['groups'])) { - $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups']); + $globals = ''; + if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], \true) . ";\n"; } - if (!empty($arguments['testsCovering'])) { - $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), array_map(static function (string $name) : string { - return '__phpunit_covers_' . $name; - }, $arguments['testsCovering'])); + if ($codeCoverageCacheDirectory === null) { + $codeCoverageCacheDirectory = 'null'; + } else { + $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; } - if (!empty($arguments['testsUsing'])) { - $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), array_map(static function (string $name) : string { - return '__phpunit_uses_' . $name; - }, $arguments['testsUsing'])); + $template->setVar(['composerAutoload' => $composerAutoload, 'phar' => $phar, 'globals' => $globals, 'job' => $files['job'], 'coverageFile' => $files['coverage'], 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory]); + file_put_contents($files['job'], $job); + $job = $template->render(); + } + private function cleanupForCoverage() : RawCodeCoverageData + { + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + $files = $this->getCoverageFiles(); + if (is_file($files['coverage'])) { + $buffer = @file_get_contents($files['coverage']); + if ($buffer !== \false) { + $coverage = @unserialize($buffer); + if ($coverage === \false) { + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + } + } } - if ($arguments['filter']) { - $filterFactory->addFilter(new ReflectionClass(NameFilterIterator::class), $arguments['filter']); + foreach ($files as $file) { + @unlink($file); } - $suite->injectFilter($filterFactory); + return $coverage; } - private function writeMessage(string $type, string $message) : void + private function stringifyIni(array $ini) : array { - if (!$this->messagePrinted) { - $this->write("\n"); + $settings = []; + foreach ($ini as $key => $value) { + if (is_array($value)) { + foreach ($value as $val) { + $settings[] = $key . '=' . $val; + } + continue; + } + $settings[] = $key . '=' . $value; } - $this->write(sprintf("%-15s%s\n", $type . ':', $message)); - $this->messagePrinted = \true; + return $settings; } - private function createPrinter(string $class, array $arguments) : \PHPUnit\TextUI\ResultPrinter + private function getLocationHintFromDiff(string $message, array $sections) : array { - $object = new $class(isset($arguments['stderr']) && $arguments['stderr'] === \true ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList']); - assert($object instanceof \PHPUnit\TextUI\ResultPrinter); - return $object; + $needle = ''; + $previousLine = ''; + $block = 'message'; + foreach (preg_split('/\\r\\n|\\r|\\n/', $message) as $line) { + $line = trim($line); + if ($block === 'message' && $line === '--- Expected') { + $block = 'expected'; + } + if ($block === 'expected' && $line === '@@ @@') { + $block = 'diff'; + } + if ($block === 'diff') { + if (strpos($line, '+') === 0) { + $needle = $this->getCleanDiffLine($previousLine); + break; + } + if (strpos($line, '-') === 0) { + $needle = $this->getCleanDiffLine($line); + break; + } + } + if (!empty($line)) { + $previousLine = $line; + } + } + return $this->getLocationHint($needle, $sections); } - private function codeCoverageGenerationStart(string $format) : void + private function getCleanDiffLine(string $line) : string { - $this->printer->write(sprintf("\nGenerating code coverage report in %s format ... ", $format)); - $this->timer->start(); + if (preg_match('/^[\\-+]([\'\\"]?)(.*)\\1$/', $line, $matches)) { + $line = $matches[2]; + } + return $line; } - private function codeCoverageGenerationSucceeded() : void + private function getLocationHint(string $needle, array $sections, ?string $sectionName = null) : array { - $this->printer->write(sprintf("done [%s]\n", $this->timer->stop()->asString())); + $needle = trim($needle); + if (empty($needle)) { + return [['file' => realpath($this->filename), 'line' => 1]]; + } + if ($sectionName) { + $search = [$sectionName]; + } else { + $search = [ + // 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + } + $sectionOffset = null; + foreach ($search as $section) { + if (!isset($sections[$section])) { + continue; + } + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFile = trim($sections[$section . '_EXTERNAL']); + return [['file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), 'line' => 1], ['file' => realpath($this->filename), 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1]]; + } + $sectionOffset = $sections[$section . '_offset'] ?? 0; + $offset = $sectionOffset + 1; + foreach (preg_split('/\\r\\n|\\r|\\n/', $sections[$section]) as $line) { + if (strpos($line, $needle) !== \false) { + return [['file' => realpath($this->filename), 'line' => $offset]]; + } + $offset++; + } + } + if ($sectionName) { + // String not found in specified section, show user the start of the named section + return [['file' => realpath($this->filename), 'line' => $sectionOffset]]; + } + // No section specified, show user start of code + return [['file' => realpath($this->filename), 'line' => 1]]; } - private function codeCoverageGenerationFailed(\Exception $e) : void + /** + * @psalm-return list + */ + private function settings(bool $collectCoverage) : array { - $this->printer->write(sprintf("failed [%s]\n%s\n", $this->timer->stop()->asString(), $e->getMessage())); + $settings = ['allow_url_fopen=1', 'auto_append_file=', 'auto_prepend_file=', 'disable_functions=', 'display_errors=1', 'docref_ext=.html', 'docref_root=', 'error_append_string=', 'error_prepend_string=', 'error_reporting=-1', 'html_errors=0', 'log_errors=0', 'open_basedir=', 'output_buffering=Off', 'output_handler=', 'report_memleaks=0', 'report_zend_debug=0']; + if (extension_loaded('pcov')) { + if ($collectCoverage) { + $settings[] = 'pcov.enabled=1'; + } else { + $settings[] = 'pcov.enabled=0'; + } + } + if (extension_loaded('xdebug')) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + if ($collectCoverage) { + $settings[] = 'xdebug.mode=coverage'; + } else { + $settings[] = 'xdebug.mode=off'; + } + } else { + $settings[] = 'xdebug.default_enable=0'; + if ($collectCoverage) { + $settings[] = 'xdebug.coverage_enable=1'; + } + } + } + return $settings; } } cache = $cache; + } + public function flush() : void + { + $this->cache->persist(); + } + public function executeAfterSuccessfulTest(string $test, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + } + public function executeAfterIncompleteTest(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE); + } + public function executeAfterRiskyTest(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY); + } + public function executeAfterSkippedTest(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED); + } + public function executeAfterTestError(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR); + } + public function executeAfterTestFailure(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE); + } + public function executeAfterTestWarning(string $test, string $message, float $time) : void + { + $testName = $this->getTestName($test); + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING); + } + public function executeAfterLastTest() : void + { + $this->flush(); + } + /** + * @param string $test A long description format of the current test + * + * @return string The test name without TestSuiteClassName:: and @dataprovider details + */ + private function getTestName(string $test) : string + { + $matches = []; + if (preg_match('/^(?\\S+::\\S+)(?:(? with data set (?:#\\d+|"[^"]+"))\\s\\()?/', $test, $matches)) { + $test = $matches['name'] . ($matches['dataname'] ?? ''); + } + return $test; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -/** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit - */ -final class RuntimeException extends \RuntimeException implements \PHPUnit\TextUI\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; +namespace PHPUnit\Runner; +use function array_diff; +use function array_values; +use function basename; +use function class_exists; +use function get_declared_classes; use function sprintf; -use RuntimeException; +use function stripos; +use function strlen; +use function substr; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\FileLoader; +use ReflectionClass; +use ReflectionException; /** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ -final class TestDirectoryNotFoundException extends RuntimeException implements \PHPUnit\TextUI\Exception +final class StandardTestSuiteLoader implements \PHPUnit\Runner\TestSuiteLoader { - public function __construct(string $path) + /** + * @throws Exception + */ + public function load(string $suiteClassFile) : ReflectionClass { - parent::__construct(sprintf('Test directory "%s" not found', $path)); + $suiteClassName = basename($suiteClassFile, '.php'); + $loadedClasses = get_declared_classes(); + if (!class_exists($suiteClassName, \false)) { + /* @noinspection UnusedFunctionResultInspection */ + FileLoader::checkAndLoad($suiteClassFile); + $loadedClasses = array_values(array_diff(get_declared_classes(), $loadedClasses)); + if (empty($loadedClasses)) { + throw $this->exceptionFor($suiteClassName, $suiteClassFile); + } + } + if (!class_exists($suiteClassName, \false)) { + $offset = 0 - strlen($suiteClassName); + foreach ($loadedClasses as $loadedClass) { + // @see https://github.com/sebastianbergmann/phpunit/issues/5020 + if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0 || stripos(substr($loadedClass, $offset - 1), '_' . $suiteClassName) === 0) { + $suiteClassName = $loadedClass; + break; + } + } + } + if (!class_exists($suiteClassName, \false)) { + throw $this->exceptionFor($suiteClassName, $suiteClassFile); + } + try { + $class = new ReflectionClass($suiteClassName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Runner\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if ($class->isSubclassOf(TestCase::class) && !$class->isAbstract()) { + return $class; + } + if ($class->hasMethod('suite')) { + try { + $method = $class->getMethod('suite'); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Runner\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { + return $class; + } + } + throw $this->exceptionFor($suiteClassName, $suiteClassFile); + } + public function reload(ReflectionClass $aClass) : ReflectionClass + { + return $aClass; + } + private function exceptionFor(string $className, string $filename) : \PHPUnit\Runner\Exception + { + return new \PHPUnit\Runner\Exception(sprintf("Class '%s' could not be found in '%s'.", $className, $filename)); } } getNumberOfColumns(); - if ($numberOfColumns === 'max' || $numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns) { - $numberOfColumns = $maxNumberOfColumns; - } - $this->numberOfColumns = $numberOfColumns; - $this->verbose = $verbose; - $this->debug = $debug; - $this->reverse = $reverse; - if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { - $this->colors = \true; - } else { - $this->colors = self::COLOR_ALWAYS === $colors; - } - $this->timer = new Timer(); - $this->timer->start(); - } - public function printResult(TestResult $result) : void - { - $this->printHeader($result); - $this->printErrors($result); - $this->printWarnings($result); - $this->printFailures($result); - $this->printRisky($result); - if ($this->verbose) { - $this->printIncompletes($result); - $this->printSkipped($result); - } - $this->printFooter($result); - } - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time) : void - { - $this->writeProgressWithColor('fg-red, bold', 'E'); - $this->lastTestFailed = \true; - } - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time) : void - { - $this->writeProgressWithColor('bg-red, fg-white', 'F'); - $this->lastTestFailed = \true; - } - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time) : void - { - $this->writeProgressWithColor('fg-yellow, bold', 'W'); - $this->lastTestFailed = \true; - } - /** - * Incomplete test. + * @var int */ - public function addIncompleteTest(Test $test, Throwable $t, float $time) : void - { - $this->writeProgressWithColor('fg-yellow, bold', 'I'); - $this->lastTestFailed = \true; - } + public const ORDER_SIZE = 5; /** - * Risky test. + * List of sorting weights for all test result codes. A higher number gives higher priority. */ - public function addRiskyTest(Test $test, Throwable $t, float $time) : void - { - $this->writeProgressWithColor('fg-yellow, bold', 'R'); - $this->lastTestFailed = \true; - } + private const DEFECT_SORT_WEIGHT = [\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR => 6, \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE => 5, \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING => 4, \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE => 3, \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY => 2, \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED => 1, \PHPUnit\Runner\BaseTestRunner::STATUS_UNKNOWN => 0]; + private const SIZE_SORT_WEIGHT = [TestUtil::SMALL => 1, TestUtil::MEDIUM => 2, TestUtil::LARGE => 3, TestUtil::UNKNOWN => 4]; /** - * Skipped test. + * @var array Associative array of (string => DEFECT_SORT_WEIGHT) elements */ - public function addSkippedTest(Test $test, Throwable $t, float $time) : void - { - $this->writeProgressWithColor('fg-cyan, bold', 'S'); - $this->lastTestFailed = \true; - } + private $defectSortOrder = []; /** - * A testsuite started. + * @var TestResultCache */ - public function startTestSuite(TestSuite $suite) : void - { - if ($this->numTests == -1) { - $this->numTests = count($suite); - $this->numTestsWidth = strlen((string) $this->numTests); - $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - 2 * $this->numTestsWidth; - } - } + private $cache; /** - * A testsuite ended. + * @var array A list of normalized names of tests before reordering */ - public function endTestSuite(TestSuite $suite) : void - { - } + private $originalExecutionOrder = []; /** - * A test started. + * @var array A list of normalized names of tests affected by reordering */ - public function startTest(Test $test) : void + private $executionOrder = []; + public function __construct(?\PHPUnit\Runner\TestResultCache $cache = null) { - if ($this->debug) { - $this->write(sprintf("Test '%s' started\n", \PHPUnit\Util\Test::describeAsString($test))); - } + $this->cache = $cache ?? new \PHPUnit\Runner\NullTestResultCache(); } /** - * A test ended. + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception */ - public function endTest(Test $test, float $time) : void + public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = \true) : void { - if ($this->debug) { - $this->write(sprintf("Test '%s' ended\n", \PHPUnit\Util\Test::describeAsString($test))); - } - if (!$this->lastTestFailed) { - $this->writeProgress('.'); - } - if ($test instanceof TestCase) { - $this->numAssertions += $test->getNumAssertions(); - } elseif ($test instanceof PhptTestCase) { - $this->numAssertions++; - } - $this->lastTestFailed = \false; - if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { - $this->write($test->getActualOutput()); + $allowedOrders = [self::ORDER_DEFAULT, self::ORDER_REVERSED, self::ORDER_RANDOMIZED, self::ORDER_DURATION, self::ORDER_SIZE]; + if (!in_array($order, $allowedOrders, \true)) { + throw new \PHPUnit\Runner\Exception('$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]'); } - } - protected function printDefects(array $defects, string $type) : void - { - $count = count($defects); - if ($count == 0) { - return; + $allowedOrderDefects = [self::ORDER_DEFAULT, self::ORDER_DEFECTS_FIRST]; + if (!in_array($orderDefects, $allowedOrderDefects, \true)) { + throw new \PHPUnit\Runner\Exception('$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST'); } - if ($this->defectListPrinted) { - $this->write("\n--\n\n"); + if ($isRootTestSuite) { + $this->originalExecutionOrder = $this->calculateTestExecutionOrder($suite); } - $this->write(sprintf("There %s %d %s%s:\n", $count == 1 ? 'was' : 'were', $count, $type, $count == 1 ? '' : 's')); - $i = 1; - if ($this->reverse) { - $defects = array_reverse($defects); + if ($suite instanceof TestSuite) { + foreach ($suite as $_suite) { + $this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects, \false); + } + if ($orderDefects === self::ORDER_DEFECTS_FIRST) { + $this->addSuiteToDefectSortOrder($suite); + } + $this->sort($suite, $order, $resolveDependencies, $orderDefects); } - foreach ($defects as $defect) { - $this->printDefect($defect, $i++); + if ($isRootTestSuite) { + $this->executionOrder = $this->calculateTestExecutionOrder($suite); } - $this->defectListPrinted = \true; } - protected function printDefect(TestFailure $defect, int $count) : void + public function getOriginalExecutionOrder() : array { - $this->printDefectHeader($defect, $count); - $this->printDefectTrace($defect); + return $this->originalExecutionOrder; } - protected function printDefectHeader(TestFailure $defect, int $count) : void + public function getExecutionOrder() : array { - $this->write(sprintf("\n%d) %s\n", $count, $defect->getTestName())); + return $this->executionOrder; } - protected function printDefectTrace(TestFailure $defect) : void + private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects) : void { - $e = $defect->thrownException(); - $this->write((string) $e); - while ($e = $e->getPrevious()) { - $this->write("\nCaused by\n" . $e); + if (empty($suite->tests())) { + return; + } + if ($order === self::ORDER_REVERSED) { + $suite->setTests($this->reverse($suite->tests())); + } elseif ($order === self::ORDER_RANDOMIZED) { + $suite->setTests($this->randomize($suite->tests())); + } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { + $suite->setTests($this->sortByDuration($suite->tests())); + } elseif ($order === self::ORDER_SIZE) { + $suite->setTests($this->sortBySize($suite->tests())); + } + if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { + $suite->setTests($this->sortDefectsFirst($suite->tests())); + } + if ($resolveDependencies && !$suite instanceof DataProviderTestSuite) { + /** @var TestCase[] $tests */ + $tests = $suite->tests(); + $suite->setTests($this->resolveDependencies($tests)); } } - protected function printErrors(TestResult $result) : void - { - $this->printDefects($result->errors(), 'error'); - } - protected function printFailures(TestResult $result) : void - { - $this->printDefects($result->failures(), 'failure'); - } - protected function printWarnings(TestResult $result) : void + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function addSuiteToDefectSortOrder(TestSuite $suite) : void { - $this->printDefects($result->warnings(), 'warning'); + $max = 0; + foreach ($suite->tests() as $test) { + if (!$test instanceof Reorderable) { + continue; + } + if (!isset($this->defectSortOrder[$test->sortId()])) { + $this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())]; + $max = max($max, $this->defectSortOrder[$test->sortId()]); + } + } + $this->defectSortOrder[$suite->sortId()] = $max; } - protected function printIncompletes(TestResult $result) : void + private function reverse(array $tests) : array { - $this->printDefects($result->notImplemented(), 'incomplete test'); + return array_reverse($tests); } - protected function printRisky(TestResult $result) : void + private function randomize(array $tests) : array { - $this->printDefects($result->risky(), 'risky test'); + shuffle($tests); + return $tests; } - protected function printSkipped(TestResult $result) : void + private function sortDefectsFirst(array $tests) : array { - $this->printDefects($result->skipped(), 'skipped test'); + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) { + return $this->cmpDefectPriorityAndTime($left, $right); + } + ); + return $tests; } - protected function printHeader(TestResult $result) : void + private function sortByDuration(array $tests) : array { - if (count($result) > 0) { - $this->write(\PHP_EOL . \PHP_EOL . (new ResourceUsageFormatter())->resourceUsage($this->timer->stop()) . \PHP_EOL . \PHP_EOL); - } + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) { + return $this->cmpDuration($left, $right); + } + ); + return $tests; } - protected function printFooter(TestResult $result) : void + private function sortBySize(array $tests) : array { - if (count($result) === 0) { - $this->writeWithColor('fg-black, bg-yellow', 'No tests executed!'); - return; - } - if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { - $this->writeWithColor('fg-black, bg-green', sprintf('OK (%d test%s, %d assertion%s)', count($result), count($result) === 1 ? '' : 's', $this->numAssertions, $this->numAssertions === 1 ? '' : 's')); - return; - } - $color = 'fg-black, bg-yellow'; - if ($result->wasSuccessful()) { - if ($this->verbose || !$result->allHarmless()) { - $this->write("\n"); - } - $this->writeWithColor($color, 'OK, but incomplete, skipped, or risky tests!'); - } else { - $this->write("\n"); - if ($result->errorCount()) { - $color = 'fg-white, bg-red'; - $this->writeWithColor($color, 'ERRORS!'); - } elseif ($result->failureCount()) { - $color = 'fg-white, bg-red'; - $this->writeWithColor($color, 'FAILURES!'); - } elseif ($result->warningCount()) { - $color = 'fg-black, bg-yellow'; - $this->writeWithColor($color, 'WARNINGS!'); + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) { + return $this->cmpSize($left, $right); } - } - $this->writeCountString(count($result), 'Tests', $color, \true); - $this->writeCountString($this->numAssertions, 'Assertions', $color, \true); - $this->writeCountString($result->errorCount(), 'Errors', $color); - $this->writeCountString($result->failureCount(), 'Failures', $color); - $this->writeCountString($result->warningCount(), 'Warnings', $color); - $this->writeCountString($result->skippedCount(), 'Skipped', $color); - $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); - $this->writeCountString($result->riskyCount(), 'Risky', $color); - $this->writeWithColor($color, '.'); + ); + return $tests; } - protected function writeProgress(string $progress) : void + /** + * Comparator callback function to sort tests for "reach failure as fast as possible". + * + * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT + * 2. when tests are equally defective, sort the fastest to the front + * 3. do not reorder successful tests + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function cmpDefectPriorityAndTime(Test $a, Test $b) : int { - if ($this->debug) { - return; + if (!($a instanceof Reorderable && $b instanceof Reorderable)) { + return 0; } - $this->write($progress); - $this->column++; - $this->numTestsRun++; - if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { - if ($this->numTestsRun == $this->numTests) { - $this->write(str_repeat(' ', $this->maxColumn - $this->column)); - } - $this->write(sprintf(' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', $this->numTestsRun, $this->numTests, floor($this->numTestsRun / $this->numTests * 100))); - if ($this->column == $this->maxColumn) { - $this->writeNewLine(); - } + $priorityA = $this->defectSortOrder[$a->sortId()] ?? 0; + $priorityB = $this->defectSortOrder[$b->sortId()] ?? 0; + if ($priorityB <=> $priorityA) { + // Sort defect weight descending + return $priorityB <=> $priorityA; } - } - protected function writeNewLine() : void - { - $this->column = 0; - $this->write("\n"); + if ($priorityA || $priorityB) { + return $this->cmpDuration($a, $b); + } + // do not change execution order + return 0; } /** - * Formats a buffer with a specified ANSI color sequence if colors are - * enabled. + * Compares test duration for sorting tests by duration ascending. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function colorizeTextBox(string $color, string $buffer) : string + private function cmpDuration(Test $a, Test $b) : int { - if (!$this->colors) { - return $buffer; - } - $lines = preg_split('/\\r\\n|\\r|\\n/', $buffer); - $padding = max(array_map('\\strlen', $lines)); - $styledLines = []; - foreach ($lines as $line) { - $styledLines[] = Color::colorize($color, str_pad($line, $padding)); + if (!($a instanceof Reorderable && $b instanceof Reorderable)) { + return 0; } - return implode(\PHP_EOL, $styledLines); + return $this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId()); } /** - * Writes a buffer out with a color sequence if colors are enabled. + * Compares test size for sorting tests small->medium->large->unknown. */ - protected function writeWithColor(string $color, string $buffer, bool $lf = \true) : void + private function cmpSize(Test $a, Test $b) : int { - $this->write($this->colorizeTextBox($color, $buffer)); - if ($lf) { - $this->write(\PHP_EOL); - } + $sizeA = $a instanceof TestCase || $a instanceof DataProviderTestSuite ? $a->getSize() : TestUtil::UNKNOWN; + $sizeB = $b instanceof TestCase || $b instanceof DataProviderTestSuite ? $b->getSize() : TestUtil::UNKNOWN; + return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; } /** - * Writes progress with a color sequence if colors are enabled. + * Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible. + * The algorithm will leave the tests in original running order when it can. + * For more details see the documentation for test dependencies. + * + * Short description of algorithm: + * 1. Pick the next Test from remaining tests to be checked for dependencies. + * 2. If the test has no dependencies: mark done, start again from the top + * 3. If the test has dependencies but none left to do: mark done, start again from the top + * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution. + * + * @param array $tests + * + * @return array */ - protected function writeProgressWithColor(string $color, string $buffer) : void + private function resolveDependencies(array $tests) : array { - $buffer = $this->colorizeTextBox($color, $buffer); - $this->writeProgress($buffer); + $newTestOrder = []; + $i = 0; + $provided = []; + do { + if ([] === array_diff($tests[$i]->requires(), $provided)) { + $provided = array_merge($provided, $tests[$i]->provides()); + $newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1)); + $i = 0; + } else { + $i++; + } + } while (!empty($tests) && $i < count($tests)); + return array_merge($newTestOrder, $tests); } - private function writeCountString(int $count, string $name, string $color, bool $always = \false) : void + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function calculateTestExecutionOrder(Test $suite) : array { - static $first = \true; - if ($always || $count > 0) { - $this->writeWithColor($color, sprintf('%s%s: %d', !$first ? ', ' : '', $name, $count), \false); - $first = \false; + $tests = []; + if ($suite instanceof TestSuite) { + foreach ($suite->tests() as $test) { + if (!$test instanceof TestSuite && $test instanceof Reorderable) { + $tests[] = $test->sortId(); + } else { + $tests = array_merge($tests, $this->calculateTestExecutionOrder($test)); + } + } } + return $tests; } } [], 'listGroups' => \false, 'listSuites' => \false, 'listTests' => \false, 'listTestsXml' => \false, 'loader' => null, 'useDefaultConfiguration' => \true, 'loadedExtensions' => [], 'unavailableExtensions' => [], 'notLoadedExtensions' => []]; - if ($arguments->hasColors()) { - $result['colors'] = $arguments->colors(); - } - if ($arguments->hasBootstrap()) { - $result['bootstrap'] = $arguments->bootstrap(); - } - if ($arguments->hasCacheResult()) { - $result['cacheResult'] = $arguments->cacheResult(); - } - if ($arguments->hasCacheResultFile()) { - $result['cacheResultFile'] = $arguments->cacheResultFile(); - } - if ($arguments->hasColumns()) { - $result['columns'] = $arguments->columns(); - } - if ($arguments->hasConfiguration()) { - $result['configuration'] = $arguments->configuration(); - } - if ($arguments->hasCoverageCacheDirectory()) { - $result['coverageCacheDirectory'] = $arguments->coverageCacheDirectory(); - } - if ($arguments->hasWarmCoverageCache()) { - $result['warmCoverageCache'] = $arguments->warmCoverageCache(); - } - if ($arguments->hasCoverageClover()) { - $result['coverageClover'] = $arguments->coverageClover(); - } - if ($arguments->hasCoverageCobertura()) { - $result['coverageCobertura'] = $arguments->coverageCobertura(); - } - if ($arguments->hasCoverageCrap4J()) { - $result['coverageCrap4J'] = $arguments->coverageCrap4J(); - } - if ($arguments->hasCoverageHtml()) { - $result['coverageHtml'] = $arguments->coverageHtml(); - } - if ($arguments->hasCoveragePhp()) { - $result['coveragePHP'] = $arguments->coveragePhp(); - } - if ($arguments->hasCoverageText()) { - $result['coverageText'] = $arguments->coverageText(); - } - if ($arguments->hasCoverageTextShowUncoveredFiles()) { - $result['coverageTextShowUncoveredFiles'] = $arguments->hasCoverageTextShowUncoveredFiles(); - } - if ($arguments->hasCoverageTextShowOnlySummary()) { - $result['coverageTextShowOnlySummary'] = $arguments->coverageTextShowOnlySummary(); - } - if ($arguments->hasCoverageXml()) { - $result['coverageXml'] = $arguments->coverageXml(); - } - if ($arguments->hasPathCoverage()) { - $result['pathCoverage'] = $arguments->pathCoverage(); - } - if ($arguments->hasDebug()) { - $result['debug'] = $arguments->debug(); - } - if ($arguments->hasHelp()) { - $result['help'] = $arguments->help(); - } - if ($arguments->hasFilter()) { - $result['filter'] = $arguments->filter(); - } - if ($arguments->hasTestSuite()) { - $result['testsuite'] = $arguments->testSuite(); - } - if ($arguments->hasGroups()) { - $result['groups'] = $arguments->groups(); - } - if ($arguments->hasExcludeGroups()) { - $result['excludeGroups'] = $arguments->excludeGroups(); - } - if ($arguments->hasTestsCovering()) { - $result['testsCovering'] = $arguments->testsCovering(); - } - if ($arguments->hasTestsUsing()) { - $result['testsUsing'] = $arguments->testsUsing(); - } - if ($arguments->hasTestSuffixes()) { - $result['testSuffixes'] = $arguments->testSuffixes(); - } - if ($arguments->hasIncludePath()) { - $result['includePath'] = $arguments->includePath(); - } - if ($arguments->hasListGroups()) { - $result['listGroups'] = $arguments->listGroups(); - } - if ($arguments->hasListSuites()) { - $result['listSuites'] = $arguments->listSuites(); - } - if ($arguments->hasListTests()) { - $result['listTests'] = $arguments->listTests(); - } - if ($arguments->hasListTestsXml()) { - $result['listTestsXml'] = $arguments->listTestsXml(); - } - if ($arguments->hasPrinter()) { - $result['printer'] = $arguments->printer(); - } - if ($arguments->hasLoader()) { - $result['loader'] = $arguments->loader(); - } - if ($arguments->hasJunitLogfile()) { - $result['junitLogfile'] = $arguments->junitLogfile(); - } - if ($arguments->hasTeamcityLogfile()) { - $result['teamcityLogfile'] = $arguments->teamcityLogfile(); - } - if ($arguments->hasExecutionOrder()) { - $result['executionOrder'] = $arguments->executionOrder(); - } - if ($arguments->hasExecutionOrderDefects()) { - $result['executionOrderDefects'] = $arguments->executionOrderDefects(); - } - if ($arguments->hasExtensions()) { - $result['extensions'] = $arguments->extensions(); - } - if ($arguments->hasUnavailableExtensions()) { - $result['unavailableExtensions'] = $arguments->unavailableExtensions(); - } - if ($arguments->hasResolveDependencies()) { - $result['resolveDependencies'] = $arguments->resolveDependencies(); - } - if ($arguments->hasProcessIsolation()) { - $result['processIsolation'] = $arguments->processIsolation(); - } - if ($arguments->hasRepeat()) { - $result['repeat'] = $arguments->repeat(); - } - if ($arguments->hasStderr()) { - $result['stderr'] = $arguments->stderr(); - } - if ($arguments->hasStopOnDefect()) { - $result['stopOnDefect'] = $arguments->stopOnDefect(); - } - if ($arguments->hasStopOnError()) { - $result['stopOnError'] = $arguments->stopOnError(); - } - if ($arguments->hasStopOnFailure()) { - $result['stopOnFailure'] = $arguments->stopOnFailure(); - } - if ($arguments->hasStopOnWarning()) { - $result['stopOnWarning'] = $arguments->stopOnWarning(); - } - if ($arguments->hasStopOnIncomplete()) { - $result['stopOnIncomplete'] = $arguments->stopOnIncomplete(); - } - if ($arguments->hasStopOnRisky()) { - $result['stopOnRisky'] = $arguments->stopOnRisky(); - } - if ($arguments->hasStopOnSkipped()) { - $result['stopOnSkipped'] = $arguments->stopOnSkipped(); - } - if ($arguments->hasFailOnEmptyTestSuite()) { - $result['failOnEmptyTestSuite'] = $arguments->failOnEmptyTestSuite(); - } - if ($arguments->hasFailOnIncomplete()) { - $result['failOnIncomplete'] = $arguments->failOnIncomplete(); - } - if ($arguments->hasFailOnRisky()) { - $result['failOnRisky'] = $arguments->failOnRisky(); - } - if ($arguments->hasFailOnSkipped()) { - $result['failOnSkipped'] = $arguments->failOnSkipped(); - } - if ($arguments->hasFailOnWarning()) { - $result['failOnWarning'] = $arguments->failOnWarning(); - } - if ($arguments->hasTestdoxGroups()) { - $result['testdoxGroups'] = $arguments->testdoxGroups(); - } - if ($arguments->hasTestdoxExcludeGroups()) { - $result['testdoxExcludeGroups'] = $arguments->testdoxExcludeGroups(); - } - if ($arguments->hasTestdoxHtmlFile()) { - $result['testdoxHTMLFile'] = $arguments->testdoxHtmlFile(); - } - if ($arguments->hasTestdoxTextFile()) { - $result['testdoxTextFile'] = $arguments->testdoxTextFile(); - } - if ($arguments->hasTestdoxXmlFile()) { - $result['testdoxXMLFile'] = $arguments->testdoxXmlFile(); - } - if ($arguments->hasUseDefaultConfiguration()) { - $result['useDefaultConfiguration'] = $arguments->useDefaultConfiguration(); - } - if ($arguments->hasNoExtensions()) { - $result['noExtensions'] = $arguments->noExtensions(); - } - if ($arguments->hasNoCoverage()) { - $result['noCoverage'] = $arguments->noCoverage(); - } - if ($arguments->hasNoLogging()) { - $result['noLogging'] = $arguments->noLogging(); - } - if ($arguments->hasNoInteraction()) { - $result['noInteraction'] = $arguments->noInteraction(); - } - if ($arguments->hasBackupGlobals()) { - $result['backupGlobals'] = $arguments->backupGlobals(); - } - if ($arguments->hasBackupStaticAttributes()) { - $result['backupStaticAttributes'] = $arguments->backupStaticAttributes(); - } - if ($arguments->hasVerbose()) { - $result['verbose'] = $arguments->verbose(); - } - if ($arguments->hasReportUselessTests()) { - $result['reportUselessTests'] = $arguments->reportUselessTests(); - } - if ($arguments->hasStrictCoverage()) { - $result['strictCoverage'] = $arguments->strictCoverage(); - } - if ($arguments->hasDisableCodeCoverageIgnore()) { - $result['disableCodeCoverageIgnore'] = $arguments->disableCodeCoverageIgnore(); - } - if ($arguments->hasBeStrictAboutChangesToGlobalState()) { - $result['beStrictAboutChangesToGlobalState'] = $arguments->beStrictAboutChangesToGlobalState(); + if (self::$pharVersion !== '') { + return self::$pharVersion; } - if ($arguments->hasDisallowTestOutput()) { - $result['disallowTestOutput'] = $arguments->disallowTestOutput(); + if (self::$version === '') { + self::$version = (new VersionId('9.5.26', dirname(__DIR__, 2)))->getVersion(); } - if ($arguments->hasBeStrictAboutResourceUsageDuringSmallTests()) { - $result['beStrictAboutResourceUsageDuringSmallTests'] = $arguments->beStrictAboutResourceUsageDuringSmallTests(); + return self::$version; + } + public static function series() : string + { + if (strpos(self::id(), '-')) { + $version = explode('-', self::id())[0]; + } else { + $version = self::id(); } - if ($arguments->hasDefaultTimeLimit()) { - $result['defaultTimeLimit'] = $arguments->defaultTimeLimit(); + return implode('.', array_slice(explode('.', $version), 0, 2)); + } + public static function getVersionString() : string + { + return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use function array_map; +use function array_merge; +use function class_exists; +use function explode; +use function is_numeric; +use function str_replace; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\TextUI\XmlConfiguration\Extension; +use PHPUnit\Util\Log\TeamCity; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use PHPUnit\SebastianBergmann\CliParser\Exception as CliParserException; +use PHPUnit\SebastianBergmann\CliParser\Parser as CliParser; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Builder +{ + private const LONG_OPTIONS = ['atleast-version=', 'prepend=', 'bootstrap=', 'cache-result', 'do-not-cache-result', 'cache-result-file=', 'check-version', 'colors==', 'columns=', 'configuration=', 'coverage-cache=', 'warm-coverage-cache', 'coverage-filter=', 'coverage-clover=', 'coverage-cobertura=', 'coverage-crap4j=', 'coverage-html=', 'coverage-php=', 'coverage-text==', 'coverage-xml=', 'path-coverage', 'debug', 'disallow-test-output', 'disallow-resource-usage', 'disallow-todo-tests', 'default-time-limit=', 'enforce-time-limit', 'exclude-group=', 'extensions=', 'filter=', 'generate-configuration', 'globals-backup', 'group=', 'covers=', 'uses=', 'help', 'resolve-dependencies', 'ignore-dependencies', 'include-path=', 'list-groups', 'list-suites', 'list-tests', 'list-tests-xml=', 'loader=', 'log-junit=', 'log-teamcity=', 'migrate-configuration', 'no-configuration', 'no-coverage', 'no-logging', 'no-interaction', 'no-extensions', 'order-by=', 'printer=', 'process-isolation', 'repeat=', 'dont-report-useless-tests', 'random-order', 'random-order-seed=', 'reverse-order', 'reverse-list', 'static-backup', 'stderr', 'stop-on-defect', 'stop-on-error', 'stop-on-failure', 'stop-on-warning', 'stop-on-incomplete', 'stop-on-risky', 'stop-on-skipped', 'fail-on-empty-test-suite', 'fail-on-incomplete', 'fail-on-risky', 'fail-on-skipped', 'fail-on-warning', 'strict-coverage', 'disable-coverage-ignore', 'strict-global-state', 'teamcity', 'testdox', 'testdox-group=', 'testdox-exclude-group=', 'testdox-html=', 'testdox-text=', 'testdox-xml=', 'test-suffix=', 'testsuite=', 'verbose', 'version', 'whitelist=', 'dump-xdebug-filter=']; + private const SHORT_OPTIONS = 'd:c:hv'; + public function fromParameters(array $parameters, array $additionalLongOptions) : \PHPUnit\TextUI\CliArguments\Configuration + { + try { + $options = (new CliParser())->parse($parameters, self::SHORT_OPTIONS, array_merge(self::LONG_OPTIONS, $additionalLongOptions)); + } catch (CliParserException $e) { + throw new \PHPUnit\TextUI\CliArguments\Exception($e->getMessage(), (int) $e->getCode(), $e); } - if ($arguments->hasEnforceTimeLimit()) { - $result['enforceTimeLimit'] = $arguments->enforceTimeLimit(); + $argument = null; + $atLeastVersion = null; + $backupGlobals = null; + $backupStaticAttributes = null; + $beStrictAboutChangesToGlobalState = null; + $beStrictAboutResourceUsageDuringSmallTests = null; + $bootstrap = null; + $cacheResult = null; + $cacheResultFile = null; + $checkVersion = null; + $colors = null; + $columns = null; + $configuration = null; + $coverageCacheDirectory = null; + $warmCoverageCache = null; + $coverageFilter = null; + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4J = null; + $coverageHtml = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = null; + $coverageTextShowOnlySummary = null; + $coverageXml = null; + $pathCoverage = null; + $debug = null; + $defaultTimeLimit = null; + $disableCodeCoverageIgnore = null; + $disallowTestOutput = null; + $disallowTodoAnnotatedTests = null; + $enforceTimeLimit = null; + $excludeGroups = null; + $executionOrder = null; + $executionOrderDefects = null; + $extensions = []; + $unavailableExtensions = []; + $failOnEmptyTestSuite = null; + $failOnIncomplete = null; + $failOnRisky = null; + $failOnSkipped = null; + $failOnWarning = null; + $filter = null; + $generateConfiguration = null; + $migrateConfiguration = null; + $groups = null; + $testsCovering = null; + $testsUsing = null; + $help = null; + $includePath = null; + $iniSettings = []; + $junitLogfile = null; + $listGroups = null; + $listSuites = null; + $listTests = null; + $listTestsXml = null; + $loader = null; + $noCoverage = null; + $noExtensions = null; + $noInteraction = null; + $noLogging = null; + $printer = null; + $processIsolation = null; + $randomOrderSeed = null; + $repeat = null; + $reportUselessTests = null; + $resolveDependencies = null; + $reverseList = null; + $stderr = null; + $strictCoverage = null; + $stopOnDefect = null; + $stopOnError = null; + $stopOnFailure = null; + $stopOnIncomplete = null; + $stopOnRisky = null; + $stopOnSkipped = null; + $stopOnWarning = null; + $teamcityLogfile = null; + $testdoxExcludeGroups = null; + $testdoxGroups = null; + $testdoxHtmlFile = null; + $testdoxTextFile = null; + $testdoxXmlFile = null; + $testSuffixes = null; + $testSuite = null; + $unrecognizedOptions = []; + $unrecognizedOrderBy = null; + $useDefaultConfiguration = null; + $verbose = null; + $version = null; + $xdebugFilterFile = null; + if (isset($options[1][0])) { + $argument = $options[1][0]; } - if ($arguments->hasDisallowTodoAnnotatedTests()) { - $result['disallowTodoAnnotatedTests'] = $arguments->disallowTodoAnnotatedTests(); + foreach ($options[0] as $option) { + switch ($option[0]) { + case '--colors': + $colors = $option[1] ?: DefaultResultPrinter::COLOR_AUTO; + break; + case '--bootstrap': + $bootstrap = $option[1]; + break; + case '--cache-result': + $cacheResult = \true; + break; + case '--do-not-cache-result': + $cacheResult = \false; + break; + case '--cache-result-file': + $cacheResultFile = $option[1]; + break; + case '--columns': + if (is_numeric($option[1])) { + $columns = (int) $option[1]; + } elseif ($option[1] === 'max') { + $columns = 'max'; + } + break; + case 'c': + case '--configuration': + $configuration = $option[1]; + break; + case '--coverage-cache': + $coverageCacheDirectory = $option[1]; + break; + case '--warm-coverage-cache': + $warmCoverageCache = \true; + break; + case '--coverage-clover': + $coverageClover = $option[1]; + break; + case '--coverage-cobertura': + $coverageCobertura = $option[1]; + break; + case '--coverage-crap4j': + $coverageCrap4J = $option[1]; + break; + case '--coverage-html': + $coverageHtml = $option[1]; + break; + case '--coverage-php': + $coveragePhp = $option[1]; + break; + case '--coverage-text': + if ($option[1] === null) { + $option[1] = 'php://stdout'; + } + $coverageText = $option[1]; + $coverageTextShowUncoveredFiles = \false; + $coverageTextShowOnlySummary = \false; + break; + case '--coverage-xml': + $coverageXml = $option[1]; + break; + case '--path-coverage': + $pathCoverage = \true; + break; + case 'd': + $tmp = explode('=', $option[1]); + if (isset($tmp[0])) { + if (isset($tmp[1])) { + $iniSettings[$tmp[0]] = $tmp[1]; + } else { + $iniSettings[$tmp[0]] = '1'; + } + } + break; + case '--debug': + $debug = \true; + break; + case 'h': + case '--help': + $help = \true; + break; + case '--filter': + $filter = $option[1]; + break; + case '--testsuite': + $testSuite = $option[1]; + break; + case '--generate-configuration': + $generateConfiguration = \true; + break; + case '--migrate-configuration': + $migrateConfiguration = \true; + break; + case '--group': + $groups = explode(',', $option[1]); + break; + case '--exclude-group': + $excludeGroups = explode(',', $option[1]); + break; + case '--covers': + $testsCovering = array_map('strtolower', explode(',', $option[1])); + break; + case '--uses': + $testsUsing = array_map('strtolower', explode(',', $option[1])); + break; + case '--test-suffix': + $testSuffixes = explode(',', $option[1]); + break; + case '--include-path': + $includePath = $option[1]; + break; + case '--list-groups': + $listGroups = \true; + break; + case '--list-suites': + $listSuites = \true; + break; + case '--list-tests': + $listTests = \true; + break; + case '--list-tests-xml': + $listTestsXml = $option[1]; + break; + case '--printer': + $printer = $option[1]; + break; + case '--loader': + $loader = $option[1]; + break; + case '--log-junit': + $junitLogfile = $option[1]; + break; + case '--log-teamcity': + $teamcityLogfile = $option[1]; + break; + case '--order-by': + foreach (explode(',', $option[1]) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + $resolveDependencies = \true; + break; + case 'defects': + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + break; + case 'depends': + $resolveDependencies = \true; + break; + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + break; + case 'no-depends': + $resolveDependencies = \false; + break; + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + break; + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + break; + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + break; + default: + $unrecognizedOrderBy = $order; + } + } + break; + case '--process-isolation': + $processIsolation = \true; + break; + case '--repeat': + $repeat = (int) $option[1]; + break; + case '--stderr': + $stderr = \true; + break; + case '--stop-on-defect': + $stopOnDefect = \true; + break; + case '--stop-on-error': + $stopOnError = \true; + break; + case '--stop-on-failure': + $stopOnFailure = \true; + break; + case '--stop-on-warning': + $stopOnWarning = \true; + break; + case '--stop-on-incomplete': + $stopOnIncomplete = \true; + break; + case '--stop-on-risky': + $stopOnRisky = \true; + break; + case '--stop-on-skipped': + $stopOnSkipped = \true; + break; + case '--fail-on-empty-test-suite': + $failOnEmptyTestSuite = \true; + break; + case '--fail-on-incomplete': + $failOnIncomplete = \true; + break; + case '--fail-on-risky': + $failOnRisky = \true; + break; + case '--fail-on-skipped': + $failOnSkipped = \true; + break; + case '--fail-on-warning': + $failOnWarning = \true; + break; + case '--teamcity': + $printer = TeamCity::class; + break; + case '--testdox': + $printer = CliTestDoxPrinter::class; + break; + case '--testdox-group': + $testdoxGroups = explode(',', $option[1]); + break; + case '--testdox-exclude-group': + $testdoxExcludeGroups = explode(',', $option[1]); + break; + case '--testdox-html': + $testdoxHtmlFile = $option[1]; + break; + case '--testdox-text': + $testdoxTextFile = $option[1]; + break; + case '--testdox-xml': + $testdoxXmlFile = $option[1]; + break; + case '--no-configuration': + $useDefaultConfiguration = \false; + break; + case '--extensions': + foreach (explode(',', $option[1]) as $extensionClass) { + if (!class_exists($extensionClass)) { + $unavailableExtensions[] = $extensionClass; + continue; + } + $extensions[] = new Extension($extensionClass, '', []); + } + break; + case '--no-extensions': + $noExtensions = \true; + break; + case '--no-coverage': + $noCoverage = \true; + break; + case '--no-logging': + $noLogging = \true; + break; + case '--no-interaction': + $noInteraction = \true; + break; + case '--globals-backup': + $backupGlobals = \true; + break; + case '--static-backup': + $backupStaticAttributes = \true; + break; + case 'v': + case '--verbose': + $verbose = \true; + break; + case '--atleast-version': + $atLeastVersion = $option[1]; + break; + case '--version': + $version = \true; + break; + case '--dont-report-useless-tests': + $reportUselessTests = \false; + break; + case '--strict-coverage': + $strictCoverage = \true; + break; + case '--disable-coverage-ignore': + $disableCodeCoverageIgnore = \true; + break; + case '--strict-global-state': + $beStrictAboutChangesToGlobalState = \true; + break; + case '--disallow-test-output': + $disallowTestOutput = \true; + break; + case '--disallow-resource-usage': + $beStrictAboutResourceUsageDuringSmallTests = \true; + break; + case '--default-time-limit': + $defaultTimeLimit = (int) $option[1]; + break; + case '--enforce-time-limit': + $enforceTimeLimit = \true; + break; + case '--disallow-todo-tests': + $disallowTodoAnnotatedTests = \true; + break; + case '--reverse-list': + $reverseList = \true; + break; + case '--check-version': + $checkVersion = \true; + break; + case '--coverage-filter': + case '--whitelist': + if ($coverageFilter === null) { + $coverageFilter = []; + } + $coverageFilter[] = $option[1]; + break; + case '--random-order': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + break; + case '--random-order-seed': + $randomOrderSeed = (int) $option[1]; + break; + case '--resolve-dependencies': + $resolveDependencies = \true; + break; + case '--ignore-dependencies': + $resolveDependencies = \false; + break; + case '--reverse-order': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + break; + case '--dump-xdebug-filter': + $xdebugFilterFile = $option[1]; + break; + default: + $unrecognizedOptions[str_replace('--', '', $option[0])] = $option[1]; + } } - if ($arguments->hasReverseList()) { - $result['reverseList'] = $arguments->reverseList(); + if (empty($extensions)) { + $extensions = null; } - if ($arguments->hasCoverageFilter()) { - $result['coverageFilter'] = $arguments->coverageFilter(); + if (empty($unavailableExtensions)) { + $unavailableExtensions = null; } - if ($arguments->hasRandomOrderSeed()) { - $result['randomOrderSeed'] = $arguments->randomOrderSeed(); + if (empty($iniSettings)) { + $iniSettings = null; } - if ($arguments->hasXdebugFilterFile()) { - $result['xdebugFilterFile'] = $arguments->xdebugFilterFile(); + if (empty($coverageFilter)) { + $coverageFilter = null; } - return $result; + return new \PHPUnit\TextUI\CliArguments\Configuration($argument, $atLeastVersion, $backupGlobals, $backupStaticAttributes, $beStrictAboutChangesToGlobalState, $beStrictAboutResourceUsageDuringSmallTests, $bootstrap, $cacheResult, $cacheResultFile, $checkVersion, $colors, $columns, $configuration, $coverageClover, $coverageCobertura, $coverageCrap4J, $coverageHtml, $coveragePhp, $coverageText, $coverageTextShowUncoveredFiles, $coverageTextShowOnlySummary, $coverageXml, $pathCoverage, $coverageCacheDirectory, $warmCoverageCache, $debug, $defaultTimeLimit, $disableCodeCoverageIgnore, $disallowTestOutput, $disallowTodoAnnotatedTests, $enforceTimeLimit, $excludeGroups, $executionOrder, $executionOrderDefects, $extensions, $unavailableExtensions, $failOnEmptyTestSuite, $failOnIncomplete, $failOnRisky, $failOnSkipped, $failOnWarning, $filter, $generateConfiguration, $migrateConfiguration, $groups, $testsCovering, $testsUsing, $help, $includePath, $iniSettings, $junitLogfile, $listGroups, $listSuites, $listTests, $listTestsXml, $loader, $noCoverage, $noExtensions, $noInteraction, $noLogging, $printer, $processIsolation, $randomOrderSeed, $repeat, $reportUselessTests, $resolveDependencies, $reverseList, $stderr, $strictCoverage, $stopOnDefect, $stopOnError, $stopOnFailure, $stopOnIncomplete, $stopOnRisky, $stopOnSkipped, $stopOnWarning, $teamcityLogfile, $testdoxExcludeGroups, $testdoxGroups, $testdoxHtmlFile, $testdoxTextFile, $testdoxXmlFile, $testSuffixes, $testSuite, $unrecognizedOptions, $unrecognizedOrderBy, $useDefaultConfiguration, $verbose, $version, $coverageFilter, $xdebugFilterFile); } } verbose === null) { throw new \PHPUnit\TextUI\CliArguments\Exception(); } - return $this->verbose; - } - public function hasVersion() : bool - { - return $this->version !== null; - } - /** - * @throws Exception - */ - public function version() : bool - { - if ($this->version === null) { - throw new \PHPUnit\TextUI\CliArguments\Exception(); + return $this->verbose; + } + public function hasVersion() : bool + { + return $this->version !== null; + } + /** + * @throws Exception + */ + public function version() : bool + { + if ($this->version === null) { + throw new \PHPUnit\TextUI\CliArguments\Exception(); + } + return $this->version; + } + public function hasXdebugFilterFile() : bool + { + return $this->xdebugFilterFile !== null; + } + /** + * @throws Exception + */ + public function xdebugFilterFile() : string + { + if ($this->xdebugFilterFile === null) { + throw new \PHPUnit\TextUI\CliArguments\Exception(); + } + return $this->xdebugFilterFile; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Mapper +{ + /** + * @throws Exception + */ + public function mapToLegacyArray(\PHPUnit\TextUI\CliArguments\Configuration $arguments) : array + { + $result = ['extensions' => [], 'listGroups' => \false, 'listSuites' => \false, 'listTests' => \false, 'listTestsXml' => \false, 'loader' => null, 'useDefaultConfiguration' => \true, 'loadedExtensions' => [], 'unavailableExtensions' => [], 'notLoadedExtensions' => []]; + if ($arguments->hasColors()) { + $result['colors'] = $arguments->colors(); + } + if ($arguments->hasBootstrap()) { + $result['bootstrap'] = $arguments->bootstrap(); + } + if ($arguments->hasCacheResult()) { + $result['cacheResult'] = $arguments->cacheResult(); + } + if ($arguments->hasCacheResultFile()) { + $result['cacheResultFile'] = $arguments->cacheResultFile(); + } + if ($arguments->hasColumns()) { + $result['columns'] = $arguments->columns(); + } + if ($arguments->hasConfiguration()) { + $result['configuration'] = $arguments->configuration(); + } + if ($arguments->hasCoverageCacheDirectory()) { + $result['coverageCacheDirectory'] = $arguments->coverageCacheDirectory(); + } + if ($arguments->hasWarmCoverageCache()) { + $result['warmCoverageCache'] = $arguments->warmCoverageCache(); + } + if ($arguments->hasCoverageClover()) { + $result['coverageClover'] = $arguments->coverageClover(); + } + if ($arguments->hasCoverageCobertura()) { + $result['coverageCobertura'] = $arguments->coverageCobertura(); + } + if ($arguments->hasCoverageCrap4J()) { + $result['coverageCrap4J'] = $arguments->coverageCrap4J(); + } + if ($arguments->hasCoverageHtml()) { + $result['coverageHtml'] = $arguments->coverageHtml(); + } + if ($arguments->hasCoveragePhp()) { + $result['coveragePHP'] = $arguments->coveragePhp(); + } + if ($arguments->hasCoverageText()) { + $result['coverageText'] = $arguments->coverageText(); + } + if ($arguments->hasCoverageTextShowUncoveredFiles()) { + $result['coverageTextShowUncoveredFiles'] = $arguments->hasCoverageTextShowUncoveredFiles(); + } + if ($arguments->hasCoverageTextShowOnlySummary()) { + $result['coverageTextShowOnlySummary'] = $arguments->coverageTextShowOnlySummary(); + } + if ($arguments->hasCoverageXml()) { + $result['coverageXml'] = $arguments->coverageXml(); + } + if ($arguments->hasPathCoverage()) { + $result['pathCoverage'] = $arguments->pathCoverage(); + } + if ($arguments->hasDebug()) { + $result['debug'] = $arguments->debug(); + } + if ($arguments->hasHelp()) { + $result['help'] = $arguments->help(); + } + if ($arguments->hasFilter()) { + $result['filter'] = $arguments->filter(); + } + if ($arguments->hasTestSuite()) { + $result['testsuite'] = $arguments->testSuite(); + } + if ($arguments->hasGroups()) { + $result['groups'] = $arguments->groups(); + } + if ($arguments->hasExcludeGroups()) { + $result['excludeGroups'] = $arguments->excludeGroups(); + } + if ($arguments->hasTestsCovering()) { + $result['testsCovering'] = $arguments->testsCovering(); + } + if ($arguments->hasTestsUsing()) { + $result['testsUsing'] = $arguments->testsUsing(); + } + if ($arguments->hasTestSuffixes()) { + $result['testSuffixes'] = $arguments->testSuffixes(); + } + if ($arguments->hasIncludePath()) { + $result['includePath'] = $arguments->includePath(); + } + if ($arguments->hasListGroups()) { + $result['listGroups'] = $arguments->listGroups(); + } + if ($arguments->hasListSuites()) { + $result['listSuites'] = $arguments->listSuites(); + } + if ($arguments->hasListTests()) { + $result['listTests'] = $arguments->listTests(); + } + if ($arguments->hasListTestsXml()) { + $result['listTestsXml'] = $arguments->listTestsXml(); + } + if ($arguments->hasPrinter()) { + $result['printer'] = $arguments->printer(); + } + if ($arguments->hasLoader()) { + $result['loader'] = $arguments->loader(); + } + if ($arguments->hasJunitLogfile()) { + $result['junitLogfile'] = $arguments->junitLogfile(); + } + if ($arguments->hasTeamcityLogfile()) { + $result['teamcityLogfile'] = $arguments->teamcityLogfile(); + } + if ($arguments->hasExecutionOrder()) { + $result['executionOrder'] = $arguments->executionOrder(); + } + if ($arguments->hasExecutionOrderDefects()) { + $result['executionOrderDefects'] = $arguments->executionOrderDefects(); + } + if ($arguments->hasExtensions()) { + $result['extensions'] = $arguments->extensions(); + } + if ($arguments->hasUnavailableExtensions()) { + $result['unavailableExtensions'] = $arguments->unavailableExtensions(); + } + if ($arguments->hasResolveDependencies()) { + $result['resolveDependencies'] = $arguments->resolveDependencies(); + } + if ($arguments->hasProcessIsolation()) { + $result['processIsolation'] = $arguments->processIsolation(); + } + if ($arguments->hasRepeat()) { + $result['repeat'] = $arguments->repeat(); + } + if ($arguments->hasStderr()) { + $result['stderr'] = $arguments->stderr(); + } + if ($arguments->hasStopOnDefect()) { + $result['stopOnDefect'] = $arguments->stopOnDefect(); + } + if ($arguments->hasStopOnError()) { + $result['stopOnError'] = $arguments->stopOnError(); + } + if ($arguments->hasStopOnFailure()) { + $result['stopOnFailure'] = $arguments->stopOnFailure(); + } + if ($arguments->hasStopOnWarning()) { + $result['stopOnWarning'] = $arguments->stopOnWarning(); + } + if ($arguments->hasStopOnIncomplete()) { + $result['stopOnIncomplete'] = $arguments->stopOnIncomplete(); + } + if ($arguments->hasStopOnRisky()) { + $result['stopOnRisky'] = $arguments->stopOnRisky(); + } + if ($arguments->hasStopOnSkipped()) { + $result['stopOnSkipped'] = $arguments->stopOnSkipped(); + } + if ($arguments->hasFailOnEmptyTestSuite()) { + $result['failOnEmptyTestSuite'] = $arguments->failOnEmptyTestSuite(); + } + if ($arguments->hasFailOnIncomplete()) { + $result['failOnIncomplete'] = $arguments->failOnIncomplete(); + } + if ($arguments->hasFailOnRisky()) { + $result['failOnRisky'] = $arguments->failOnRisky(); + } + if ($arguments->hasFailOnSkipped()) { + $result['failOnSkipped'] = $arguments->failOnSkipped(); + } + if ($arguments->hasFailOnWarning()) { + $result['failOnWarning'] = $arguments->failOnWarning(); + } + if ($arguments->hasTestdoxGroups()) { + $result['testdoxGroups'] = $arguments->testdoxGroups(); + } + if ($arguments->hasTestdoxExcludeGroups()) { + $result['testdoxExcludeGroups'] = $arguments->testdoxExcludeGroups(); + } + if ($arguments->hasTestdoxHtmlFile()) { + $result['testdoxHTMLFile'] = $arguments->testdoxHtmlFile(); + } + if ($arguments->hasTestdoxTextFile()) { + $result['testdoxTextFile'] = $arguments->testdoxTextFile(); + } + if ($arguments->hasTestdoxXmlFile()) { + $result['testdoxXMLFile'] = $arguments->testdoxXmlFile(); + } + if ($arguments->hasUseDefaultConfiguration()) { + $result['useDefaultConfiguration'] = $arguments->useDefaultConfiguration(); + } + if ($arguments->hasNoExtensions()) { + $result['noExtensions'] = $arguments->noExtensions(); } - return $this->version; - } - public function hasXdebugFilterFile() : bool - { - return $this->xdebugFilterFile !== null; - } - /** - * @throws Exception - */ - public function xdebugFilterFile() : string - { - if ($this->xdebugFilterFile === null) { - throw new \PHPUnit\TextUI\CliArguments\Exception(); + if ($arguments->hasNoCoverage()) { + $result['noCoverage'] = $arguments->noCoverage(); } - return $this->xdebugFilterFile; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use function array_map; -use function array_merge; -use function class_exists; -use function explode; -use function is_numeric; -use function str_replace; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\Extension; -use PHPUnit\Util\Log\TeamCity; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use PHPUnit\SebastianBergmann\CliParser\Exception as CliParserException; -use PHPUnit\SebastianBergmann\CliParser\Parser as CliParser; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Builder -{ - private const LONG_OPTIONS = ['atleast-version=', 'prepend=', 'bootstrap=', 'cache-result', 'do-not-cache-result', 'cache-result-file=', 'check-version', 'colors==', 'columns=', 'configuration=', 'coverage-cache=', 'warm-coverage-cache', 'coverage-filter=', 'coverage-clover=', 'coverage-cobertura=', 'coverage-crap4j=', 'coverage-html=', 'coverage-php=', 'coverage-text==', 'coverage-xml=', 'path-coverage', 'debug', 'disallow-test-output', 'disallow-resource-usage', 'disallow-todo-tests', 'default-time-limit=', 'enforce-time-limit', 'exclude-group=', 'extensions=', 'filter=', 'generate-configuration', 'globals-backup', 'group=', 'covers=', 'uses=', 'help', 'resolve-dependencies', 'ignore-dependencies', 'include-path=', 'list-groups', 'list-suites', 'list-tests', 'list-tests-xml=', 'loader=', 'log-junit=', 'log-teamcity=', 'migrate-configuration', 'no-configuration', 'no-coverage', 'no-logging', 'no-interaction', 'no-extensions', 'order-by=', 'printer=', 'process-isolation', 'repeat=', 'dont-report-useless-tests', 'random-order', 'random-order-seed=', 'reverse-order', 'reverse-list', 'static-backup', 'stderr', 'stop-on-defect', 'stop-on-error', 'stop-on-failure', 'stop-on-warning', 'stop-on-incomplete', 'stop-on-risky', 'stop-on-skipped', 'fail-on-empty-test-suite', 'fail-on-incomplete', 'fail-on-risky', 'fail-on-skipped', 'fail-on-warning', 'strict-coverage', 'disable-coverage-ignore', 'strict-global-state', 'teamcity', 'testdox', 'testdox-group=', 'testdox-exclude-group=', 'testdox-html=', 'testdox-text=', 'testdox-xml=', 'test-suffix=', 'testsuite=', 'verbose', 'version', 'whitelist=', 'dump-xdebug-filter=']; - private const SHORT_OPTIONS = 'd:c:hv'; - public function fromParameters(array $parameters, array $additionalLongOptions) : \PHPUnit\TextUI\CliArguments\Configuration - { - try { - $options = (new CliParser())->parse($parameters, self::SHORT_OPTIONS, array_merge(self::LONG_OPTIONS, $additionalLongOptions)); - } catch (CliParserException $e) { - throw new \PHPUnit\TextUI\CliArguments\Exception($e->getMessage(), (int) $e->getCode(), $e); + if ($arguments->hasNoLogging()) { + $result['noLogging'] = $arguments->noLogging(); } - $argument = null; - $atLeastVersion = null; - $backupGlobals = null; - $backupStaticAttributes = null; - $beStrictAboutChangesToGlobalState = null; - $beStrictAboutResourceUsageDuringSmallTests = null; - $bootstrap = null; - $cacheResult = null; - $cacheResultFile = null; - $checkVersion = null; - $colors = null; - $columns = null; - $configuration = null; - $coverageCacheDirectory = null; - $warmCoverageCache = null; - $coverageFilter = null; - $coverageClover = null; - $coverageCobertura = null; - $coverageCrap4J = null; - $coverageHtml = null; - $coveragePhp = null; - $coverageText = null; - $coverageTextShowUncoveredFiles = null; - $coverageTextShowOnlySummary = null; - $coverageXml = null; - $pathCoverage = null; - $debug = null; - $defaultTimeLimit = null; - $disableCodeCoverageIgnore = null; - $disallowTestOutput = null; - $disallowTodoAnnotatedTests = null; - $enforceTimeLimit = null; - $excludeGroups = null; - $executionOrder = null; - $executionOrderDefects = null; - $extensions = []; - $unavailableExtensions = []; - $failOnEmptyTestSuite = null; - $failOnIncomplete = null; - $failOnRisky = null; - $failOnSkipped = null; - $failOnWarning = null; - $filter = null; - $generateConfiguration = null; - $migrateConfiguration = null; - $groups = null; - $testsCovering = null; - $testsUsing = null; - $help = null; - $includePath = null; - $iniSettings = []; - $junitLogfile = null; - $listGroups = null; - $listSuites = null; - $listTests = null; - $listTestsXml = null; - $loader = null; - $noCoverage = null; - $noExtensions = null; - $noInteraction = null; - $noLogging = null; - $printer = null; - $processIsolation = null; - $randomOrderSeed = null; - $repeat = null; - $reportUselessTests = null; - $resolveDependencies = null; - $reverseList = null; - $stderr = null; - $strictCoverage = null; - $stopOnDefect = null; - $stopOnError = null; - $stopOnFailure = null; - $stopOnIncomplete = null; - $stopOnRisky = null; - $stopOnSkipped = null; - $stopOnWarning = null; - $teamcityLogfile = null; - $testdoxExcludeGroups = null; - $testdoxGroups = null; - $testdoxHtmlFile = null; - $testdoxTextFile = null; - $testdoxXmlFile = null; - $testSuffixes = null; - $testSuite = null; - $unrecognizedOptions = []; - $unrecognizedOrderBy = null; - $useDefaultConfiguration = null; - $verbose = null; - $version = null; - $xdebugFilterFile = null; - if (isset($options[1][0])) { - $argument = $options[1][0]; + if ($arguments->hasNoInteraction()) { + $result['noInteraction'] = $arguments->noInteraction(); } - foreach ($options[0] as $option) { - switch ($option[0]) { - case '--colors': - $colors = $option[1] ?: DefaultResultPrinter::COLOR_AUTO; - break; - case '--bootstrap': - $bootstrap = $option[1]; - break; - case '--cache-result': - $cacheResult = \true; - break; - case '--do-not-cache-result': - $cacheResult = \false; - break; - case '--cache-result-file': - $cacheResultFile = $option[1]; - break; - case '--columns': - if (is_numeric($option[1])) { - $columns = (int) $option[1]; - } elseif ($option[1] === 'max') { - $columns = 'max'; - } - break; - case 'c': - case '--configuration': - $configuration = $option[1]; - break; - case '--coverage-cache': - $coverageCacheDirectory = $option[1]; - break; - case '--warm-coverage-cache': - $warmCoverageCache = \true; - break; - case '--coverage-clover': - $coverageClover = $option[1]; - break; - case '--coverage-cobertura': - $coverageCobertura = $option[1]; - break; - case '--coverage-crap4j': - $coverageCrap4J = $option[1]; - break; - case '--coverage-html': - $coverageHtml = $option[1]; - break; - case '--coverage-php': - $coveragePhp = $option[1]; - break; - case '--coverage-text': - if ($option[1] === null) { - $option[1] = 'php://stdout'; - } - $coverageText = $option[1]; - $coverageTextShowUncoveredFiles = \false; - $coverageTextShowOnlySummary = \false; - break; - case '--coverage-xml': - $coverageXml = $option[1]; - break; - case '--path-coverage': - $pathCoverage = \true; - break; - case 'd': - $tmp = explode('=', $option[1]); - if (isset($tmp[0])) { - if (isset($tmp[1])) { - $iniSettings[$tmp[0]] = $tmp[1]; - } else { - $iniSettings[$tmp[0]] = '1'; - } - } - break; - case '--debug': - $debug = \true; - break; - case 'h': - case '--help': - $help = \true; - break; - case '--filter': - $filter = $option[1]; - break; - case '--testsuite': - $testSuite = $option[1]; - break; - case '--generate-configuration': - $generateConfiguration = \true; - break; - case '--migrate-configuration': - $migrateConfiguration = \true; - break; - case '--group': - $groups = explode(',', $option[1]); - break; - case '--exclude-group': - $excludeGroups = explode(',', $option[1]); - break; - case '--covers': - $testsCovering = array_map('strtolower', explode(',', $option[1])); - break; - case '--uses': - $testsUsing = array_map('strtolower', explode(',', $option[1])); - break; - case '--test-suffix': - $testSuffixes = explode(',', $option[1]); - break; - case '--include-path': - $includePath = $option[1]; - break; - case '--list-groups': - $listGroups = \true; - break; - case '--list-suites': - $listSuites = \true; - break; - case '--list-tests': - $listTests = \true; - break; - case '--list-tests-xml': - $listTestsXml = $option[1]; - break; - case '--printer': - $printer = $option[1]; - break; - case '--loader': - $loader = $option[1]; - break; - case '--log-junit': - $junitLogfile = $option[1]; - break; - case '--log-teamcity': - $teamcityLogfile = $option[1]; - break; - case '--order-by': - foreach (explode(',', $option[1]) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; - $resolveDependencies = \true; - break; - case 'defects': - $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; - break; - case 'depends': - $resolveDependencies = \true; - break; - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - break; - case 'no-depends': - $resolveDependencies = \false; - break; - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - break; - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - break; - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - break; - default: - $unrecognizedOrderBy = $order; - } - } - break; - case '--process-isolation': - $processIsolation = \true; - break; - case '--repeat': - $repeat = (int) $option[1]; - break; - case '--stderr': - $stderr = \true; - break; - case '--stop-on-defect': - $stopOnDefect = \true; - break; - case '--stop-on-error': - $stopOnError = \true; - break; - case '--stop-on-failure': - $stopOnFailure = \true; - break; - case '--stop-on-warning': - $stopOnWarning = \true; - break; - case '--stop-on-incomplete': - $stopOnIncomplete = \true; - break; - case '--stop-on-risky': - $stopOnRisky = \true; - break; - case '--stop-on-skipped': - $stopOnSkipped = \true; - break; - case '--fail-on-empty-test-suite': - $failOnEmptyTestSuite = \true; - break; - case '--fail-on-incomplete': - $failOnIncomplete = \true; - break; - case '--fail-on-risky': - $failOnRisky = \true; - break; - case '--fail-on-skipped': - $failOnSkipped = \true; - break; - case '--fail-on-warning': - $failOnWarning = \true; - break; - case '--teamcity': - $printer = TeamCity::class; - break; - case '--testdox': - $printer = CliTestDoxPrinter::class; - break; - case '--testdox-group': - $testdoxGroups = explode(',', $option[1]); - break; - case '--testdox-exclude-group': - $testdoxExcludeGroups = explode(',', $option[1]); - break; - case '--testdox-html': - $testdoxHtmlFile = $option[1]; - break; - case '--testdox-text': - $testdoxTextFile = $option[1]; - break; - case '--testdox-xml': - $testdoxXmlFile = $option[1]; - break; - case '--no-configuration': - $useDefaultConfiguration = \false; - break; - case '--extensions': - foreach (explode(',', $option[1]) as $extensionClass) { - if (!class_exists($extensionClass)) { - $unavailableExtensions[] = $extensionClass; - continue; - } - $extensions[] = new Extension($extensionClass, '', []); - } - break; - case '--no-extensions': - $noExtensions = \true; - break; - case '--no-coverage': - $noCoverage = \true; - break; - case '--no-logging': - $noLogging = \true; - break; - case '--no-interaction': - $noInteraction = \true; - break; - case '--globals-backup': - $backupGlobals = \true; - break; - case '--static-backup': - $backupStaticAttributes = \true; - break; - case 'v': - case '--verbose': - $verbose = \true; - break; - case '--atleast-version': - $atLeastVersion = $option[1]; - break; - case '--version': - $version = \true; - break; - case '--dont-report-useless-tests': - $reportUselessTests = \false; - break; - case '--strict-coverage': - $strictCoverage = \true; - break; - case '--disable-coverage-ignore': - $disableCodeCoverageIgnore = \true; - break; - case '--strict-global-state': - $beStrictAboutChangesToGlobalState = \true; - break; - case '--disallow-test-output': - $disallowTestOutput = \true; - break; - case '--disallow-resource-usage': - $beStrictAboutResourceUsageDuringSmallTests = \true; - break; - case '--default-time-limit': - $defaultTimeLimit = (int) $option[1]; - break; - case '--enforce-time-limit': - $enforceTimeLimit = \true; - break; - case '--disallow-todo-tests': - $disallowTodoAnnotatedTests = \true; - break; - case '--reverse-list': - $reverseList = \true; - break; - case '--check-version': - $checkVersion = \true; - break; - case '--coverage-filter': - case '--whitelist': - if ($coverageFilter === null) { - $coverageFilter = []; - } - $coverageFilter[] = $option[1]; - break; - case '--random-order': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - break; - case '--random-order-seed': - $randomOrderSeed = (int) $option[1]; - break; - case '--resolve-dependencies': - $resolveDependencies = \true; - break; - case '--ignore-dependencies': - $resolveDependencies = \false; - break; - case '--reverse-order': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - break; - case '--dump-xdebug-filter': - $xdebugFilterFile = $option[1]; - break; - default: - $unrecognizedOptions[str_replace('--', '', $option[0])] = $option[1]; - } + if ($arguments->hasBackupGlobals()) { + $result['backupGlobals'] = $arguments->backupGlobals(); + } + if ($arguments->hasBackupStaticAttributes()) { + $result['backupStaticAttributes'] = $arguments->backupStaticAttributes(); + } + if ($arguments->hasVerbose()) { + $result['verbose'] = $arguments->verbose(); } - if (empty($extensions)) { - $extensions = null; + if ($arguments->hasReportUselessTests()) { + $result['reportUselessTests'] = $arguments->reportUselessTests(); } - if (empty($unavailableExtensions)) { - $unavailableExtensions = null; + if ($arguments->hasStrictCoverage()) { + $result['strictCoverage'] = $arguments->strictCoverage(); } - if (empty($iniSettings)) { - $iniSettings = null; + if ($arguments->hasDisableCodeCoverageIgnore()) { + $result['disableCodeCoverageIgnore'] = $arguments->disableCodeCoverageIgnore(); } - if (empty($coverageFilter)) { - $coverageFilter = null; + if ($arguments->hasBeStrictAboutChangesToGlobalState()) { + $result['beStrictAboutChangesToGlobalState'] = $arguments->beStrictAboutChangesToGlobalState(); } - return new \PHPUnit\TextUI\CliArguments\Configuration($argument, $atLeastVersion, $backupGlobals, $backupStaticAttributes, $beStrictAboutChangesToGlobalState, $beStrictAboutResourceUsageDuringSmallTests, $bootstrap, $cacheResult, $cacheResultFile, $checkVersion, $colors, $columns, $configuration, $coverageClover, $coverageCobertura, $coverageCrap4J, $coverageHtml, $coveragePhp, $coverageText, $coverageTextShowUncoveredFiles, $coverageTextShowOnlySummary, $coverageXml, $pathCoverage, $coverageCacheDirectory, $warmCoverageCache, $debug, $defaultTimeLimit, $disableCodeCoverageIgnore, $disallowTestOutput, $disallowTodoAnnotatedTests, $enforceTimeLimit, $excludeGroups, $executionOrder, $executionOrderDefects, $extensions, $unavailableExtensions, $failOnEmptyTestSuite, $failOnIncomplete, $failOnRisky, $failOnSkipped, $failOnWarning, $filter, $generateConfiguration, $migrateConfiguration, $groups, $testsCovering, $testsUsing, $help, $includePath, $iniSettings, $junitLogfile, $listGroups, $listSuites, $listTests, $listTestsXml, $loader, $noCoverage, $noExtensions, $noInteraction, $noLogging, $printer, $processIsolation, $randomOrderSeed, $repeat, $reportUselessTests, $resolveDependencies, $reverseList, $stderr, $strictCoverage, $stopOnDefect, $stopOnError, $stopOnFailure, $stopOnIncomplete, $stopOnRisky, $stopOnSkipped, $stopOnWarning, $teamcityLogfile, $testdoxExcludeGroups, $testdoxGroups, $testdoxHtmlFile, $testdoxTextFile, $testdoxXmlFile, $testSuffixes, $testSuite, $unrecognizedOptions, $unrecognizedOrderBy, $useDefaultConfiguration, $verbose, $version, $coverageFilter, $xdebugFilterFile); + if ($arguments->hasDisallowTestOutput()) { + $result['disallowTestOutput'] = $arguments->disallowTestOutput(); + } + if ($arguments->hasBeStrictAboutResourceUsageDuringSmallTests()) { + $result['beStrictAboutResourceUsageDuringSmallTests'] = $arguments->beStrictAboutResourceUsageDuringSmallTests(); + } + if ($arguments->hasDefaultTimeLimit()) { + $result['defaultTimeLimit'] = $arguments->defaultTimeLimit(); + } + if ($arguments->hasEnforceTimeLimit()) { + $result['enforceTimeLimit'] = $arguments->enforceTimeLimit(); + } + if ($arguments->hasDisallowTodoAnnotatedTests()) { + $result['disallowTodoAnnotatedTests'] = $arguments->disallowTodoAnnotatedTests(); + } + if ($arguments->hasReverseList()) { + $result['reverseList'] = $arguments->reverseList(); + } + if ($arguments->hasCoverageFilter()) { + $result['coverageFilter'] = $arguments->coverageFilter(); + } + if ($arguments->hasRandomOrderSeed()) { + $result['randomOrderSeed'] = $arguments->randomOrderSeed(); + } + if ($arguments->hasXdebugFilterFile()) { + $result['xdebugFilterFile'] = $arguments->xdebugFilterFile(); + } + return $result; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use RuntimeException; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} -run($suite, $this->arguments, $this->warnings, $exit); } catch (Throwable $t) { - print $t->getMessage() . \PHP_EOL; + print $t->getMessage() . PHP_EOL; } $return = \PHPUnit\TextUI\TestRunner::FAILURE_EXIT; if (isset($result) && $result->wasSuccessful()) { @@ -68280,7 +70854,7 @@ class Command } } if ($arguments->hasIncludePath()) { - ini_set('include_path', $arguments->includePath() . \PATH_SEPARATOR . ini_get('include_path')); + ini_set('include_path', $arguments->includePath() . PATH_SEPARATOR . ini_get('include_path')); } $this->arguments = (new Mapper())->mapToLegacyArray($arguments); $this->handleCustomOptions($arguments->unrecognizedOptions()); @@ -68312,7 +70886,7 @@ class Command } if ($arguments->hasMigrateConfiguration() && $arguments->migrateConfiguration()) { if (!isset($this->arguments['configuration'])) { - print 'No configuration file found to migrate.' . \PHP_EOL; + print 'No configuration file found to migrate.' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } $this->migrateConfiguration(realpath($this->arguments['configuration'])); @@ -68321,7 +70895,7 @@ class Command try { $this->arguments['configurationObject'] = (new Loader())->load($this->arguments['configuration']); } catch (Throwable $e) { - print $e->getMessage() . \PHP_EOL; + print $e->getMessage() . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::FAILURE_EXIT); } $phpunitConfiguration = $this->arguments['configurationObject']->phpunit(); @@ -68359,7 +70933,7 @@ class Command $this->arguments['test'] = (new \PHPUnit\TextUI\TestSuiteMapper())->map($this->arguments['configurationObject']->testSuite(), $this->arguments['testsuite'] ?? ''); } catch (\PHPUnit\TextUI\Exception $e) { $this->printVersionString(); - print $e->getMessage() . \PHP_EOL; + print $e->getMessage() . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } } @@ -68393,6 +70967,7 @@ class Command if ($loaderFile) { /** * @noinspection PhpIncludeInspection + * * @psalm-suppress UnresolvableInclude */ require $loaderFile; @@ -68433,6 +71008,7 @@ class Command if ($printerFile) { /** * @noinspection PhpIncludeInspection + * * @psalm-suppress UnresolvableInclude */ require $printerFile; @@ -68471,7 +71047,7 @@ class Command if ($t instanceof \PHPUnit\Exception) { $this->exitWithErrorMessage($t->getMessage()); } - $this->exitWithErrorMessage(sprintf('Error in bootstrap script: %s:%s%s', get_class($t), \PHP_EOL, $t->getMessage())); + $this->exitWithErrorMessage(sprintf('Error in bootstrap script: %s:%s%s%s%s', get_class($t), PHP_EOL, $t->getMessage(), PHP_EOL, $t->getTraceAsString())); } } protected function handleVersionCheck() : void @@ -68480,9 +71056,9 @@ class Command $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); $isOutdated = version_compare($latestVersion, Version::id(), '>'); if ($isOutdated) { - printf('You are not using the latest version of PHPUnit.' . \PHP_EOL . 'The latest version is PHPUnit %s.' . \PHP_EOL, $latestVersion); + printf('You are not using the latest version of PHPUnit.' . PHP_EOL . 'The latest version is PHPUnit %s.' . PHP_EOL, $latestVersion); } else { - print 'You are using the latest version of PHPUnit.' . \PHP_EOL; + print 'You are using the latest version of PHPUnit.' . PHP_EOL; } exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); } @@ -68505,26 +71081,27 @@ class Command if ($this->versionStringPrinted) { return; } - print Version::getVersionString() . \PHP_EOL . \PHP_EOL; + print Version::getVersionString() . PHP_EOL . PHP_EOL; $this->versionStringPrinted = \true; } private function exitWithErrorMessage(string $message) : void { $this->printVersionString(); - print $message . \PHP_EOL; + print $message . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::FAILURE_EXIT); } private function handleListGroups(TestSuite $suite, bool $exit) : int { $this->printVersionString(); - print 'Available test group(s):' . \PHP_EOL; + $this->warnAboutConflictingOptions('listGroups', ['filter', 'groups', 'excludeGroups', 'testsuite']); + print 'Available test group(s):' . PHP_EOL; $groups = $suite->getGroups(); sort($groups); foreach ($groups as $group) { if (strpos($group, '__phpunit_') === 0) { continue; } - printf(' - %s' . \PHP_EOL, $group); + printf(' - %s' . PHP_EOL, $group); } if ($exit) { exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); @@ -68538,9 +71115,10 @@ class Command private function handleListSuites(bool $exit) : int { $this->printVersionString(); - print 'Available test suite(s):' . \PHP_EOL; + $this->warnAboutConflictingOptions('listSuites', ['filter', 'groups', 'excludeGroups', 'testsuite']); + print 'Available test suite(s):' . PHP_EOL; foreach ($this->arguments['configurationObject']->testSuite() as $testSuite) { - printf(' - %s' . \PHP_EOL, $testSuite->name()); + printf(' - %s' . PHP_EOL, $testSuite->name()); } if ($exit) { exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); @@ -68553,6 +71131,7 @@ class Command private function handleListTests(TestSuite $suite, bool $exit) : int { $this->printVersionString(); + $this->warnAboutConflictingOptions('listTests', ['filter', 'groups', 'excludeGroups', 'testsuite']); $renderer = new TextTestListRenderer(); print $renderer->render($suite); if ($exit) { @@ -68566,9 +71145,10 @@ class Command private function handleListTestsXml(TestSuite $suite, string $target, bool $exit) : int { $this->printVersionString(); + $this->warnAboutConflictingOptions('listTestsXml', ['filter', 'groups', 'excludeGroups', 'testsuite']); $renderer = new XmlTestListRenderer(); file_put_contents($target, $renderer->render($suite)); - printf('Wrote list of tests that would have been run to %s' . \PHP_EOL, $target); + printf('Wrote list of tests that would have been run to %s' . PHP_EOL, $target); if ($exit) { exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); } @@ -68577,15 +71157,15 @@ class Command private function generateConfiguration() : void { $this->printVersionString(); - print 'Generating phpunit.xml in ' . getcwd() . \PHP_EOL . \PHP_EOL; + print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL; print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; - $bootstrapScript = trim(fgets(\STDIN)); + $bootstrapScript = trim(fgets(STDIN)); print 'Tests directory (relative to path shown above; default: tests): '; - $testsDirectory = trim(fgets(\STDIN)); + $testsDirectory = trim(fgets(STDIN)); print 'Source directory (relative to path shown above; default: src): '; - $src = trim(fgets(\STDIN)); + $src = trim(fgets(STDIN)); print 'Cache directory (relative to path shown above; default: .phpunit.cache): '; - $cacheDirectory = trim(fgets(\STDIN)); + $cacheDirectory = trim(fgets(STDIN)); if ($bootstrapScript === '') { $bootstrapScript = 'vendor/autoload.php'; } @@ -68600,24 +71180,24 @@ class Command } $generator = new Generator(); file_put_contents('phpunit.xml', $generator->generateDefaultConfiguration(Version::series(), $bootstrapScript, $testsDirectory, $src, $cacheDirectory)); - print \PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . '.' . \PHP_EOL; - print 'Make sure to exclude the ' . $cacheDirectory . ' directory from version control.' . \PHP_EOL; + print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . '.' . PHP_EOL; + print 'Make sure to exclude the ' . $cacheDirectory . ' directory from version control.' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); } private function migrateConfiguration(string $filename) : void { $this->printVersionString(); if (!(new SchemaDetector())->detect($filename)->detected()) { - print $filename . ' does not need to be migrated.' . \PHP_EOL; + print $filename . ' does not need to be migrated.' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } copy($filename, $filename . '.bak'); - print 'Created backup: ' . $filename . '.bak' . \PHP_EOL; + print 'Created backup: ' . $filename . '.bak' . PHP_EOL; try { file_put_contents($filename, (new Migrator())->migrate($filename)); - print 'Migrated configuration: ' . $filename . \PHP_EOL; + print 'Migrated configuration: ' . $filename . PHP_EOL; } catch (Throwable $t) { - print 'Migration failed: ' . $t->getMessage() . \PHP_EOL; + print 'Migration failed: ' . $t->getMessage() . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); @@ -68646,14 +71226,14 @@ class Command } elseif ($configuration->codeCoverage()->hasCacheDirectory()) { $cacheDirectory = $configuration->codeCoverage()->cacheDirectory()->path(); } else { - print 'Cache for static analysis has not been configured' . \PHP_EOL; + print 'Cache for static analysis has not been configured' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } $filter = new Filter(); if ($configuration->codeCoverage()->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { (new FilterMapper())->map($filter, $configuration->codeCoverage()); } elseif (isset($this->arguments['coverageFilter'])) { - if (!\is_array($this->arguments['coverageFilter'])) { + if (!is_array($this->arguments['coverageFilter'])) { $coverageFilterDirectories = [$this->arguments['coverageFilter']]; } else { $coverageFilterDirectories = $this->arguments['coverageFilter']; @@ -68662,25 +71242,579 @@ class Command $filter->includeDirectory($coverageFilterDirectory); } } else { - print 'Filter for code coverage has not been configured' . \PHP_EOL; + print 'Filter for code coverage has not been configured' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::EXCEPTION_EXIT); } $timer = new Timer(); $timer->start(); print 'Warming cache for static analysis ... '; (new CacheWarmer())->warmCache($cacheDirectory, !$configuration->codeCoverage()->disableCodeCoverageIgnore(), $configuration->codeCoverage()->ignoreDeprecatedCodeUnits(), $filter); - print 'done [' . $timer->stop()->asString() . ']' . \PHP_EOL; + print 'done [' . $timer->stop()->asString() . ']' . PHP_EOL; exit(\PHPUnit\TextUI\TestRunner::SUCCESS_EXIT); } - private function configurationFileInDirectory(string $directory) : ?string + private function configurationFileInDirectory(string $directory) : ?string + { + $candidates = [$directory . '/phpunit.xml', $directory . '/phpunit.xml.dist']; + foreach ($candidates as $candidate) { + if (is_file($candidate)) { + return realpath($candidate); + } + } + return null; + } + /** + * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key + * @psalm-param list<"listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite"> $keys + */ + private function warnAboutConflictingOptions(string $key, array $keys) : void + { + $warningPrinted = \false; + foreach ($keys as $_key) { + if (!empty($this->arguments[$_key])) { + printf('The %s and %s options cannot be combined, %s is ignored' . PHP_EOL, $this->mapKeyToOptionForWarning($_key), $this->mapKeyToOptionForWarning($key), $this->mapKeyToOptionForWarning($_key)); + $warningPrinted = \true; + } + } + if ($warningPrinted) { + print PHP_EOL; + } + } + /** + * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key + */ + private function mapKeyToOptionForWarning(string $key) : string + { + switch ($key) { + case 'listGroups': + return '--list-groups'; + case 'listSuites': + return '--list-suites'; + case 'listTests': + return '--list-tests'; + case 'listTestsXml': + return '--list-tests-xml'; + case 'filter': + return '--filter'; + case 'groups': + return '--group'; + case 'excludeGroups': + return '--exclude-group'; + case 'testsuite': + return '--testsuite'; + } + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use function array_map; +use function array_reverse; +use function count; +use function floor; +use function implode; +use function in_array; +use function is_int; +use function max; +use function preg_split; +use function sprintf; +use function str_pad; +use function str_repeat; +use function strlen; +use function trim; +use function vsprintf; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Color; +use PHPUnit\Util\Printer; +use PHPUnit\SebastianBergmann\Environment\Console; +use PHPUnit\SebastianBergmann\Timer\ResourceUsageFormatter; +use PHPUnit\SebastianBergmann\Timer\Timer; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class DefaultResultPrinter extends Printer implements \PHPUnit\TextUI\ResultPrinter +{ + public const EVENT_TEST_START = 0; + public const EVENT_TEST_END = 1; + public const EVENT_TESTSUITE_START = 2; + public const EVENT_TESTSUITE_END = 3; + public const COLOR_NEVER = 'never'; + public const COLOR_AUTO = 'auto'; + public const COLOR_ALWAYS = 'always'; + public const COLOR_DEFAULT = self::COLOR_NEVER; + private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS]; + /** + * @var int + */ + protected $column = 0; + /** + * @var int + */ + protected $maxColumn; + /** + * @var bool + */ + protected $lastTestFailed = \false; + /** + * @var int + */ + protected $numAssertions = 0; + /** + * @var int + */ + protected $numTests = -1; + /** + * @var int + */ + protected $numTestsRun = 0; + /** + * @var int + */ + protected $numTestsWidth; + /** + * @var bool + */ + protected $colors = \false; + /** + * @var bool + */ + protected $debug = \false; + /** + * @var bool + */ + protected $verbose = \false; + /** + * @var int + */ + private $numberOfColumns; + /** + * @var bool + */ + private $reverse; + /** + * @var bool + */ + private $defectListPrinted = \false; + /** + * @var Timer + */ + private $timer; + /** + * Constructor. + * + * @param null|resource|string $out + * @param int|string $numberOfColumns + * + * @throws Exception + */ + public function __construct($out = null, bool $verbose = \false, string $colors = self::COLOR_DEFAULT, bool $debug = \false, $numberOfColumns = 80, bool $reverse = \false) + { + parent::__construct($out); + if (!in_array($colors, self::AVAILABLE_COLORS, \true)) { + throw InvalidArgumentException::create(3, vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS)); + } + if (!is_int($numberOfColumns) && $numberOfColumns !== 'max') { + throw InvalidArgumentException::create(5, 'integer or "max"'); + } + $console = new Console(); + $maxNumberOfColumns = $console->getNumberOfColumns(); + if ($numberOfColumns === 'max' || $numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns) { + $numberOfColumns = $maxNumberOfColumns; + } + $this->numberOfColumns = $numberOfColumns; + $this->verbose = $verbose; + $this->debug = $debug; + $this->reverse = $reverse; + if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { + $this->colors = \true; + } else { + $this->colors = self::COLOR_ALWAYS === $colors; + } + $this->timer = new Timer(); + $this->timer->start(); + } + public function printResult(TestResult $result) : void + { + $this->printHeader($result); + $this->printErrors($result); + $this->printWarnings($result); + $this->printFailures($result); + $this->printRisky($result); + if ($this->verbose) { + $this->printIncompletes($result); + $this->printSkipped($result); + } + $this->printFooter($result); + } + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time) : void + { + $this->writeProgressWithColor('fg-red, bold', 'E'); + $this->lastTestFailed = \true; + } + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + { + $this->writeProgressWithColor('bg-red, fg-white', 'F'); + $this->lastTestFailed = \true; + } + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time) : void + { + $this->writeProgressWithColor('fg-yellow, bold', 'W'); + $this->lastTestFailed = \true; + } + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void + { + $this->writeProgressWithColor('fg-yellow, bold', 'I'); + $this->lastTestFailed = \true; + } + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time) : void + { + $this->writeProgressWithColor('fg-yellow, bold', 'R'); + $this->lastTestFailed = \true; + } + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time) : void + { + $this->writeProgressWithColor('fg-cyan, bold', 'S'); + $this->lastTestFailed = \true; + } + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite) : void + { + if ($this->numTests == -1) { + $this->numTests = count($suite); + $this->numTestsWidth = strlen((string) $this->numTests); + $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - 2 * $this->numTestsWidth; + } + } + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite) : void + { + } + /** + * A test started. + */ + public function startTest(Test $test) : void + { + if ($this->debug) { + $this->write(sprintf("Test '%s' started\n", \PHPUnit\Util\Test::describeAsString($test))); + } + } + /** + * A test ended. + */ + public function endTest(Test $test, float $time) : void + { + if ($this->debug) { + $this->write(sprintf("Test '%s' ended\n", \PHPUnit\Util\Test::describeAsString($test))); + } + if (!$this->lastTestFailed) { + $this->writeProgress('.'); + } + if ($test instanceof TestCase) { + $this->numAssertions += $test->getNumAssertions(); + } elseif ($test instanceof PhptTestCase) { + $this->numAssertions++; + } + $this->lastTestFailed = \false; + if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { + $this->write($test->getActualOutput()); + } + } + protected function printDefects(array $defects, string $type) : void + { + $count = count($defects); + if ($count == 0) { + return; + } + if ($this->defectListPrinted) { + $this->write("\n--\n\n"); + } + $this->write(sprintf("There %s %d %s%s:\n", $count == 1 ? 'was' : 'were', $count, $type, $count == 1 ? '' : 's')); + $i = 1; + if ($this->reverse) { + $defects = array_reverse($defects); + } + foreach ($defects as $defect) { + $this->printDefect($defect, $i++); + } + $this->defectListPrinted = \true; + } + protected function printDefect(TestFailure $defect, int $count) : void + { + $this->printDefectHeader($defect, $count); + $this->printDefectTrace($defect); + } + protected function printDefectHeader(TestFailure $defect, int $count) : void + { + $this->write(sprintf("\n%d) %s\n", $count, $defect->getTestName())); + } + protected function printDefectTrace(TestFailure $defect) : void + { + $e = $defect->thrownException(); + $this->write((string) $e); + while ($e = $e->getPrevious()) { + $this->write("\nCaused by\n" . trim((string) $e) . "\n"); + } + } + protected function printErrors(TestResult $result) : void + { + $this->printDefects($result->errors(), 'error'); + } + protected function printFailures(TestResult $result) : void + { + $this->printDefects($result->failures(), 'failure'); + } + protected function printWarnings(TestResult $result) : void + { + $this->printDefects($result->warnings(), 'warning'); + } + protected function printIncompletes(TestResult $result) : void + { + $this->printDefects($result->notImplemented(), 'incomplete test'); + } + protected function printRisky(TestResult $result) : void + { + $this->printDefects($result->risky(), 'risky test'); + } + protected function printSkipped(TestResult $result) : void + { + $this->printDefects($result->skipped(), 'skipped test'); + } + protected function printHeader(TestResult $result) : void + { + if (count($result) > 0) { + $this->write(PHP_EOL . PHP_EOL . (new ResourceUsageFormatter())->resourceUsage($this->timer->stop()) . PHP_EOL . PHP_EOL); + } + } + protected function printFooter(TestResult $result) : void + { + if (count($result) === 0) { + $this->writeWithColor('fg-black, bg-yellow', 'No tests executed!'); + return; + } + if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { + $this->writeWithColor('fg-black, bg-green', sprintf('OK (%d test%s, %d assertion%s)', count($result), count($result) === 1 ? '' : 's', $this->numAssertions, $this->numAssertions === 1 ? '' : 's')); + return; + } + $color = 'fg-black, bg-yellow'; + if ($result->wasSuccessful()) { + if ($this->verbose || !$result->allHarmless()) { + $this->write("\n"); + } + $this->writeWithColor($color, 'OK, but incomplete, skipped, or risky tests!'); + } else { + $this->write("\n"); + if ($result->errorCount()) { + $color = 'fg-white, bg-red'; + $this->writeWithColor($color, 'ERRORS!'); + } elseif ($result->failureCount()) { + $color = 'fg-white, bg-red'; + $this->writeWithColor($color, 'FAILURES!'); + } elseif ($result->warningCount()) { + $color = 'fg-black, bg-yellow'; + $this->writeWithColor($color, 'WARNINGS!'); + } + } + $this->writeCountString(count($result), 'Tests', $color, \true); + $this->writeCountString($this->numAssertions, 'Assertions', $color, \true); + $this->writeCountString($result->errorCount(), 'Errors', $color); + $this->writeCountString($result->failureCount(), 'Failures', $color); + $this->writeCountString($result->warningCount(), 'Warnings', $color); + $this->writeCountString($result->skippedCount(), 'Skipped', $color); + $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); + $this->writeCountString($result->riskyCount(), 'Risky', $color); + $this->writeWithColor($color, '.'); + } + protected function writeProgress(string $progress) : void + { + if ($this->debug) { + return; + } + $this->write($progress); + $this->column++; + $this->numTestsRun++; + if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { + if ($this->numTestsRun == $this->numTests) { + $this->write(str_repeat(' ', $this->maxColumn - $this->column)); + } + $this->write(sprintf(' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', $this->numTestsRun, $this->numTests, floor($this->numTestsRun / $this->numTests * 100))); + if ($this->column == $this->maxColumn) { + $this->writeNewLine(); + } + } + } + protected function writeNewLine() : void + { + $this->column = 0; + $this->write("\n"); + } + /** + * Formats a buffer with a specified ANSI color sequence if colors are + * enabled. + */ + protected function colorizeTextBox(string $color, string $buffer) : string + { + if (!$this->colors) { + return $buffer; + } + $lines = preg_split('/\\r\\n|\\r|\\n/', $buffer); + $padding = max(array_map('\\strlen', $lines)); + $styledLines = []; + foreach ($lines as $line) { + $styledLines[] = Color::colorize($color, str_pad($line, $padding)); + } + return implode(PHP_EOL, $styledLines); + } + /** + * Writes a buffer out with a color sequence if colors are enabled. + */ + protected function writeWithColor(string $color, string $buffer, bool $lf = \true) : void + { + $this->write($this->colorizeTextBox($color, $buffer)); + if ($lf) { + $this->write(PHP_EOL); + } + } + /** + * Writes progress with a color sequence if colors are enabled. + */ + protected function writeProgressWithColor(string $color, string $buffer) : void + { + $buffer = $this->colorizeTextBox($color, $buffer); + $this->writeProgress($buffer); + } + private function writeCountString(int $count, string $name, string $color, bool $always = \false) : void + { + static $first = \true; + if ($always || $count > 0) { + $this->writeWithColor($color, sprintf('%s%s: %d', !$first ? ', ' : '', $name, $count), \false); + $first = \false; + } + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use Throwable; +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use RuntimeException; +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class ReflectionException extends RuntimeException implements \PHPUnit\TextUI\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements \PHPUnit\TextUI\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDirectoryNotFoundException extends RuntimeException implements \PHPUnit\TextUI\Exception +{ + public function __construct(string $path) { - $candidates = [$directory . '/phpunit.xml', $directory . '/phpunit.xml.dist']; - foreach ($candidates as $candidate) { - if (is_file($candidate)) { - return realpath($candidate); - } - } - return null; + parent::__construct(sprintf('Test directory "%s" not found', $path)); } } name(), $filterAsArray, \true)) { - continue; - } - $testSuite = new TestSuiteObject($testSuiteConfiguration->name()); - $testSuiteEmpty = \true; - foreach ($testSuiteConfiguration->directories() as $directory) { - if (!version_compare(\PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { - continue; - } - $exclude = []; - foreach ($testSuiteConfiguration->exclude()->asArray() as $file) { - $exclude[] = $file->path(); - } - $files = (new Facade())->getFilesAsArray($directory->path(), $directory->suffix(), $directory->prefix(), $exclude); - if (!empty($files)) { - $testSuite->addTestFiles($files); - $testSuiteEmpty = \false; - } elseif (strpos($directory->path(), '*') === \false && !is_dir($directory->path())) { - throw new \PHPUnit\TextUI\TestDirectoryNotFoundException($directory->path()); - } - } - foreach ($testSuiteConfiguration->files() as $file) { - if (!is_file($file->path())) { - throw new \PHPUnit\TextUI\TestFileNotFoundException($file->path()); - } - if (!version_compare(\PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { - continue; - } - $testSuite->addTestFile($file->path()); - $testSuiteEmpty = \false; - } - if (!$testSuiteEmpty) { - $result->addTest($testSuite); - } - } - return $result; - } catch (FrameworkException $e) { - throw new \PHPUnit\TextUI\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - } + parent::__construct(sprintf('Test file "%s" not found', $path)); } } [['text' => 'phpunit [options] UnitTest.php'], ['text' => 'phpunit [options] ']], 'Code Coverage Options' => [['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], ['arg' => '--coverage-cobertura ', 'desc' => 'Generate code coverage report in Cobertura XML format'], ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], ['arg' => '--coverage-cache ', 'desc' => 'Cache static analysis results'], ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage analysis'], ['arg' => '--path-coverage', 'desc' => 'Perform path coverage analysis'], ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration']], 'Logging Options' => [['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration']], 'Test Selection Options' => [['arg' => '--list-suites', 'desc' => 'List available test suites'], ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], ['arg' => '--list-groups', 'desc' => 'List available test groups'], ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--covers ', 'desc' => 'Only runs tests annotated with "@covers "'], ['arg' => '--uses ', 'desc' => 'Only runs tests annotated with "@uses "'], ['arg' => '--list-tests', 'desc' => 'List available tests'], ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt']], 'Test Execution Options' => [['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], ['spacer' => ''], ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], ['spacer' => ''], ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], ['arg' => '--fail-on-incomplete', 'desc' => 'Treat incomplete tests as failures'], ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], ['arg' => '--fail-on-skipped', 'desc' => 'Treat skipped tests as failures'], ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], ['arg' => '--debug', 'desc' => 'Display debugging information'], ['spacer' => ''], ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], ['spacer' => ''], ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], ['arg' => '--random-order-seed ', 'desc' => 'Use a specific random seed for random order'], ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file']], 'Configuration Options' => [['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], ['arg' => '--extensions ', 'desc' => 'A comma separated list of PHPUnit extensions to load'], ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], ['arg' => '--cache-result-file ', 'desc' => 'Specify result cache path and filename'], ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format']], 'Miscellaneous Options' => [['arg' => '-h|--help', 'desc' => 'Prints this usage information'], ['arg' => '--version', 'desc' => 'Prints the version and exits'], ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], ['arg' => '--check-version', 'desc' => 'Check whether PHPUnit is the latest version']]]; /** - * @var bool - */ - protected $backupGlobals = \false; - /** - * @var bool + * @var int Number of columns required to write the longest option name to the console */ - protected $backupStaticAttributes = \false; + private $maxArgLength = 0; /** - * @var bool + * @var int Number of columns left for the description field after padding and option */ - protected $runTestInSeparateProcess = \false; + private $maxDescLength; /** - * @var string + * @var bool Use color highlights for sections, options and parameters */ - private $message; - public function __construct(string $message = '') - { - $this->message = $message; - parent::__construct('Error'); - } - public function getMessage() : string + private $hasColor = \false; + public function __construct(?int $width = null, ?bool $withColor = null) { - return $this->message; + if ($width === null) { + $width = (new Console())->getNumberOfColumns(); + } + if ($withColor === null) { + $this->hasColor = (new Console())->hasColorSupport(); + } else { + $this->hasColor = $withColor; + } + foreach (self::HELP_TEXT as $options) { + foreach ($options as $option) { + if (isset($option['arg'])) { + $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); + } + } + } + $this->maxDescLength = $width - $this->maxArgLength - 4; } /** - * Returns a string representation of the test case. + * Write the help file to the CLI, adapting width and colors to the console. */ - public function toString() : string + public function writeToConsole() : void { - return 'Error'; + if ($this->hasColor) { + $this->writeWithColor(); + } else { + $this->writePlaintext(); + } } - /** - * @throws Exception - * - * @psalm-return never-return - */ - protected function runTest() : void + private function writePlaintext() : void { - throw new \PHPUnit\Framework\Error($this->message); + foreach (self::HELP_TEXT as $section => $options) { + print "{$section}:" . PHP_EOL; + if ($section !== 'Usage') { + print PHP_EOL; + } + foreach ($options as $option) { + if (isset($option['spacer'])) { + print PHP_EOL; + } + if (isset($option['text'])) { + print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + } + if (isset($option['arg'])) { + $arg = str_pad($option['arg'], $this->maxArgLength); + print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; + } + } + print PHP_EOL; + } + } + private function writeWithColor() : void + { + foreach (self::HELP_TEXT as $section => $options) { + print Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + foreach ($options as $option) { + if (isset($option['spacer'])) { + print PHP_EOL; + } + if (isset($option['text'])) { + print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + } + if (isset($option['arg'])) { + $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); + $arg = preg_replace_callback('/(<[^>]+>)/', static function ($matches) { + return Color::colorize('fg-cyan', $matches[0]); + }, $arg); + $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, PHP_EOL)); + print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; + for ($i = 1; $i < count($desc); $i++) { + print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . PHP_EOL; + } + } + } + print PHP_EOL; + } } } > - */ - protected $groups = []; - /** - * The tests in the test suite. - * - * @var Test[] - */ - protected $tests = []; - /** - * The number of tests in the test suite. - * - * @var int - */ - protected $numTests = -1; - /** - * @var bool - */ - protected $testCase = \false; - /** - * @var string[] + * @var CodeCoverageFilter */ - protected $foundClasses = []; + private $codeCoverageFilter; /** - * @var null|list + * @var TestSuiteLoader */ - protected $providedTests; + private $loader; /** - * @var null|list + * @var ResultPrinter */ - protected $requiredTests; + private $printer; /** * @var bool */ - private $beStrictAboutChangesToGlobalState; - /** - * @var Factory - */ - private $iteratorFilter; - /** - * @var string[] - */ - private $declaredClasses; - /** - * @psalm-var array - */ - private $warnings = []; - /** - * Constructs a new TestSuite. - * - * - PHPUnit\Framework\TestSuite() constructs an empty TestSuite. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a - * TestSuite from the given class. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass, String) - * constructs a TestSuite from the given class with the given - * name. - * - * - PHPUnit\Framework\TestSuite(String) either constructs a - * TestSuite from the given class (if the passed string is the - * name of an existing class) or constructs an empty TestSuite - * with the given name. - * - * @param ReflectionClass|string $theClass - * - * @throws Exception - */ - public function __construct($theClass = '', string $name = '') - { - if (!is_string($theClass) && !$theClass instanceof ReflectionClass) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'ReflectionClass object or string'); - } - $this->declaredClasses = get_declared_classes(); - if (!$theClass instanceof ReflectionClass) { - if (class_exists($theClass, \true)) { - if ($name === '') { - $name = $theClass; - } - try { - $theClass = new ReflectionClass($theClass); - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } else { - $this->setName($theClass); - return; - } - } - if (!$theClass->isSubclassOf(\PHPUnit\Framework\TestCase::class)) { - $this->setName((string) $theClass); - return; - } - if ($name !== '') { - $this->setName($name); - } else { - $this->setName($theClass->getName()); - } - $constructor = $theClass->getConstructor(); - if ($constructor !== null && !$constructor->isPublic()) { - $this->addTest(new \PHPUnit\Framework\WarningTestCase(sprintf('Class "%s" has no public constructor.', $theClass->getName()))); - return; - } - foreach ($theClass->getMethods() as $method) { - if ($method->getDeclaringClass()->getName() === \PHPUnit\Framework\Assert::class) { - continue; - } - if ($method->getDeclaringClass()->getName() === \PHPUnit\Framework\TestCase::class) { - continue; - } - if (!TestUtil::isTestMethod($method)) { - continue; - } - $this->addTestMethod($theClass, $method); - } - if (empty($this->tests)) { - $this->addTest(new \PHPUnit\Framework\WarningTestCase(sprintf('No tests found in class "%s".', $theClass->getName()))); - } - $this->testCase = \true; - } + private $messagePrinted = \false; /** - * Returns a string representation of the test suite. + * @var Hook[] */ - public function toString() : string - { - return $this->getName(); - } + private $extensions = []; /** - * Adds a test to the suite. - * - * @param array $groups + * @var Timer */ - public function addTest(\PHPUnit\Framework\Test $test, $groups = []) : void + private $timer; + public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) { - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if (!$class->isAbstract()) { - $this->tests[] = $test; - $this->clearCaches(); - if ($test instanceof self && empty($groups)) { - $groups = $test->getGroups(); - } - if ($this->containsOnlyVirtualGroups($groups)) { - $groups[] = 'default'; - } - foreach ($groups as $group) { - if (!isset($this->groups[$group])) { - $this->groups[$group] = [$test]; - } else { - $this->groups[$group][] = $test; - } - } - if ($test instanceof \PHPUnit\Framework\TestCase) { - $test->setGroups($groups); - } + if ($filter === null) { + $filter = new CodeCoverageFilter(); } + $this->codeCoverageFilter = $filter; + $this->loader = $loader; + $this->timer = new Timer(); } /** - * Adds the tests from the given class to the suite. - * - * @psalm-param object|class-string $testClass - * + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception * @throws Exception */ - public function addTestSuite($testClass) : void + public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = \true) : TestResult { - if (!(is_object($testClass) || is_string($testClass) && class_exists($testClass))) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class name or object'); + if (isset($arguments['configuration'])) { + $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; } - if (!is_object($testClass)) { - try { - $testClass = new ReflectionClass($testClass); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + $this->handleConfiguration($arguments); + $warnings = array_merge($warnings, $arguments['warnings']); + if (is_int($arguments['columns']) && $arguments['columns'] < 16) { + $arguments['columns'] = 16; + $tooFewColumnsRequested = \true; } - if ($testClass instanceof self) { - $this->addTest($testClass); - } elseif ($testClass instanceof ReflectionClass) { - $suiteMethod = \false; - if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { - try { - $method = $testClass->getMethod(BaseTestRunner::SUITE_METHODNAME); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($method->isStatic()) { - $this->addTest($method->invoke(null, $testClass->getName())); - $suiteMethod = \true; - } - } - if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(\PHPUnit\Framework\TestCase::class)) { - $this->addTest(new self($testClass)); - } - } else { - throw new \PHPUnit\Framework\Exception(); + if (isset($arguments['bootstrap'])) { + $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } - } - public function addWarning(string $warning) : void - { - $this->warnings[] = $warning; - } - /** - * Wraps both addTest() and addTestSuite - * as well as the separate import statements for the user's convenience. - * - * If the named file cannot be read or there are no new tests that can be - * added, a PHPUnit\Framework\WarningTestCase will be created instead, - * leaving the current test run untouched. - * - * @throws Exception - */ - public function addTestFile(string $filename) : void - { - if (is_file($filename) && substr($filename, -5) === '.phpt') { - $this->addTest(new PhptTestCase($filename)); - $this->declaredClasses = get_declared_classes(); - return; + if ($arguments['backupGlobals'] === \true) { + $suite->setBackupGlobals(\true); } - $numTests = count($this->tests); - // The given file may contain further stub classes in addition to the - // test class itself. Figure out the actual test class. - $filename = FileLoader::checkAndLoad($filename); - $newClasses = array_diff(get_declared_classes(), $this->declaredClasses); - // The diff is empty in case a parent class (with test methods) is added - // AFTER a child class that inherited from it. To account for that case, - // accumulate all discovered classes, so the parent class may be found in - // a later invocation. - if (!empty($newClasses)) { - // On the assumption that test classes are defined first in files, - // process discovered classes in approximate LIFO order, so as to - // avoid unnecessary reflection. - $this->foundClasses = array_merge($newClasses, $this->foundClasses); - $this->declaredClasses = get_declared_classes(); + if ($arguments['backupStaticAttributes'] === \true) { + $suite->setBackupStaticAttributes(\true); } - // The test class's name must match the filename, either in full, or as - // a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a - // PSR-1 local short name ('NameSpace\ShortName'). The comparison must be - // anchored to prevent false-positive matches (e.g., 'OtherShortName'). - $shortName = basename($filename, '.php'); - $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; - foreach ($this->foundClasses as $i => $className) { - if (preg_match($shortNameRegEx, $className)) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + if ($arguments['beStrictAboutChangesToGlobalState'] === \true) { + $suite->setBeStrictAboutChangesToGlobalState(\true); + } + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + mt_srand($arguments['randomOrderSeed']); + } + if ($arguments['cacheResult']) { + if (!isset($arguments['cacheResultFile'])) { + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + $cacheLocation = $arguments['configurationObject']->filename(); + } else { + $cacheLocation = $_SERVER['PHP_SELF']; } - // @codeCoverageIgnoreEnd - if ($class->getFileName() == $filename) { - $newClasses = [$className]; - unset($this->foundClasses[$i]); - break; + $arguments['cacheResultFile'] = null; + $cacheResultFile = realpath($cacheLocation); + if ($cacheResultFile !== \false) { + $arguments['cacheResultFile'] = dirname($cacheResultFile); } } + $cache = new DefaultTestResultCache($arguments['cacheResultFile']); + $this->addExtension(new ResultCacheExtension($cache)); } - foreach ($newClasses as $className) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { + $cache = $cache ?? new NullTestResultCache(); + $cache->load(); + $sorter = new TestSuiteSorter($cache); + $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); + $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); + unset($sorter); + } + if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { + $_suite = new TestSuite(); + /* @noinspection PhpUnusedLocalVariableInspection */ + foreach (range(1, $arguments['repeat']) as $step) { + $_suite->addTest($suite); } - // @codeCoverageIgnoreEnd - if (dirname($class->getFileName()) === __DIR__) { - continue; + $suite = $_suite; + unset($_suite); + } + $result = $this->createTestResult(); + $listener = new TestListenerAdapter(); + $listenerNeeded = \false; + foreach ($this->extensions as $extension) { + if ($extension instanceof TestHook) { + $listener->add($extension); + $listenerNeeded = \true; } - if (!$class->isAbstract()) { - if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { + } + if ($listenerNeeded) { + $result->addListener($listener); + } + unset($listener, $listenerNeeded); + if ($arguments['convertDeprecationsToExceptions']) { + $result->convertDeprecationsToExceptions(\true); + } + if (!$arguments['convertErrorsToExceptions']) { + $result->convertErrorsToExceptions(\false); + } + if (!$arguments['convertNoticesToExceptions']) { + $result->convertNoticesToExceptions(\false); + } + if (!$arguments['convertWarningsToExceptions']) { + $result->convertWarningsToExceptions(\false); + } + if ($arguments['stopOnError']) { + $result->stopOnError(\true); + } + if ($arguments['stopOnFailure']) { + $result->stopOnFailure(\true); + } + if ($arguments['stopOnWarning']) { + $result->stopOnWarning(\true); + } + if ($arguments['stopOnIncomplete']) { + $result->stopOnIncomplete(\true); + } + if ($arguments['stopOnRisky']) { + $result->stopOnRisky(\true); + } + if ($arguments['stopOnSkipped']) { + $result->stopOnSkipped(\true); + } + if ($arguments['stopOnDefect']) { + $result->stopOnDefect(\true); + } + if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { + $result->setRegisterMockObjectsFromTestArgumentsRecursively(\true); + } + if ($this->printer === null) { + if (isset($arguments['printer'])) { + if ($arguments['printer'] instanceof \PHPUnit\TextUI\ResultPrinter) { + $this->printer = $arguments['printer']; + } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], \false)) { try { - $method = $class->getMethod(BaseTestRunner::SUITE_METHODNAME); + $reflector = new ReflectionClass($arguments['printer']); + if ($reflector->implementsInterface(\PHPUnit\TextUI\ResultPrinter::class)) { + $this->printer = $this->createPrinter($arguments['printer'], $arguments); + } // @codeCoverageIgnoreStart } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } // @codeCoverageIgnoreEnd - if ($method->isStatic()) { - $this->addTest($method->invoke(null, $className)); - } - } elseif ($class->implementsInterface(\PHPUnit\Framework\Test::class)) { - $expectedClassName = $shortName; - if (($pos = strpos($expectedClassName, '.')) !== \false) { - $expectedClassName = substr($expectedClassName, 0, $pos); - } - if ($class->getShortName() !== $expectedClassName) { - $this->addWarning(sprintf("Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'", $filename, $class->getShortName(), $expectedClassName)); - } - $this->addTestSuite($class); } + } else { + $this->printer = $this->createPrinter(\PHPUnit\TextUI\DefaultResultPrinter::class, $arguments); } } - if (count($this->tests) > ++$numTests) { - $this->addWarning(sprintf("Multiple test case classes per file is deprecated\n in %s", $filename)); + if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { + assert($this->printer instanceof CliTestDoxPrinter); + $this->printer->setOriginalExecutionOrder($originalExecutionOrder); + $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); } - $this->numTests = -1; - } - /** - * Wrapper for addTestFile() that adds multiple test files. - * - * @throws Exception - */ - public function addTestFiles(iterable $fileNames) : void - { - foreach ($fileNames as $filename) { - $this->addTestFile((string) $filename); + $this->write(Version::getVersionString() . "\n"); + foreach ($arguments['listeners'] as $listener) { + $result->addListener($listener); } - } - /** - * Counts the number of test cases that will be run by this test. - * - * @todo refactor usage of numTests in DefaultResultPrinter - */ - public function count() : int - { - $this->numTests = 0; - foreach ($this as $test) { - $this->numTests += count($test); + $result->addListener($this->printer); + $coverageFilterFromConfigurationFile = \false; + $coverageFilterFromOption = \false; + $codeCoverageReports = 0; + if (isset($arguments['testdoxHTMLFile'])) { + $result->addListener(new HtmlResultPrinter($arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'])); } - return $this->numTests; - } - /** - * Returns the name of the suite. - */ - public function getName() : string - { - return $this->name; - } - /** - * Returns the test groups of the suite. - * - * @psalm-return list - */ - public function getGroups() : array - { - return array_map(static function ($key) : string { - return (string) $key; - }, array_keys($this->groups)); - } - public function getGroupDetails() : array - { - return $this->groups; - } - /** - * Set tests groups of the test case. - */ - public function setGroupDetails(array $groups) : void - { - $this->groups = $groups; - } - /** - * Runs the tests and collects their result in a TestResult. - * - * @throws \PHPUnit\Framework\CodeCoverageException - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Warning - */ - public function run(\PHPUnit\Framework\TestResult $result = null) : \PHPUnit\Framework\TestResult - { - if ($result === null) { - $result = $this->createResult(); + if (isset($arguments['testdoxTextFile'])) { + $result->addListener(new TextResultPrinter($arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'])); } - if (count($this) === 0) { - return $result; + if (isset($arguments['testdoxXMLFile'])) { + $result->addListener(new XmlResultPrinter($arguments['testdoxXMLFile'])); } - /** @psalm-var class-string $className */ - $className = $this->name; - $hookMethods = TestUtil::getHookMethods($className); - $result->startTestSuite($this); - $test = null; - if ($this->testCase && class_exists($this->name, \false)) { + if (isset($arguments['teamcityLogfile'])) { + $result->addListener(new TeamCity($arguments['teamcityLogfile'])); + } + if (isset($arguments['junitLogfile'])) { + $result->addListener(new JUnit($arguments['junitLogfile'], $arguments['reportUselessTests'])); + } + if (isset($arguments['coverageClover'])) { + $codeCoverageReports++; + } + if (isset($arguments['coverageCobertura'])) { + $codeCoverageReports++; + } + if (isset($arguments['coverageCrap4J'])) { + $codeCoverageReports++; + } + if (isset($arguments['coverageHtml'])) { + $codeCoverageReports++; + } + if (isset($arguments['coveragePHP'])) { + $codeCoverageReports++; + } + if (isset($arguments['coverageText'])) { + $codeCoverageReports++; + } + if (isset($arguments['coverageXml'])) { + $codeCoverageReports++; + } + if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { + if (isset($arguments['coverageFilter'])) { + if (!is_array($arguments['coverageFilter'])) { + $coverageFilterDirectories = [$arguments['coverageFilter']]; + } else { + $coverageFilterDirectories = $arguments['coverageFilter']; + } + foreach ($coverageFilterDirectories as $coverageFilterDirectory) { + $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); + } + $coverageFilterFromOption = \true; + } + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + $coverageFilterFromConfigurationFile = \true; + (new FilterMapper())->map($this->codeCoverageFilter, $codeCoverageConfiguration); + } + } + } + if ($codeCoverageReports > 0) { try { - foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { - if (method_exists($this->name, $beforeClassMethod)) { - if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { - $this->markTestSuiteSkipped(implode(\PHP_EOL, $missingRequirements)); - } - call_user_func([$this->name, $beforeClassMethod]); + if (isset($codeCoverageConfiguration) && ($codeCoverageConfiguration->pathCoverage() || isset($arguments['pathCoverage']) && $arguments['pathCoverage'] === \true)) { + $codeCoverageDriver = (new Selector())->forLineAndPathCoverage($this->codeCoverageFilter); + } else { + $codeCoverageDriver = (new Selector())->forLineCoverage($this->codeCoverageFilter); + } + $codeCoverage = new CodeCoverage($codeCoverageDriver, $this->codeCoverageFilter); + if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->hasCacheDirectory()) { + $codeCoverage->cacheStaticAnalysis($codeCoverageConfiguration->cacheDirectory()->path()); + } + if (isset($arguments['coverageCacheDirectory'])) { + $codeCoverage->cacheStaticAnalysis($arguments['coverageCacheDirectory']); + } + $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); + if ($arguments['strictCoverage']) { + $codeCoverage->enableCheckForUnintentionallyCoveredCode(); + } + if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { + if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { + $codeCoverage->ignoreDeprecatedCode(); + } else { + $codeCoverage->doNotIgnoreDeprecatedCode(); } } - } catch (\PHPUnit\Framework\SkippedTestSuiteError $error) { - foreach ($this->tests() as $test) { - $result->startTest($test); - $result->addFailure($test, $error, 0); - $result->endTest($test, 0); + if (isset($arguments['disableCodeCoverageIgnore'])) { + if ($arguments['disableCodeCoverageIgnore']) { + $codeCoverage->disableAnnotationsForIgnoringCode(); + } else { + $codeCoverage->enableAnnotationsForIgnoringCode(); + } } - $result->endTestSuite($this); - return $result; - } catch (Throwable $t) { - $errorAdded = \false; - foreach ($this->tests() as $test) { - if ($result->shouldStop()) { - break; + if (isset($arguments['configurationObject'])) { + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + if ($codeCoverageConfiguration->includeUncoveredFiles()) { + $codeCoverage->includeUncoveredFiles(); + } else { + $codeCoverage->excludeUncoveredFiles(); + } + if ($codeCoverageConfiguration->processUncoveredFiles()) { + $codeCoverage->processUncoveredFiles(); + } else { + $codeCoverage->doNotProcessUncoveredFiles(); + } } - $result->startTest($test); - if (!$errorAdded) { - $result->addError($test, $t, 0); - $errorAdded = \true; + } + if ($this->codeCoverageFilter->isEmpty()) { + if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { + $warnings[] = 'No filter is configured, code coverage will not be processed'; } else { - $result->addFailure($test, new \PHPUnit\Framework\SkippedTestError('Test skipped because of an error in hook method'), 0); + $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; } - $result->endTest($test, 0); + unset($codeCoverage); + } + } catch (CodeCoverageException $e) { + $warnings[] = $e->getMessage(); + } + } + if ($arguments['verbose']) { + if (PHP_SAPI === 'phpdbg') { + $this->writeMessage('Runtime', 'PHPDBG ' . PHP_VERSION); + } else { + $runtime = 'PHP ' . PHP_VERSION; + if (isset($codeCoverageDriver)) { + $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); + } + $this->writeMessage('Runtime', $runtime); + } + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + $this->writeMessage('Configuration', $arguments['configurationObject']->filename()); + } + foreach ($arguments['loadedExtensions'] as $extension) { + $this->writeMessage('Extension', $extension); + } + foreach ($arguments['notLoadedExtensions'] as $extension) { + $this->writeMessage('Extension', $extension); + } + } + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + $this->writeMessage('Random Seed', (string) $arguments['randomOrderSeed']); + } + if (isset($tooFewColumnsRequested)) { + $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; + } + if ((new Runtime())->discardsComments()) { + $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; + } + if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { + $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; + } + foreach ($warnings as $warning) { + $this->writeMessage('Warning', $warning); + } + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + if ($arguments['configurationObject']->hasValidationErrors()) { + if ((new SchemaDetector())->detect($arguments['configurationObject']->filename())->detected()) { + $this->writeMessage('Warning', 'Your XML configuration validates against a deprecated schema.'); + $this->writeMessage('Suggestion', 'Migrate your XML configuration using "--migrate-configuration"!'); + } else { + $this->write("\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n"); + $this->write($arguments['configurationObject']->validationErrors()); + $this->write("\n Test results may not be as expected.\n\n"); } - $result->endTestSuite($this); - return $result; } } - foreach ($this as $test) { - if ($result->shouldStop()) { - break; - } - if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof self) { - $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); - $test->setBackupGlobals($this->backupGlobals); - $test->setBackupStaticAttributes($this->backupStaticAttributes); - $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); + if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { + $this->write(PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . PHP_EOL); + $script = (new XdebugFilterScriptGenerator())->generate($codeCoverageConfiguration); + if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { + $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); + exit(self::EXCEPTION_EXIT); } - $test->run($result); + file_put_contents($arguments['xdebugFilterFile'], $script); + $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL . PHP_EOL, $arguments['xdebugFilterFile'])); + exit(self::SUCCESS_EXIT); } - if ($this->testCase && class_exists($this->name, \false)) { - foreach ($hookMethods['afterClass'] as $afterClassMethod) { - if (method_exists($this->name, $afterClassMethod)) { - try { - call_user_func([$this->name, $afterClassMethod]); - } catch (Throwable $t) { - $message = "Exception in {$this->name}::{$afterClassMethod}" . \PHP_EOL . $t->getMessage(); - $error = new \PHPUnit\Framework\SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); - $placeholderTest = clone $test; - $placeholderTest->setName($afterClassMethod); - $result->startTest($placeholderTest); - $result->addFailure($placeholderTest, $error, 0); - $result->endTest($placeholderTest, 0); - } - } - } + $this->write("\n"); + if (isset($codeCoverage)) { + $result->setCodeCoverage($codeCoverage); } - $result->endTestSuite($this); - return $result; - } - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess) : void - { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; - } - public function setName(string $name) : void - { - $this->name = $name; - } - /** - * Returns the tests as an enumeration. - * - * @return Test[] - */ - public function tests() : array - { - return $this->tests; - } - /** - * Set tests of the test suite. - * - * @param Test[] $tests - */ - public function setTests(array $tests) : void - { - $this->tests = $tests; - } - /** - * Mark the test suite as skipped. - * - * @param string $message - * - * @throws SkippedTestSuiteError - * - * @psalm-return never-return - */ - public function markTestSuiteSkipped($message = '') : void - { - throw new \PHPUnit\Framework\SkippedTestSuiteError($message); - } - /** - * @param bool $beStrictAboutChangesToGlobalState - */ - public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState) : void - { - if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); + $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); + $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); + $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); + if ($arguments['enforceTimeLimit'] === \true && !(new Invoker())->canInvokeWithTimeout()) { + $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); } - } - /** - * @param bool $backupGlobals - */ - public function setBackupGlobals($backupGlobals) : void - { - if (null === $this->backupGlobals && is_bool($backupGlobals)) { - $this->backupGlobals = $backupGlobals; + $result->enforceTimeLimit($arguments['enforceTimeLimit']); + $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); + $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); + $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); + $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); + if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === \true) { + $result->forceCoversAnnotation(); } - } - /** - * @param bool $backupStaticAttributes - */ - public function setBackupStaticAttributes($backupStaticAttributes) : void - { - if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { - $this->backupStaticAttributes = $backupStaticAttributes; + $this->processSuiteFilters($suite, $arguments); + $suite->setRunTestInSeparateProcess($arguments['processIsolation']); + foreach ($this->extensions as $extension) { + if ($extension instanceof BeforeFirstTestHook) { + $extension->executeBeforeFirstTest(); + } } - } - /** - * Returns an iterator for this test suite. - */ - public function getIterator() : Iterator - { - $iterator = new \PHPUnit\Framework\TestSuiteIterator($this); - if ($this->iteratorFilter !== null) { - $iterator = $this->iteratorFilter->factory($iterator, $this); + $testSuiteWarningsPrinted = \false; + foreach ($suite->warnings() as $warning) { + $this->writeMessage('Warning', $warning); + $testSuiteWarningsPrinted = \true; } - return $iterator; - } - public function injectFilter(Factory $filter) : void - { - $this->iteratorFilter = $filter; - foreach ($this as $test) { - if ($test instanceof self) { - $test->injectFilter($filter); - } + if ($testSuiteWarningsPrinted) { + $this->write(PHP_EOL); } - } - /** - * @psalm-return array - */ - public function warnings() : array - { - return array_unique($this->warnings); - } - /** - * @return list - */ - public function provides() : array - { - if ($this->providedTests === null) { - $this->providedTests = []; - if (is_callable($this->sortId(), \true)) { - $this->providedTests[] = new \PHPUnit\Framework\ExecutionOrderDependency($this->sortId()); + $suite->run($result); + foreach ($this->extensions as $extension) { + if ($extension instanceof AfterLastTestHook) { + $extension->executeAfterLastTest(); } - foreach ($this->tests as $test) { - if (!$test instanceof \PHPUnit\Framework\Reorderable) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd + } + $result->flushListeners(); + $this->printer->printResult($result); + if (isset($codeCoverage)) { + if (isset($arguments['coverageClover'])) { + $this->codeCoverageGenerationStart('Clover XML'); + try { + $writer = new CloverReport(); + $writer->process($codeCoverage, $arguments['coverageClover']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); } - $this->providedTests = \PHPUnit\Framework\ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides()); } - } - return $this->providedTests; - } - /** - * @return list - */ - public function requires() : array - { - if ($this->requiredTests === null) { - $this->requiredTests = []; - foreach ($this->tests as $test) { - if (!$test instanceof \PHPUnit\Framework\Reorderable) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd + if (isset($arguments['coverageCobertura'])) { + $this->codeCoverageGenerationStart('Cobertura XML'); + try { + $writer = new CoberturaReport(); + $writer->process($codeCoverage, $arguments['coverageCobertura']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); } - $this->requiredTests = \PHPUnit\Framework\ExecutionOrderDependency::mergeUnique(\PHPUnit\Framework\ExecutionOrderDependency::filterInvalid($this->requiredTests), $test->requires()); } - $this->requiredTests = \PHPUnit\Framework\ExecutionOrderDependency::diff($this->requiredTests, $this->provides()); - } - return $this->requiredTests; - } - public function sortId() : string - { - return $this->getName() . '::class'; - } - /** - * Creates a default TestResult object. - */ - protected function createResult() : \PHPUnit\Framework\TestResult - { - return new \PHPUnit\Framework\TestResult(); - } - /** - * @throws Exception - */ - protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method) : void - { - $methodName = $method->getName(); - $test = (new \PHPUnit\Framework\TestBuilder())->build($class, $methodName); - if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof \PHPUnit\Framework\DataProviderTestSuite) { - $test->setDependencies(TestUtil::getDependencies($class->getName(), $methodName)); - } - $this->addTest($test, TestUtil::getGroups($class->getName(), $methodName)); - } - private function clearCaches() : void - { - $this->numTests = -1; - $this->providedTests = null; - $this->requiredTests = null; - } - private function containsOnlyVirtualGroups(array $groups) : bool - { - foreach ($groups as $group) { - if (strpos($group, '__phpunit_') !== 0) { - return \false; + if (isset($arguments['coverageCrap4J'])) { + $this->codeCoverageGenerationStart('Crap4J XML'); + try { + $writer = new Crap4jReport($arguments['crap4jThreshold']); + $writer->process($codeCoverage, $arguments['coverageCrap4J']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } } - } - return \true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function explode; -use PHPUnit\Util\Test as TestUtil; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DataProviderTestSuite extends \PHPUnit\Framework\TestSuite -{ - /** - * @var list - */ - private $dependencies = []; - /** - * @param list $dependencies - */ - public function setDependencies(array $dependencies) : void - { - $this->dependencies = $dependencies; - foreach ($this->tests as $test) { - if (!$test instanceof \PHPUnit\Framework\TestCase) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreStart + if (isset($arguments['coverageHtml'])) { + $this->codeCoverageGenerationStart('HTML'); + try { + $writer = new HtmlReport($arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], sprintf(' and PHPUnit %s', Version::id())); + $writer->process($codeCoverage, $arguments['coverageHtml']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } } - $test->setDependencies($dependencies); - } - } - /** - * @return list - */ - public function provides() : array - { - if ($this->providedTests === null) { - $this->providedTests = [new \PHPUnit\Framework\ExecutionOrderDependency($this->getName())]; - } - return $this->providedTests; - } - /** - * @return list - */ - public function requires() : array - { - // A DataProviderTestSuite does not have to traverse its child tests - // as these are inherited and cannot reference dataProvider rows directly - return $this->dependencies; - } - /** - * Returns the size of the each test created using the data provider(s). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function getSize() : int - { - [$className, $methodName] = explode('::', $this->getName()); - return TestUtil::getSize($className, $methodName); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface IncompleteTest extends Throwable -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function get_class; -use function sprintf; -use function trim; -use PHPUnit\Framework\Error\Error; -use Throwable; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestFailure -{ - /** - * @var null|Test - */ - private $failedTest; - /** - * @var Throwable - */ - private $thrownException; - /** - * @var string - */ - private $testName; - /** - * Returns a description for an exception. - */ - public static function exceptionToString(Throwable $e) : string - { - if ($e instanceof \PHPUnit\Framework\SelfDescribing) { - $buffer = $e->toString(); - if ($e instanceof \PHPUnit\Framework\ExpectationFailedException && $e->getComparisonFailure()) { - $buffer .= $e->getComparisonFailure()->getDiff(); + if (isset($arguments['coveragePHP'])) { + $this->codeCoverageGenerationStart('PHP'); + try { + $writer = new PhpReport(); + $writer->process($codeCoverage, $arguments['coveragePHP']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } } - if ($e instanceof \PHPUnit\Framework\PHPTAssertionFailedError) { - $buffer .= $e->getDiff(); + if (isset($arguments['coverageText'])) { + if ($arguments['coverageText'] === 'php://stdout') { + $outputStream = $this->printer; + $colors = $arguments['colors'] && $arguments['colors'] !== \PHPUnit\TextUI\DefaultResultPrinter::COLOR_NEVER; + } else { + $outputStream = new Printer($arguments['coverageText']); + $colors = \false; + } + $processor = new TextReport($arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary']); + $outputStream->write($processor->process($codeCoverage, $colors)); } - if (!empty($buffer)) { - $buffer = trim($buffer) . "\n"; + if (isset($arguments['coverageXml'])) { + $this->codeCoverageGenerationStart('PHPUnit XML'); + try { + $writer = new XmlReport(Version::id()); + $writer->process($codeCoverage, $arguments['coverageXml']); + $this->codeCoverageGenerationSucceeded(); + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } } - return $buffer; - } - if ($e instanceof Error) { - return $e->getMessage() . "\n"; - } - if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) { - return $e->getClassName() . ': ' . $e->getMessage() . "\n"; - } - return get_class($e) . ': ' . $e->getMessage() . "\n"; - } - /** - * Constructs a TestFailure with the given test and exception. - */ - public function __construct(\PHPUnit\Framework\Test $failedTest, Throwable $t) - { - if ($failedTest instanceof \PHPUnit\Framework\SelfDescribing) { - $this->testName = $failedTest->toString(); - } else { - $this->testName = get_class($failedTest); } - if (!$failedTest instanceof \PHPUnit\Framework\TestCase || !$failedTest->isInIsolation()) { - $this->failedTest = $failedTest; - } - $this->thrownException = $t; - } - /** - * Returns a short description of the failure. - */ - public function toString() : string - { - return sprintf('%s: %s', $this->testName, $this->thrownException->getMessage()); - } - /** - * Returns a description for the thrown exception. - */ - public function getExceptionAsString() : string - { - return self::exceptionToString($this->thrownException); - } - /** - * Returns the name of the failing test (including data set, if any). - */ - public function getTestName() : string - { - return $this->testName; - } - /** - * Returns the failing test. - * - * Note: The test object is not set when the test is executed in process - * isolation. - * - * @see Exception - */ - public function failedTest() : ?\PHPUnit\Framework\Test - { - return $this->failedTest; - } - /** - * Gets the thrown exception. - */ - public function thrownException() : Throwable - { - return $this->thrownException; + if ($exit) { + if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === \true && count($result) === 0) { + exit(self::FAILURE_EXIT); + } + if ($result->wasSuccessfulIgnoringWarnings()) { + if ($arguments['failOnRisky'] && !$result->allHarmless()) { + exit(self::FAILURE_EXIT); + } + if ($arguments['failOnWarning'] && $result->warningCount() > 0) { + exit(self::FAILURE_EXIT); + } + if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { + exit(self::FAILURE_EXIT); + } + if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { + exit(self::FAILURE_EXIT); + } + exit(self::SUCCESS_EXIT); + } + if ($result->errorCount() > 0) { + exit(self::EXCEPTION_EXIT); + } + if ($result->failureCount() > 0) { + exit(self::FAILURE_EXIT); + } + } + return $result; } /** - * Returns the exception's message. + * Returns the loader to be used. */ - public function exceptionMessage() : string + public function getLoader() : TestSuiteLoader { - return $this->thrownException()->getMessage(); + if ($this->loader === null) { + $this->loader = new StandardTestSuiteLoader(); + } + return $this->loader; } - /** - * Returns true if the thrown exception - * is of type AssertionFailedError. - */ - public function isFailure() : bool + public function addExtension(Hook $extension) : void { - return $this->thrownException() instanceof \PHPUnit\Framework\AssertionFailedError; + $this->extensions[] = $extension; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestCase extends \PHPUnit\Framework\TestCase -{ - /** - * @var bool - */ - protected $backupGlobals = \false; - /** - * @var bool - */ - protected $backupStaticAttributes = \false; - /** - * @var bool - */ - protected $runTestInSeparateProcess = \false; /** - * @var string + * Override to define how to handle a failed loading of + * a test suite. */ - private $message; - public function __construct(string $className, string $methodName, string $message = '') + protected function runFailed(string $message) : void { - parent::__construct($className . '::' . $methodName); - $this->message = $message; + $this->write($message . PHP_EOL); + exit(self::FAILURE_EXIT); } - public function getMessage() : string + private function createTestResult() : TestResult { - return $this->message; + return new TestResult(); } - /** - * Returns a string representation of the test case. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string + private function write(string $buffer) : void { - return $this->getName(); + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { + $buffer = htmlspecialchars($buffer); + } + if ($this->printer !== null) { + $this->printer->write($buffer); + } else { + print $buffer; + } } /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception * @throws Exception */ - protected function runTest() : void - { - $this->markTestSkipped($this->message); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PHPTAssertionFailedError extends \PHPUnit\Framework\SyntheticError -{ - /** - * @var string - */ - private $diff; - public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) - { - parent::__construct($message, $code, $file, $line, $trace); - $this->diff = $diff; - } - public function getDiff() : string - { - return $this->diff; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ComparisonMethodDoesNotAcceptParameterTypeException extends \PHPUnit\Framework\Exception -{ - public function __construct(string $className, string $methodName, string $type) + private function handleConfiguration(array &$arguments) : void { - parent::__construct(sprintf('%s is not an accepted argument type for comparison method %s::%s().', $type, $className, $methodName), 0, null); + if (!isset($arguments['configurationObject']) && isset($arguments['configuration'])) { + $arguments['configurationObject'] = (new Loader())->load($arguments['configuration']); + } + if (!isset($arguments['warnings'])) { + $arguments['warnings'] = []; + } + $arguments['debug'] = $arguments['debug'] ?? \false; + $arguments['filter'] = $arguments['filter'] ?? \false; + $arguments['listeners'] = $arguments['listeners'] ?? []; + if (isset($arguments['configurationObject'])) { + (new PhpHandler())->handle($arguments['configurationObject']->php()); + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + if (!isset($arguments['noCoverage'])) { + if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { + $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); + } + if (!isset($arguments['coverageCobertura']) && $codeCoverageConfiguration->hasCobertura()) { + $arguments['coverageCobertura'] = $codeCoverageConfiguration->cobertura()->target()->path(); + } + if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { + $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); + if (!isset($arguments['crap4jThreshold'])) { + $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); + } + } + if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { + $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); + if (!isset($arguments['reportLowUpperBound'])) { + $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); + } + if (!isset($arguments['reportHighLowerBound'])) { + $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); + } + } + if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { + $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); + } + if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { + $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); + $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); + $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); + } + if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { + $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); + } + } + $phpunitConfiguration = $arguments['configurationObject']->phpunit(); + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); + $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); + $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); + $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); + $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); + $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); + $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); + $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); + $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); + $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); + $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); + $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); + $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); + if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { + $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); + } + if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { + $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); + } + if (!isset($arguments['executionOrderDefects'])) { + $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; + } + if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { + $arguments['conflictBetweenPrinterClassAndTestdox'] = \true; + } + $groupCliArgs = []; + if (!empty($arguments['groups'])) { + $groupCliArgs = $arguments['groups']; + } + $groupConfiguration = $arguments['configurationObject']->groups(); + if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { + $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); + } + if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { + $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); + } + $extensionHandler = new ExtensionHandler(); + foreach ($arguments['configurationObject']->extensions() as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + foreach ($arguments['configurationObject']->listeners() as $listener) { + $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); + } + unset($extensionHandler); + foreach ($arguments['unavailableExtensions'] as $extension) { + $arguments['warnings'][] = sprintf('Extension "%s" is not available', $extension); + } + $loggingConfiguration = $arguments['configurationObject']->logging(); + if (!isset($arguments['noLogging'])) { + if ($loggingConfiguration->hasText()) { + $arguments['listeners'][] = new \PHPUnit\TextUI\DefaultResultPrinter($loggingConfiguration->text()->target()->path(), \true); + } + if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { + $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); + } + if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { + $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); + } + if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { + $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); + } + if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { + $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); + } + if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { + $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); + } + } + $testdoxGroupConfiguration = $arguments['configurationObject']->testdoxGroups(); + if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { + $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); + } + if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { + $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); + } + } + $extensionHandler = new ExtensionHandler(); + foreach ($arguments['extensions'] as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + unset($extensionHandler); + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? \false; + $arguments['cacheResult'] = $arguments['cacheResult'] ?? \true; + $arguments['colors'] = $arguments['colors'] ?? \PHPUnit\TextUI\DefaultResultPrinter::COLOR_DEFAULT; + $arguments['columns'] = $arguments['columns'] ?? 80; + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? \false; + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? \true; + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? \true; + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? \true; + $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? \false; + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? \false; + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? \false; + $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; + $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? \false; + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? \false; + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? \false; + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? \false; + $arguments['groups'] = $arguments['groups'] ?? []; + $arguments['noInteraction'] = $arguments['noInteraction'] ?? \false; + $arguments['processIsolation'] = $arguments['processIsolation'] ?? \false; + $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? \false; + $arguments['repeat'] = $arguments['repeat'] ?? \false; + $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; + $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? \true; + $arguments['reverseList'] = $arguments['reverseList'] ?? \false; + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? \true; + $arguments['stopOnError'] = $arguments['stopOnError'] ?? \false; + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? \false; + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? \false; + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? \false; + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? \false; + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? \false; + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? \false; + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? \false; + $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; + $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; + $arguments['verbose'] = $arguments['verbose'] ?? \false; + if ($arguments['reportLowUpperBound'] > $arguments['reportHighLowerBound']) { + $arguments['reportLowUpperBound'] = 50; + $arguments['reportHighLowerBound'] = 90; + } } - public function __toString() : string + private function processSuiteFilters(TestSuite $suite, array $arguments) : void { - return $this->getMessage() . \PHP_EOL; + if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups']) && empty($arguments['testsCovering']) && empty($arguments['testsUsing'])) { + return; + } + $filterFactory = new Factory(); + if (!empty($arguments['excludeGroups'])) { + $filterFactory->addFilter(new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups']); + } + if (!empty($arguments['groups'])) { + $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups']); + } + if (!empty($arguments['testsCovering'])) { + $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), array_map(static function (string $name) : string { + return '__phpunit_covers_' . $name; + }, $arguments['testsCovering'])); + } + if (!empty($arguments['testsUsing'])) { + $filterFactory->addFilter(new ReflectionClass(IncludeGroupFilterIterator::class), array_map(static function (string $name) : string { + return '__phpunit_uses_' . $name; + }, $arguments['testsUsing'])); + } + if ($arguments['filter']) { + $filterFactory->addFilter(new ReflectionClass(NameFilterIterator::class), $arguments['filter']); + } + $suite->injectFilter($filterFactory); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SyntheticSkippedError extends \PHPUnit\Framework\SyntheticError implements \PHPUnit\Framework\SkippedTest -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ComparisonMethodDoesNotExistException extends \PHPUnit\Framework\Exception -{ - public function __construct(string $className, string $methodName) + private function writeMessage(string $type, string $message) : void { - parent::__construct(sprintf('Comparison method %s::%s() does not exist.', $className, $methodName), 0, null); + if (!$this->messagePrinted) { + $this->write("\n"); + } + $this->write(sprintf("%-15s%s\n", $type . ':', $message)); + $this->messagePrinted = \true; } - public function __toString() : string + private function createPrinter(string $class, array $arguments) : \PHPUnit\TextUI\ResultPrinter { - return $this->getMessage() . \PHP_EOL; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class OutputError extends \PHPUnit\Framework\AssertionFailedError -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IncompleteTestError extends \PHPUnit\Framework\AssertionFailedError implements \PHPUnit\Framework\IncompleteTest -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class CodeCoverageException extends \PHPUnit\Framework\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class RiskyTestError extends \PHPUnit\Framework\AssertionFailedError -{ + $object = new $class(isset($arguments['stderr']) && $arguments['stderr'] === \true ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList']); + assert($object instanceof \PHPUnit\TextUI\ResultPrinter); + return $object; + } + private function codeCoverageGenerationStart(string $format) : void + { + $this->write(sprintf("\nGenerating code coverage report in %s format ... ", $format)); + $this->timer->start(); + } + private function codeCoverageGenerationSucceeded() : void + { + $this->write(sprintf("done [%s]\n", $this->timer->stop()->asString())); + } + private function codeCoverageGenerationFailed(\Exception $e) : void + { + $this->write(sprintf("failed [%s]\n%s\n", $this->timer->stop()->asString(), $e->getMessage())); + } } getMessage(); + try { + $filterAsArray = $filter ? explode(',', $filter) : []; + $result = new TestSuiteObject(); + foreach ($configuration as $testSuiteConfiguration) { + if (!empty($filterAsArray) && !in_array($testSuiteConfiguration->name(), $filterAsArray, \true)) { + continue; + } + $testSuite = new TestSuiteObject($testSuiteConfiguration->name()); + $testSuiteEmpty = \true; + $exclude = []; + foreach ($testSuiteConfiguration->exclude()->asArray() as $file) { + $exclude[] = $file->path(); + } + foreach ($testSuiteConfiguration->directories() as $directory) { + if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { + continue; + } + $files = (new Facade())->getFilesAsArray($directory->path(), $directory->suffix(), $directory->prefix(), $exclude); + if (!empty($files)) { + $testSuite->addTestFiles($files); + $testSuiteEmpty = \false; + } elseif (strpos($directory->path(), '*') === \false && !is_dir($directory->path())) { + throw new \PHPUnit\TextUI\TestDirectoryNotFoundException($directory->path()); + } + } + foreach ($testSuiteConfiguration->files() as $file) { + if (!is_file($file->path())) { + throw new \PHPUnit\TextUI\TestFileNotFoundException($file->path()); + } + if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { + continue; + } + $testSuite->addTestFile($file->path()); + $testSuiteEmpty = \false; + } + if (!$testSuiteEmpty) { + $result->addTest($testSuite); + } + } + return $result; + } catch (FrameworkException $e) { + throw new \PHPUnit\TextUI\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + } } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class CoveredCodeNotExecutedException extends \PHPUnit\Framework\RiskyTestError -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; +use function count; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; +use PHPUnit\TextUI\XmlConfiguration\Directory; +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\FileCollection; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NoChildTestSuiteException extends \PHPUnit\Framework\Exception -{ -} - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class Warning extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\SelfDescribing +final class CodeCoverage { /** - * Wrapper for getMessage() which is declared as final. + * @var ?Directory */ - public function toString() : string + private $cacheDirectory; + /** + * @var DirectoryCollection + */ + private $directories; + /** + * @var FileCollection + */ + private $files; + /** + * @var DirectoryCollection + */ + private $excludeDirectories; + /** + * @var FileCollection + */ + private $excludeFiles; + /** + * @var bool + */ + private $pathCoverage; + /** + * @var bool + */ + private $includeUncoveredFiles; + /** + * @var bool + */ + private $processUncoveredFiles; + /** + * @var bool + */ + private $ignoreDeprecatedCodeUnits; + /** + * @var bool + */ + private $disableCodeCoverageIgnore; + /** + * @var ?Clover + */ + private $clover; + /** + * @var ?Cobertura + */ + private $cobertura; + /** + * @var ?Crap4j + */ + private $crap4j; + /** + * @var ?Html + */ + private $html; + /** + * @var ?Php + */ + private $php; + /** + * @var ?Text + */ + private $text; + /** + * @var ?Xml + */ + private $xml; + public function __construct(?Directory $cacheDirectory, DirectoryCollection $directories, FileCollection $files, DirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $pathCoverage, bool $includeUncoveredFiles, bool $processUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?Php $php, ?Text $text, ?Xml $xml) { - return $this->getMessage(); + $this->cacheDirectory = $cacheDirectory; + $this->directories = $directories; + $this->files = $files; + $this->excludeDirectories = $excludeDirectories; + $this->excludeFiles = $excludeFiles; + $this->pathCoverage = $pathCoverage; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->processUncoveredFiles = $processUncoveredFiles; + $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->clover = $clover; + $this->cobertura = $cobertura; + $this->crap4j = $crap4j; + $this->html = $html; + $this->php = $php; + $this->text = $text; + $this->xml = $xml; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ComparisonMethodDoesNotDeclareExactlyOneParameterException extends \PHPUnit\Framework\Exception -{ - public function __construct(string $className, string $methodName) + /** + * @psalm-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory() : bool { - parent::__construct(sprintf('Comparison method %s::%s() does not declare exactly one parameter.', $className, $methodName), 0, null); + return $this->cacheDirectory !== null; } - public function __toString() : string + /** + * @throws Exception + */ + public function cacheDirectory() : Directory { - return $this->getMessage() . \PHP_EOL; + if (!$this->hasCacheDirectory()) { + throw new Exception('No cache directory has been configured'); + } + return $this->cacheDirectory; + } + public function hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport() : bool + { + return count($this->directories) > 0 || count($this->files) > 0; + } + public function directories() : DirectoryCollection + { + return $this->directories; + } + public function files() : FileCollection + { + return $this->files; + } + public function excludeDirectories() : DirectoryCollection + { + return $this->excludeDirectories; + } + public function excludeFiles() : FileCollection + { + return $this->excludeFiles; + } + public function pathCoverage() : bool + { + return $this->pathCoverage; + } + public function includeUncoveredFiles() : bool + { + return $this->includeUncoveredFiles; + } + public function ignoreDeprecatedCodeUnits() : bool + { + return $this->ignoreDeprecatedCodeUnits; + } + public function disableCodeCoverageIgnore() : bool + { + return $this->disableCodeCoverageIgnore; + } + public function processUncoveredFiles() : bool + { + return $this->processUncoveredFiles; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Exception; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -/** - * Exception for expectations which failed their check. - * - * The exception contains the error message and optionally a - * SebastianBergmann\Comparator\ComparisonFailure which is used to - * generate diff output of the failed expectations. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExpectationFailedException extends \PHPUnit\Framework\AssertionFailedError -{ /** - * @var ComparisonFailure + * @psalm-assert-if-true !null $this->clover */ - protected $comparisonFailure; - public function __construct(string $message, ComparisonFailure $comparisonFailure = null, Exception $previous = null) + public function hasClover() : bool { - $this->comparisonFailure = $comparisonFailure; - parent::__construct($message, 0, $previous); + return $this->clover !== null; } - public function getComparisonFailure() : ?ComparisonFailure + /** + * @throws Exception + */ + public function clover() : Clover { - return $this->comparisonFailure; + if (!$this->hasClover()) { + throw new Exception('Code Coverage report "Clover XML" has not been configured'); + } + return $this->clover; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestSuiteError extends \PHPUnit\Framework\AssertionFailedError implements \PHPUnit\Framework\SkippedTest -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MissingCoversAnnotationException extends \PHPUnit\Framework\RiskyTestError -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ActualValueIsNotAnObjectException extends \PHPUnit\Framework\Exception -{ - public function __construct() + /** + * @psalm-assert-if-true !null $this->cobertura + */ + public function hasCobertura() : bool { - parent::__construct('Actual value is not an object', 0, null); + return $this->cobertura !== null; } - public function __toString() : string + /** + * @throws Exception + */ + public function cobertura() : Cobertura + { + if (!$this->hasCobertura()) { + throw new Exception('Code Coverage report "Cobertura XML" has not been configured'); + } + return $this->cobertura; + } + /** + * @psalm-assert-if-true !null $this->crap4j + */ + public function hasCrap4j() : bool + { + return $this->crap4j !== null; + } + /** + * @throws Exception + */ + public function crap4j() : Crap4j + { + if (!$this->hasCrap4j()) { + throw new Exception('Code Coverage report "Crap4J" has not been configured'); + } + return $this->crap4j; + } + /** + * @psalm-assert-if-true !null $this->html + */ + public function hasHtml() : bool + { + return $this->html !== null; + } + /** + * @throws Exception + */ + public function html() : Html + { + if (!$this->hasHtml()) { + throw new Exception('Code Coverage report "HTML" has not been configured'); + } + return $this->html; + } + /** + * @psalm-assert-if-true !null $this->php + */ + public function hasPhp() : bool + { + return $this->php !== null; + } + /** + * @throws Exception + */ + public function php() : Php + { + if (!$this->hasPhp()) { + throw new Exception('Code Coverage report "PHP" has not been configured'); + } + return $this->php; + } + /** + * @psalm-assert-if-true !null $this->text + */ + public function hasText() : bool + { + return $this->text !== null; + } + /** + * @throws Exception + */ + public function text() : Text + { + if (!$this->hasText()) { + throw new Exception('Code Coverage report "Text" has not been configured'); + } + return $this->text; + } + /** + * @psalm-assert-if-true !null $this->xml + */ + public function hasXml() : bool + { + return $this->xml !== null; + } + /** + * @throws Exception + */ + public function xml() : Xml { - return $this->getMessage() . \PHP_EOL; + if (!$this->hasXml()) { + throw new Exception('Code Coverage report "XML" has not been configured'); + } + return $this->xml; } } syntheticFile = $file; - $this->syntheticLine = $line; - $this->syntheticTrace = $trace; + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->group = $group; } - public function getSyntheticFile() : string + public function path() : string { - return $this->syntheticFile; + return $this->path; } - public function getSyntheticLine() : int + public function prefix() : string { - return $this->syntheticLine; + return $this->prefix; } - public function getSyntheticTrace() : array + public function suffix() : string { - return $this->syntheticTrace; + return $this->suffix; + } + public function group() : string + { + return $this->group; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; +use function count; +use Countable; +use IteratorAggregate; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidDataProviderException extends \PHPUnit\Framework\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function array_keys; -use function get_object_vars; -use PHPUnit\Util\Filter; -use RuntimeException; -use Throwable; -/** - * Base class for all PHPUnit Framework exceptions. - * - * Ensures that exceptions thrown during a test run do not leave stray - * references behind. - * - * Every Exception contains a stack trace. Each stack frame contains the 'args' - * of the called function. The function arguments can contain references to - * instantiated objects. The references prevent the objects from being - * destructed (until test results are eventually printed), so memory cannot be - * freed up. - * - * With enabled process isolation, test results are serialized in the child - * process and unserialized in the parent process. The stack trace of Exceptions - * may contain objects that cannot be serialized or unserialized (e.g., PDO - * connections). Unserializing user-space objects from the child process into - * the parent would break the intended encapsulation of process isolation. * - * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -class Exception extends RuntimeException implements \PHPUnit\Exception +final class DirectoryCollection implements Countable, IteratorAggregate { /** - * @var array + * @var Directory[] */ - protected $serializableTrace; - public function __construct($message = '', $code = 0, Throwable $previous = null) - { - parent::__construct($message, $code, $previous); - $this->serializableTrace = $this->getTrace(); - foreach (array_keys($this->serializableTrace) as $key) { - unset($this->serializableTrace[$key]['args']); - } - } - public function __toString() : string + private $directories; + /** + * @param Directory[] $directories + */ + public static function fromArray(array $directories) : self { - $string = \PHPUnit\Framework\TestFailure::exceptionToString($this); - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - return $string; + return new self(...$directories); } - public function __sleep() : array + private function __construct(\PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory ...$directories) { - return array_keys(get_object_vars($this)); + $this->directories = $directories; } /** - * Returns the serializable trace (without 'args'). + * @return Directory[] */ - public function getSerializableTrace() : array + public function asArray() : array { - return $this->serializableTrace; + return $this->directories; + } + public function count() : int + { + return count($this->directories); + } + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollectionIterator + { + return new \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollectionIterator($this); } } getMessage(); + $this->directories = $directories->asArray(); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends \PHPUnit\Framework\Exception -{ - public function __construct(string $className, string $methodName) + public function count() : int { - parent::__construct(sprintf('Comparison method %s::%s() does not declare bool return type.', $className, $methodName), 0, null); + return iterator_count($this); } - public function __toString() : string + public function rewind() : void { - return $this->getMessage() . \PHP_EOL; + $this->position = 0; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidCoversTargetException extends \PHPUnit\Framework\CodeCoverageException -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ComparisonMethodDoesNotDeclareParameterTypeException extends \PHPUnit\Framework\Exception -{ - public function __construct(string $className, string $methodName) + public function valid() : bool { - parent::__construct(sprintf('Parameter of comparison method %s::%s() does not have a declared type.', $className, $methodName), 0, null); + return $this->position < count($this->directories); } - public function __toString() : string + public function key() : int { - return $this->getMessage() . \PHP_EOL; + return $this->position; + } + public function current() : \PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory + { + return $this->directories[$this->position]; + } + public function next() : void + { + $this->position++; } } directories() as $directory) { + $filter->includeDirectory($directory->path(), $directory->suffix(), $directory->prefix()); + } + foreach ($configuration->files() as $file) { + $filter->includeFile($file->path()); + } + foreach ($configuration->excludeDirectories() as $directory) { + $filter->excludeDirectory($directory->path(), $directory->suffix(), $directory->prefix()); + } + foreach ($configuration->excludeFiles() as $file) { + $filter->excludeFile($file->path()); + } } } target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertEquals')) { - /** - * Asserts that two variables are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEquals - */ - function assertEquals($expected, $actual, string $message = '') : void + public function target() : File { - \PHPUnit\Framework\Assert::assertEquals(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\assertEqualsCanonicalizing')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Cobertura +{ /** - * Asserts that two variables are equal (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEqualsCanonicalizing + * @var File */ - function assertEqualsCanonicalizing($expected, $actual, string $message = '') : void + private $target; + public function __construct(File $target) { - \PHPUnit\Framework\Assert::assertEqualsCanonicalizing(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertEqualsIgnoringCase')) { - /** - * Asserts that two variables are equal (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEqualsIgnoringCase - */ - function assertEqualsIgnoringCase($expected, $actual, string $message = '') : void + public function target() : File { - \PHPUnit\Framework\Assert::assertEqualsIgnoringCase(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\assertEqualsWithDelta')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Crap4j +{ /** - * Asserts that two variables are equal (with delta). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEqualsWithDelta + * @var File */ - function assertEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertEqualsWithDelta(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotEquals')) { + private $target; /** - * Asserts that two variables are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotEquals + * @var int */ - function assertNotEquals($expected, $actual, string $message = '') : void + private $threshold; + public function __construct(File $target, int $threshold) { - \PHPUnit\Framework\Assert::assertNotEquals(...func_get_args()); + $this->target = $target; + $this->threshold = $threshold; } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotEqualsCanonicalizing')) { - /** - * Asserts that two variables are not equal (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotEqualsCanonicalizing - */ - function assertNotEqualsCanonicalizing($expected, $actual, string $message = '') : void + public function target() : File { - \PHPUnit\Framework\Assert::assertNotEqualsCanonicalizing(...func_get_args()); + return $this->target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotEqualsIgnoringCase')) { - /** - * Asserts that two variables are not equal (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotEqualsIgnoringCase - */ - function assertNotEqualsIgnoringCase($expected, $actual, string $message = '') : void + public function threshold() : int { - \PHPUnit\Framework\Assert::assertNotEqualsIgnoringCase(...func_get_args()); + return $this->threshold; } } -if (!\function_exists('PHPUnit\\Framework\\assertNotEqualsWithDelta')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\Directory; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Html +{ /** - * Asserts that two variables are not equal (with delta). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotEqualsWithDelta + * @var Directory */ - function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertNotEqualsWithDelta(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertObjectEquals')) { + private $target; /** - * @throws ExpectationFailedException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertObjectEquals + * @var int */ - function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = '') : void - { - \PHPUnit\Framework\Assert::assertObjectEquals(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertEmpty')) { + private $lowUpperBound; /** - * Asserts that a variable is empty. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert empty $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEmpty + * @var int */ - function assertEmpty($actual, string $message = '') : void + private $highLowerBound; + public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound) { - \PHPUnit\Framework\Assert::assertEmpty(...func_get_args()); + $this->target = $target; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotEmpty')) { - /** - * Asserts that a variable is not empty. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !empty $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotEmpty - */ - function assertNotEmpty($actual, string $message = '') : void + public function target() : Directory { - \PHPUnit\Framework\Assert::assertNotEmpty(...func_get_args()); + return $this->target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertGreaterThan')) { - /** - * Asserts that a value is greater than another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertGreaterThan - */ - function assertGreaterThan($expected, $actual, string $message = '') : void + public function lowUpperBound() : int { - \PHPUnit\Framework\Assert::assertGreaterThan(...func_get_args()); + return $this->lowUpperBound; } -} -if (!\function_exists('PHPUnit\\Framework\\assertGreaterThanOrEqual')) { - /** - * Asserts that a value is greater than or equal to another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertGreaterThanOrEqual - */ - function assertGreaterThanOrEqual($expected, $actual, string $message = '') : void + public function highLowerBound() : int { - \PHPUnit\Framework\Assert::assertGreaterThanOrEqual(...func_get_args()); + return $this->highLowerBound; } } -if (!\function_exists('PHPUnit\\Framework\\assertLessThan')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Php +{ /** - * Asserts that a value is smaller than another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertLessThan + * @var File */ - function assertLessThan($expected, $actual, string $message = '') : void + private $target; + public function __construct(File $target) { - \PHPUnit\Framework\Assert::assertLessThan(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertLessThanOrEqual')) { - /** - * Asserts that a value is smaller than or equal to another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertLessThanOrEqual - */ - function assertLessThanOrEqual($expected, $actual, string $message = '') : void + public function target() : File { - \PHPUnit\Framework\Assert::assertLessThanOrEqual(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\assertFileEquals')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ /** - * Asserts that the contents of one file is equal to the contents of another - * file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileEquals + * @var File */ - function assertFileEquals(string $expected, string $actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertFileEquals(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileEqualsCanonicalizing')) { + private $target; /** - * Asserts that the contents of one file is equal to the contents of another - * file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileEqualsCanonicalizing + * @var bool */ - function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertFileEqualsCanonicalizing(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileEqualsIgnoringCase')) { + private $showUncoveredFiles; /** - * Asserts that the contents of one file is equal to the contents of another - * file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileEqualsIgnoringCase + * @var bool */ - function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void + private $showOnlySummary; + public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) { - \PHPUnit\Framework\Assert::assertFileEqualsIgnoringCase(...func_get_args()); + $this->target = $target; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotEquals')) { - /** - * Asserts that the contents of one file is not equal to the contents of - * another file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotEquals - */ - function assertFileNotEquals(string $expected, string $actual, string $message = '') : void + public function target() : File { - \PHPUnit\Framework\Assert::assertFileNotEquals(...func_get_args()); + return $this->target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotEqualsCanonicalizing')) { - /** - * Asserts that the contents of one file is not equal to the contents of another - * file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotEqualsCanonicalizing - */ - function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void + public function showUncoveredFiles() : bool { - \PHPUnit\Framework\Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); + return $this->showUncoveredFiles; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotEqualsIgnoringCase')) { - /** - * Asserts that the contents of one file is not equal to the contents of another - * file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotEqualsIgnoringCase - */ - function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void + public function showOnlySummary() : bool { - \PHPUnit\Framework\Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); + return $this->showOnlySummary; } } -if (!\function_exists('PHPUnit\\Framework\\assertStringEqualsFile')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\Directory; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Xml +{ /** - * Asserts that the contents of a string is equal - * to the contents of a file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringEqualsFile + * @var Directory */ - function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = '') : void + private $target; + public function __construct(Directory $target) { - \PHPUnit\Framework\Assert::assertStringEqualsFile(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringEqualsFileCanonicalizing')) { - /** - * Asserts that the contents of a string is equal - * to the contents of a file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringEqualsFileCanonicalizing - */ - function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void + public function target() : Directory { - \PHPUnit\Framework\Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\assertStringEqualsFileIgnoringCase')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\Util\Xml\ValidationResult; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Configuration +{ /** - * Asserts that the contents of a string is equal - * to the contents of a file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringEqualsFileIgnoringCase + * @var string */ - function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotEqualsFile')) { + private $filename; /** - * Asserts that the contents of a string is not equal - * to the contents of a file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotEqualsFile + * @var ValidationResult */ - function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertStringNotEqualsFile(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotEqualsFileCanonicalizing')) { + private $validationResult; /** - * Asserts that the contents of a string is not equal - * to the contents of a file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotEqualsFileCanonicalizing + * @var ExtensionCollection */ - function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotEqualsFileIgnoringCase')) { + private $extensions; /** - * Asserts that the contents of a string is not equal - * to the contents of a file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotEqualsFileIgnoringCase + * @var CodeCoverage */ - function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsReadable')) { + private $codeCoverage; /** - * Asserts that a file/dir is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsReadable + * @var Groups */ - function assertIsReadable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsReadable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotReadable')) { + private $groups; /** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotReadable + * @var Groups */ - function assertIsNotReadable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsNotReadable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotIsReadable')) { + private $testdoxGroups; /** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotIsReadable + * @var ExtensionCollection */ - function assertNotIsReadable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertNotIsReadable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsWritable')) { + private $listeners; /** - * Asserts that a file/dir exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsWritable + * @var Logging */ - function assertIsWritable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsWritable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotWritable')) { + private $logging; /** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotWritable + * @var Php */ - function assertIsNotWritable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsNotWritable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotIsWritable')) { + private $php; /** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotIsWritable + * @var PHPUnit */ - function assertNotIsWritable(string $filename, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertNotIsWritable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryExists')) { + private $phpunit; /** - * Asserts that a directory exists. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryExists + * @var TestSuiteCollection */ - function assertDirectoryExists(string $directory, string $message = '') : void + private $testSuite; + public function __construct(string $filename, ValidationResult $validationResult, \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $extensions, CodeCoverage $codeCoverage, \PHPUnit\TextUI\XmlConfiguration\Groups $groups, \PHPUnit\TextUI\XmlConfiguration\Groups $testdoxGroups, \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $listeners, Logging $logging, \PHPUnit\TextUI\XmlConfiguration\Php $php, \PHPUnit\TextUI\XmlConfiguration\PHPUnit $phpunit, \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection $testSuite) { - \PHPUnit\Framework\Assert::assertDirectoryExists(...func_get_args()); + $this->filename = $filename; + $this->validationResult = $validationResult; + $this->extensions = $extensions; + $this->codeCoverage = $codeCoverage; + $this->groups = $groups; + $this->testdoxGroups = $testdoxGroups; + $this->listeners = $listeners; + $this->logging = $logging; + $this->php = $php; + $this->phpunit = $phpunit; + $this->testSuite = $testSuite; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryDoesNotExist')) { - /** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryDoesNotExist - */ - function assertDirectoryDoesNotExist(string $directory, string $message = '') : void + public function filename() : string { - \PHPUnit\Framework\Assert::assertDirectoryDoesNotExist(...func_get_args()); + return $this->filename; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryNotExists')) { - /** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryNotExists - */ - function assertDirectoryNotExists(string $directory, string $message = '') : void + public function hasValidationErrors() : bool { - \PHPUnit\Framework\Assert::assertDirectoryNotExists(...func_get_args()); + return $this->validationResult->hasValidationErrors(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryIsReadable')) { - /** - * Asserts that a directory exists and is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryIsReadable - */ - function assertDirectoryIsReadable(string $directory, string $message = '') : void + public function validationErrors() : string { - \PHPUnit\Framework\Assert::assertDirectoryIsReadable(...func_get_args()); + return $this->validationResult->asString(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryIsNotReadable')) { - /** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryIsNotReadable - */ - function assertDirectoryIsNotReadable(string $directory, string $message = '') : void + public function extensions() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection { - \PHPUnit\Framework\Assert::assertDirectoryIsNotReadable(...func_get_args()); + return $this->extensions; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryNotIsReadable')) { - /** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryNotIsReadable - */ - function assertDirectoryNotIsReadable(string $directory, string $message = '') : void + public function codeCoverage() : CodeCoverage { - \PHPUnit\Framework\Assert::assertDirectoryNotIsReadable(...func_get_args()); + return $this->codeCoverage; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryIsWritable')) { - /** - * Asserts that a directory exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryIsWritable - */ - function assertDirectoryIsWritable(string $directory, string $message = '') : void + public function groups() : \PHPUnit\TextUI\XmlConfiguration\Groups { - \PHPUnit\Framework\Assert::assertDirectoryIsWritable(...func_get_args()); + return $this->groups; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryIsNotWritable')) { - /** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryIsNotWritable - */ - function assertDirectoryIsNotWritable(string $directory, string $message = '') : void + public function testdoxGroups() : \PHPUnit\TextUI\XmlConfiguration\Groups { - \PHPUnit\Framework\Assert::assertDirectoryIsNotWritable(...func_get_args()); + return $this->testdoxGroups; } -} -if (!\function_exists('PHPUnit\\Framework\\assertDirectoryNotIsWritable')) { - /** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryNotIsWritable - */ - function assertDirectoryNotIsWritable(string $directory, string $message = '') : void + public function listeners() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection { - \PHPUnit\Framework\Assert::assertDirectoryNotIsWritable(...func_get_args()); + return $this->listeners; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileExists')) { - /** - * Asserts that a file exists. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileExists - */ - function assertFileExists(string $filename, string $message = '') : void + public function logging() : Logging { - \PHPUnit\Framework\Assert::assertFileExists(...func_get_args()); + return $this->logging; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileDoesNotExist')) { - /** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileDoesNotExist - */ - function assertFileDoesNotExist(string $filename, string $message = '') : void + public function php() : \PHPUnit\TextUI\XmlConfiguration\Php { - \PHPUnit\Framework\Assert::assertFileDoesNotExist(...func_get_args()); + return $this->php; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotExists')) { - /** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotExists - */ - function assertFileNotExists(string $filename, string $message = '') : void + public function phpunit() : \PHPUnit\TextUI\XmlConfiguration\PHPUnit { - \PHPUnit\Framework\Assert::assertFileNotExists(...func_get_args()); + return $this->phpunit; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileIsReadable')) { - /** - * Asserts that a file exists and is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileIsReadable - */ - function assertFileIsReadable(string $file, string $message = '') : void + public function testSuite() : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection { - \PHPUnit\Framework\Assert::assertFileIsReadable(...func_get_args()); + return $this->testSuite; } } -if (!\function_exists('PHPUnit\\Framework\\assertFileIsNotReadable')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Directory +{ /** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileIsNotReadable + * @var string */ - function assertFileIsNotReadable(string $file, string $message = '') : void + private $path; + public function __construct(string $path) { - \PHPUnit\Framework\Assert::assertFileIsNotReadable(...func_get_args()); + $this->path = $path; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotIsReadable')) { - /** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotIsReadable - */ - function assertFileNotIsReadable(string $file, string $message = '') : void + public function path() : string { - \PHPUnit\Framework\Assert::assertFileNotIsReadable(...func_get_args()); + return $this->path; } } -if (!\function_exists('PHPUnit\\Framework\\assertFileIsWritable')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class DirectoryCollection implements Countable, IteratorAggregate +{ /** - * Asserts that a file exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileIsWritable + * @var Directory[] */ - function assertFileIsWritable(string $file, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertFileIsWritable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileIsNotWritable')) { + private $directories; /** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileIsNotWritable + * @param Directory[] $directories */ - function assertFileIsNotWritable(string $file, string $message = '') : void + public static function fromArray(array $directories) : self + { + return new self(...$directories); + } + private function __construct(\PHPUnit\TextUI\XmlConfiguration\Directory ...$directories) { - \PHPUnit\Framework\Assert::assertFileIsNotWritable(...func_get_args()); + $this->directories = $directories; } -} -if (!\function_exists('PHPUnit\\Framework\\assertFileNotIsWritable')) { /** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotIsWritable + * @return Directory[] */ - function assertFileNotIsWritable(string $file, string $message = '') : void + public function asArray() : array { - \PHPUnit\Framework\Assert::assertFileNotIsWritable(...func_get_args()); + return $this->directories; } -} -if (!\function_exists('PHPUnit\\Framework\\assertTrue')) { - /** - * Asserts that a condition is true. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert true $condition - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertTrue - */ - function assertTrue($condition, string $message = '') : void + public function count() : int { - \PHPUnit\Framework\Assert::assertTrue(...func_get_args()); + return count($this->directories); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotTrue')) { - /** - * Asserts that a condition is not true. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !true $condition - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotTrue - */ - function assertNotTrue($condition, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\DirectoryCollectionIterator { - \PHPUnit\Framework\Assert::assertNotTrue(...func_get_args()); + return new \PHPUnit\TextUI\XmlConfiguration\DirectoryCollectionIterator($this); } -} -if (!\function_exists('PHPUnit\\Framework\\assertFalse')) { - /** - * Asserts that a condition is false. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert false $condition - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFalse - */ - function assertFalse($condition, string $message = '') : void + public function isEmpty() : bool { - \PHPUnit\Framework\Assert::assertFalse(...func_get_args()); + return $this->count() === 0; } } -if (!\function_exists('PHPUnit\\Framework\\assertNotFalse')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DirectoryCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a condition is not false. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !false $condition - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotFalse + * @var Directory[] */ - function assertNotFalse($condition, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertNotFalse(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertNull')) { + private $directories; /** - * Asserts that a variable is null. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert null $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNull + * @var int */ - function assertNull($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection $directories) { - \PHPUnit\Framework\Assert::assertNull(...func_get_args()); + $this->directories = $directories->asArray(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotNull')) { - /** - * Asserts that a variable is not null. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !null $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotNull - */ - function assertNotNull($actual, string $message = '') : void + public function count() : int { - \PHPUnit\Framework\Assert::assertNotNull(...func_get_args()); + return iterator_count($this); } -} -if (!\function_exists('PHPUnit\\Framework\\assertFinite')) { - /** - * Asserts that a variable is finite. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFinite - */ - function assertFinite($actual, string $message = '') : void + public function rewind() : void { - \PHPUnit\Framework\Assert::assertFinite(...func_get_args()); + $this->position = 0; } -} -if (!\function_exists('PHPUnit\\Framework\\assertInfinite')) { - /** - * Asserts that a variable is infinite. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertInfinite - */ - function assertInfinite($actual, string $message = '') : void + public function valid() : bool { - \PHPUnit\Framework\Assert::assertInfinite(...func_get_args()); + return $this->position < count($this->directories); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNan')) { - /** - * Asserts that a variable is nan. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNan - */ - function assertNan($actual, string $message = '') : void + public function key() : int { - \PHPUnit\Framework\Assert::assertNan(...func_get_args()); + return $this->position; } -} -if (!\function_exists('PHPUnit\\Framework\\assertClassHasAttribute')) { - /** - * Asserts that a class has a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassHasAttribute - */ - function assertClassHasAttribute(string $attributeName, string $className, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\Directory { - \PHPUnit\Framework\Assert::assertClassHasAttribute(...func_get_args()); + return $this->directories[$this->position]; } -} -if (!\function_exists('PHPUnit\\Framework\\assertClassNotHasAttribute')) { - /** - * Asserts that a class does not have a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassNotHasAttribute - */ - function assertClassNotHasAttribute(string $attributeName, string $className, string $message = '') : void + public function next() : void { - \PHPUnit\Framework\Assert::assertClassNotHasAttribute(...func_get_args()); + $this->position++; } } -if (!\function_exists('PHPUnit\\Framework\\assertClassHasStaticAttribute')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class File +{ /** - * Asserts that a class has a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassHasStaticAttribute + * @var string */ - function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = '') : void + private $path; + public function __construct(string $path) { - \PHPUnit\Framework\Assert::assertClassHasStaticAttribute(...func_get_args()); + $this->path = $path; } -} -if (!\function_exists('PHPUnit\\Framework\\assertClassNotHasStaticAttribute')) { - /** - * Asserts that a class does not have a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassNotHasStaticAttribute - */ - function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = '') : void + public function path() : string { - \PHPUnit\Framework\Assert::assertClassNotHasStaticAttribute(...func_get_args()); + return $this->path; } } -if (!\function_exists('PHPUnit\\Framework\\assertObjectHasAttribute')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class FileCollection implements Countable, IteratorAggregate +{ /** - * Asserts that an object has a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertObjectHasAttribute + * @var File[] */ - function assertObjectHasAttribute(string $attributeName, $object, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertObjectHasAttribute(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertObjectNotHasAttribute')) { + private $files; /** - * Asserts that an object does not have a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertObjectNotHasAttribute + * @param File[] $files */ - function assertObjectNotHasAttribute(string $attributeName, $object, string $message = '') : void + public static function fromArray(array $files) : self { - \PHPUnit\Framework\Assert::assertObjectNotHasAttribute(...func_get_args()); + return new self(...$files); } -} -if (!\function_exists('PHPUnit\\Framework\\assertSame')) { - /** - * Asserts that two variables have the same type and value. - * Used on objects, it asserts that two variables reference - * the same object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $expected - * @psalm-assert =ExpectedType $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertSame - */ - function assertSame($expected, $actual, string $message = '') : void + private function __construct(\PHPUnit\TextUI\XmlConfiguration\File ...$files) { - \PHPUnit\Framework\Assert::assertSame(...func_get_args()); + $this->files = $files; } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotSame')) { /** - * Asserts that two variables do not have the same type and value. - * Used on objects, it asserts that two variables do not reference - * the same object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotSame + * @return File[] */ - function assertNotSame($expected, $actual, string $message = '') : void + public function asArray() : array { - \PHPUnit\Framework\Assert::assertNotSame(...func_get_args()); + return $this->files; } -} -if (!\function_exists('PHPUnit\\Framework\\assertInstanceOf')) { - /** - * Asserts that a variable is of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert =ExpectedType $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertInstanceOf - */ - function assertInstanceOf(string $expected, $actual, string $message = '') : void + public function count() : int { - \PHPUnit\Framework\Assert::assertInstanceOf(...func_get_args()); + return count($this->files); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotInstanceOf')) { - /** - * Asserts that a variable is not of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert !ExpectedType $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotInstanceOf - */ - function assertNotInstanceOf(string $expected, $actual, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\FileCollectionIterator { - \PHPUnit\Framework\Assert::assertNotInstanceOf(...func_get_args()); + return new \PHPUnit\TextUI\XmlConfiguration\FileCollectionIterator($this); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsArray')) { - /** - * Asserts that a variable is of type array. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert array $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsArray - */ - function assertIsArray($actual, string $message = '') : void + public function isEmpty() : bool { - \PHPUnit\Framework\Assert::assertIsArray(...func_get_args()); + return $this->count() === 0; } } -if (!\function_exists('PHPUnit\\Framework\\assertIsBool')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a variable is of type bool. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert bool $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsBool + * @var File[] */ - function assertIsBool($actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsBool(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsFloat')) { + private $files; /** - * Asserts that a variable is of type float. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert float $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsFloat + * @var int */ - function assertIsFloat($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\FileCollection $files) { - \PHPUnit\Framework\Assert::assertIsFloat(...func_get_args()); + $this->files = $files->asArray(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsInt')) { - /** - * Asserts that a variable is of type int. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert int $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsInt - */ - function assertIsInt($actual, string $message = '') : void + public function count() : int + { + return iterator_count($this); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < count($this->files); + } + public function key() : int + { + return $this->position; + } + public function current() : \PHPUnit\TextUI\XmlConfiguration\File + { + return $this->files[$this->position]; + } + public function next() : void { - \PHPUnit\Framework\Assert::assertIsInt(...func_get_args()); + $this->position++; } } -if (!\function_exists('PHPUnit\\Framework\\assertIsNumeric')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function str_replace; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ /** - * Asserts that a variable is of type numeric. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert numeric $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNumeric + * @var string */ - function assertIsNumeric($actual, string $message = '') : void + private const TEMPLATE = <<<'EOT' + + + + + {tests_directory} + + + + + + {src_directory} + + + + +EOT; + public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory) : string { - \PHPUnit\Framework\Assert::assertIsNumeric(...func_get_args()); + return str_replace(['{phpunit_version}', '{bootstrap_script}', '{tests_directory}', '{src_directory}', '{cache_directory}'], [$phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory, $cacheDirectory], self::TEMPLATE); } } -if (!\function_exists('PHPUnit\\Framework\\assertIsObject')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Group +{ /** - * Asserts that a variable is of type object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert object $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsObject + * @var string */ - function assertIsObject($actual, string $message = '') : void + private $name; + public function __construct(string $name) { - \PHPUnit\Framework\Assert::assertIsObject(...func_get_args()); + $this->name = $name; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsResource')) { - /** - * Asserts that a variable is of type resource. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert resource $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsResource - */ - function assertIsResource($actual, string $message = '') : void + public function name() : string { - \PHPUnit\Framework\Assert::assertIsResource(...func_get_args()); + return $this->name; } } -if (!\function_exists('PHPUnit\\Framework\\assertIsClosedResource')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class GroupCollection implements IteratorAggregate +{ /** - * Asserts that a variable is of type resource and is closed. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert resource $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsClosedResource + * @var Group[] */ - function assertIsClosedResource($actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsClosedResource(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsString')) { + private $groups; /** - * Asserts that a variable is of type string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert string $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsString + * @param Group[] $groups */ - function assertIsString($actual, string $message = '') : void + public static function fromArray(array $groups) : self { - \PHPUnit\Framework\Assert::assertIsString(...func_get_args()); + return new self(...$groups); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsScalar')) { - /** - * Asserts that a variable is of type scalar. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert scalar $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsScalar - */ - function assertIsScalar($actual, string $message = '') : void + private function __construct(\PHPUnit\TextUI\XmlConfiguration\Group ...$groups) { - \PHPUnit\Framework\Assert::assertIsScalar(...func_get_args()); + $this->groups = $groups; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsCallable')) { /** - * Asserts that a variable is of type callable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert callable $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsCallable + * @return Group[] */ - function assertIsCallable($actual, string $message = '') : void + public function asArray() : array { - \PHPUnit\Framework\Assert::assertIsCallable(...func_get_args()); + return $this->groups; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsIterable')) { /** - * Asserts that a variable is of type iterable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert iterable $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsIterable + * @return string[] */ - function assertIsIterable($actual, string $message = '') : void + public function asArrayOfStrings() : array { - \PHPUnit\Framework\Assert::assertIsIterable(...func_get_args()); + $result = []; + foreach ($this->groups as $group) { + $result[] = $group->name(); + } + return $result; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotArray')) { - /** - * Asserts that a variable is not of type array. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !array $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotArray - */ - function assertIsNotArray($actual, string $message = '') : void + public function isEmpty() : bool { - \PHPUnit\Framework\Assert::assertIsNotArray(...func_get_args()); + return empty($this->groups); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotBool')) { - /** - * Asserts that a variable is not of type bool. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !bool $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotBool - */ - function assertIsNotBool($actual, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\GroupCollectionIterator { - \PHPUnit\Framework\Assert::assertIsNotBool(...func_get_args()); + return new \PHPUnit\TextUI\XmlConfiguration\GroupCollectionIterator($this); } } -if (!\function_exists('PHPUnit\\Framework\\assertIsNotFloat')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GroupCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a variable is not of type float. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !float $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotFloat + * @var Group[] */ - function assertIsNotFloat($actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsNotFloat(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotInt')) { + private $groups; /** - * Asserts that a variable is not of type int. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !int $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotInt + * @var int */ - function assertIsNotInt($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\GroupCollection $groups) { - \PHPUnit\Framework\Assert::assertIsNotInt(...func_get_args()); + $this->groups = $groups->asArray(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotNumeric')) { - /** - * Asserts that a variable is not of type numeric. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !numeric $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotNumeric - */ - function assertIsNotNumeric($actual, string $message = '') : void + public function count() : int { - \PHPUnit\Framework\Assert::assertIsNotNumeric(...func_get_args()); + return iterator_count($this); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotObject')) { - /** - * Asserts that a variable is not of type object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !object $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotObject - */ - function assertIsNotObject($actual, string $message = '') : void + public function rewind() : void { - \PHPUnit\Framework\Assert::assertIsNotObject(...func_get_args()); + $this->position = 0; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotResource')) { - /** - * Asserts that a variable is not of type resource. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !resource $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotResource - */ - function assertIsNotResource($actual, string $message = '') : void + public function valid() : bool { - \PHPUnit\Framework\Assert::assertIsNotResource(...func_get_args()); + return $this->position < count($this->groups); } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotClosedResource')) { - /** - * Asserts that a variable is not of type resource. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !resource $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotClosedResource - */ - function assertIsNotClosedResource($actual, string $message = '') : void + public function key() : int { - \PHPUnit\Framework\Assert::assertIsNotClosedResource(...func_get_args()); + return $this->position; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotString')) { - /** - * Asserts that a variable is not of type string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !string $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotString - */ - function assertIsNotString($actual, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\Group { - \PHPUnit\Framework\Assert::assertIsNotString(...func_get_args()); + return $this->groups[$this->position]; } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotScalar')) { - /** - * Asserts that a variable is not of type scalar. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !scalar $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotScalar - */ - function assertIsNotScalar($actual, string $message = '') : void + public function next() : void { - \PHPUnit\Framework\Assert::assertIsNotScalar(...func_get_args()); + $this->position++; } } -if (!\function_exists('PHPUnit\\Framework\\assertIsNotCallable')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Groups +{ /** - * Asserts that a variable is not of type callable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !callable $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotCallable + * @var GroupCollection */ - function assertIsNotCallable($actual, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertIsNotCallable(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertIsNotIterable')) { + private $include; /** - * Asserts that a variable is not of type iterable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !iterable $actual - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertIsNotIterable + * @var GroupCollection */ - function assertIsNotIterable($actual, string $message = '') : void + private $exclude; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\GroupCollection $include, \PHPUnit\TextUI\XmlConfiguration\GroupCollection $exclude) { - \PHPUnit\Framework\Assert::assertIsNotIterable(...func_get_args()); + $this->include = $include; + $this->exclude = $exclude; } -} -if (!\function_exists('PHPUnit\\Framework\\assertMatchesRegularExpression')) { - /** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertMatchesRegularExpression - */ - function assertMatchesRegularExpression(string $pattern, string $string, string $message = '') : void + public function hasInclude() : bool { - \PHPUnit\Framework\Assert::assertMatchesRegularExpression(...func_get_args()); + return !$this->include->isEmpty(); } -} -if (!\function_exists('PHPUnit\\Framework\\assertRegExp')) { - /** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertRegExp - */ - function assertRegExp(string $pattern, string $string, string $message = '') : void + public function include() : \PHPUnit\TextUI\XmlConfiguration\GroupCollection + { + return $this->include; + } + public function hasExclude() : bool + { + return !$this->exclude->isEmpty(); + } + public function exclude() : \PHPUnit\TextUI\XmlConfiguration\GroupCollection { - \PHPUnit\Framework\Assert::assertRegExp(...func_get_args()); + return $this->exclude; } } -if (!\function_exists('PHPUnit\\Framework\\assertDoesNotMatchRegularExpression')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const DIRECTORY_SEPARATOR; +use const PHP_VERSION; +use function assert; +use function defined; +use function dirname; +use function explode; +use function is_file; +use function is_numeric; +use function preg_match; +use function stream_resolve_include_path; +use function strlen; +use function strpos; +use function strtolower; +use function substr; +use function trim; +use DOMDocument; +use DOMElement; +use DOMNodeList; +use DOMXPath; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory as FilterDirectory; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection as FilterDirectoryCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Text; +use PHPUnit\TextUI\XmlConfiguration\TestSuite as TestSuiteConfiguration; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use PHPUnit\Util\VersionComparisonOperator; +use PHPUnit\Util\Xml; +use PHPUnit\Util\Xml\Exception as XmlException; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\SchemaFinder; +use PHPUnit\Util\Xml\Validator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Loader +{ /** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDoesNotMatchRegularExpression + * @throws Exception */ - function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = '') : void + public function load(string $filename) : \PHPUnit\TextUI\XmlConfiguration\Configuration { - \PHPUnit\Framework\Assert::assertDoesNotMatchRegularExpression(...func_get_args()); + try { + $document = (new XmlLoader())->loadFile($filename, \false, \true, \true); + } catch (XmlException $e) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + $xpath = new DOMXPath($document); + try { + $xsdFilename = (new SchemaFinder())->find(Version::series()); + } catch (XmlException $e) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + return new \PHPUnit\TextUI\XmlConfiguration\Configuration($filename, (new Validator())->validate($document, $xsdFilename), $this->extensions($filename, $xpath), $this->codeCoverage($filename, $xpath, $document), $this->groups($xpath), $this->testdoxGroups($xpath), $this->listeners($filename, $xpath), $this->logging($filename, $xpath), $this->php($filename, $xpath), $this->phpunit($filename, $document), $this->testSuite($filename, $xpath)); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotRegExp')) { - /** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotRegExp - */ - function assertNotRegExp(string $pattern, string $string, string $message = '') : void + public function logging(string $filename, DOMXPath $xpath) : Logging { - \PHPUnit\Framework\Assert::assertNotRegExp(...func_get_args()); + if ($xpath->query('logging/log')->length !== 0) { + return $this->legacyLogging($filename, $xpath); + } + $junit = null; + $element = $this->element($xpath, 'logging/junit'); + if ($element) { + $junit = new Junit(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $text = null; + $element = $this->element($xpath, 'logging/text'); + if ($element) { + $text = new Text(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $teamCity = null; + $element = $this->element($xpath, 'logging/teamcity'); + if ($element) { + $teamCity = new TeamCity(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $testDoxHtml = null; + $element = $this->element($xpath, 'logging/testdoxHtml'); + if ($element) { + $testDoxHtml = new TestDoxHtml(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $testDoxText = null; + $element = $this->element($xpath, 'logging/testdoxText'); + if ($element) { + $testDoxText = new TestDoxText(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $testDoxXml = null; + $element = $this->element($xpath, 'logging/testdoxXml'); + if ($element) { + $testDoxXml = new TestDoxXml(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + return new Logging($junit, $text, $teamCity, $testDoxHtml, $testDoxText, $testDoxXml); } -} -if (!\function_exists('PHPUnit\\Framework\\assertSameSize')) { - /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertSameSize - */ - function assertSameSize($expected, $actual, string $message = '') : void + public function legacyLogging(string $filename, DOMXPath $xpath) : Logging { - \PHPUnit\Framework\Assert::assertSameSize(...func_get_args()); + $junit = null; + $teamCity = null; + $testDoxHtml = null; + $testDoxText = null; + $testDoxXml = null; + $text = null; + foreach ($xpath->query('logging/log') as $log) { + assert($log instanceof DOMElement); + $type = (string) $log->getAttribute('type'); + $target = (string) $log->getAttribute('target'); + if (!$target) { + continue; + } + $target = $this->toAbsolutePath($filename, $target); + switch ($type) { + case 'plain': + $text = new Text(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'junit': + $junit = new Junit(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'teamcity': + $teamCity = new TeamCity(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'testdox-html': + $testDoxHtml = new TestDoxHtml(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'testdox-text': + $testDoxText = new TestDoxText(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'testdox-xml': + $testDoxXml = new TestDoxXml(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + } + } + return new Logging($junit, $text, $teamCity, $testDoxHtml, $testDoxText, $testDoxXml); } -} -if (!\function_exists('PHPUnit\\Framework\\assertNotSameSize')) { - /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is not the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotSameSize - */ - function assertNotSameSize($expected, $actual, string $message = '') : void + private function extensions(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection { - \PHPUnit\Framework\Assert::assertNotSameSize(...func_get_args()); + $extensions = []; + foreach ($xpath->query('extensions/extension') as $extension) { + assert($extension instanceof DOMElement); + $extensions[] = $this->getElementConfigurationParameters($filename, $extension); + } + return \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection::fromArray($extensions); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringMatchesFormat')) { - /** - * Asserts that a string matches a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringMatchesFormat - */ - function assertStringMatchesFormat(string $format, string $string, string $message = '') : void + private function getElementConfigurationParameters(string $filename, DOMElement $element) : \PHPUnit\TextUI\XmlConfiguration\Extension { - \PHPUnit\Framework\Assert::assertStringMatchesFormat(...func_get_args()); + /** @psalm-var class-string $class */ + $class = (string) $element->getAttribute('class'); + $file = ''; + $arguments = $this->getConfigurationArguments($filename, $element->childNodes); + if ($element->getAttribute('file')) { + $file = $this->toAbsolutePath($filename, (string) $element->getAttribute('file'), \true); + } + return new \PHPUnit\TextUI\XmlConfiguration\Extension($class, $file, $arguments); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotMatchesFormat')) { - /** - * Asserts that a string does not match a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotMatchesFormat - */ - function assertStringNotMatchesFormat(string $format, string $string, string $message = '') : void + private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = \false) : string { - \PHPUnit\Framework\Assert::assertStringNotMatchesFormat(...func_get_args()); + $path = trim($path); + if (strpos($path, '/') === 0) { + return $path; + } + // Matches the following on Windows: + // - \\NetworkComputer\Path + // - \\.\D: + // - \\.\c: + // - C:\Windows + // - C:\windows + // - C:/windows + // - c:/windows + if (defined('PHP_WINDOWS_VERSION_BUILD') && ($path[0] === '\\' || strlen($path) >= 3 && preg_match('#^[A-Z]\\:[/\\\\]#i', substr($path, 0, 3)))) { + return $path; + } + if (strpos($path, '://') !== \false) { + return $path; + } + $file = dirname($filename) . DIRECTORY_SEPARATOR . $path; + if ($useIncludePath && !is_file($file)) { + $includePathFile = stream_resolve_include_path($path); + if ($includePathFile) { + $file = $includePathFile; + } + } + return $file; } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringMatchesFormatFile')) { - /** - * Asserts that a string matches a given format file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringMatchesFormatFile - */ - function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = '') : void + private function getConfigurationArguments(string $filename, DOMNodeList $nodes) : array { - \PHPUnit\Framework\Assert::assertStringMatchesFormatFile(...func_get_args()); + $arguments = []; + if ($nodes->length === 0) { + return $arguments; + } + foreach ($nodes as $node) { + if (!$node instanceof DOMElement) { + continue; + } + if ($node->tagName !== 'arguments') { + continue; + } + foreach ($node->childNodes as $argument) { + if (!$argument instanceof DOMElement) { + continue; + } + if ($argument->tagName === 'file' || $argument->tagName === 'directory') { + $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); + } else { + $arguments[] = Xml::xmlToVariable($argument); + } + } + } + return $arguments; } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotMatchesFormatFile')) { - /** - * Asserts that a string does not match a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotMatchesFormatFile - */ - function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = '') : void + private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document) : CodeCoverage { - \PHPUnit\Framework\Assert::assertStringNotMatchesFormatFile(...func_get_args()); + if ($xpath->query('filter/whitelist')->length !== 0) { + return $this->legacyCodeCoverage($filename, $xpath, $document); + } + $cacheDirectory = null; + $pathCoverage = \false; + $includeUncoveredFiles = \true; + $processUncoveredFiles = \false; + $ignoreDeprecatedCodeUnits = \false; + $disableCodeCoverageIgnore = \false; + $element = $this->element($xpath, 'coverage'); + if ($element) { + $cacheDirectory = $this->getStringAttribute($element, 'cacheDirectory'); + if ($cacheDirectory !== null) { + $cacheDirectory = new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, $cacheDirectory)); + } + $pathCoverage = $this->getBooleanAttribute($element, 'pathCoverage', \false); + $includeUncoveredFiles = $this->getBooleanAttribute($element, 'includeUncoveredFiles', \true); + $processUncoveredFiles = $this->getBooleanAttribute($element, 'processUncoveredFiles', \false); + $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute($element, 'ignoreDeprecatedCodeUnits', \false); + $disableCodeCoverageIgnore = $this->getBooleanAttribute($element, 'disableCodeCoverageIgnore', \false); + } + $clover = null; + $element = $this->element($xpath, 'coverage/report/clover'); + if ($element) { + $clover = new Clover(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $cobertura = null; + $element = $this->element($xpath, 'coverage/report/cobertura'); + if ($element) { + $cobertura = new Cobertura(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $crap4j = null; + $element = $this->element($xpath, 'coverage/report/crap4j'); + if ($element) { + $crap4j = new Crap4j(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile'))), $this->getIntegerAttribute($element, 'threshold', 30)); + } + $html = null; + $element = $this->element($xpath, 'coverage/report/html'); + if ($element) { + $html = new CodeCoverageHtml(new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputDirectory'))), $this->getIntegerAttribute($element, 'lowUpperBound', 50), $this->getIntegerAttribute($element, 'highLowerBound', 90)); + } + $php = null; + $element = $this->element($xpath, 'coverage/report/php'); + if ($element) { + $php = new CodeCoveragePhp(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile')))); + } + $text = null; + $element = $this->element($xpath, 'coverage/report/text'); + if ($element) { + $text = new CodeCoverageText(new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputFile'))), $this->getBooleanAttribute($element, 'showUncoveredFiles', \false), $this->getBooleanAttribute($element, 'showOnlySummary', \false)); + } + $xml = null; + $element = $this->element($xpath, 'coverage/report/xml'); + if ($element) { + $xml = new CodeCoverageXml(new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, (string) $this->getStringAttribute($element, 'outputDirectory')))); + } + return new CodeCoverage($cacheDirectory, $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'), $this->readFilterFiles($filename, $xpath, 'coverage/include/file'), $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'), $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'), $pathCoverage, $includeUncoveredFiles, $processUncoveredFiles, $ignoreDeprecatedCodeUnits, $disableCodeCoverageIgnore, $clover, $cobertura, $crap4j, $html, $php, $text, $xml); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringStartsWith')) { /** - * Asserts that a string starts with a given prefix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringStartsWith + * @deprecated */ - function assertStringStartsWith(string $prefix, string $string, string $message = '') : void + private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document) : CodeCoverage { - \PHPUnit\Framework\Assert::assertStringStartsWith(...func_get_args()); + $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute($document->documentElement, 'ignoreDeprecatedCodeUnitsFromCodeCoverage', \false); + $disableCodeCoverageIgnore = $this->getBooleanAttribute($document->documentElement, 'disableCodeCoverageIgnore', \false); + $includeUncoveredFiles = \true; + $processUncoveredFiles = \false; + $element = $this->element($xpath, 'filter/whitelist'); + if ($element) { + if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) { + $includeUncoveredFiles = (bool) $this->getBoolean((string) $element->getAttribute('addUncoveredFilesFromWhitelist'), \true); + } + if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) { + $processUncoveredFiles = (bool) $this->getBoolean((string) $element->getAttribute('processUncoveredFilesFromWhitelist'), \false); + } + } + $clover = null; + $cobertura = null; + $crap4j = null; + $html = null; + $php = null; + $text = null; + $xml = null; + foreach ($xpath->query('logging/log') as $log) { + assert($log instanceof DOMElement); + $type = (string) $log->getAttribute('type'); + $target = (string) $log->getAttribute('target'); + if (!$target) { + continue; + } + $target = $this->toAbsolutePath($filename, $target); + switch ($type) { + case 'coverage-clover': + $clover = new Clover(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'coverage-cobertura': + $cobertura = new Cobertura(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'coverage-crap4j': + $crap4j = new Crap4j(new \PHPUnit\TextUI\XmlConfiguration\File($target), $this->getIntegerAttribute($log, 'threshold', 30)); + break; + case 'coverage-html': + $html = new CodeCoverageHtml(new \PHPUnit\TextUI\XmlConfiguration\Directory($target), $this->getIntegerAttribute($log, 'lowUpperBound', 50), $this->getIntegerAttribute($log, 'highLowerBound', 90)); + break; + case 'coverage-php': + $php = new CodeCoveragePhp(new \PHPUnit\TextUI\XmlConfiguration\File($target)); + break; + case 'coverage-text': + $text = new CodeCoverageText(new \PHPUnit\TextUI\XmlConfiguration\File($target), $this->getBooleanAttribute($log, 'showUncoveredFiles', \false), $this->getBooleanAttribute($log, 'showOnlySummary', \false)); + break; + case 'coverage-xml': + $xml = new CodeCoverageXml(new \PHPUnit\TextUI\XmlConfiguration\Directory($target)); + break; + } + } + return new CodeCoverage(null, $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), \false, $includeUncoveredFiles, $processUncoveredFiles, $ignoreDeprecatedCodeUnits, $disableCodeCoverageIgnore, $clover, $cobertura, $crap4j, $html, $php, $text, $xml); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringStartsNotWith')) { /** - * Asserts that a string starts not with a given prefix. - * - * @param string $prefix - * @param string $string + * If $value is 'false' or 'true', this returns the value that $value represents. + * Otherwise, returns $default, which may be a string in rare cases. * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @see \PHPUnit\TextUI\XmlConfigurationTest::testPHPConfigurationIsReadCorrectly * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @param bool|string $default * - * @see Assert::assertStringStartsNotWith + * @return bool|string */ - function assertStringStartsNotWith($prefix, $string, string $message = '') : void + private function getBoolean(string $value, $default) { - \PHPUnit\Framework\Assert::assertStringStartsNotWith(...func_get_args()); + if (strtolower($value) === 'false') { + return \false; + } + if (strtolower($value) === 'true') { + return \true; + } + return $default; } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringContainsString')) { - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringContainsString - */ - function assertStringContainsString(string $needle, string $haystack, string $message = '') : void + private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query) : FilterDirectoryCollection + { + $directories = []; + foreach ($xpath->query($query) as $directoryNode) { + assert($directoryNode instanceof DOMElement); + $directoryPath = (string) $directoryNode->textContent; + if (!$directoryPath) { + continue; + } + $directories[] = new FilterDirectory($this->toAbsolutePath($filename, $directoryPath), $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT'); + } + return FilterDirectoryCollection::fromArray($directories); + } + private function readFilterFiles(string $filename, DOMXPath $xpath, string $query) : \PHPUnit\TextUI\XmlConfiguration\FileCollection + { + $files = []; + foreach ($xpath->query($query) as $file) { + $filePath = (string) $file->textContent; + if ($filePath) { + $files[] = new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, $filePath)); + } + } + return \PHPUnit\TextUI\XmlConfiguration\FileCollection::fromArray($files); + } + private function groups(DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Groups { - \PHPUnit\Framework\Assert::assertStringContainsString(...func_get_args()); + return $this->parseGroupConfiguration($xpath, 'groups'); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringContainsStringIgnoringCase')) { - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringContainsStringIgnoringCase - */ - function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void + private function testdoxGroups(DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Groups { - \PHPUnit\Framework\Assert::assertStringContainsStringIgnoringCase(...func_get_args()); + return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotContainsString')) { - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotContainsString - */ - function assertStringNotContainsString(string $needle, string $haystack, string $message = '') : void + private function parseGroupConfiguration(DOMXPath $xpath, string $root) : \PHPUnit\TextUI\XmlConfiguration\Groups { - \PHPUnit\Framework\Assert::assertStringNotContainsString(...func_get_args()); + $include = []; + $exclude = []; + foreach ($xpath->query($root . '/include/group') as $group) { + $include[] = new \PHPUnit\TextUI\XmlConfiguration\Group((string) $group->textContent); + } + foreach ($xpath->query($root . '/exclude/group') as $group) { + $exclude[] = new \PHPUnit\TextUI\XmlConfiguration\Group((string) $group->textContent); + } + return new \PHPUnit\TextUI\XmlConfiguration\Groups(\PHPUnit\TextUI\XmlConfiguration\GroupCollection::fromArray($include), \PHPUnit\TextUI\XmlConfiguration\GroupCollection::fromArray($exclude)); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringNotContainsStringIgnoringCase')) { - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringNotContainsStringIgnoringCase - */ - function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void + private function listeners(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection { - \PHPUnit\Framework\Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); + $listeners = []; + foreach ($xpath->query('listeners/listener') as $listener) { + assert($listener instanceof DOMElement); + $listeners[] = $this->getElementConfigurationParameters($filename, $listener); + } + return \PHPUnit\TextUI\XmlConfiguration\ExtensionCollection::fromArray($listeners); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringEndsWith')) { - /** - * Asserts that a string ends with a given suffix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringEndsWith - */ - function assertStringEndsWith(string $suffix, string $string, string $message = '') : void + private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default) : bool { - \PHPUnit\Framework\Assert::assertStringEndsWith(...func_get_args()); + if (!$element->hasAttribute($attribute)) { + return $default; + } + return (bool) $this->getBoolean((string) $element->getAttribute($attribute), \false); } -} -if (!\function_exists('PHPUnit\\Framework\\assertStringEndsNotWith')) { - /** - * Asserts that a string ends not with a given suffix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertStringEndsNotWith - */ - function assertStringEndsNotWith(string $suffix, string $string, string $message = '') : void + private function getIntegerAttribute(DOMElement $element, string $attribute, int $default) : int { - \PHPUnit\Framework\Assert::assertStringEndsNotWith(...func_get_args()); + if (!$element->hasAttribute($attribute)) { + return $default; + } + return $this->getInteger((string) $element->getAttribute($attribute), $default); } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlFileEqualsXmlFile')) { - /** - * Asserts that two XML files are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlFileEqualsXmlFile - */ - function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void + private function getStringAttribute(DOMElement $element, string $attribute) : ?string { - \PHPUnit\Framework\Assert::assertXmlFileEqualsXmlFile(...func_get_args()); + if (!$element->hasAttribute($attribute)) { + return null; + } + return (string) $element->getAttribute($attribute); } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlFileNotEqualsXmlFile')) { - /** - * Asserts that two XML files are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws \PHPUnit\Util\Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlFileNotEqualsXmlFile - */ - function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void + private function getInteger(string $value, int $default) : int { - \PHPUnit\Framework\Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); + if (is_numeric($value)) { + return (int) $value; + } + return $default; } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlStringEqualsXmlFile')) { - /** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlStringEqualsXmlFile - */ - function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void + private function php(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\Php { - \PHPUnit\Framework\Assert::assertXmlStringEqualsXmlFile(...func_get_args()); + $includePaths = []; + foreach ($xpath->query('php/includePath') as $includePath) { + $path = (string) $includePath->textContent; + if ($path) { + $includePaths[] = new \PHPUnit\TextUI\XmlConfiguration\Directory($this->toAbsolutePath($filename, $path)); + } + } + $iniSettings = []; + foreach ($xpath->query('php/ini') as $ini) { + assert($ini instanceof DOMElement); + $iniSettings[] = new \PHPUnit\TextUI\XmlConfiguration\IniSetting((string) $ini->getAttribute('name'), (string) $ini->getAttribute('value')); + } + $constants = []; + foreach ($xpath->query('php/const') as $const) { + assert($const instanceof DOMElement); + $value = (string) $const->getAttribute('value'); + $constants[] = new \PHPUnit\TextUI\XmlConfiguration\Constant((string) $const->getAttribute('name'), $this->getBoolean($value, $value)); + } + $variables = ['var' => [], 'env' => [], 'post' => [], 'get' => [], 'cookie' => [], 'server' => [], 'files' => [], 'request' => []]; + foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { + foreach ($xpath->query('php/' . $array) as $var) { + assert($var instanceof DOMElement); + $name = (string) $var->getAttribute('name'); + $value = (string) $var->getAttribute('value'); + $force = \false; + $verbatim = \false; + if ($var->hasAttribute('force')) { + $force = (bool) $this->getBoolean($var->getAttribute('force'), \false); + } + if ($var->hasAttribute('verbatim')) { + $verbatim = $this->getBoolean($var->getAttribute('verbatim'), \false); + } + if (!$verbatim) { + $value = $this->getBoolean($value, $value); + } + $variables[$array][] = new \PHPUnit\TextUI\XmlConfiguration\Variable($name, $value, $force); + } + } + return new \PHPUnit\TextUI\XmlConfiguration\Php(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection::fromArray($includePaths), \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection::fromArray($iniSettings), \PHPUnit\TextUI\XmlConfiguration\ConstantCollection::fromArray($constants), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['var']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['env']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['post']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['get']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['cookie']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['server']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['files']), \PHPUnit\TextUI\XmlConfiguration\VariableCollection::fromArray($variables['request'])); } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlStringNotEqualsXmlFile')) { - /** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlStringNotEqualsXmlFile - */ - function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void + private function phpunit(string $filename, DOMDocument $document) : \PHPUnit\TextUI\XmlConfiguration\PHPUnit { - \PHPUnit\Framework\Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = \false; + $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', \true); + if ($document->documentElement->hasAttribute('executionOrder')) { + foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = \false; + $resolveDependencies = \true; + break; + case 'depends': + $resolveDependencies = \true; + break; + case 'no-depends': + $resolveDependencies = \false; + break; + case 'defects': + $defectsFirst = \true; + break; + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + break; + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + break; + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + break; + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + break; + } + } + } + $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); + $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', \false); + $conflictBetweenPrinterClassAndTestdox = \false; + if ($testdox) { + if ($printerClass !== null) { + $conflictBetweenPrinterClassAndTestdox = \true; + } + $printerClass = CliTestDoxPrinter::class; + } + $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); + if ($cacheResultFile !== null) { + $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); + } + $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); + if ($bootstrap !== null) { + $bootstrap = $this->toAbsolutePath($filename, $bootstrap); + } + $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); + if ($extensionsDirectory !== null) { + $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); + } + $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); + if ($testSuiteLoaderFile !== null) { + $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); + } + $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); + if ($printerFile !== null) { + $printerFile = $this->toAbsolutePath($filename, $printerFile); + } + return new \PHPUnit\TextUI\XmlConfiguration\PHPUnit($this->getBooleanAttribute($document->documentElement, 'cacheResult', \true), $cacheResultFile, $this->getColumns($document), $this->getColors($document), $this->getBooleanAttribute($document->documentElement, 'stderr', \false), $this->getBooleanAttribute($document->documentElement, 'noInteraction', \false), $this->getBooleanAttribute($document->documentElement, 'verbose', \false), $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', \false), $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', \false), $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', \true), $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', \false), $bootstrap, $this->getBooleanAttribute($document->documentElement, 'processIsolation', \false), $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', \false), $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', \false), $this->getBooleanAttribute($document->documentElement, 'failOnRisky', \false), $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', \false), $this->getBooleanAttribute($document->documentElement, 'failOnWarning', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnError', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', \false), $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', \false), $extensionsDirectory, $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), $testSuiteLoaderFile, $printerClass, $printerFile, $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', \true), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', \false), $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', \false), $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', \false), $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), $executionOrder, $resolveDependencies, $defectsFirst, $this->getBooleanAttribute($document->documentElement, 'backupGlobals', \false), $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', \false), $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', \false), $conflictBetweenPrinterClassAndTestdox); } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlStringEqualsXmlString')) { - /** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlStringEqualsXmlString - */ - function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = '') : void + private function getColors(DOMDocument $document) : string { - \PHPUnit\Framework\Assert::assertXmlStringEqualsXmlString(...func_get_args()); + $colors = DefaultResultPrinter::COLOR_DEFAULT; + if ($document->documentElement->hasAttribute('colors')) { + /* only allow boolean for compatibility with previous versions + 'always' only allowed from command line */ + if ($this->getBoolean($document->documentElement->getAttribute('colors'), \false)) { + $colors = DefaultResultPrinter::COLOR_AUTO; + } else { + $colors = DefaultResultPrinter::COLOR_NEVER; + } + } + return $colors; } -} -if (!\function_exists('PHPUnit\\Framework\\assertXmlStringNotEqualsXmlString')) { /** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertXmlStringNotEqualsXmlString + * @return int|string */ - function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = '') : void + private function getColumns(DOMDocument $document) { - \PHPUnit\Framework\Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); + $columns = 80; + if ($document->documentElement->hasAttribute('columns')) { + $columns = (string) $document->documentElement->getAttribute('columns'); + if ($columns !== 'max') { + $columns = $this->getInteger($columns, 80); + } + } + return $columns; } -} -if (!\function_exists('PHPUnit\\Framework\\assertEqualXMLStructure')) { - /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws AssertionFailedError - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEqualXMLStructure - */ - function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = \false, string $message = '') : void + private function testSuite(string $filename, DOMXPath $xpath) : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection { - \PHPUnit\Framework\Assert::assertEqualXMLStructure(...func_get_args()); + $testSuites = []; + foreach ($this->getTestSuiteElements($xpath) as $element) { + $exclude = []; + foreach ($element->getElementsByTagName('exclude') as $excludeNode) { + $excludeFile = (string) $excludeNode->textContent; + if ($excludeFile) { + $exclude[] = new \PHPUnit\TextUI\XmlConfiguration\File($this->toAbsolutePath($filename, $excludeFile)); + } + } + $directories = []; + foreach ($element->getElementsByTagName('directory') as $directoryNode) { + assert($directoryNode instanceof DOMElement); + $directory = (string) $directoryNode->textContent; + if (empty($directory)) { + continue; + } + $prefix = ''; + if ($directoryNode->hasAttribute('prefix')) { + $prefix = (string) $directoryNode->getAttribute('prefix'); + } + $suffix = 'Test.php'; + if ($directoryNode->hasAttribute('suffix')) { + $suffix = (string) $directoryNode->getAttribute('suffix'); + } + $phpVersion = PHP_VERSION; + if ($directoryNode->hasAttribute('phpVersion')) { + $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); + } + $phpVersionOperator = new VersionComparisonOperator('>='); + if ($directoryNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); + } + $directories[] = new \PHPUnit\TextUI\XmlConfiguration\TestDirectory($this->toAbsolutePath($filename, $directory), $prefix, $suffix, $phpVersion, $phpVersionOperator); + } + $files = []; + foreach ($element->getElementsByTagName('file') as $fileNode) { + assert($fileNode instanceof DOMElement); + $file = (string) $fileNode->textContent; + if (empty($file)) { + continue; + } + $phpVersion = PHP_VERSION; + if ($fileNode->hasAttribute('phpVersion')) { + $phpVersion = (string) $fileNode->getAttribute('phpVersion'); + } + $phpVersionOperator = new VersionComparisonOperator('>='); + if ($fileNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); + } + $files[] = new \PHPUnit\TextUI\XmlConfiguration\TestFile($this->toAbsolutePath($filename, $file), $phpVersion, $phpVersionOperator); + } + $testSuites[] = new TestSuiteConfiguration((string) $element->getAttribute('name'), \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection::fromArray($directories), \PHPUnit\TextUI\XmlConfiguration\TestFileCollection::fromArray($files), \PHPUnit\TextUI\XmlConfiguration\FileCollection::fromArray($exclude)); + } + return \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection::fromArray($testSuites); } -} -if (!\function_exists('PHPUnit\\Framework\\assertThat')) { /** - * Evaluates a PHPUnit\Framework\Constraint matcher object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertThat + * @return DOMElement[] */ - function assertThat($value, Constraint $constraint, string $message = '') : void + private function getTestSuiteElements(DOMXPath $xpath) : array { - \PHPUnit\Framework\Assert::assertThat(...func_get_args()); + /** @var DOMElement[] $elements */ + $elements = []; + $testSuiteNodes = $xpath->query('testsuites/testsuite'); + if ($testSuiteNodes->length === 0) { + $testSuiteNodes = $xpath->query('testsuite'); + } + if ($testSuiteNodes->length === 1) { + $element = $testSuiteNodes->item(0); + assert($element instanceof DOMElement); + $elements[] = $element; + } else { + foreach ($testSuiteNodes as $testSuiteNode) { + assert($testSuiteNode instanceof DOMElement); + $elements[] = $testSuiteNode; + } + } + return $elements; } -} -if (!\function_exists('PHPUnit\\Framework\\assertJson')) { - /** - * Asserts that a string is a valid JSON string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJson - */ - function assertJson(string $actualJson, string $message = '') : void + private function element(DOMXPath $xpath, string $element) : ?DOMElement { - \PHPUnit\Framework\Assert::assertJson(...func_get_args()); + $nodes = $xpath->query($element); + if ($nodes->length === 1) { + $node = $nodes->item(0); + assert($node instanceof DOMElement); + return $node; + } + return null; } } -if (!\function_exists('PHPUnit\\Framework\\assertJsonStringEqualsJsonString')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Junit +{ /** - * Asserts that two given JSON encoded objects or arrays are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonStringEqualsJsonString + * @var File */ - function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = '') : void + private $target; + public function __construct(File $target) { - \PHPUnit\Framework\Assert::assertJsonStringEqualsJsonString(...func_get_args()); + $this->target = $target; + } + public function target() : File + { + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\assertJsonStringNotEqualsJsonString')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Logging +{ + /** + * @var ?Junit + */ + private $junit; /** - * Asserts that two given JSON encoded objects or arrays are not equal. - * - * @param string $expectedJson - * @param string $actualJson - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonStringNotEqualsJsonString + * @var ?Text */ - function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertJsonStringEqualsJsonFile')) { + private $text; /** - * Asserts that the generated JSON encoded object and the content of the given file are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonStringEqualsJsonFile + * @var ?TeamCity */ - function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertJsonStringEqualsJsonFile(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertJsonStringNotEqualsJsonFile')) { + private $teamCity; /** - * Asserts that the generated JSON encoded object and the content of the given file are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonStringNotEqualsJsonFile + * @var ?TestDoxHtml */ - function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertJsonFileEqualsJsonFile')) { + private $testDoxHtml; /** - * Asserts that two JSON files are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonFileEqualsJsonFile + * @var ?TestDoxText */ - function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertJsonFileEqualsJsonFile(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\assertJsonFileNotEqualsJsonFile')) { + private $testDoxText; /** - * Asserts that two JSON files are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertJsonFileNotEqualsJsonFile + * @var ?TestDoxXml */ - function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void - { - \PHPUnit\Framework\Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\logicalAnd')) { - function logicalAnd() : LogicalAnd - { - return \PHPUnit\Framework\Assert::logicalAnd(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\logicalOr')) { - function logicalOr() : LogicalOr - { - return \PHPUnit\Framework\Assert::logicalOr(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\logicalNot')) { - function logicalNot(Constraint $constraint) : LogicalNot - { - return \PHPUnit\Framework\Assert::logicalNot(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\logicalXor')) { - function logicalXor() : LogicalXor - { - return \PHPUnit\Framework\Assert::logicalXor(...func_get_args()); - } -} -if (!\function_exists('PHPUnit\\Framework\\anything')) { - function anything() : IsAnything + private $testDoxXml; + public function __construct(?\PHPUnit\TextUI\XmlConfiguration\Logging\Junit $junit, ?\PHPUnit\TextUI\XmlConfiguration\Logging\Text $text, ?\PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText, ?TestDoxXml $testDoxXml) { - return \PHPUnit\Framework\Assert::anything(...func_get_args()); + $this->junit = $junit; + $this->text = $text; + $this->teamCity = $teamCity; + $this->testDoxHtml = $testDoxHtml; + $this->testDoxText = $testDoxText; + $this->testDoxXml = $testDoxXml; } -} -if (!\function_exists('PHPUnit\\Framework\\isTrue')) { - function isTrue() : IsTrue + public function hasJunit() : bool { - return \PHPUnit\Framework\Assert::isTrue(...func_get_args()); + return $this->junit !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\callback')) { - function callback(callable $callback) : Callback + public function junit() : \PHPUnit\TextUI\XmlConfiguration\Logging\Junit { - return \PHPUnit\Framework\Assert::callback(...func_get_args()); + if ($this->junit === null) { + throw new Exception('Logger "JUnit XML" is not configured'); + } + return $this->junit; } -} -if (!\function_exists('PHPUnit\\Framework\\isFalse')) { - function isFalse() : IsFalse + public function hasText() : bool { - return \PHPUnit\Framework\Assert::isFalse(...func_get_args()); + return $this->text !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\isJson')) { - function isJson() : IsJson + public function text() : \PHPUnit\TextUI\XmlConfiguration\Logging\Text { - return \PHPUnit\Framework\Assert::isJson(...func_get_args()); + if ($this->text === null) { + throw new Exception('Logger "Text" is not configured'); + } + return $this->text; } -} -if (!\function_exists('PHPUnit\\Framework\\isNull')) { - function isNull() : IsNull + public function hasTeamCity() : bool { - return \PHPUnit\Framework\Assert::isNull(...func_get_args()); + return $this->teamCity !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\isFinite')) { - function isFinite() : IsFinite + public function teamCity() : \PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity { - return \PHPUnit\Framework\Assert::isFinite(...func_get_args()); + if ($this->teamCity === null) { + throw new Exception('Logger "Team City" is not configured'); + } + return $this->teamCity; } -} -if (!\function_exists('PHPUnit\\Framework\\isInfinite')) { - function isInfinite() : IsInfinite + public function hasTestDoxHtml() : bool { - return \PHPUnit\Framework\Assert::isInfinite(...func_get_args()); + return $this->testDoxHtml !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\isNan')) { - function isNan() : IsNan + public function testDoxHtml() : TestDoxHtml { - return \PHPUnit\Framework\Assert::isNan(...func_get_args()); + if ($this->testDoxHtml === null) { + throw new Exception('Logger "TestDox HTML" is not configured'); + } + return $this->testDoxHtml; } -} -if (!\function_exists('PHPUnit\\Framework\\containsEqual')) { - function containsEqual($value) : TraversableContainsEqual + public function hasTestDoxText() : bool { - return \PHPUnit\Framework\Assert::containsEqual(...func_get_args()); + return $this->testDoxText !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\containsIdentical')) { - function containsIdentical($value) : TraversableContainsIdentical + public function testDoxText() : TestDoxText { - return \PHPUnit\Framework\Assert::containsIdentical(...func_get_args()); + if ($this->testDoxText === null) { + throw new Exception('Logger "TestDox Text" is not configured'); + } + return $this->testDoxText; } -} -if (!\function_exists('PHPUnit\\Framework\\containsOnly')) { - function containsOnly(string $type) : TraversableContainsOnly + public function hasTestDoxXml() : bool { - return \PHPUnit\Framework\Assert::containsOnly(...func_get_args()); + return $this->testDoxXml !== null; } -} -if (!\function_exists('PHPUnit\\Framework\\containsOnlyInstancesOf')) { - function containsOnlyInstancesOf(string $className) : TraversableContainsOnly + public function testDoxXml() : TestDoxXml { - return \PHPUnit\Framework\Assert::containsOnlyInstancesOf(...func_get_args()); + if ($this->testDoxXml === null) { + throw new Exception('Logger "TestDox XML" is not configured'); + } + return $this->testDoxXml; } } -if (!\function_exists('PHPUnit\\Framework\\arrayHasKey')) { - function arrayHasKey($key) : ArrayHasKey + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class TeamCity +{ + /** + * @var File + */ + private $target; + public function __construct(File $target) { - return \PHPUnit\Framework\Assert::arrayHasKey(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\equalTo')) { - function equalTo($value) : IsEqual + public function target() : File { - return \PHPUnit\Framework\Assert::equalTo(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\equalToCanonicalizing')) { - function equalToCanonicalizing($value) : IsEqualCanonicalizing + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Html +{ + /** + * @var File + */ + private $target; + public function __construct(File $target) { - return \PHPUnit\Framework\Assert::equalToCanonicalizing(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\equalToIgnoringCase')) { - function equalToIgnoringCase($value) : IsEqualIgnoringCase + public function target() : File { - return \PHPUnit\Framework\Assert::equalToIgnoringCase(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\equalToWithDelta')) { - function equalToWithDelta($value, float $delta) : IsEqualWithDelta + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ + /** + * @var File + */ + private $target; + public function __construct(File $target) { - return \PHPUnit\Framework\Assert::equalToWithDelta(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\isEmpty')) { - function isEmpty() : IsEmpty + public function target() : File { - return \PHPUnit\Framework\Assert::isEmpty(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\isWritable')) { - function isWritable() : IsWritable + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Xml +{ + /** + * @var File + */ + private $target; + public function __construct(File $target) { - return \PHPUnit\Framework\Assert::isWritable(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\isReadable')) { - function isReadable() : IsReadable + public function target() : File { - return \PHPUnit\Framework\Assert::isReadable(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\directoryExists')) { - function directoryExists() : DirectoryExists + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ + /** + * @var File + */ + private $target; + public function __construct(File $target) { - return \PHPUnit\Framework\Assert::directoryExists(...func_get_args()); + $this->target = $target; } -} -if (!\function_exists('PHPUnit\\Framework\\fileExists')) { - function fileExists() : FileExists + public function target() : File { - return \PHPUnit\Framework\Assert::fileExists(...func_get_args()); + return $this->target; } } -if (!\function_exists('PHPUnit\\Framework\\greaterThan')) { - function greaterThan($value) : GreaterThan + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function array_key_exists; +use function sprintf; +use function version_compare; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationBuilder +{ + private const AVAILABLE_MIGRATIONS = ['8.5' => [\PHPUnit\TextUI\XmlConfiguration\RemoveLogTypes::class], '9.2' => [\PHPUnit\TextUI\XmlConfiguration\RemoveCacheTokensAttribute::class, \PHPUnit\TextUI\XmlConfiguration\IntroduceCoverageElement::class, \PHPUnit\TextUI\XmlConfiguration\MoveAttributesFromRootToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveAttributesFromFilterWhitelistToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveWhitelistDirectoriesToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\MoveWhitelistExcludesToCoverage::class, \PHPUnit\TextUI\XmlConfiguration\RemoveEmptyFilter::class, \PHPUnit\TextUI\XmlConfiguration\CoverageCloverToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageCrap4jToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageHtmlToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoveragePhpToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageTextToReport::class, \PHPUnit\TextUI\XmlConfiguration\CoverageXmlToReport::class, \PHPUnit\TextUI\XmlConfiguration\ConvertLogTypes::class, \PHPUnit\TextUI\XmlConfiguration\UpdateSchemaLocationTo93::class]]; + /** + * @throws MigrationBuilderException + */ + public function build(string $fromVersion) : array { - return \PHPUnit\Framework\Assert::greaterThan(...func_get_args()); + if (!array_key_exists($fromVersion, self::AVAILABLE_MIGRATIONS)) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationBuilderException(sprintf('Migration from schema version %s is not supported', $fromVersion)); + } + $stack = []; + foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { + if (version_compare($version, $fromVersion, '<')) { + continue; + } + foreach ($migrations as $migration) { + $stack[] = new $migration(); + } + } + return $stack; } } -if (!\function_exists('PHPUnit\\Framework\\greaterThanOrEqual')) { - function greaterThanOrEqual($value) : LogicalOr - { - return \PHPUnit\Framework\Assert::greaterThanOrEqual(...func_get_args()); - } + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationBuilderException extends RuntimeException implements \PHPUnit\Exception +{ } -if (!\function_exists('PHPUnit\\Framework\\classHasAttribute')) { - function classHasAttribute(string $attributeName) : ClassHasAttribute - { - return \PHPUnit\Framework\Assert::classHasAttribute(...func_get_args()); - } + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationException extends RuntimeException implements \PHPUnit\Exception +{ } -if (!\function_exists('PHPUnit\\Framework\\classHasStaticAttribute')) { - function classHasStaticAttribute(string $attributeName) : ClassHasStaticAttribute + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConvertLogTypes implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + public function migrate(DOMDocument $document) : void { - return \PHPUnit\Framework\Assert::classHasStaticAttribute(...func_get_args()); + $logging = $document->getElementsByTagName('logging')->item(0); + if (!$logging instanceof DOMElement) { + return; + } + $types = ['junit' => 'junit', 'teamcity' => 'teamcity', 'testdox-html' => 'testdoxHtml', 'testdox-text' => 'testdoxText', 'testdox-xml' => 'testdoxXml', 'plain' => 'text']; + $logNodes = []; + foreach ($logging->getElementsByTagName('log') as $logNode) { + if (!isset($types[$logNode->getAttribute('type')])) { + continue; + } + $logNodes[] = $logNode; + } + foreach ($logNodes as $oldNode) { + $newLogNode = $document->createElement($types[$oldNode->getAttribute('type')]); + $newLogNode->setAttribute('outputFile', $oldNode->getAttribute('target')); + $logging->replaceChild($newLogNode, $oldNode); + } } } -if (!\function_exists('PHPUnit\\Framework\\objectHasAttribute')) { - function objectHasAttribute($attributeName) : ObjectHasAttribute + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageCloverToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::objectHasAttribute(...func_get_args()); + return 'coverage-clover'; } -} -if (!\function_exists('PHPUnit\\Framework\\identicalTo')) { - function identicalTo($value) : IsIdentical + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::identicalTo(...func_get_args()); + $clover = $logNode->ownerDocument->createElement('clover'); + $clover->setAttribute('outputFile', $logNode->getAttribute('target')); + return $clover; } } -if (!\function_exists('PHPUnit\\Framework\\isInstanceOf')) { - function isInstanceOf(string $className) : IsInstanceOf + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageCrap4jToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::isInstanceOf(...func_get_args()); + return 'coverage-crap4j'; } -} -if (!\function_exists('PHPUnit\\Framework\\isType')) { - function isType(string $type) : IsType + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::isType(...func_get_args()); + $crap4j = $logNode->ownerDocument->createElement('crap4j'); + $crap4j->setAttribute('outputFile', $logNode->getAttribute('target')); + $this->migrateAttributes($logNode, $crap4j, ['threshold']); + return $crap4j; } } -if (!\function_exists('PHPUnit\\Framework\\lessThan')) { - function lessThan($value) : LessThan + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageHtmlToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::lessThan(...func_get_args()); + return 'coverage-html'; } -} -if (!\function_exists('PHPUnit\\Framework\\lessThanOrEqual')) { - function lessThanOrEqual($value) : LogicalOr + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::lessThanOrEqual(...func_get_args()); + $html = $logNode->ownerDocument->createElement('html'); + $html->setAttribute('outputDirectory', $logNode->getAttribute('target')); + $this->migrateAttributes($logNode, $html, ['lowUpperBound', 'highLowerBound']); + return $html; } } -if (!\function_exists('PHPUnit\\Framework\\matchesRegularExpression')) { - function matchesRegularExpression(string $pattern) : RegularExpression + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoveragePhpToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::matchesRegularExpression(...func_get_args()); + return 'coverage-php'; } -} -if (!\function_exists('PHPUnit\\Framework\\matches')) { - function matches(string $string) : StringMatchesFormatDescription + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::matches(...func_get_args()); + $php = $logNode->ownerDocument->createElement('php'); + $php->setAttribute('outputFile', $logNode->getAttribute('target')); + return $php; } } -if (!\function_exists('PHPUnit\\Framework\\stringStartsWith')) { - function stringStartsWith($prefix) : StringStartsWith + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageTextToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::stringStartsWith(...func_get_args()); + return 'coverage-text'; } -} -if (!\function_exists('PHPUnit\\Framework\\stringContains')) { - function stringContains(string $string, bool $case = \true) : StringContains + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::stringContains(...func_get_args()); + $text = $logNode->ownerDocument->createElement('text'); + $text->setAttribute('outputFile', $logNode->getAttribute('target')); + $this->migrateAttributes($logNode, $text, ['showUncoveredFiles', 'showOnlySummary']); + return $text; } } -if (!\function_exists('PHPUnit\\Framework\\stringEndsWith')) { - function stringEndsWith(string $suffix) : StringEndsWith + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageXmlToReport extends \PHPUnit\TextUI\XmlConfiguration\LogToReportMigration +{ + protected function forType() : string { - return \PHPUnit\Framework\Assert::stringEndsWith(...func_get_args()); + return 'coverage-xml'; } -} -if (!\function_exists('PHPUnit\\Framework\\countOf')) { - function countOf(int $count) : Count + protected function toReportFormat(DOMElement $logNode) : DOMElement { - return \PHPUnit\Framework\Assert::countOf(...func_get_args()); + $xml = $logNode->ownerDocument->createElement('xml'); + $xml->setAttribute('outputDirectory', $logNode->getAttribute('target')); + return $xml; } } -if (!\function_exists('PHPUnit\\Framework\\objectEquals')) { - function objectEquals(object $object, string $method = 'equals') : ObjectEquals + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IntroduceCoverageElement implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + public function migrate(DOMDocument $document) : void { - return \PHPUnit\Framework\Assert::objectEquals(...func_get_args()); + $coverage = $document->createElement('coverage'); + $document->documentElement->insertBefore($coverage, $document->documentElement->firstChild); } } -if (!\function_exists('PHPUnit\\Framework\\any')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use DOMDocument; +use DOMElement; +use DOMXPath; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class LogToReportMigration implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ /** - * Returns a matcher that matches when the method is executed - * zero or more times. + * @throws MigrationException */ - function any() : AnyInvokedCountMatcher + public function migrate(DOMDocument $document) : void { - return new AnyInvokedCountMatcher(); + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + } + $logNode = $this->findLogNode($document); + if ($logNode === null) { + return; + } + $reportChild = $this->toReportFormat($logNode); + $report = $coverage->getElementsByTagName('report')->item(0); + if ($report === null) { + $report = $coverage->appendChild($document->createElement('report')); + } + $report->appendChild($reportChild); + $logNode->parentNode->removeChild($logNode); } -} -if (!\function_exists('PHPUnit\\Framework\\never')) { - /** - * Returns a matcher that matches when the method is never executed. - */ - function never() : InvokedCountMatcher + protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes) : void { - return new InvokedCountMatcher(0); + foreach ($attributes as $attr) { + if (!$src->hasAttribute($attr)) { + continue; + } + $dest->setAttribute($attr, $src->getAttribute($attr)); + $src->removeAttribute($attr); + } } -} -if (!\function_exists('PHPUnit\\Framework\\atLeast')) { - /** - * Returns a matcher that matches when the method is executed - * at least N times. - */ - function atLeast(int $requiredInvocations) : InvokedAtLeastCountMatcher + protected abstract function forType() : string; + protected abstract function toReportFormat(DOMElement $logNode) : DOMElement; + private function findLogNode(DOMDocument $document) : ?DOMElement { - return new InvokedAtLeastCountMatcher($requiredInvocations); + $logNode = (new DOMXPath($document))->query(sprintf('//logging/log[@type="%s"]', $this->forType()))->item(0); + if (!$logNode instanceof DOMElement) { + return null; + } + return $logNode; } } -if (!\function_exists('PHPUnit\\Framework\\atLeastOnce')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Migration +{ + public function migrate(DOMDocument $document) : void; +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveAttributesFromFilterWhitelistToCoverage implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ /** - * Returns a matcher that matches when the method is executed at least once. + * @throws MigrationException */ - function atLeastOnce() : InvokedAtLeastOnceMatcher + public function migrate(DOMDocument $document) : void { - return new InvokedAtLeastOnceMatcher(); + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + if (!$whitelist) { + return; + } + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + } + $map = ['addUncoveredFilesFromWhitelist' => 'includeUncoveredFiles', 'processUncoveredFilesFromWhitelist' => 'processUncoveredFiles']; + foreach ($map as $old => $new) { + if (!$whitelist->hasAttribute($old)) { + continue; + } + $coverage->setAttribute($new, $whitelist->getAttribute($old)); + $whitelist->removeAttribute($old); + } } } -if (!\function_exists('PHPUnit\\Framework\\once')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveAttributesFromRootToCoverage implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ /** - * Returns a matcher that matches when the method is executed exactly once. + * @throws MigrationException */ - function once() : InvokedCountMatcher + public function migrate(DOMDocument $document) : void { - return new InvokedCountMatcher(1); + $map = ['disableCodeCoverageIgnore' => 'disableCodeCoverageIgnore', 'ignoreDeprecatedCodeUnitsFromCodeCoverage' => 'ignoreDeprecatedCodeUnits']; + $root = $document->documentElement; + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + } + foreach ($map as $old => $new) { + if (!$root->hasAttribute($old)) { + continue; + } + $coverage->setAttribute($new, $root->getAttribute($old)); + $root->removeAttribute($old); + } } } -if (!\function_exists('PHPUnit\\Framework\\exactly')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveWhitelistDirectoriesToCoverage implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ /** - * Returns a matcher that matches when the method is executed - * exactly $count times. + * @throws MigrationException */ - function exactly(int $count) : InvokedCountMatcher + public function migrate(DOMDocument $document) : void { - return new InvokedCountMatcher($count); + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + if ($whitelist === null) { + return; + } + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + } + $include = $document->createElement('include'); + $coverage->appendChild($include); + foreach (SnapshotNodeList::fromNodeList($whitelist->childNodes) as $child) { + if (!$child instanceof DOMElement || $child->nodeName !== 'directory') { + continue; + } + $include->appendChild($child); + } } } -if (!\function_exists('PHPUnit\\Framework\\atMost')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function in_array; +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveWhitelistExcludesToCoverage implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ /** - * Returns a matcher that matches when the method is executed - * at most N times. + * @throws MigrationException */ - function atMost(int $allowedInvocations) : InvokedAtMostCountMatcher + public function migrate(DOMDocument $document) : void { - return new InvokedAtMostCountMatcher($allowedInvocations); + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + if ($whitelist === null) { + return; + } + $excludeNodes = SnapshotNodeList::fromNodeList($whitelist->getElementsByTagName('exclude')); + if ($excludeNodes->count() === 0) { + return; + } + $coverage = $document->getElementsByTagName('coverage')->item(0); + if (!$coverage instanceof DOMElement) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Unexpected state - No coverage element'); + } + $targetExclude = $coverage->getElementsByTagName('exclude')->item(0); + if ($targetExclude === null) { + $targetExclude = $coverage->appendChild($document->createElement('exclude')); + } + foreach ($excludeNodes as $excludeNode) { + assert($excludeNode instanceof DOMElement); + foreach (SnapshotNodeList::fromNodeList($excludeNode->childNodes) as $child) { + if (!$child instanceof DOMElement || !in_array($child->nodeName, ['directory', 'file'], \true)) { + continue; + } + $targetExclude->appendChild($child); + } + if ($excludeNode->getElementsByTagName('*')->count() !== 0) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException('Dangling child elements in exclude found.'); + } + $whitelist->removeChild($excludeNode); + } } } -if (!\function_exists('PHPUnit\\Framework\\at')) { - /** - * Returns a matcher that matches when the method is executed - * at the given index. - */ - function at(int $index) : InvokedAtIndexMatcher + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveCacheTokensAttribute implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + public function migrate(DOMDocument $document) : void { - return new InvokedAtIndexMatcher($index); + $root = $document->documentElement; + if ($root->hasAttribute('cacheTokens')) { + $root->removeAttribute('cacheTokens'); + } } } -if (!\function_exists('PHPUnit\\Framework\\returnValue')) { - function returnValue($value) : ReturnStub + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use DOMDocument; +use DOMElement; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveEmptyFilter implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document) : void { - return new ReturnStub($value); + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + if ($whitelist instanceof DOMElement) { + $this->ensureEmpty($whitelist); + $whitelist->parentNode->removeChild($whitelist); + } + $filter = $document->getElementsByTagName('filter')->item(0); + if ($filter instanceof DOMElement) { + $this->ensureEmpty($filter); + $filter->parentNode->removeChild($filter); + } } -} -if (!\function_exists('PHPUnit\\Framework\\returnValueMap')) { - function returnValueMap(array $valueMap) : ReturnValueMapStub + /** + * @throws MigrationException + */ + private function ensureEmpty(DOMElement $element) : void { - return new ReturnValueMapStub($valueMap); + if ($element->attributes->length > 0) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException(sprintf('%s element has unexpected attributes', $element->nodeName)); + } + if ($element->getElementsByTagName('*')->length > 0) { + throw new \PHPUnit\TextUI\XmlConfiguration\MigrationException(sprintf('%s element has unexpected children', $element->nodeName)); + } } } -if (!\function_exists('PHPUnit\\Framework\\returnArgument')) { - function returnArgument(int $argumentIndex) : ReturnArgumentStub + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveLogTypes implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + public function migrate(DOMDocument $document) : void { - return new ReturnArgumentStub($argumentIndex); + $logging = $document->getElementsByTagName('logging')->item(0); + if (!$logging instanceof DOMElement) { + return; + } + foreach (SnapshotNodeList::fromNodeList($logging->getElementsByTagName('log')) as $logNode) { + switch ($logNode->getAttribute('type')) { + case 'json': + case 'tap': + $logging->removeChild($logNode); + } + } } } -if (!\function_exists('PHPUnit\\Framework\\returnCallback')) { - function returnCallback($callback) : ReturnCallbackStub + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UpdateSchemaLocationTo93 implements \PHPUnit\TextUI\XmlConfiguration\Migration +{ + public function migrate(DOMDocument $document) : void { - return new ReturnCallbackStub($callback); + $document->documentElement->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation', 'https://schema.phpunit.de/9.3/phpunit.xsd'); } } -if (!\function_exists('PHPUnit\\Framework\\returnSelf')) { + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use PHPUnit\Util\Xml\Exception as XmlException; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\SchemaDetector; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Migrator +{ /** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. + * @throws Exception + * @throws MigrationBuilderException + * @throws MigrationException + * @throws XmlException */ - function returnSelf() : ReturnSelfStub - { - return new ReturnSelfStub(); - } -} -if (!\function_exists('PHPUnit\\Framework\\throwException')) { - function throwException(Throwable $exception) : ExceptionStub - { - return new ExceptionStub($exception); - } -} -if (!\function_exists('PHPUnit\\Framework\\onConsecutiveCalls')) { - function onConsecutiveCalls() : ConsecutiveCallsStub + public function migrate(string $filename) : string { - $args = func_get_args(); - return new ConsecutiveCallsStub($args); + $origin = (new SchemaDetector())->detect($filename); + if (!$origin->detected()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception(sprintf('"%s" is not a valid PHPUnit XML configuration file that can be migrated', $filename)); + } + $configurationDocument = (new XmlLoader())->loadFile($filename, \false, \true, \true); + foreach ((new \PHPUnit\TextUI\XmlConfiguration\MigrationBuilder())->build($origin->version()) as $migration) { + $migration->migrate($configurationDocument); + } + $configurationDocument->formatOutput = \true; + $configurationDocument->preserveWhiteSpace = \false; + return $configurationDocument->saveXML(); } } name = $name; + $this->value = $value; + } + public function name() : string + { + return $this->name; + } + public function value() + { + return $this->value; + } } constants = $constants; } /** - * Asserts that a haystack contains a needle. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @return Constant[] */ - public static function assertContains($needle, iterable $haystack, string $message = '') : void + public function asArray() : array { - $constraint = new TraversableContainsIdentical($needle); - static::assertThat($haystack, $constraint, $message); + return $this->constants; } - public static function assertContainsEquals($needle, iterable $haystack, string $message = '') : void + public function count() : int { - $constraint = new TraversableContainsEqual($needle); - static::assertThat($haystack, $constraint, $message); + return count($this->constants); + } + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\ConstantCollectionIterator + { + return new \PHPUnit\TextUI\XmlConfiguration\ConstantCollectionIterator($this); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConstantCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a haystack does not contain a needle. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @var Constant[] */ - public static function assertNotContains($needle, iterable $haystack, string $message = '') : void + private $constants; + /** + * @var int + */ + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants) { - $constraint = new LogicalNot(new TraversableContainsIdentical($needle)); - static::assertThat($haystack, $constraint, $message); + $this->constants = $constants->asArray(); } - public static function assertNotContainsEquals($needle, iterable $haystack, string $message = '') : void + public function count() : int { - $constraint = new LogicalNot(new TraversableContainsEqual($needle)); - static::assertThat($haystack, $constraint, $message); + return iterator_count($this); } - /** - * Asserts that a haystack contains only values of a given type. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = '') : void + public function rewind() : void { - if ($isNativeType === null) { - $isNativeType = Type::isType($type); - } - static::assertThat($haystack, new TraversableContainsOnly($type, $isNativeType), $message); + $this->position = 0; } - /** - * Asserts that a haystack contains only instances of a given class name. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = '') : void + public function valid() : bool { - static::assertThat($haystack, new TraversableContainsOnly($className, \false), $message); + return $this->position < count($this->constants); } - /** - * Asserts that a haystack does not contain only values of a given type. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = '') : void + public function key() : int { - if ($isNativeType === null) { - $isNativeType = Type::isType($type); - } - static::assertThat($haystack, new LogicalNot(new TraversableContainsOnly($type, $isNativeType)), $message); + return $this->position; } - /** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @param Countable|iterable $haystack - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertCount(int $expectedCount, $haystack, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\Constant { - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); - } - static::assertThat($haystack, new Count($expectedCount), $message); + return $this->constants[$this->position]; } - /** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @param Countable|iterable $haystack - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertNotCount(int $expectedCount, $haystack, string $message = '') : void + public function next() : void { - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); - } - $constraint = new LogicalNot(new Count($expectedCount)); - static::assertThat($haystack, $constraint, $message); + $this->position++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class IniSetting +{ /** - * Asserts that two variables are equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var string */ - public static function assertEquals($expected, $actual, string $message = '') : void - { - $constraint = new IsEqual($expected); - static::assertThat($actual, $constraint, $message); - } + private $name; /** - * Asserts that two variables are equal (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var string */ - public static function assertEqualsCanonicalizing($expected, $actual, string $message = '') : void + private $value; + public function __construct(string $name, string $value) { - $constraint = new IsEqualCanonicalizing($expected); - static::assertThat($actual, $constraint, $message); + $this->name = $name; + $this->value = $value; } - /** - * Asserts that two variables are equal (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertEqualsIgnoringCase($expected, $actual, string $message = '') : void + public function name() : string { - $constraint = new IsEqualIgnoringCase($expected); - static::assertThat($actual, $constraint, $message); + return $this->name; + } + public function value() : string + { + return $this->value; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class IniSettingCollection implements Countable, IteratorAggregate +{ /** - * Asserts that two variables are equal (with delta). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var IniSetting[] + */ + private $iniSettings; + /** + * @param IniSetting[] $iniSettings */ - public static function assertEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void + public static function fromArray(array $iniSettings) : self { - $constraint = new IsEqualWithDelta($expected, $delta); - static::assertThat($actual, $constraint, $message); + return new self(...$iniSettings); } - /** - * Asserts that two variables are not equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNotEquals($expected, $actual, string $message = '') : void + private function __construct(\PHPUnit\TextUI\XmlConfiguration\IniSetting ...$iniSettings) { - $constraint = new LogicalNot(new IsEqual($expected)); - static::assertThat($actual, $constraint, $message); + $this->iniSettings = $iniSettings; } /** - * Asserts that two variables are not equal (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @return IniSetting[] */ - public static function assertNotEqualsCanonicalizing($expected, $actual, string $message = '') : void + public function asArray() : array { - $constraint = new LogicalNot(new IsEqualCanonicalizing($expected)); - static::assertThat($actual, $constraint, $message); + return $this->iniSettings; } - /** - * Asserts that two variables are not equal (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNotEqualsIgnoringCase($expected, $actual, string $message = '') : void + public function count() : int { - $constraint = new LogicalNot(new IsEqualIgnoringCase($expected)); - static::assertThat($actual, $constraint, $message); + return count($this->iniSettings); } - /** - * Asserts that two variables are not equal (with delta). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\IniSettingCollectionIterator { - $constraint = new LogicalNot(new IsEqualWithDelta($expected, $delta)); - static::assertThat($actual, $constraint, $message); + return new \PHPUnit\TextUI\XmlConfiguration\IniSettingCollectionIterator($this); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IniSettingCollectionIterator implements Countable, Iterator +{ /** - * @throws ExpectationFailedException + * @var IniSetting[] */ - public static function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = '') : void - { - static::assertThat($actual, static::objectEquals($expected, $method), $message); - } + private $iniSettings; /** - * Asserts that a variable is empty. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert empty $actual + * @var int */ - public static function assertEmpty($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings) { - static::assertThat($actual, static::isEmpty(), $message); + $this->iniSettings = $iniSettings->asArray(); } - /** - * Asserts that a variable is not empty. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !empty $actual - */ - public static function assertNotEmpty($actual, string $message = '') : void + public function count() : int { - static::assertThat($actual, static::logicalNot(static::isEmpty()), $message); + return iterator_count($this); } - /** - * Asserts that a value is greater than another value. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertGreaterThan($expected, $actual, string $message = '') : void + public function rewind() : void { - static::assertThat($actual, static::greaterThan($expected), $message); + $this->position = 0; } - /** - * Asserts that a value is greater than or equal to another value. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertGreaterThanOrEqual($expected, $actual, string $message = '') : void + public function valid() : bool { - static::assertThat($actual, static::greaterThanOrEqual($expected), $message); + return $this->position < count($this->iniSettings); } - /** - * Asserts that a value is smaller than another value. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertLessThan($expected, $actual, string $message = '') : void + public function key() : int { - static::assertThat($actual, static::lessThan($expected), $message); + return $this->position; } - /** - * Asserts that a value is smaller than or equal to another value. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertLessThanOrEqual($expected, $actual, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\IniSetting { - static::assertThat($actual, static::lessThanOrEqual($expected), $message); + return $this->iniSettings[$this->position]; } - /** - * Asserts that the contents of one file is equal to the contents of another - * file. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileEquals(string $expected, string $actual, string $message = '') : void + public function next() : void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new IsEqual(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); + $this->position++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Php +{ /** - * Asserts that the contents of one file is equal to the contents of another - * file (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var DirectoryCollection */ - public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void - { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new IsEqualCanonicalizing(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); - } + private $includePaths; /** - * Asserts that the contents of one file is equal to the contents of another - * file (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var IniSettingCollection */ - public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void - { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new IsEqualIgnoringCase(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); - } + private $iniSettings; /** - * Asserts that the contents of one file is not equal to the contents of - * another file. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var ConstantCollection */ - public static function assertFileNotEquals(string $expected, string $actual, string $message = '') : void - { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new LogicalNot(new IsEqual(file_get_contents($expected))); - static::assertThat(file_get_contents($actual), $constraint, $message); - } + private $constants; /** - * Asserts that the contents of one file is not equal to the contents of another - * file (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = '') : void - { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new LogicalNot(new IsEqualCanonicalizing(file_get_contents($expected))); - static::assertThat(file_get_contents($actual), $constraint, $message); - } + private $globalVariables; /** - * Asserts that the contents of one file is not equal to the contents of another - * file (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = '') : void - { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); - $constraint = new LogicalNot(new IsEqualIgnoringCase(file_get_contents($expected))); - static::assertThat(file_get_contents($actual), $constraint, $message); - } + private $envVariables; /** - * Asserts that the contents of a string is equal - * to the contents of a file. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $constraint = new IsEqual(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); - } + private $postVariables; /** - * Asserts that the contents of a string is equal - * to the contents of a file (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $constraint = new IsEqualCanonicalizing(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); - } + private $getVariables; /** - * Asserts that the contents of a string is equal - * to the contents of a file (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $constraint = new IsEqualIgnoringCase(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); - } + private $cookieVariables; /** - * Asserts that the contents of a string is not equal - * to the contents of a file. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $constraint = new LogicalNot(new IsEqual(file_get_contents($expectedFile))); - static::assertThat($actualString, $constraint, $message); - } + private $serverVariables; /** - * Asserts that the contents of a string is not equal - * to the contents of a file (canonicalizing). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $constraint = new LogicalNot(new IsEqualCanonicalizing(file_get_contents($expectedFile))); - static::assertThat($actualString, $constraint, $message); - } + private $filesVariables; /** - * Asserts that the contents of a string is not equal - * to the contents of a file (ignoring case). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var VariableCollection */ - public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = '') : void + private $requestVariables; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection $includePaths, \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings, \PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $globalVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $envVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $postVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $getVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $cookieVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $serverVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $filesVariables, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $requestVariables) { - static::assertFileExists($expectedFile, $message); - $constraint = new LogicalNot(new IsEqualIgnoringCase(file_get_contents($expectedFile))); - static::assertThat($actualString, $constraint, $message); + $this->includePaths = $includePaths; + $this->iniSettings = $iniSettings; + $this->constants = $constants; + $this->globalVariables = $globalVariables; + $this->envVariables = $envVariables; + $this->postVariables = $postVariables; + $this->getVariables = $getVariables; + $this->cookieVariables = $cookieVariables; + $this->serverVariables = $serverVariables; + $this->filesVariables = $filesVariables; + $this->requestVariables = $requestVariables; } - /** - * Asserts that a file/dir is readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertIsReadable(string $filename, string $message = '') : void + public function includePaths() : \PHPUnit\TextUI\XmlConfiguration\DirectoryCollection { - static::assertThat($filename, new IsReadable(), $message); + return $this->includePaths; } - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertIsNotReadable(string $filename, string $message = '') : void + public function iniSettings() : \PHPUnit\TextUI\XmlConfiguration\IniSettingCollection { - static::assertThat($filename, new LogicalNot(new IsReadable()), $message); + return $this->iniSettings; } - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - */ - public static function assertNotIsReadable(string $filename, string $message = '') : void + public function constants() : \PHPUnit\TextUI\XmlConfiguration\ConstantCollection { - self::createWarning('assertNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotReadable() instead.'); - static::assertThat($filename, new LogicalNot(new IsReadable()), $message); + return $this->constants; } - /** - * Asserts that a file/dir exists and is writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertIsWritable(string $filename, string $message = '') : void + public function globalVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - static::assertThat($filename, new IsWritable(), $message); + return $this->globalVariables; } - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertIsNotWritable(string $filename, string $message = '') : void + public function envVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - static::assertThat($filename, new LogicalNot(new IsWritable()), $message); + return $this->envVariables; } - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 - */ - public static function assertNotIsWritable(string $filename, string $message = '') : void + public function postVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - self::createWarning('assertNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotWritable() instead.'); - static::assertThat($filename, new LogicalNot(new IsWritable()), $message); + return $this->postVariables; } - /** - * Asserts that a directory exists. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryExists(string $directory, string $message = '') : void + public function getVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - static::assertThat($directory, new DirectoryExists(), $message); + return $this->getVariables; } - /** - * Asserts that a directory does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryDoesNotExist(string $directory, string $message = '') : void + public function cookieVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - static::assertThat($directory, new LogicalNot(new DirectoryExists()), $message); + return $this->cookieVariables; } - /** - * Asserts that a directory does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 - */ - public static function assertDirectoryNotExists(string $directory, string $message = '') : void + public function serverVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - self::createWarning('assertDirectoryNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryDoesNotExist() instead.'); - static::assertThat($directory, new LogicalNot(new DirectoryExists()), $message); + return $this->serverVariables; } - /** - * Asserts that a directory exists and is readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsReadable(string $directory, string $message = '') : void + public function filesVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - self::assertDirectoryExists($directory, $message); - self::assertIsReadable($directory, $message); + return $this->filesVariables; } - /** - * Asserts that a directory exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsNotReadable(string $directory, string $message = '') : void + public function requestVariables() : \PHPUnit\TextUI\XmlConfiguration\VariableCollection { - self::assertDirectoryExists($directory, $message); - self::assertIsNotReadable($directory, $message); + return $this->requestVariables; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const PATH_SEPARATOR; +use function constant; +use function define; +use function defined; +use function getenv; +use function implode; +use function ini_get; +use function ini_set; +use function putenv; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhpHandler +{ + public function handle(\PHPUnit\TextUI\XmlConfiguration\Php $configuration) : void + { + $this->handleIncludePaths($configuration->includePaths()); + $this->handleIniSettings($configuration->iniSettings()); + $this->handleConstants($configuration->constants()); + $this->handleGlobalVariables($configuration->globalVariables()); + $this->handleServerVariables($configuration->serverVariables()); + $this->handleEnvVariables($configuration->envVariables()); + $this->handleVariables('_POST', $configuration->postVariables()); + $this->handleVariables('_GET', $configuration->getVariables()); + $this->handleVariables('_COOKIE', $configuration->cookieVariables()); + $this->handleVariables('_FILES', $configuration->filesVariables()); + $this->handleVariables('_REQUEST', $configuration->requestVariables()); + } + private function handleIncludePaths(\PHPUnit\TextUI\XmlConfiguration\DirectoryCollection $includePaths) : void + { + if (!$includePaths->isEmpty()) { + $includePathsAsStrings = []; + foreach ($includePaths as $includePath) { + $includePathsAsStrings[] = $includePath->path(); + } + ini_set('include_path', implode(PATH_SEPARATOR, $includePathsAsStrings) . PATH_SEPARATOR . ini_get('include_path')); + } } - /** - * Asserts that a directory exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 - */ - public static function assertDirectoryNotIsReadable(string $directory, string $message = '') : void + private function handleIniSettings(\PHPUnit\TextUI\XmlConfiguration\IniSettingCollection $iniSettings) : void { - self::createWarning('assertDirectoryNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotReadable() instead.'); - self::assertDirectoryExists($directory, $message); - self::assertIsNotReadable($directory, $message); + foreach ($iniSettings as $iniSetting) { + $value = $iniSetting->value(); + if (defined($value)) { + $value = (string) constant($value); + } + ini_set($iniSetting->name(), $value); + } } - /** - * Asserts that a directory exists and is writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsWritable(string $directory, string $message = '') : void + private function handleConstants(\PHPUnit\TextUI\XmlConfiguration\ConstantCollection $constants) : void { - self::assertDirectoryExists($directory, $message); - self::assertIsWritable($directory, $message); + foreach ($constants as $constant) { + if (!defined($constant->name())) { + define($constant->name(), $constant->value()); + } + } } - /** - * Asserts that a directory exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsNotWritable(string $directory, string $message = '') : void + private function handleGlobalVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void { - self::assertDirectoryExists($directory, $message); - self::assertIsNotWritable($directory, $message); + foreach ($variables as $variable) { + $GLOBALS[$variable->name()] = $variable->value(); + } } - /** - * Asserts that a directory exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 - */ - public static function assertDirectoryNotIsWritable(string $directory, string $message = '') : void + private function handleServerVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void { - self::createWarning('assertDirectoryNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotWritable() instead.'); - self::assertDirectoryExists($directory, $message); - self::assertIsNotWritable($directory, $message); + foreach ($variables as $variable) { + $_SERVER[$variable->name()] = $variable->value(); + } } - /** - * Asserts that a file exists. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileExists(string $filename, string $message = '') : void + private function handleVariables(string $target, \PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void { - static::assertThat($filename, new FileExists(), $message); + foreach ($variables as $variable) { + $GLOBALS[$target][$variable->name()] = $variable->value(); + } } - /** - * Asserts that a file does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileDoesNotExist(string $filename, string $message = '') : void + private function handleEnvVariables(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) : void { - static::assertThat($filename, new LogicalNot(new FileExists()), $message); + foreach ($variables as $variable) { + $name = $variable->name(); + $value = $variable->value(); + $force = $variable->force(); + if ($force || getenv($name) === \false) { + putenv("{$name}={$value}"); + } + $value = getenv($name); + if ($force || !isset($_ENV[$name])) { + $_ENV[$name] = $value; + } + } } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Variable +{ /** - * Asserts that a file does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 + * @var string */ - public static function assertFileNotExists(string $filename, string $message = '') : void - { - self::createWarning('assertFileNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileDoesNotExist() instead.'); - static::assertThat($filename, new LogicalNot(new FileExists()), $message); - } + private $name; /** - * Asserts that a file exists and is readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var mixed */ - public static function assertFileIsReadable(string $file, string $message = '') : void - { - self::assertFileExists($file, $message); - self::assertIsReadable($file, $message); - } + private $value; /** - * Asserts that a file exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertFileIsNotReadable(string $file, string $message = '') : void + private $force; + public function __construct(string $name, $value, bool $force) { - self::assertFileExists($file, $message); - self::assertIsNotReadable($file, $message); + $this->name = $name; + $this->value = $value; + $this->force = $force; } - /** - * Asserts that a file exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 - */ - public static function assertFileNotIsReadable(string $file, string $message = '') : void + public function name() : string { - self::createWarning('assertFileNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotReadable() instead.'); - self::assertFileExists($file, $message); - self::assertIsNotReadable($file, $message); + return $this->name; } - /** - * Asserts that a file exists and is writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileIsWritable(string $file, string $message = '') : void + public function value() { - self::assertFileExists($file, $message); - self::assertIsWritable($file, $message); + return $this->value; } - /** - * Asserts that a file exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileIsNotWritable(string $file, string $message = '') : void + public function force() : bool { - self::assertFileExists($file, $message); - self::assertIsNotWritable($file, $message); + return $this->force; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class VariableCollection implements Countable, IteratorAggregate +{ /** - * Asserts that a file exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 + * @var Variable[] */ - public static function assertFileNotIsWritable(string $file, string $message = '') : void - { - self::createWarning('assertFileNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotWritable() instead.'); - self::assertFileExists($file, $message); - self::assertIsNotWritable($file, $message); - } + private $variables; /** - * Asserts that a condition is true. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert true $condition + * @param Variable[] $variables */ - public static function assertTrue($condition, string $message = '') : void + public static function fromArray(array $variables) : self { - static::assertThat($condition, static::isTrue(), $message); + return new self(...$variables); } - /** - * Asserts that a condition is not true. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !true $condition - */ - public static function assertNotTrue($condition, string $message = '') : void + private function __construct(\PHPUnit\TextUI\XmlConfiguration\Variable ...$variables) { - static::assertThat($condition, static::logicalNot(static::isTrue()), $message); + $this->variables = $variables; } /** - * Asserts that a condition is false. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert false $condition + * @return Variable[] */ - public static function assertFalse($condition, string $message = '') : void + public function asArray() : array { - static::assertThat($condition, static::isFalse(), $message); + return $this->variables; } - /** - * Asserts that a condition is not false. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !false $condition - */ - public static function assertNotFalse($condition, string $message = '') : void + public function count() : int { - static::assertThat($condition, static::logicalNot(static::isFalse()), $message); + return count($this->variables); } - /** - * Asserts that a variable is null. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert null $actual - */ - public static function assertNull($actual, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\VariableCollectionIterator { - static::assertThat($actual, static::isNull(), $message); + return new \PHPUnit\TextUI\XmlConfiguration\VariableCollectionIterator($this); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class VariableCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a variable is not null. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !null $actual + * @var Variable[] */ - public static function assertNotNull($actual, string $message = '') : void - { - static::assertThat($actual, static::logicalNot(static::isNull()), $message); - } + private $variables; /** - * Asserts that a variable is finite. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertFinite($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\VariableCollection $variables) { - static::assertThat($actual, static::isFinite(), $message); + $this->variables = $variables->asArray(); } - /** - * Asserts that a variable is infinite. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertInfinite($actual, string $message = '') : void + public function count() : int { - static::assertThat($actual, static::isInfinite(), $message); + return iterator_count($this); } - /** - * Asserts that a variable is nan. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNan($actual, string $message = '') : void + public function rewind() : void { - static::assertThat($actual, static::isNan(), $message); + $this->position = 0; } - /** - * Asserts that a class has a specified attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertClassHasAttribute(string $attributeName, string $className, string $message = '') : void + public function valid() : bool { - if (!self::isValidClassAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!class_exists($className)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'class name'); - } - static::assertThat($className, new ClassHasAttribute($attributeName), $message); + return $this->position < count($this->variables); } - /** - * Asserts that a class does not have a specified attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertClassNotHasAttribute(string $attributeName, string $className, string $message = '') : void + public function key() : int { - if (!self::isValidClassAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!class_exists($className)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'class name'); - } - static::assertThat($className, new LogicalNot(new ClassHasAttribute($attributeName)), $message); + return $this->position; } - /** - * Asserts that a class has a specified static attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\Variable { - if (!self::isValidClassAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!class_exists($className)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'class name'); - } - static::assertThat($className, new ClassHasStaticAttribute($attributeName), $message); + return $this->variables[$this->position]; } - /** - * Asserts that a class does not have a specified static attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - public static function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = '') : void + public function next() : void { - if (!self::isValidClassAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!class_exists($className)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'class name'); - } - static::assertThat($className, new LogicalNot(new ClassHasStaticAttribute($attributeName)), $message); + $this->position++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Extension +{ /** - * Asserts that an object has a specified attribute. - * - * @param object $object + * @var string * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @psalm-var class-string */ - public static function assertObjectHasAttribute(string $attributeName, $object, string $message = '') : void - { - if (!self::isValidObjectAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!is_object($object)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'object'); - } - static::assertThat($object, new ObjectHasAttribute($attributeName), $message); - } + private $className; /** - * Asserts that an object does not have a specified attribute. - * - * @param object $object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @var string */ - public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = '') : void - { - if (!self::isValidObjectAttributeName($attributeName)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'valid attribute name'); - } - if (!is_object($object)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'object'); - } - static::assertThat($object, new LogicalNot(new ObjectHasAttribute($attributeName)), $message); - } + private $sourceFile; /** - * Asserts that two variables have the same type and value. - * Used on objects, it asserts that two variables reference - * the same object. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $expected - * @psalm-assert =ExpectedType $actual + * @var array */ - public static function assertSame($expected, $actual, string $message = '') : void - { - static::assertThat($actual, new IsIdentical($expected), $message); - } + private $arguments; /** - * Asserts that two variables do not have the same type and value. - * Used on objects, it asserts that two variables do not reference - * the same object. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @psalm-param class-string $className */ - public static function assertNotSame($expected, $actual, string $message = '') : void + public function __construct(string $className, string $sourceFile, array $arguments) { - if (is_bool($expected) && is_bool($actual)) { - static::assertNotEquals($expected, $actual, $message); - } - static::assertThat($actual, new LogicalNot(new IsIdentical($expected)), $message); + $this->className = $className; + $this->sourceFile = $sourceFile; + $this->arguments = $arguments; } /** - * Asserts that a variable is of a given type. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert =ExpectedType $actual + * @psalm-return class-string */ - public static function assertInstanceOf(string $expected, $actual, string $message = '') : void + public function className() : string { - if (!class_exists($expected) && !interface_exists($expected)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class or interface name'); - } - static::assertThat($actual, new IsInstanceOf($expected), $message); + return $this->className; } - /** - * Asserts that a variable is not of a given type. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert !ExpectedType $actual - */ - public static function assertNotInstanceOf(string $expected, $actual, string $message = '') : void + public function hasSourceFile() : bool { - if (!class_exists($expected) && !interface_exists($expected)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'class or interface name'); - } - static::assertThat($actual, new LogicalNot(new IsInstanceOf($expected)), $message); + return $this->sourceFile !== ''; } - /** - * Asserts that a variable is of type array. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert array $actual - */ - public static function assertIsArray($actual, string $message = '') : void + public function sourceFile() : string { - static::assertThat($actual, new IsType(IsType::TYPE_ARRAY), $message); + return $this->sourceFile; } - /** - * Asserts that a variable is of type bool. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert bool $actual - */ - public static function assertIsBool($actual, string $message = '') : void + public function hasArguments() : bool { - static::assertThat($actual, new IsType(IsType::TYPE_BOOL), $message); + return !empty($this->arguments); } - /** - * Asserts that a variable is of type float. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert float $actual - */ - public static function assertIsFloat($actual, string $message = '') : void + public function arguments() : array { - static::assertThat($actual, new IsType(IsType::TYPE_FLOAT), $message); + return $this->arguments; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class ExtensionCollection implements IteratorAggregate +{ /** - * Asserts that a variable is of type int. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert int $actual + * @var Extension[] */ - public static function assertIsInt($actual, string $message = '') : void - { - static::assertThat($actual, new IsType(IsType::TYPE_INT), $message); - } + private $extensions; /** - * Asserts that a variable is of type numeric. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert numeric $actual + * @param Extension[] $extensions */ - public static function assertIsNumeric($actual, string $message = '') : void + public static function fromArray(array $extensions) : self { - static::assertThat($actual, new IsType(IsType::TYPE_NUMERIC), $message); + return new self(...$extensions); } - /** - * Asserts that a variable is of type object. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert object $actual - */ - public static function assertIsObject($actual, string $message = '') : void + private function __construct(\PHPUnit\TextUI\XmlConfiguration\Extension ...$extensions) { - static::assertThat($actual, new IsType(IsType::TYPE_OBJECT), $message); + $this->extensions = $extensions; } /** - * Asserts that a variable is of type resource. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert resource $actual + * @return Extension[] */ - public static function assertIsResource($actual, string $message = '') : void + public function asArray() : array { - static::assertThat($actual, new IsType(IsType::TYPE_RESOURCE), $message); + return $this->extensions; } - /** - * Asserts that a variable is of type resource and is closed. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert resource $actual - */ - public static function assertIsClosedResource($actual, string $message = '') : void + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\ExtensionCollectionIterator { - static::assertThat($actual, new IsType(IsType::TYPE_CLOSED_RESOURCE), $message); + return new \PHPUnit\TextUI\XmlConfiguration\ExtensionCollectionIterator($this); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExtensionCollectionIterator implements Countable, Iterator +{ /** - * Asserts that a variable is of type string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert string $actual + * @var Extension[] */ - public static function assertIsString($actual, string $message = '') : void - { - static::assertThat($actual, new IsType(IsType::TYPE_STRING), $message); - } + private $extensions; /** - * Asserts that a variable is of type scalar. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert scalar $actual + * @var int */ - public static function assertIsScalar($actual, string $message = '') : void + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\ExtensionCollection $extensions) { - static::assertThat($actual, new IsType(IsType::TYPE_SCALAR), $message); + $this->extensions = $extensions->asArray(); } - /** - * Asserts that a variable is of type callable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert callable $actual - */ - public static function assertIsCallable($actual, string $message = '') : void + public function count() : int { - static::assertThat($actual, new IsType(IsType::TYPE_CALLABLE), $message); + return iterator_count($this); } - /** - * Asserts that a variable is of type iterable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert iterable $actual - */ - public static function assertIsIterable($actual, string $message = '') : void + public function rewind() : void { - static::assertThat($actual, new IsType(IsType::TYPE_ITERABLE), $message); + $this->position = 0; } - /** - * Asserts that a variable is not of type array. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !array $actual - */ - public static function assertIsNotArray($actual, string $message = '') : void + public function valid() : bool { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_ARRAY)), $message); + return $this->position < count($this->extensions); } - /** - * Asserts that a variable is not of type bool. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !bool $actual - */ - public static function assertIsNotBool($actual, string $message = '') : void + public function key() : int { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_BOOL)), $message); + return $this->position; } - /** - * Asserts that a variable is not of type float. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !float $actual - */ - public static function assertIsNotFloat($actual, string $message = '') : void + public function current() : \PHPUnit\TextUI\XmlConfiguration\Extension { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_FLOAT)), $message); + return $this->extensions[$this->position]; } - /** - * Asserts that a variable is not of type int. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !int $actual - */ - public static function assertIsNotInt($actual, string $message = '') : void + public function next() : void { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_INT)), $message); + $this->position++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class PHPUnit +{ /** - * Asserts that a variable is not of type numeric. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !numeric $actual + * @var bool */ - public static function assertIsNotNumeric($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_NUMERIC)), $message); - } + private $cacheResult; /** - * Asserts that a variable is not of type object. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !object $actual + * @var ?string */ - public static function assertIsNotObject($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_OBJECT)), $message); - } + private $cacheResultFile; /** - * Asserts that a variable is not of type resource. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !resource $actual + * @var int|string */ - public static function assertIsNotResource($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_RESOURCE)), $message); - } + private $columns; /** - * Asserts that a variable is not of type resource. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !resource $actual + * @var string */ - public static function assertIsNotClosedResource($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_CLOSED_RESOURCE)), $message); - } + private $colors; /** - * Asserts that a variable is not of type string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !string $actual + * @var bool */ - public static function assertIsNotString($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_STRING)), $message); - } + private $stderr; /** - * Asserts that a variable is not of type scalar. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !scalar $actual + * @var bool */ - public static function assertIsNotScalar($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_SCALAR)), $message); - } + private $noInteraction; /** - * Asserts that a variable is not of type callable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !callable $actual + * @var bool */ - public static function assertIsNotCallable($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_CALLABLE)), $message); - } + private $verbose; /** - * Asserts that a variable is not of type iterable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !iterable $actual + * @var bool */ - public static function assertIsNotIterable($actual, string $message = '') : void - { - static::assertThat($actual, new LogicalNot(new IsType(IsType::TYPE_ITERABLE)), $message); - } + private $reverseDefectList; /** - * Asserts that a string matches a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = '') : void - { - static::assertThat($string, new RegularExpression($pattern), $message); - } + private $convertDeprecationsToExceptions; /** - * Asserts that a string matches a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + * @var bool */ - public static function assertRegExp(string $pattern, string $string, string $message = '') : void - { - self::createWarning('assertRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertMatchesRegularExpression() instead.'); - static::assertThat($string, new RegularExpression($pattern), $message); - } + private $convertErrorsToExceptions; /** - * Asserts that a string does not match a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = '') : void - { - static::assertThat($string, new LogicalNot(new RegularExpression($pattern)), $message); - } + private $convertNoticesToExceptions; /** - * Asserts that a string does not match a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + * @var bool */ - public static function assertNotRegExp(string $pattern, string $string, string $message = '') : void - { - self::createWarning('assertNotRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDoesNotMatchRegularExpression() instead.'); - static::assertThat($string, new LogicalNot(new RegularExpression($pattern)), $message); - } + private $convertWarningsToExceptions; /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @var bool */ - public static function assertSameSize($expected, $actual, string $message = '') : void - { - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'countable or iterable'); - } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); - } - static::assertThat($actual, new SameSize($expected), $message); - } + private $forceCoversAnnotation; /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is not the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @var ?string */ - public static function assertNotSameSize($expected, $actual, string $message = '') : void - { - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(1, 'countable or iterable'); - } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw \PHPUnit\Framework\InvalidArgumentException::create(2, 'countable or iterable'); - } - static::assertThat($actual, new LogicalNot(new SameSize($expected)), $message); - } + private $bootstrap; /** - * Asserts that a string matches a given format string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringMatchesFormat(string $format, string $string, string $message = '') : void - { - static::assertThat($string, new StringMatchesFormatDescription($format), $message); - } + private $processIsolation; + /** + * @var bool + */ + private $failOnEmptyTestSuite; + /** + * @var bool + */ + private $failOnIncomplete; + /** + * @var bool + */ + private $failOnRisky; + /** + * @var bool + */ + private $failOnSkipped; /** - * Asserts that a string does not match a given format string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringNotMatchesFormat(string $format, string $string, string $message = '') : void - { - static::assertThat($string, new LogicalNot(new StringMatchesFormatDescription($format)), $message); - } + private $failOnWarning; /** - * Asserts that a string matches a given format file. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = '') : void - { - static::assertFileExists($formatFile, $message); - static::assertThat($string, new StringMatchesFormatDescription(file_get_contents($formatFile)), $message); - } + private $stopOnDefect; /** - * Asserts that a string does not match a given format string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = '') : void - { - static::assertFileExists($formatFile, $message); - static::assertThat($string, new LogicalNot(new StringMatchesFormatDescription(file_get_contents($formatFile))), $message); - } + private $stopOnError; /** - * Asserts that a string starts with a given prefix. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringStartsWith(string $prefix, string $string, string $message = '') : void - { - static::assertThat($string, new StringStartsWith($prefix), $message); - } + private $stopOnFailure; /** - * Asserts that a string starts not with a given prefix. - * - * @param string $prefix - * @param string $string - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringStartsNotWith($prefix, $string, string $message = '') : void - { - static::assertThat($string, new LogicalNot(new StringStartsWith($prefix)), $message); - } + private $stopOnWarning; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringContainsString(string $needle, string $haystack, string $message = '') : void - { - $constraint = new StringContains($needle, \false); - static::assertThat($haystack, $constraint, $message); - } + private $stopOnIncomplete; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void - { - $constraint = new StringContains($needle, \true); - static::assertThat($haystack, $constraint, $message); - } + private $stopOnRisky; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertStringNotContainsString(string $needle, string $haystack, string $message = '') : void - { - $constraint = new LogicalNot(new StringContains($needle)); - static::assertThat($haystack, $constraint, $message); - } + private $stopOnSkipped; /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var ?string */ - public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = '') : void - { - $constraint = new LogicalNot(new StringContains($needle, \true)); - static::assertThat($haystack, $constraint, $message); - } + private $extensionsDirectory; /** - * Asserts that a string ends with a given suffix. + * @var ?string * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - public static function assertStringEndsWith(string $suffix, string $string, string $message = '') : void - { - static::assertThat($string, new StringEndsWith($suffix), $message); - } + private $testSuiteLoaderClass; /** - * Asserts that a string ends not with a given suffix. + * @var ?string * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - public static function assertStringEndsNotWith(string $suffix, string $string, string $message = '') : void - { - static::assertThat($string, new LogicalNot(new StringEndsWith($suffix)), $message); - } + private $testSuiteLoaderFile; /** - * Asserts that two XML files are equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @var ?string */ - public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void - { - $expected = (new XmlLoader())->loadFile($expectedFile); - $actual = (new XmlLoader())->loadFile($actualFile); - static::assertEquals($expected, $actual, $message); - } + private $printerClass; /** - * Asserts that two XML files are not equal. - * - * @throws \PHPUnit\Util\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var ?string */ - public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = '') : void - { - $expected = (new XmlLoader())->loadFile($expectedFile); - $actual = (new XmlLoader())->loadFile($actualFile); - static::assertNotEquals($expected, $actual, $message); - } + private $printerFile; /** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $actualXml - * - * @throws \PHPUnit\Util\Xml\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void - { - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $actual = $actualXml; - } else { - $actual = (new XmlLoader())->load($actualXml); - } - $expected = (new XmlLoader())->loadFile($expectedFile); - static::assertEquals($expected, $actual, $message); - } + private $beStrictAboutChangesToGlobalState; /** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $actualXml - * - * @throws \PHPUnit\Util\Xml\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = '') : void - { - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $actual = $actualXml; - } else { - $actual = (new XmlLoader())->load($actualXml); - } - $expected = (new XmlLoader())->loadFile($expectedFile); - static::assertNotEquals($expected, $actual, $message); - } + private $beStrictAboutOutputDuringTests; /** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws \PHPUnit\Util\Xml\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = '') : void - { - if (!is_string($expectedXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $expected = $expectedXml; - } else { - $expected = (new XmlLoader())->load($expectedXml); - } - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $actual = $actualXml; - } else { - $actual = (new XmlLoader())->load($actualXml); - } - static::assertEquals($expected, $actual, $message); - } + private $beStrictAboutResourceUsageDuringSmallTests; /** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws \PHPUnit\Util\Xml\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = '') : void - { - if (!is_string($expectedXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $expected = $expectedXml; - } else { - $expected = (new XmlLoader())->load($expectedXml); - } - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - $actual = $actualXml; - } else { - $actual = (new XmlLoader())->load($actualXml); - } - static::assertNotEquals($expected, $actual, $message); - } + private $beStrictAboutTestsThatDoNotTestAnything; /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws AssertionFailedError - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + * @var bool */ - public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = \false, string $message = '') : void - { - self::createWarning('assertEqualXMLStructure() is deprecated and will be removed in PHPUnit 10.'); - $expectedElement = Xml::import($expectedElement); - $actualElement = Xml::import($actualElement); - static::assertSame($expectedElement->tagName, $actualElement->tagName, $message); - if ($checkAttributes) { - static::assertSame($expectedElement->attributes->length, $actualElement->attributes->length, sprintf('%s%sNumber of attributes on node "%s" does not match', $message, !empty($message) ? "\n" : '', $expectedElement->tagName)); - for ($i = 0; $i < $expectedElement->attributes->length; $i++) { - $expectedAttribute = $expectedElement->attributes->item($i); - $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); - assert($expectedAttribute instanceof DOMAttr); - if (!$actualAttribute) { - static::fail(sprintf('%s%sCould not find attribute "%s" on node "%s"', $message, !empty($message) ? "\n" : '', $expectedAttribute->name, $expectedElement->tagName)); - } - } - } - Xml::removeCharacterDataNodes($expectedElement); - Xml::removeCharacterDataNodes($actualElement); - static::assertSame($expectedElement->childNodes->length, $actualElement->childNodes->length, sprintf('%s%sNumber of child nodes of "%s" differs', $message, !empty($message) ? "\n" : '', $expectedElement->tagName)); - for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { - static::assertEqualXMLStructure($expectedElement->childNodes->item($i), $actualElement->childNodes->item($i), $checkAttributes, $message); - } - } + private $beStrictAboutTodoAnnotatedTests; /** - * Evaluates a PHPUnit\Framework\Constraint matcher object. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertThat($value, Constraint $constraint, string $message = '') : void - { - self::$count += count($constraint); - $constraint->evaluate($value, $message); - } + private $beStrictAboutCoversAnnotation; /** - * Asserts that a string is a valid JSON string. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public static function assertJson(string $actualJson, string $message = '') : void - { - static::assertThat($actualJson, static::isJson(), $message); - } + private $enforceTimeLimit; /** - * Asserts that two given JSON encoded objects or arrays are equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = '') : void - { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); - } + private $defaultTimeLimit; /** - * Asserts that two given JSON encoded objects or arrays are not equal. - * - * @param string $expectedJson - * @param string $actualJson - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = '') : void - { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - static::assertThat($actualJson, new LogicalNot(new JsonMatches($expectedJson)), $message); - } + private $timeoutForSmallTests; /** - * Asserts that the generated JSON encoded object and the content of the given file are equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); - } + private $timeoutForMediumTests; /** - * Asserts that the generated JSON encoded object and the content of the given file are not equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - static::assertThat($actualJson, new LogicalNot(new JsonMatches($expectedJson)), $message); - } + private $timeoutForLargeTests; /** - * Asserts that two JSON files are equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var ?string */ - public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); - $actualJson = file_get_contents($actualFile); - $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - $constraintExpected = new JsonMatches($expectedJson); - $constraintActual = new JsonMatches($actualJson); - static::assertThat($expectedJson, $constraintActual, $message); - static::assertThat($actualJson, $constraintExpected, $message); - } + private $defaultTestSuite; /** - * Asserts that two JSON files are not equal. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var int */ - public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = '') : void - { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); - $actualJson = file_get_contents($actualFile); - $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); - $constraintExpected = new JsonMatches($expectedJson); - $constraintActual = new JsonMatches($actualJson); - static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); - static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); - } + private $executionOrder; /** - * @throws Exception + * @var bool */ - public static function logicalAnd() : LogicalAnd - { - $constraints = func_get_args(); - $constraint = new LogicalAnd(); - $constraint->setConstraints($constraints); - return $constraint; - } - public static function logicalOr() : LogicalOr - { - $constraints = func_get_args(); - $constraint = new LogicalOr(); - $constraint->setConstraints($constraints); - return $constraint; - } - public static function logicalNot(Constraint $constraint) : LogicalNot - { - return new LogicalNot($constraint); - } - public static function logicalXor() : LogicalXor - { - $constraints = func_get_args(); - $constraint = new LogicalXor(); - $constraint->setConstraints($constraints); - return $constraint; - } - public static function anything() : IsAnything - { - return new IsAnything(); - } - public static function isTrue() : IsTrue - { - return new IsTrue(); - } + private $resolveDependencies; /** - * @psalm-template CallbackInput of mixed - * - * @psalm-param callable(CallbackInput $callback): bool $callback - * - * @psalm-return Callback + * @var bool */ - public static function callback(callable $callback) : Callback - { - return new Callback($callback); - } - public static function isFalse() : IsFalse - { - return new IsFalse(); - } - public static function isJson() : IsJson - { - return new IsJson(); - } - public static function isNull() : IsNull - { - return new IsNull(); - } - public static function isFinite() : IsFinite - { - return new IsFinite(); - } - public static function isInfinite() : IsInfinite + private $defectsFirst; + /** + * @var bool + */ + private $backupGlobals; + /** + * @var bool + */ + private $backupStaticAttributes; + /** + * @var bool + */ + private $registerMockObjectsFromTestArgumentsRecursively; + /** + * @var bool + */ + private $conflictBetweenPrinterClassAndTestdox; + public function __construct(bool $cacheResult, ?string $cacheResultFile, $columns, string $colors, bool $stderr, bool $noInteraction, bool $verbose, bool $reverseDefectList, bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions, bool $forceCoversAnnotation, ?string $bootstrap, bool $processIsolation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnError, bool $stopOnFailure, bool $stopOnWarning, bool $stopOnIncomplete, bool $stopOnRisky, bool $stopOnSkipped, ?string $extensionsDirectory, ?string $testSuiteLoaderClass, ?string $testSuiteLoaderFile, ?string $printerClass, ?string $printerFile, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutResourceUsageDuringSmallTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutTodoAnnotatedTests, bool $beStrictAboutCoversAnnotation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticAttributes, bool $registerMockObjectsFromTestArgumentsRecursively, bool $conflictBetweenPrinterClassAndTestdox) { - return new IsInfinite(); + $this->cacheResult = $cacheResult; + $this->cacheResultFile = $cacheResultFile; + $this->columns = $columns; + $this->colors = $colors; + $this->stderr = $stderr; + $this->noInteraction = $noInteraction; + $this->verbose = $verbose; + $this->reverseDefectList = $reverseDefectList; + $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; + $this->convertErrorsToExceptions = $convertErrorsToExceptions; + $this->convertNoticesToExceptions = $convertNoticesToExceptions; + $this->convertWarningsToExceptions = $convertWarningsToExceptions; + $this->forceCoversAnnotation = $forceCoversAnnotation; + $this->bootstrap = $bootstrap; + $this->processIsolation = $processIsolation; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnWarning = $stopOnWarning; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->extensionsDirectory = $extensionsDirectory; + $this->testSuiteLoaderClass = $testSuiteLoaderClass; + $this->testSuiteLoaderFile = $testSuiteLoaderFile; + $this->printerClass = $printerClass; + $this->printerFile = $printerFile; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; + $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; + $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; + $this->beStrictAboutTodoAnnotatedTests = $beStrictAboutTodoAnnotatedTests; + $this->beStrictAboutCoversAnnotation = $beStrictAboutCoversAnnotation; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->defaultTestSuite = $defaultTestSuite; + $this->executionOrder = $executionOrder; + $this->resolveDependencies = $resolveDependencies; + $this->defectsFirst = $defectsFirst; + $this->backupGlobals = $backupGlobals; + $this->backupStaticAttributes = $backupStaticAttributes; + $this->registerMockObjectsFromTestArgumentsRecursively = $registerMockObjectsFromTestArgumentsRecursively; + $this->conflictBetweenPrinterClassAndTestdox = $conflictBetweenPrinterClassAndTestdox; } - public static function isNan() : IsNan + public function cacheResult() : bool { - return new IsNan(); + return $this->cacheResult; } - public static function containsEqual($value) : TraversableContainsEqual + /** + * @psalm-assert-if-true !null $this->cacheResultFile + */ + public function hasCacheResultFile() : bool { - return new TraversableContainsEqual($value); + return $this->cacheResultFile !== null; } - public static function containsIdentical($value) : TraversableContainsIdentical + /** + * @throws Exception + */ + public function cacheResultFile() : string { - return new TraversableContainsIdentical($value); + if (!$this->hasCacheResultFile()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Cache result file is not configured'); + } + return (string) $this->cacheResultFile; } - public static function containsOnly(string $type) : TraversableContainsOnly + public function columns() { - return new TraversableContainsOnly($type); + return $this->columns; } - public static function containsOnlyInstancesOf(string $className) : TraversableContainsOnly + public function colors() : string { - return new TraversableContainsOnly($className, \false); + return $this->colors; } - /** - * @param int|string $key - */ - public static function arrayHasKey($key) : ArrayHasKey + public function stderr() : bool { - return new ArrayHasKey($key); + return $this->stderr; } - public static function equalTo($value) : IsEqual + public function noInteraction() : bool { - return new IsEqual($value, 0.0, \false, \false); + return $this->noInteraction; } - public static function equalToCanonicalizing($value) : IsEqualCanonicalizing + public function verbose() : bool { - return new IsEqualCanonicalizing($value); + return $this->verbose; } - public static function equalToIgnoringCase($value) : IsEqualIgnoringCase + public function reverseDefectList() : bool { - return new IsEqualIgnoringCase($value); + return $this->reverseDefectList; } - public static function equalToWithDelta($value, float $delta) : IsEqualWithDelta + public function convertDeprecationsToExceptions() : bool { - return new IsEqualWithDelta($value, $delta); + return $this->convertDeprecationsToExceptions; } - public static function isEmpty() : IsEmpty + public function convertErrorsToExceptions() : bool { - return new IsEmpty(); + return $this->convertErrorsToExceptions; } - public static function isWritable() : IsWritable + public function convertNoticesToExceptions() : bool { - return new IsWritable(); + return $this->convertNoticesToExceptions; } - public static function isReadable() : IsReadable + public function convertWarningsToExceptions() : bool { - return new IsReadable(); + return $this->convertWarningsToExceptions; } - public static function directoryExists() : DirectoryExists + public function forceCoversAnnotation() : bool { - return new DirectoryExists(); + return $this->forceCoversAnnotation; } - public static function fileExists() : FileExists + /** + * @psalm-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap() : bool { - return new FileExists(); + return $this->bootstrap !== null; } - public static function greaterThan($value) : GreaterThan + /** + * @throws Exception + */ + public function bootstrap() : string { - return new GreaterThan($value); + if (!$this->hasBootstrap()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Bootstrap script is not configured'); + } + return (string) $this->bootstrap; } - public static function greaterThanOrEqual($value) : LogicalOr + public function processIsolation() : bool { - return static::logicalOr(new IsEqual($value), new GreaterThan($value)); + return $this->processIsolation; } - public static function classHasAttribute(string $attributeName) : ClassHasAttribute + public function failOnEmptyTestSuite() : bool { - return new ClassHasAttribute($attributeName); + return $this->failOnEmptyTestSuite; } - public static function classHasStaticAttribute(string $attributeName) : ClassHasStaticAttribute + public function failOnIncomplete() : bool { - return new ClassHasStaticAttribute($attributeName); + return $this->failOnIncomplete; } - public static function objectHasAttribute($attributeName) : ObjectHasAttribute + public function failOnRisky() : bool { - return new ObjectHasAttribute($attributeName); + return $this->failOnRisky; } - public static function identicalTo($value) : IsIdentical + public function failOnSkipped() : bool { - return new IsIdentical($value); + return $this->failOnSkipped; } - public static function isInstanceOf(string $className) : IsInstanceOf + public function failOnWarning() : bool { - return new IsInstanceOf($className); + return $this->failOnWarning; } - public static function isType(string $type) : IsType + public function stopOnDefect() : bool { - return new IsType($type); + return $this->stopOnDefect; } - public static function lessThan($value) : LessThan + public function stopOnError() : bool { - return new LessThan($value); + return $this->stopOnError; } - public static function lessThanOrEqual($value) : LogicalOr + public function stopOnFailure() : bool { - return static::logicalOr(new IsEqual($value), new LessThan($value)); + return $this->stopOnFailure; } - public static function matchesRegularExpression(string $pattern) : RegularExpression + public function stopOnWarning() : bool { - return new RegularExpression($pattern); + return $this->stopOnWarning; } - public static function matches(string $string) : StringMatchesFormatDescription + public function stopOnIncomplete() : bool { - return new StringMatchesFormatDescription($string); + return $this->stopOnIncomplete; } - public static function stringStartsWith($prefix) : StringStartsWith + public function stopOnRisky() : bool { - return new StringStartsWith($prefix); + return $this->stopOnRisky; } - public static function stringContains(string $string, bool $case = \true) : StringContains + public function stopOnSkipped() : bool { - return new StringContains($string, $case); + return $this->stopOnSkipped; } - public static function stringEndsWith(string $suffix) : StringEndsWith + /** + * @psalm-assert-if-true !null $this->extensionsDirectory + */ + public function hasExtensionsDirectory() : bool { - return new StringEndsWith($suffix); + return $this->extensionsDirectory !== null; } - public static function countOf(int $count) : Count + /** + * @throws Exception + */ + public function extensionsDirectory() : string { - return new Count($count); + if (!$this->hasExtensionsDirectory()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Extensions directory is not configured'); + } + return (string) $this->extensionsDirectory; } - public static function objectEquals(object $object, string $method = 'equals') : ObjectEquals + /** + * @psalm-assert-if-true !null $this->testSuiteLoaderClass + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + public function hasTestSuiteLoaderClass() : bool { - return new ObjectEquals($object, $method); + return $this->testSuiteLoaderClass !== null; } /** - * Fails a test with the given message. - * - * @throws AssertionFailedError + * @throws Exception * - * @psalm-return never-return + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - public static function fail(string $message = '') : void + public function testSuiteLoaderClass() : string { - self::$count++; - throw new \PHPUnit\Framework\AssertionFailedError($message); + if (!$this->hasTestSuiteLoaderClass()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('TestSuiteLoader class is not configured'); + } + return (string) $this->testSuiteLoaderClass; } /** - * Mark the test as incomplete. - * - * @throws IncompleteTestError + * @psalm-assert-if-true !null $this->testSuiteLoaderFile * - * @psalm-return never-return + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - public static function markTestIncomplete(string $message = '') : void + public function hasTestSuiteLoaderFile() : bool { - throw new \PHPUnit\Framework\IncompleteTestError($message); + return $this->testSuiteLoaderFile !== null; } /** - * Mark the test as skipped. - * - * @throws SkippedTestError - * @throws SyntheticSkippedError + * @throws Exception * - * @psalm-return never-return + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 */ - public static function markTestSkipped(string $message = '') : void + public function testSuiteLoaderFile() : string { - if ($hint = self::detectLocationHint($message)) { - $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); - array_unshift($trace, $hint); - throw new \PHPUnit\Framework\SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); + if (!$this->hasTestSuiteLoaderFile()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('TestSuiteLoader sourcecode file is not configured'); } - throw new \PHPUnit\Framework\SkippedTestError($message); + return (string) $this->testSuiteLoaderFile; } /** - * Return the current assertion count. + * @psalm-assert-if-true !null $this->printerClass */ - public static function getCount() : int + public function hasPrinterClass() : bool { - return self::$count; + return $this->printerClass !== null; } /** - * Reset the assertion counter. + * @throws Exception */ - public static function resetCount() : void + public function printerClass() : string { - self::$count = 0; + if (!$this->hasPrinterClass()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('ResultPrinter class is not configured'); + } + return (string) $this->printerClass; } - private static function detectLocationHint(string $message) : ?array + /** + * @psalm-assert-if-true !null $this->printerFile + */ + public function hasPrinterFile() : bool { - $hint = null; - $lines = preg_split('/\\r\\n|\\r|\\n/', $message); - while (strpos($lines[0], '__OFFSET') !== \false) { - $offset = explode('=', array_shift($lines)); - if ($offset[0] === '__OFFSET_FILE') { - $hint['file'] = $offset[1]; - } - if ($offset[0] === '__OFFSET_LINE') { - $hint['line'] = $offset[1]; - } - } - if ($hint) { - $hint['message'] = implode(\PHP_EOL, $lines); + return $this->printerFile !== null; + } + /** + * @throws Exception + */ + public function printerFile() : string + { + if (!$this->hasPrinterFile()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('ResultPrinter sourcecode file is not configured'); } - return $hint; + return (string) $this->printerFile; } - private static function isValidObjectAttributeName(string $attributeName) : bool + public function beStrictAboutChangesToGlobalState() : bool { - return (bool) preg_match('/[^\\x00-\\x1f\\x7f-\\x9f]+/', $attributeName); + return $this->beStrictAboutChangesToGlobalState; } - private static function isValidClassAttributeName(string $attributeName) : bool + public function beStrictAboutOutputDuringTests() : bool { - return (bool) preg_match('/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/', $attributeName); + return $this->beStrictAboutOutputDuringTests; } - /** - * @codeCoverageIgnore - */ - private static function createWarning(string $warning) : void + public function beStrictAboutResourceUsageDuringSmallTests() : bool { - foreach (debug_backtrace() as $step) { - if (isset($step['object']) && $step['object'] instanceof \PHPUnit\Framework\TestCase) { - assert($step['object'] instanceof \PHPUnit\Framework\TestCase); - $step['object']->addWarning($warning); - break; - } - } + return $this->beStrictAboutResourceUsageDuringSmallTests; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function assert; -use function count; -use RecursiveIterator; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestSuiteIterator implements RecursiveIterator -{ - /** - * @var int - */ - private $position = 0; - /** - * @var Test[] - */ - private $tests; - public function __construct(\PHPUnit\Framework\TestSuite $testSuite) + public function beStrictAboutTestsThatDoNotTestAnything() : bool { - $this->tests = $testSuite->tests(); + return $this->beStrictAboutTestsThatDoNotTestAnything; } - public function rewind() : void + public function beStrictAboutTodoAnnotatedTests() : bool { - $this->position = 0; + return $this->beStrictAboutTodoAnnotatedTests; } - public function valid() : bool + public function beStrictAboutCoversAnnotation() : bool { - return $this->position < count($this->tests); + return $this->beStrictAboutCoversAnnotation; } - public function key() : int + public function enforceTimeLimit() : bool { - return $this->position; + return $this->enforceTimeLimit; } - public function current() : \PHPUnit\Framework\Test + public function defaultTimeLimit() : int { - return $this->tests[$this->position]; + return $this->defaultTimeLimit; } - public function next() : void + public function timeoutForSmallTests() : int { - $this->position++; + return $this->timeoutForSmallTests; } - /** - * @throws NoChildTestSuiteException - */ - public function getChildren() : self + public function timeoutForMediumTests() : int { - if (!$this->hasChildren()) { - throw new \PHPUnit\Framework\NoChildTestSuiteException('The current item is not a TestSuite instance and therefore does not have any children.'); - } - $current = $this->current(); - assert($current instanceof \PHPUnit\Framework\TestSuite); - return new self($current); + return $this->timeoutForMediumTests; } - public function hasChildren() : bool + public function timeoutForLargeTests() : int { - return $this->valid() && $this->current() instanceof \PHPUnit\Framework\TestSuite; + return $this->timeoutForLargeTests; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationStubber; -/** - * @method InvocationStubber method($constraint) - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface Stub -{ - public function __phpunit_getInvocationHandler() : \PHPUnit\Framework\MockObject\InvocationHandler; - public function __phpunit_hasMatchers() : bool; - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration) : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func; -use function class_exists; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockClass implements \PHPUnit\Framework\MockObject\MockType -{ - /** - * @var string - */ - private $classCode; - /** - * @var class-string - */ - private $mockName; - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; /** - * @psalm-param class-string $mockName + * @psalm-assert-if-true !null $this->defaultTestSuite */ - public function __construct(string $classCode, string $mockName, array $configurableMethods) + public function hasDefaultTestSuite() : bool { - $this->classCode = $classCode; - $this->mockName = $mockName; - $this->configurableMethods = $configurableMethods; + return $this->defaultTestSuite !== null; } /** - * @psalm-return class-string + * @throws Exception */ - public function generate() : string + public function defaultTestSuite() : string { - if (!class_exists($this->mockName, \false)) { - eval($this->classCode); - call_user_func([$this->mockName, '__phpunit_initConfigurableMethods'], ...$this->configurableMethods); + if (!$this->hasDefaultTestSuite()) { + throw new \PHPUnit\TextUI\XmlConfiguration\Exception('Default test suite is not configured'); } - return $this->mockName; + return (string) $this->defaultTestSuite; } - public function getClassCode() : string + public function executionOrder() : int { - return $this->classCode; + return $this->executionOrder; + } + public function resolveDependencies() : bool + { + return $this->resolveDependencies; + } + public function defectsFirst() : bool + { + return $this->defectsFirst; + } + public function backupGlobals() : bool + { + return $this->backupGlobals; + } + public function backupStaticAttributes() : bool + { + return $this->backupStaticAttributes; + } + public function registerMockObjectsFromTestArgumentsRecursively() : bool + { + return $this->registerMockObjectsFromTestArgumentsRecursively; + } + public function conflictBetweenPrinterClassAndTestdox() : bool + { + return $this->conflictBetweenPrinterClassAndTestdox; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface MethodNameMatch extends \PHPUnit\Framework\MockObject\Builder\ParametersMatch -{ - /** - * Adds a new method name match and returns the parameter match object for - * further matching possibilities. - * - * @param \PHPUnit\Framework\Constraint\Constraint $constraint Constraint for matching method, if a string is passed it will use the PHPUnit_Framework_Constraint_IsEqual - * - * @return ParametersMatch - */ - public function method($constraint); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub\Stub as BaseStub; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends \PHPUnit\Framework\MockObject\Builder\Identity -{ - /** - * Stubs the matching method with the stub object $stub. Any invocations of - * the matched method will now be handled by the stub instead. - */ - public function will(BaseStub $stub) : \PHPUnit\Framework\MockObject\Builder\Identity; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; +namespace PHPUnit\TextUI\XmlConfiguration; -use PHPUnit\Framework\MockObject\Stub\Stub; -use Throwable; +use PHPUnit\Util\VersionComparisonOperator; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable */ -interface InvocationStubber +final class TestDirectory { - public function will(Stub $stub) : \PHPUnit\Framework\MockObject\Builder\Identity; - /** @return self */ - public function willReturn($value, ...$nextValues); /** - * @param mixed $reference - * - * @return self + * @var string */ - public function willReturnReference(&$reference); + private $path; /** - * @param array> $valueMap - * - * @return self + * @var string */ - public function willReturnMap(array $valueMap); + private $prefix; /** - * @param int $argumentIndex - * - * @return self + * @var string */ - public function willReturnArgument($argumentIndex); + private $suffix; /** - * @param callable $callback - * - * @return self + * @var string */ - public function willReturnCallback($callback); - /** @return self */ - public function willReturnSelf(); + private $phpVersion; /** - * @param mixed $values - * - * @return self + * @var VersionComparisonOperator */ - public function willReturnOnConsecutiveCalls(...$values); - /** @return self */ - public function willThrowException(Throwable $exception); + private $phpVersionOperator; + public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + } + public function path() : string + { + return $this->path; + } + public function prefix() : string + { + return $this->prefix; + } + public function suffix() : string + { + return $this->suffix; + } + public function phpVersion() : string + { + return $this->phpVersion; + } + public function phpVersionOperator() : VersionComparisonOperator + { + return $this->phpVersionOperator; + } } * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -interface ParametersMatch extends \PHPUnit\Framework\MockObject\Builder\Stub +final class TestDirectoryCollection implements Countable, IteratorAggregate { /** - * Defines the expectation which must occur before the current is valid. - * - * @param string $id the identification of the expectation that should - * occur before this one - * - * @return Stub + * @var TestDirectory[] */ - public function after($id); + private $directories; /** - * Sets the parameters to match for, each parameter to this function will - * be part of match. To perform specific matches or constraints create a - * new PHPUnit\Framework\Constraint\Constraint and use it for the parameter. - * If the parameter value is not a constraint it will use the - * PHPUnit\Framework\Constraint\IsEqual for the value. - * - * Some examples: - * - * // match first parameter with value 2 - * $b->with(2); - * // match first parameter with value 'smock' and second identical to 42 - * $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42)); - * - * - * @return ParametersMatch + * @param TestDirectory[] $directories */ - public function with(...$arguments); + public static function fromArray(array $directories) : self + { + return new self(...$directories); + } + private function __construct(\PHPUnit\TextUI\XmlConfiguration\TestDirectory ...$directories) + { + $this->directories = $directories; + } /** - * Sets a rule which allows any kind of parameters. - * - * Some examples: - * - * // match any number of parameters - * $b->withAnyParameters(); - * - * - * @return ParametersMatch + * @return TestDirectory[] */ - public function withAnyParameters(); + public function asArray() : array + { + return $this->directories; + } + public function count() : int + { + return count($this->directories); + } + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollectionIterator + { + return new \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollectionIterator($this); + } + public function isEmpty() : bool + { + return $this->count() === 0; + } } invocationHandler = $handler; - $this->matcher = $matcher; - $this->configurableMethods = $configurableMethods; - } - /** - * @throws MatcherAlreadyRegisteredException - * - * @return $this - */ - public function id($id) : self - { - $this->invocationHandler->registerMatcher($id, $this->matcher); - return $this; - } - /** - * @return $this - */ - public function will(Stub $stub) : \PHPUnit\Framework\MockObject\Builder\Identity - { - $this->matcher->setStub($stub); - return $this; - } - /** - * @param mixed $value - * @param mixed[] $nextValues - * - * @throws IncompatibleReturnValueException - */ - public function willReturn($value, ...$nextValues) : self - { - if (count($nextValues) === 0) { - $this->ensureTypeOfReturnValues([$value]); - $stub = $value instanceof Stub ? $value : new ReturnStub($value); - } else { - $values = array_merge([$value], $nextValues); - $this->ensureTypeOfReturnValues($values); - $stub = new ConsecutiveCalls($values); - } - return $this->will($stub); - } - public function willReturnReference(&$reference) : self - { - $stub = new ReturnReference($reference); - return $this->will($stub); - } - public function willReturnMap(array $valueMap) : self - { - $stub = new ReturnValueMap($valueMap); - return $this->will($stub); - } - public function willReturnArgument($argumentIndex) : self - { - $stub = new ReturnArgument($argumentIndex); - return $this->will($stub); - } - public function willReturnCallback($callback) : self - { - $stub = new ReturnCallback($callback); - return $this->will($stub); - } - public function willReturnSelf() : self - { - $stub = new ReturnSelf(); - return $this->will($stub); - } - public function willReturnOnConsecutiveCalls(...$values) : self - { - $stub = new ConsecutiveCalls($values); - return $this->will($stub); - } - public function willThrowException(Throwable $exception) : self - { - $stub = new Exception($exception); - return $this->will($stub); - } - /** - * @return $this + * @var TestDirectory[] */ - public function after($id) : self - { - $this->matcher->setAfterMatchBuilderId($id); - return $this; - } + private $directories; /** - * @param mixed[] $arguments - * - * @throws \PHPUnit\Framework\Exception - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this + * @var int */ - public function with(...$arguments) : self + private $position; + public function __construct(\PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection $directories) { - $this->ensureParametersCanBeConfigured(); - $this->matcher->setParametersRule(new Rule\Parameters($arguments)); - return $this; + $this->directories = $directories->asArray(); } - /** - * @param array ...$arguments - * - * @throws \PHPUnit\Framework\Exception - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this - */ - public function withConsecutive(...$arguments) : self + public function count() : int { - $this->ensureParametersCanBeConfigured(); - $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); - return $this; + return iterator_count($this); } - /** - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this - */ - public function withAnyParameters() : self + public function rewind() : void { - $this->ensureParametersCanBeConfigured(); - $this->matcher->setParametersRule(new Rule\AnyParameters()); - return $this; + $this->position = 0; } - /** - * @param Constraint|string $constraint - * - * @throws \PHPUnit\Framework\InvalidArgumentException - * @throws MethodCannotBeConfiguredException - * @throws MethodNameAlreadyConfiguredException - * - * @return $this - */ - public function method($constraint) : self + public function valid() : bool { - if ($this->matcher->hasMethodNameRule()) { - throw new MethodNameAlreadyConfiguredException(); - } - $configurableMethodNames = array_map(static function (ConfigurableMethod $configurable) { - return strtolower($configurable->getName()); - }, $this->configurableMethods); - if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, \true)) { - throw new MethodCannotBeConfiguredException($constraint); - } - $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); - return $this; + return $this->position < count($this->directories); } - /** - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - */ - private function ensureParametersCanBeConfigured() : void + public function key() : int { - if (!$this->matcher->hasMethodNameRule()) { - throw new MethodNameNotConfiguredException(); - } - if ($this->matcher->hasParametersRule()) { - throw new MethodParametersAlreadyConfiguredException(); - } + return $this->position; } - private function getConfiguredMethod() : ?ConfigurableMethod + public function current() : \PHPUnit\TextUI\XmlConfiguration\TestDirectory { - $configuredMethod = null; - foreach ($this->configurableMethods as $configurableMethod) { - if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { - if ($configuredMethod !== null) { - return null; - } - $configuredMethod = $configurableMethod; - } - } - return $configuredMethod; + return $this->directories[$this->position]; } - /** - * @throws IncompatibleReturnValueException - */ - private function ensureTypeOfReturnValues(array $values) : void + public function next() : void { - $configuredMethod = $this->getConfiguredMethod(); - if ($configuredMethod === null) { - return; - } - foreach ($values as $value) { - if (!$configuredMethod->mayReturn($value)) { - throw new IncompatibleReturnValueException($configuredMethod, $value); - } - } + $this->position++; } } is already registered', $id)); + $this->path = $path; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + } + public function path() : string + { + return $this->path; + } + public function phpVersion() : string + { + return $this->phpVersion; + } + public function phpVersionOperator() : VersionComparisonOperator + { + return $this->phpVersionOperator; } } files = $files; + } + /** + * @return TestFile[] + */ + public function asArray() : array + { + return $this->files; + } + public function count() : int + { + return count($this->files); + } + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestFileCollectionIterator + { + return new \PHPUnit\TextUI\XmlConfiguration\TestFileCollectionIterator($this); + } + public function isEmpty() : bool + { + return $this->count() === 0; + } } files = $files->asArray(); + } + public function count() : int + { + return iterator_count($this); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < count($this->files); + } + public function key() : int + { + return $this->position; + } + public function current() : \PHPUnit\TextUI\XmlConfiguration\TestFile + { + return $this->files[$this->position]; + } + public function next() : void + { + $this->position++; } } name = $name; + $this->directories = $directories; + $this->files = $files; + $this->exclude = $exclude; + } + public function name() : string + { + return $this->name; + } + public function directories() : \PHPUnit\TextUI\XmlConfiguration\TestDirectoryCollection + { + return $this->directories; + } + public function files() : \PHPUnit\TextUI\XmlConfiguration\TestFileCollection + { + return $this->files; + } + public function exclude() : \PHPUnit\TextUI\XmlConfiguration\FileCollection { - parent::__construct(\sprintf('Return value inference disabled and no expectation set up for %s::%s()', $invocation->getClassName(), $invocation->getMethodName())); + return $this->exclude; } } testSuites = $testSuites; + } + /** + * @return TestSuite[] + */ + public function asArray() : array + { + return $this->testSuites; + } + public function count() : int + { + return count($this->testSuites); + } + public function getIterator() : \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollectionIterator { - parent::__construct(sprintf('Method %s may not return value of type %s, its declared return type is "%s"', $method->getName(), \is_object($value) ? \get_class($value) : \gettype($value), $method->getReturnTypeDeclaration())); + return new \PHPUnit\TextUI\XmlConfiguration\TestSuiteCollectionIterator($this); + } + public function isEmpty() : bool + { + return $this->count() === 0; } } testSuites = $testSuites->asArray(); + } + public function count() : int + { + return iterator_count($this); + } + public function rewind() : void + { + $this->position = 0; + } + public function valid() : bool + { + return $this->position < count($this->testSuites); + } + public function key() : int + { + return $this->position; + } + public function current() : \PHPUnit\TextUI\XmlConfiguration\TestSuite + { + return $this->testSuites[$this->position]; + } + public function next() : void + { + $this->position++; } } PHP(?:Unit)?)\\s+(?P[<>=!]{0,2})\\s*(?P[\\d\\.-]+(dev|(RC|alpha|beta)[\\d\\.])?)[ \\t]*\\r?$/m'; + private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\\s+(?PPHP(?:Unit)?)\\s+(?P[\\d\\t \\-.|~^]+)[ \\t]*\\r?$/m'; + private const REGEX_REQUIRES_OS = '/@requires\\s+(?POS(?:FAMILY)?)\\s+(?P.+?)[ \\t]*\\r?$/m'; + private const REGEX_REQUIRES_SETTING = '/@requires\\s+(?Psetting)\\s+(?P([^ ]+?))\\s*(?P[\\w\\.-]+[\\w\\.]?)?[ \\t]*\\r?$/m'; + private const REGEX_REQUIRES = '/@requires\\s+(?Pfunction|extension)\\s+(?P([^\\s<>=!]+))\\s*(?P[<>=!]{0,2})\\s*(?P[\\d\\.-]+[\\d\\.]?)?[ \\t]*\\r?$/m'; + private const REGEX_TEST_WITH = '/@testWith\\s+/'; + /** @var string */ + private $docComment; + /** @var bool */ + private $isMethod; + /** @var array> pre-parsed annotations indexed by name and occurrence index */ + private $symbolAnnotations; + /** + * @var null|array + * + * @psalm-var null|(array{ + * __OFFSET: array&array{__FILE: string}, + * setting?: array, + * extension_versions?: array + * }&array< + * string, + * string|array{version: string, operator: string}|array{constraint: string}|array + * >) + */ + private $parsedRequirements; + /** @var int */ + private $startLine; + /** @var int */ + private $endLine; + /** @var string */ + private $fileName; + /** @var string */ + private $name; + /** + * @var string + * + * @psalm-var class-string + */ + private $className; + public static function ofClass(ReflectionClass $class) : self + { + $className = $class->getName(); + return new self((string) $class->getDocComment(), \false, self::extractAnnotationsFromReflector($class), $class->getStartLine(), $class->getEndLine(), $class->getFileName(), $className, $className); + } + /** + * @psalm-param class-string $classNameInHierarchy + */ + public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy) : self + { + return new self((string) $method->getDocComment(), \true, self::extractAnnotationsFromReflector($method), $method->getStartLine(), $method->getEndLine(), $method->getFileName(), $method->getName(), $classNameInHierarchy); + } + /** + * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized. + * + * @param array> $symbolAnnotations + * + * @psalm-param class-string $className + */ + private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) + { + $this->docComment = $docComment; + $this->isMethod = $isMethod; + $this->symbolAnnotations = $symbolAnnotations; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->fileName = $fileName; + $this->name = $name; + $this->className = $className; + } + /** + * @psalm-return array{ + * __OFFSET: array&array{__FILE: string}, + * setting?: array, + * extension_versions?: array + * }&array< + * string, + * string|array{version: string, operator: string}|array{constraint: string}|array + * > + * + * @throws Warning if the requirements version constraint is not well-formed + */ + public function requirements() : array + { + if ($this->parsedRequirements !== null) { + return $this->parsedRequirements; + } + $offset = $this->startLine; + $requires = []; + $recordedSettings = []; + $extensionVersions = []; + $recordedOffsets = ['__FILE' => realpath($this->fileName)]; + // Trim docblock markers, split it into lines and rewind offset to start of docblock + $lines = preg_replace(['#^/\\*{2}#', '#\\*/$#'], '', preg_split('/\\r\\n|\\r|\\n/', $this->docComment)); + $offset -= count($lines); + foreach ($lines as $line) { + if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { + $requires[$matches['name']] = $matches['value']; + $recordedOffsets[$matches['name']] = $offset; + } + if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { + $requires[$matches['name']] = ['version' => $matches['version'], 'operator' => $matches['operator']]; + $recordedOffsets[$matches['name']] = $offset; + } + if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { + if (!empty($requires[$matches['name']])) { + $offset++; + continue; + } + try { + $versionConstraintParser = new VersionConstraintParser(); + $requires[$matches['name'] . '_constraint'] = ['constraint' => $versionConstraintParser->parse(trim($matches['constraint']))]; + $recordedOffsets[$matches['name'] . '_constraint'] = $offset; + } catch (\PHPUnit\PharIo\Version\Exception $e) { + throw new Warning($e->getMessage(), $e->getCode(), $e); + } + } + if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { + $recordedSettings[$matches['setting']] = $matches['value']; + $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; + } + if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { + $name = $matches['name'] . 's'; + if (!isset($requires[$name])) { + $requires[$name] = []; + } + $requires[$name][] = $matches['value']; + $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; + if ($name === 'extensions' && !empty($matches['version'])) { + $extensionVersions[$matches['value']] = ['version' => $matches['version'], 'operator' => $matches['operator']]; + } + } + $offset++; + } + return $this->parsedRequirements = array_merge($requires, ['__OFFSET' => $recordedOffsets], array_filter(['setting' => $recordedSettings, 'extension_versions' => $extensionVersions])); + } + /** + * Returns the provided data for a method. + * + * @throws Exception + */ + public function getProvidedData() : ?array + { + /** @noinspection SuspiciousBinaryOperationInspection */ + $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); + if ($data === null) { + return null; + } + if ($data === []) { + throw new SkippedTestError(); + } + foreach ($data as $key => $value) { + if (!is_array($value)) { + throw new InvalidDataSetException(sprintf('Data set %s is invalid.', is_int($key) ? '#' . $key : '"' . $key . '"')); + } + } + return $data; + } + /** + * @psalm-return array + */ + public function getInlineAnnotations() : array + { + $code = file($this->fileName); + $lineNumber = $this->startLine; + $startLine = $this->startLine - 1; + $endLine = $this->endLine - 1; + $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); + $annotations = []; + foreach ($codeLines as $line) { + if (preg_match('#/\\*\\*?\\s*@(?P[A-Za-z_-]+)(?:[ \\t]+(?P.*?))?[ \\t]*\\r?\\*/$#m', $line, $matches)) { + $annotations[strtolower($matches['name'])] = ['line' => $lineNumber, 'value' => $matches['value']]; + } + $lineNumber++; + } + return $annotations; + } + public function symbolAnnotations() : array + { + return $this->symbolAnnotations; + } + public function isHookToBeExecutedBeforeClass() : bool + { + return $this->isMethod && \false !== strpos($this->docComment, '@beforeClass'); + } + public function isHookToBeExecutedAfterClass() : bool + { + return $this->isMethod && \false !== strpos($this->docComment, '@afterClass'); + } + public function isToBeExecutedBeforeTest() : bool + { + return 1 === preg_match('/@before\\b/', $this->docComment); + } + public function isToBeExecutedAfterTest() : bool + { + return 1 === preg_match('/@after\\b/', $this->docComment); + } + public function isToBeExecutedAsPreCondition() : bool + { + return 1 === preg_match('/@preCondition\\b/', $this->docComment); + } + public function isToBeExecutedAsPostCondition() : bool + { + return 1 === preg_match('/@postCondition\\b/', $this->docComment); + } + private function getDataFromDataProviderAnnotation(string $docComment) : ?array + { + $methodName = null; + $className = $this->className; + if ($this->isMethod) { + $methodName = $this->name; + } + if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { + return null; + } + $result = []; + foreach ($matches[1] as $match) { + $dataProviderMethodNameNamespace = explode('\\', $match); + $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); + $dataProviderMethodName = array_pop($leaf); + if (empty($dataProviderMethodNameNamespace)) { + $dataProviderMethodNameNamespace = ''; + } else { + $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; + } + if (empty($leaf)) { + $dataProviderClassName = $className; + } else { + /** @psalm-var class-string $dataProviderClassName */ + $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); + } + try { + $dataProviderClass = new ReflectionClass($dataProviderClassName); + $dataProviderMethod = $dataProviderClass->getMethod($dataProviderMethodName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); + // @codeCoverageIgnoreEnd + } + if ($dataProviderMethod->isStatic()) { + $object = null; + } else { + $object = $dataProviderClass->newInstance(); + } + if ($dataProviderMethod->getNumberOfParameters() === 0) { + $data = $dataProviderMethod->invoke($object); + } else { + $data = $dataProviderMethod->invoke($object, $methodName); + } + if ($data instanceof Traversable) { + $origData = $data; + $data = []; + foreach ($origData as $key => $value) { + if (is_int($key)) { + $data[] = $value; + } elseif (array_key_exists($key, $data)) { + throw new InvalidDataProviderException(sprintf('The key "%s" has already been defined in the data provider "%s".', $key, $match)); + } else { + $data[$key] = $value; + } + } + } + if (is_array($data)) { + $result = array_merge($result, $data); + } + } + return $result; + } + /** + * @throws Exception + */ + private function getDataFromTestWithAnnotation(string $docComment) : ?array + { + $docComment = $this->cleanUpMultiLineAnnotation($docComment); + if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { + return null; + } + $offset = strlen($matches[0][0]) + $matches[0][1]; + $annotationContent = substr($docComment, $offset); + $data = []; + foreach (explode("\n", $annotationContent) as $candidateRow) { + $candidateRow = trim($candidateRow); + if ($candidateRow[0] !== '[') { + break; + } + $dataSet = json_decode($candidateRow, \true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception('The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg()); + } + $data[] = $dataSet; + } + if (!$data) { + throw new Exception('The data set for the @testWith annotation cannot be parsed.'); + } + return $data; + } + private function cleanUpMultiLineAnnotation(string $docComment) : string + { + //removing initial ' * ' for docComment + $docComment = str_replace("\r\n", "\n", $docComment); + $docComment = preg_replace('/' . '\\n' . '\\s*' . '\\*' . '\\s?' . '/', "\n", $docComment); + $docComment = (string) substr($docComment, 0, -1); + return rtrim($docComment, "\n"); + } + /** @return array> */ + private static function parseDocBlock(string $docBlock) : array { - parent::__construct(sprintf('Trying to configure method "%s" with addMethods(), but it exists in class "%s". Use onlyMethods() for methods that exist in the class', $methodName, $type)); + // Strip away the docblock header and footer to ease parsing of one line annotations + $docBlock = (string) substr($docBlock, 3, -2); + $annotations = []; + if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \\t]+(?P.*?))?[ \\t]*\\r?$/m', $docBlock, $matches)) { + $numMatches = count($matches[0]); + for ($i = 0; $i < $numMatches; $i++) { + $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; + } + } + return $annotations; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodParametersAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct() + /** @param ReflectionClass|ReflectionFunctionAbstract $reflector */ + private static function extractAnnotationsFromReflector(Reflector $reflector) : array { - parent::__construct('Method parameters already configured'); + $annotations = []; + if ($reflector instanceof ReflectionClass) { + $annotations = array_merge($annotations, ...array_map(static function (ReflectionClass $trait) : array { + return self::parseDocBlock((string) $trait->getDocComment()); + }, array_values($reflector->getTraits()))); + } + return array_merge($annotations, self::parseDocBlock((string) $reflector->getDocComment())); } } indexed by class name */ + private $classDocBlocks = []; + /** @var array> indexed by class name and method name */ + private $methodDocBlocks = []; + public static function getInstance() : self { - parent::__construct(sprintf('No builder found for match builder identification <%s>', $id)); + return self::$instance ?? (self::$instance = new self()); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class OriginalConstructorInvocationRequiredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct() + private function __construct() { - parent::__construct('Proxying to original methods requires invoking the original constructor'); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnknownTypeException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct(string $type) + /** + * @throws Exception + * + * @psalm-param class-string $class + */ + public function forClassName(string $class) : \PHPUnit\Util\Annotation\DocBlock { - parent::__construct(sprintf('Class or interface "%s" does not exist', $type)); + if (array_key_exists($class, $this->classDocBlocks)) { + return $this->classDocBlocks[$class]; + } + try { + $reflection = new ReflectionClass($class); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + return $this->classDocBlocks[$class] = \PHPUnit\Util\Annotation\DocBlock::ofClass($reflection); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class CannotUseOnlyMethodsException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct(string $type, string $methodName) + /** + * @throws Exception + * + * @psalm-param class-string $classInHierarchy + */ + public function forMethod(string $classInHierarchy, string $method) : \PHPUnit\Util\Annotation\DocBlock { - parent::__construct(sprintf('Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s". Use addMethods() for methods that do not exist in the class', $methodName, $type)); + if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { + return $this->methodDocBlocks[$classInHierarchy][$method]; + } + try { + $reflection = new ReflectionMethod($classInHierarchy, $method); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + return $this->methodDocBlocks[$classInHierarchy][$method] = \PHPUnit\Util\Annotation\DocBlock::ofMethod($reflection, $classInHierarchy); } } + * @deprecated Use ExcludeList instead * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class UnknownClassException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +final class Blacklist { - public function __construct(string $className) + public static function addDirectory(string $directory) : void { - parent::__construct(sprintf('Class "%s" does not exist', $className)); + \PHPUnit\Util\ExcludeList::addDirectory($directory); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class RuntimeException extends \RuntimeException implements \PHPUnit\Framework\MockObject\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class BadMethodCallException extends \BadMethodCallException implements \PHPUnit\Framework\MockObject\Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodNameAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct() + /** + * @throws Exception + * + * @return string[] + */ + public function getBlacklistedDirectories() : array { - parent::__construct('Method name is already configured'); + return (new \PHPUnit\Util\ExcludeList())->getExcludedDirectories(); + } + /** + * @throws Exception + */ + public function isBlacklisted(string $file) : bool + { + return (new \PHPUnit\Util\ExcludeList())->isExcluded($file); } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnknownTraitException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception +final class Cloner { - public function __construct(string $traitName) + /** + * @psalm-template OriginalType + * + * @psalm-param OriginalType $original + * + * @psalm-return OriginalType + */ + public static function clone(object $original) : object { - parent::__construct(sprintf('Trait "%s" does not exist', $traitName)); + try { + return clone $original; + } catch (Throwable $t) { + return $original; + } } } + */ + private const WHITESPACE_MAP = [' ' => '·', "\t" => '⇥']; + /** + * @var array + */ + private const WHITESPACE_EOL_MAP = [' ' => '·', "\t" => '⇥', "\n" => '↵', "\r" => '⟵']; + /** + * @var array + */ + private static $ansiCodes = ['reset' => '0', 'bold' => '1', 'dim' => '2', 'dim-reset' => '22', 'underlined' => '4', 'fg-default' => '39', 'fg-black' => '30', 'fg-red' => '31', 'fg-green' => '32', 'fg-yellow' => '33', 'fg-blue' => '34', 'fg-magenta' => '35', 'fg-cyan' => '36', 'fg-white' => '37', 'bg-default' => '49', 'bg-black' => '40', 'bg-red' => '41', 'bg-green' => '42', 'bg-yellow' => '43', 'bg-blue' => '44', 'bg-magenta' => '45', 'bg-cyan' => '46', 'bg-white' => '47']; + public static function colorize(string $color, string $buffer) : string { - parent::__construct(sprintf('Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', $method)); + if (trim($buffer) === '') { + return $buffer; + } + $codes = array_map('\\trim', explode(',', $color)); + $styles = []; + foreach ($codes as $code) { + if (isset(self::$ansiCodes[$code])) { + $styles[] = self::$ansiCodes[$code] ?? ''; + } + } + if (empty($styles)) { + return $buffer; + } + return self::optimizeColor(sprintf("\x1b[%sm", implode(';', $styles)) . $buffer . "\x1b[0m"); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodNameNotConfiguredException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct() + public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = \false) : string { - parent::__construct('Method name is not configured'); + if ($prevPath === null) { + $prevPath = ''; + } + $path = explode(DIRECTORY_SEPARATOR, $path); + $prevPath = explode(DIRECTORY_SEPARATOR, $prevPath); + for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { + if ($path[$i] == $prevPath[$i]) { + $path[$i] = self::dim($path[$i]); + } + } + if ($colorizeFilename) { + $last = count($path) - 1; + $path[$last] = preg_replace_callback('/([\\-_\\.]+|phpt$)/', static function ($matches) { + return self::dim($matches[0]); + }, $path[$last]); + } + return self::optimizeColor(implode(self::dim(DIRECTORY_SEPARATOR), $path)); + } + public static function dim(string $buffer) : string + { + if (trim($buffer) === '') { + return $buffer; + } + return "\x1b[2m{$buffer}\x1b[22m"; + } + public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = \false) : string + { + $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; + return preg_replace_callback('/\\s+/', static function ($matches) use($replaceMap) { + return self::dim(strtr($matches[0], $replaceMap)); + }, $buffer); + } + private static function optimizeColor(string $buffer) : string + { + $patterns = ["/\x1b\\[22m\x1b\\[2m/" => '', "/\x1b\\[([^m]*)m\x1b\\[([1-9][0-9;]*)m/" => "\x1b[\$1;\$2m", "/(\x1b\\[[^m]*m)+(\x1b\\[0m)/" => '$2']; + return preg_replace(array_keys($patterns), array_values($patterns), $buffer); } } $methods + * @var bool */ - public function __construct(array $methods) + private $convertDeprecationsToExceptions; + /** + * @var bool + */ + private $convertErrorsToExceptions; + /** + * @var bool + */ + private $convertNoticesToExceptions; + /** + * @var bool + */ + private $convertWarningsToExceptions; + /** + * @var bool + */ + private $registered = \false; + public static function invokeIgnoringWarnings(callable $callable) + { + set_error_handler(static function ($errorNumber, $errorString) { + if ($errorNumber === E_WARNING) { + return; + } + return \false; + }); + $result = $callable(); + restore_error_handler(); + return $result; + } + public function __construct(bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions) { - parent::__construct(sprintf('Cannot double using a method list that contains duplicates: "%s" (duplicate: "%s")', \implode(', ', $methods), \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods)))))); + $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; + $this->convertErrorsToExceptions = $convertErrorsToExceptions; + $this->convertNoticesToExceptions = $convertNoticesToExceptions; + $this->convertWarningsToExceptions = $convertWarningsToExceptions; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidMethodNameException extends \PHPUnit\Framework\Exception implements \PHPUnit\Framework\MockObject\Exception -{ - public function __construct(string $method) + public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine) : bool + { + /* + * Do not raise an exception when the error suppression operator (@) was used. + * + * @see https://github.com/sebastianbergmann/phpunit/issues/3739 + */ + if (!($errorNumber & error_reporting())) { + return \false; + } + switch ($errorNumber) { + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + if (!$this->convertNoticesToExceptions) { + return \false; + } + throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); + case E_WARNING: + case E_USER_WARNING: + if (!$this->convertWarningsToExceptions) { + return \false; + } + throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); + case E_DEPRECATED: + case E_USER_DEPRECATED: + if (!$this->convertDeprecationsToExceptions) { + return \false; + } + throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); + default: + if (!$this->convertErrorsToExceptions) { + return \false; + } + throw new Error($errorString, $errorNumber, $errorFile, $errorLine); + } + } + public function register() : void { - parent::__construct(sprintf('Cannot double method with invalid name "%s"', $method)); + if ($this->registered) { + return; + } + $oldErrorHandler = set_error_handler($this); + if ($oldErrorHandler !== null) { + restore_error_handler(); + return; + } + $this->registered = \true; + } + public function unregister() : void + { + if (!$this->registered) { + return; + } + restore_error_handler(); } } */ - private $reference; - public function __construct(&$reference) - { - $this->reference =& $reference; - } - public function invoke(Invocation $invocation) - { - return $this->reference; - } - public function toString() : string + private const EXCLUDED_CLASS_NAMES = [ + // composer + ClassLoader::class => 1, + // doctrine/instantiator + Instantiator::class => 1, + // myclabs/deepcopy + DeepCopy::class => 1, + // nikic/php-parser + Parser::class => 1, + // phar-io/manifest + Manifest::class => 1, + // phar-io/version + PharIoVersion::class => 1, + // phpdocumentor/reflection-common + Project::class => 1, + // phpdocumentor/reflection-docblock + DocBlock::class => 1, + // phpdocumentor/type-resolver + Type::class => 1, + // phpspec/prophecy + Prophet::class => 1, + // phpunit/phpunit + TestCase::class => 2, + // phpunit/php-code-coverage + CodeCoverage::class => 1, + // phpunit/php-file-iterator + FileIteratorFacade::class => 1, + // phpunit/php-invoker + Invoker::class => 1, + // phpunit/php-text-template + Template::class => 1, + // phpunit/php-timer + Timer::class => 1, + // sebastian/cli-parser + CliParser::class => 1, + // sebastian/code-unit + CodeUnit::class => 1, + // sebastian/code-unit-reverse-lookup + Wizard::class => 1, + // sebastian/comparator + Comparator::class => 1, + // sebastian/complexity + Calculator::class => 1, + // sebastian/diff + Diff::class => 1, + // sebastian/environment + Runtime::class => 1, + // sebastian/exporter + Exporter::class => 1, + // sebastian/global-state + Snapshot::class => 1, + // sebastian/lines-of-code + Counter::class => 1, + // sebastian/object-enumerator + Enumerator::class => 1, + // sebastian/recursion-context + Context::class => 1, + // sebastian/resource-operations + ResourceOperations::class => 1, + // sebastian/type + TypeName::class => 1, + // sebastian/version + Version::class => 1, + // theseer/tokenizer + Tokenizer::class => 1, + // webmozart/assert + Assert::class => 1, + ]; + /** + * @var string[] + */ + private static $directories = []; + /** + * @var bool + */ + private static $initialized = \false; + public static function addDirectory(string $directory) : void { - $exporter = new Exporter(); - return sprintf('return user-specified reference %s', $exporter->export($this->reference)); + if (!is_dir($directory)) { + throw new \PHPUnit\Util\Exception(sprintf('"%s" is not a directory', $directory)); + } + self::$directories[] = realpath($directory); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use PHPUnit\Framework\MockObject\Invocation; -use PHPUnit\Framework\SelfDescribing; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends SelfDescribing -{ /** - * Fakes the processing of the invocation $invocation by returning a - * specific value. + * @throws Exception * - * @param Invocation $invocation The invocation which was mocked and matched by the current method and argument matchers + * @return string[] */ - public function invoke(Invocation $invocation); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function array_pop; -use function count; -use function is_array; -use PHPUnit\Framework\MockObject\Invocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnValueMap implements \PHPUnit\Framework\MockObject\Stub\Stub -{ + public function getExcludedDirectories() : array + { + $this->initialize(); + return self::$directories; + } /** - * @var array + * @throws Exception */ - private $valueMap; - public function __construct(array $valueMap) + public function isExcluded(string $file) : bool { - $this->valueMap = $valueMap; + if (defined('PHPUnit\\PHPUNIT_TESTSUITE')) { + return \false; + } + $this->initialize(); + foreach (self::$directories as $directory) { + if (strpos($file, $directory) === 0) { + return \true; + } + } + return \false; } - public function invoke(Invocation $invocation) + /** + * @throws Exception + */ + private function initialize() : void { - $parameterCount = count($invocation->getParameters()); - foreach ($this->valueMap as $map) { - if (!is_array($map) || $parameterCount !== count($map) - 1) { + if (self::$initialized) { + return; + } + foreach (self::EXCLUDED_CLASS_NAMES as $className => $parent) { + if (!class_exists($className)) { continue; } - $return = array_pop($map); - if ($invocation->getParameters() === $map) { - return $return; + $directory = (new ReflectionClass($className))->getFileName(); + for ($i = 0; $i < $parent; $i++) { + $directory = dirname($directory); } + self::$directories[] = $directory; } - } - public function toString() : string - { - return 'return value from a map'; + // Hide process isolation workaround on Windows. + if (DIRECTORY_SEPARATOR === '\\') { + // tempnam() prefix is limited to first 3 chars. + // @see https://php.net/manual/en/function.tempnam.php + self::$directories[] = sys_get_temp_dir() . '\\PHP'; + } + self::$initialized = \true; } } callback = $callback; - } - public function invoke(Invocation $invocation) + /** + * Checks if a PHP sourcecode file is readable. The sourcecode file is loaded through the load() method. + * + * As a fallback, PHP looks in the directory of the file executing the stream_resolve_include_path function. + * We do not want to load the Test.php file here, so skip it if it found that. + * PHP prioritizes the include_path setting, so if the current directory is in there, it will first look in the + * current working directory. + * + * @throws Exception + */ + public static function checkAndLoad(string $filename) : string { - return call_user_func_array($this->callback, $invocation->getParameters()); + $includePathFilename = stream_resolve_include_path($filename); + $localFile = __DIR__ . DIRECTORY_SEPARATOR . $filename; + if (!$includePathFilename || $includePathFilename === $localFile || !self::isReadable($includePathFilename)) { + throw new \PHPUnit\Util\Exception(sprintf('Cannot open file "%s".' . "\n", $filename)); + } + self::load($includePathFilename); + return $includePathFilename; } - public function toString() : string + /** + * Loads a PHP sourcefile. + */ + public static function load(string $filename) : void { - if (is_array($this->callback)) { - if (is_object($this->callback[0])) { - $class = get_class($this->callback[0]); - $type = '->'; - } else { - $class = $this->callback[0]; - $type = '::'; + $oldVariableNames = array_keys(get_defined_vars()); + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + include_once $filename; + $newVariables = get_defined_vars(); + foreach (array_diff(array_keys($newVariables), $oldVariableNames) as $variableName) { + if ($variableName !== 'oldVariableNames') { + $GLOBALS[$variableName] = $newVariables[$variableName]; } - return sprintf('return result of user defined callback %s%s%s() with the ' . 'passed arguments', $class, $type, $this->callback[1]); } - return 'return result of user defined callback ' . $this->callback . ' with the passed arguments'; + } + /** + * @see https://github.com/sebastianbergmann/phpunit/pull/2751 + */ + private static function isReadable(string $filename) : bool + { + return @fopen($filename, 'r') !== \false; } } Foo/Bar/Baz.php + * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php */ - public function invoke(Invocation $invocation) + public static function classNameToFilename(string $className) : string { - return $invocation->getObject(); + return str_replace(['_', '\\'], DIRECTORY_SEPARATOR, $className) . '.php'; } - public function toString() : string + public static function createDirectory(string $directory) : bool { - return 'return the current object'; + return !(!is_dir($directory) && !@mkdir($directory, 0777, \true) && !is_dir($directory)); } } exception = $exception; - } /** - * @throws Throwable + * @throws Exception */ - public function invoke(Invocation $invocation) : void + public static function getFilteredStacktrace(Throwable $t) : string { - throw $this->exception; + $filteredStacktrace = ''; + if ($t instanceof SyntheticError) { + $eTrace = $t->getSyntheticTrace(); + $eFile = $t->getSyntheticFile(); + $eLine = $t->getSyntheticLine(); + } elseif ($t instanceof Exception) { + $eTrace = $t->getSerializableTrace(); + $eFile = $t->getFile(); + $eLine = $t->getLine(); + } else { + if ($t->getPrevious()) { + $t = $t->getPrevious(); + } + $eTrace = $t->getTrace(); + $eFile = $t->getFile(); + $eLine = $t->getLine(); + } + if (!self::frameExists($eTrace, $eFile, $eLine)) { + array_unshift($eTrace, ['file' => $eFile, 'line' => $eLine]); + } + $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? \__PHPUNIT_PHAR_ROOT__ : \false; + $excludeList = new \PHPUnit\Util\ExcludeList(); + foreach ($eTrace as $frame) { + if (self::shouldPrintFrame($frame, $prefix, $excludeList)) { + $filteredStacktrace .= sprintf("%s:%s\n", $frame['file'], $frame['line'] ?? '?'); + } + } + return $filteredStacktrace; } - public function toString() : string + private static function shouldPrintFrame(array $frame, $prefix, \PHPUnit\Util\ExcludeList $excludeList) : bool { - $exporter = new Exporter(); - return sprintf('raise user-specified exception %s', $exporter->export($this->exception)); + if (!isset($frame['file'])) { + return \false; + } + $file = $frame['file']; + $fileIsNotPrefixed = $prefix === \false || strpos($file, $prefix) !== 0; + // @see https://github.com/sebastianbergmann/phpunit/issues/4033 + if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { + $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); + } else { + $script = ''; + } + return is_file($file) && self::fileIsExcluded($file, $excludeList) && $fileIsNotPrefixed && $file !== $script; + } + private static function fileIsExcluded(string $file, \PHPUnit\Util\ExcludeList $excludeList) : bool + { + return (empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], \true)) && !$excludeList->isExcluded($file); + } + private static function frameExists(array $trace, string $file, int $line) : bool + { + foreach ($trace as $frame) { + if (isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line) { + return \true; + } + } + return \false; } } isExcluded($file) && is_file($file)) { + $result = 'require_once \'' . $file . "';\n" . $result; + } + } + return $result; + } + public static function getIniSettingsAsString() : string + { + $result = ''; + foreach (ini_get_all(null, \false) as $key => $value) { + $result .= sprintf('@ini_set(%s, %s);' . "\n", self::exportVariable($key), self::exportVariable((string) $value)); + } + return $result; + } + public static function getConstantsAsString() : string + { + $constants = get_defined_constants(\true); + $result = ''; + if (isset($constants['user'])) { + foreach ($constants['user'] as $name => $value) { + $result .= sprintf('if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, self::exportVariable($value)); + } + } + return $result; + } + public static function getGlobalsAsString() : string { - $this->argumentIndex = $argumentIndex; + $result = ''; + foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { + if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { + foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { + if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { + continue; + } + $result .= sprintf('$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, self::exportVariable($GLOBALS[$superGlobalArray][$key])); + } + } + } + $excludeList = self::SUPER_GLOBAL_ARRAYS; + $excludeList[] = 'GLOBALS'; + foreach (array_keys($GLOBALS) as $key) { + if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $excludeList, \true)) { + $result .= sprintf('$GLOBALS[\'%s\'] = %s;' . "\n", $key, self::exportVariable($GLOBALS[$key])); + } + } + return $result; } - public function invoke(Invocation $invocation) + private static function exportVariable($variable) : string { - if (isset($invocation->getParameters()[$this->argumentIndex])) { - return $invocation->getParameters()[$this->argumentIndex]; + if (is_scalar($variable) || $variable === null || is_array($variable) && self::arrayOnlyContainsScalars($variable)) { + return var_export($variable, \true); } + return 'unserialize(' . var_export(serialize($variable), \true) . ')'; } - public function toString() : string + private static function arrayOnlyContainsScalars(array $array) : bool { - return sprintf('return argument #%d', $this->argumentIndex); + $result = \true; + foreach ($array as $element) { + if (is_array($element)) { + $result = self::arrayOnlyContainsScalars($element); + } elseif (!is_scalar($element) && $element !== null) { + $result = \false; + } + if (!$result) { + break; + } + } + return $result; } } value = $value; - } - public function invoke(Invocation $invocation) - { - return $this->value; - } - public function toString() : string - { - $exporter = new Exporter(); - return sprintf('return user-specified value %s', $exporter->export($this->value)); - } } stack = $stack; + $decodedJson = json_decode($json, \false); + if (json_last_error()) { + throw new Exception('Cannot prettify invalid json'); + } + return json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } - public function invoke(Invocation $invocation) + /** + * To allow comparison of JSON strings, first process them into a consistent + * format so that they can be compared as strings. + * + * @return array ($error, $canonicalized_json) The $error parameter is used + * to indicate an error decoding the json. This is used to avoid ambiguity + * with JSON strings consisting entirely of 'null' or 'false'. + */ + public static function canonicalize(string $json) : array { - $this->value = array_shift($this->stack); - if ($this->value instanceof \PHPUnit\Framework\MockObject\Stub\Stub) { - $this->value = $this->value->invoke($invocation); + $decodedJson = json_decode($json); + if (json_last_error()) { + return [\true, null]; } - return $this->value; + self::recursiveSort($decodedJson); + $reencodedJson = json_encode($decodedJson); + return [\false, $reencodedJson]; } - public function toString() : string + /** + * JSON object keys are unordered while PHP array keys are ordered. + * + * Sort all array keys to ensure both the expected and actual values have + * their keys in the same order. + */ + private static function recursiveSort(&$json) : void { - $exporter = new Exporter(); - return sprintf('return user-specified value %s', $exporter->export($this->value)); + if (!is_array($json)) { + // If the object is not empty, change it to an associative array + // so we can sort the keys (and we will still re-encode it + // correctly, since PHP encodes associative arrays as JSON objects.) + // But EMPTY objects MUST remain empty objects. (Otherwise we will + // re-encode it as a JSON array rather than a JSON object.) + // See #2919. + if (is_object($json) && count((array) $json) > 0) { + $json = (array) $json; + } else { + return; + } + } + ksort($json); + foreach ($json as $key => &$value) { + self::recursiveSort($value); + } } } \true, '__DIR__' => \true, '__FILE__' => \true, '__FUNCTION__' => \true, '__LINE__' => \true, '__METHOD__' => \true, '__NAMESPACE__' => \true, '__TRAIT__' => \true, '__clone' => \true, '__halt_compiler' => \true]; + private $document; /** - * @var array + * @var DOMElement */ - private static $cache = []; + private $root; /** - * @var Template[] + * @var bool */ - private static $templates = []; + private $reportRiskyTests = \false; /** - * Returns a mock object for the specified class. - * - * @param null|array $methods - * - * @throws \PHPUnit\Framework\InvalidArgumentException - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws DuplicateMethodException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownTypeException + * @var DOMElement[] */ - public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \true, bool $callOriginalMethods = \false, object $proxyTarget = null, bool $allowMockingUnknownTypes = \true, bool $returnValueGeneration = \true) : \PHPUnit\Framework\MockObject\MockObject + private $testSuites = []; + /** + * @var int[] + */ + private $testSuiteTests = [0]; + /** + * @var int[] + */ + private $testSuiteAssertions = [0]; + /** + * @var int[] + */ + private $testSuiteErrors = [0]; + /** + * @var int[] + */ + private $testSuiteWarnings = [0]; + /** + * @var int[] + */ + private $testSuiteFailures = [0]; + /** + * @var int[] + */ + private $testSuiteSkipped = [0]; + /** + * @var int[] + */ + private $testSuiteTimes = [0]; + /** + * @var int + */ + private $testSuiteLevel = 0; + /** + * @var DOMElement + */ + private $currentTestCase; + /** + * @param null|mixed $out + */ + public function __construct($out = null, bool $reportRiskyTests = \false) { - if (!is_array($methods) && null !== $methods) { - throw InvalidArgumentException::create(2, 'array'); - } - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - if (!$allowMockingUnknownTypes && !class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { - throw new \PHPUnit\Framework\MockObject\UnknownTypeException($type); - } - if (null !== $methods) { - foreach ($methods as $method) { - if (!preg_match('~[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*~', (string) $method)) { - throw new \PHPUnit\Framework\MockObject\InvalidMethodNameException((string) $method); - } - } - if ($methods !== array_unique($methods)) { - throw new \PHPUnit\Framework\MockObject\DuplicateMethodException($methods); - } - } - if ($mockClassName !== '' && class_exists($mockClassName, \false)) { - try { - $reflector = new ReflectionClass($mockClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if (!$reflector->implementsInterface(\PHPUnit\Framework\MockObject\MockObject::class)) { - throw new \PHPUnit\Framework\MockObject\ClassAlreadyExistsException($mockClassName); - } - } - if (!$callOriginalConstructor && $callOriginalMethods) { - throw new \PHPUnit\Framework\MockObject\OriginalConstructorInvocationRequiredException(); - } - $mock = $this->generate($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); - return $this->getObject($mock, $type, $callOriginalConstructor, $callAutoload, $arguments, $callOriginalMethods, $proxyTarget, $returnValueGeneration); + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = \true; + $this->root = $this->document->createElement('testsuites'); + $this->document->appendChild($this->root); + parent::__construct($out); + $this->reportRiskyTests = $reportRiskyTests; } /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. - * - * Concrete methods to mock can be specified with the $mockedMethods parameter. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - * - * @throws \PHPUnit\Framework\InvalidArgumentException - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws DuplicateMethodException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownClassException - * @throws UnknownTypeException + * Flush buffer and close output. */ - public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = null, bool $cloneArguments = \true) : \PHPUnit\Framework\MockObject\MockObject + public function flush() : void { - if (class_exists($originalClassName, $callAutoload) || interface_exists($originalClassName, $callAutoload)) { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $methods = $mockedMethods; - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], \true)) { - $methods[] = $method->getName(); - } - } - if (empty($methods)) { - $methods = null; - } - return $this->getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments); - } - throw new \PHPUnit\Framework\MockObject\UnknownClassException($originalClassName); + $this->write($this->getXML()); + parent::flush(); } /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @psalm-param trait-string $traitName - * - * @throws \PHPUnit\Framework\InvalidArgumentException - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws DuplicateMethodException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownClassException - * @throws UnknownTraitException - * @throws UnknownTypeException + * An error occurred. */ - public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = null, bool $cloneArguments = \true) : \PHPUnit\Framework\MockObject\MockObject + public function addError(Test $test, Throwable $t, float $time) : void { - if (!trait_exists($traitName, $callAutoload)) { - throw new \PHPUnit\Framework\MockObject\UnknownTraitException($traitName); - } - $className = $this->generateClassName($traitName, '', 'Trait_'); - $classTemplate = $this->getTemplate('trait_class.tpl'); - $classTemplate->setVar(['prologue' => 'abstract ', 'class_name' => $className['className'], 'trait_name' => $traitName]); - $mockTrait = new \PHPUnit\Framework\MockObject\MockTrait($classTemplate->render(), $className['className']); - $mockTrait->generate(); - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + $this->doAddFault($test, $t, 'error'); + $this->testSuiteErrors[$this->testSuiteLevel]++; } /** - * Returns an object for the specified trait. - * - * @psalm-param trait-string $traitName - * - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownTraitException + * A warning occurred. */ - public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = \true, bool $callOriginalConstructor = \false, array $arguments = []) : object + public function addWarning(Test $test, Warning $e, float $time) : void { - if (!trait_exists($traitName, $callAutoload)) { - throw new \PHPUnit\Framework\MockObject\UnknownTraitException($traitName); - } - $className = $this->generateClassName($traitName, $traitClassName, 'Trait_'); - $classTemplate = $this->getTemplate('trait_class.tpl'); - $classTemplate->setVar(['prologue' => '', 'class_name' => $className['className'], 'trait_name' => $traitName]); - return $this->getObject(new \PHPUnit\Framework\MockObject\MockTrait($classTemplate->render(), $className['className']), '', $callOriginalConstructor, $callAutoload, $arguments); + $this->doAddFault($test, $e, 'warning'); + $this->testSuiteWarnings[$this->testSuiteLevel]++; } /** - * @throws ClassIsFinalException - * @throws ReflectionException - * @throws RuntimeException + * A failure occurred. */ - public function generate(string $type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \true, bool $callOriginalMethods = \false) : \PHPUnit\Framework\MockObject\MockClass + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void { - if ($mockClassName !== '') { - return $this->generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); - } - $key = md5($type . serialize($methods) . serialize($callOriginalClone) . serialize($cloneArguments) . serialize($callOriginalMethods)); - if (!isset(self::$cache[$key])) { - self::$cache[$key] = $this->generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods); - } - return self::$cache[$key]; + $this->doAddFault($test, $e, 'failure'); + $this->testSuiteFailures[$this->testSuiteLevel]++; } /** - * @throws RuntimeException - * @throws SoapExtensionNotAvailableException + * Incomplete test. */ - public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []) : string + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void { - if (!extension_loaded('soap')) { - throw new \PHPUnit\Framework\MockObject\SoapExtensionNotAvailableException(); - } - $options = array_merge($options, ['cache_wsdl' => \WSDL_CACHE_NONE]); - try { - $client = new SoapClient($wsdlFile, $options); - $_methods = array_unique($client->__getFunctions()); - unset($client); - } catch (SoapFault $e) { - throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + $this->doAddSkipped(); + } + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time) : void + { + if (!$this->reportRiskyTests) { + return; } - sort($_methods); - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - foreach ($_methods as $method) { - preg_match_all('/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*\\(/', $method, $matches, \PREG_OFFSET_CAPTURE); - $lastFunction = array_pop($matches[0]); - $nameStart = $lastFunction[1]; - $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; - $name = str_replace('(', '', $lastFunction[0]); - if (empty($methods) || in_array($name, $methods, \true)) { - $args = explode(',', str_replace(')', '', substr($method, $nameEnd + 1))); - foreach (range(0, count($args) - 1) as $i) { - $parameterStart = strpos($args[$i], '$'); - if (!$parameterStart) { - continue; - } - $args[$i] = substr($args[$i], $parameterStart); - } - $methodTemplate->setVar(['method_name' => $name, 'arguments' => implode(', ', $args)]); - $methodsBuffer .= $methodTemplate->render(); + $this->doAddFault($test, $t, 'error'); + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time) : void + { + $this->doAddSkipped(); + } + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite) : void + { + $testSuite = $this->document->createElement('testsuite'); + $testSuite->setAttribute('name', $suite->getName()); + if (class_exists($suite->getName(), \false)) { + try { + $class = new ReflectionClass($suite->getName()); + $testSuite->setAttribute('file', $class->getFileName()); + } catch (ReflectionException $e) { } } - $optionsBuffer = '['; - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - $optionsBuffer .= ']'; - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - if (strpos($className, '\\') !== \false) { - $parts = explode('\\', $className); - $className = array_pop($parts); - $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; + if ($this->testSuiteLevel > 0) { + $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); + } else { + $this->root->appendChild($testSuite); } - $classTemplate->setVar(['namespace' => $namespace, 'class_name' => $className, 'wsdl' => $wsdlFile, 'options' => $optionsBuffer, 'methods' => $methodsBuffer]); - return $classTemplate->render(); + $this->testSuiteLevel++; + $this->testSuites[$this->testSuiteLevel] = $testSuite; + $this->testSuiteTests[$this->testSuiteLevel] = 0; + $this->testSuiteAssertions[$this->testSuiteLevel] = 0; + $this->testSuiteErrors[$this->testSuiteLevel] = 0; + $this->testSuiteWarnings[$this->testSuiteLevel] = 0; + $this->testSuiteFailures[$this->testSuiteLevel] = 0; + $this->testSuiteSkipped[$this->testSuiteLevel] = 0; + $this->testSuiteTimes[$this->testSuiteLevel] = 0; } /** - * @throws ReflectionException - * - * @return string[] + * A testsuite ended. */ - public function getClassMethods(string $className) : array + public function endTestSuite(TestSuite $suite) : void { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $methods = []; - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } + $this->testSuites[$this->testSuiteLevel]->setAttribute('tests', (string) $this->testSuiteTests[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('assertions', (string) $this->testSuiteAssertions[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('errors', (string) $this->testSuiteErrors[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('warnings', (string) $this->testSuiteWarnings[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('failures', (string) $this->testSuiteFailures[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('skipped', (string) $this->testSuiteSkipped[$this->testSuiteLevel]); + $this->testSuites[$this->testSuiteLevel]->setAttribute('time', sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])); + if ($this->testSuiteLevel > 1) { + $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; + $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; + $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; + $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; + $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; + $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; + $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } - return $methods; + $this->testSuiteLevel--; } /** - * @throws ReflectionException - * - * @return MockMethod[] + * A test started. */ - public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments) : array + public function startTest(Test $test) : void { + $usesDataprovider = \false; + if (method_exists($test, 'usesDataProvider')) { + $usesDataprovider = $test->usesDataProvider(); + } + $testCase = $this->document->createElement('testcase'); + $testCase->setAttribute('name', $test->getName()); try { - $class = new ReflectionClass($className); + $class = new ReflectionClass($test); // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } // @codeCoverageIgnoreEnd - $methods = []; - foreach ($class->getMethods() as $method) { - if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { - $methods[] = \PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); + $methodName = $test->getName(!$usesDataprovider); + if ($class->hasMethod($methodName)) { + try { + $method = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } + // @codeCoverageIgnoreEnd + $testCase->setAttribute('class', $class->getName()); + $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); + $testCase->setAttribute('file', $class->getFileName()); + $testCase->setAttribute('line', (string) $method->getStartLine()); } - return $methods; + $this->currentTestCase = $testCase; } /** - * @throws ReflectionException - * - * @return MockMethod[] + * A test ended. */ - public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments) : array + public function endTest(Test $test, float $time) : void { - try { - $class = new ReflectionClass($interfaceName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + $numAssertions = 0; + if (method_exists($test, 'getNumAssertions')) { + $numAssertions = $test->getNumAssertions(); } - // @codeCoverageIgnoreEnd - $methods = []; - foreach ($class->getMethods() as $method) { - $methods[] = \PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, \false, $cloneArguments); + $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; + $this->currentTestCase->setAttribute('assertions', (string) $numAssertions); + $this->currentTestCase->setAttribute('time', sprintf('%F', $time)); + $this->testSuites[$this->testSuiteLevel]->appendChild($this->currentTestCase); + $this->testSuiteTests[$this->testSuiteLevel]++; + $this->testSuiteTimes[$this->testSuiteLevel] += $time; + $testOutput = ''; + if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { + $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; } - return $methods; + if (!empty($testOutput)) { + $systemOut = $this->document->createElement('system-out', Xml::prepareString($testOutput)); + $this->currentTestCase->appendChild($systemOut); + } + $this->currentTestCase = null; } /** - * @psalm-param class-string $interfaceName - * - * @throws ReflectionException - * - * @return ReflectionMethod[] + * Returns the XML as a string. */ - private function userDefinedInterfaceMethods(string $interfaceName) : array + public function getXML() : string { - try { - // @codeCoverageIgnoreStart - $interface = new ReflectionClass($interfaceName); - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $methods = []; - foreach ($interface->getMethods() as $method) { - if (!$method->isUserDefined()) { - continue; - } - $methods[] = $method; - } - return $methods; + return $this->document->saveXML(); } - /** - * @throws ReflectionException - * @throws RuntimeException - */ - private function getObject(\PHPUnit\Framework\MockObject\MockType $mockClass, $type = '', bool $callOriginalConstructor = \false, bool $callAutoload = \false, array $arguments = [], bool $callOriginalMethods = \false, object $proxyTarget = null, bool $returnValueGeneration = \true) + private function doAddFault(Test $test, Throwable $t, string $type) : void { - $className = $mockClass->generate(); - if ($callOriginalConstructor) { - if (count($arguments) === 0) { - $object = new $className(); - } else { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $object = $class->newInstanceArgs($arguments); - } + if ($this->currentTestCase === null) { + return; + } + if ($test instanceof SelfDescribing) { + $buffer = $test->toString() . "\n"; } else { - try { - $object = (new Instantiator())->instantiate($className); - } catch (InstantiatorException $e) { - throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage()); - } + $buffer = ''; } - if ($callOriginalMethods) { - if (!is_object($proxyTarget)) { - if (count($arguments) === 0) { - $proxyTarget = new $type(); - } else { - try { - $class = new ReflectionClass($type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - $object->__phpunit_setOriginalObject($proxyTarget); + $buffer .= trim(TestFailure::exceptionToString($t) . "\n" . Filter::getFilteredStacktrace($t)); + $fault = $this->document->createElement($type, Xml::prepareString($buffer)); + if ($t instanceof ExceptionWrapper) { + $fault->setAttribute('type', $t->getClassName()); + } else { + $fault->setAttribute('type', get_class($t)); } - if ($object instanceof \PHPUnit\Framework\MockObject\MockObject) { - $object->__phpunit_setReturnValueGeneration($returnValueGeneration); + $this->currentTestCase->appendChild($fault); + } + private function doAddSkipped() : void + { + if ($this->currentTestCase === null) { + return; } - return $object; + $skipped = $this->document->createElement('skipped'); + $this->currentTestCase->appendChild($skipped); + $this->testSuiteSkipped[$this->testSuiteLevel]++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Log; + +use function class_exists; +use function count; +use function explode; +use function get_class; +use function getmypid; +use function ini_get; +use function is_bool; +use function is_scalar; +use function method_exists; +use function print_r; +use function round; +use function str_replace; +use function stripos; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExceptionWrapper; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\Util\Exception; +use PHPUnit\Util\Filter; +use ReflectionClass; +use ReflectionException; +use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TeamCity extends DefaultResultPrinter +{ /** - * @throws ClassIsFinalException - * @throws ReflectionException - * @throws RuntimeException + * @var bool */ - private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods) : \PHPUnit\Framework\MockObject\MockClass + private $isSummaryTestCountPrinted = \false; + /** + * @var string + */ + private $startedTestName; + /** + * @var false|int + */ + private $flowId; + public function printResult(TestResult $result) : void { - $classTemplate = $this->getTemplate('mocked_class.tpl'); - $additionalInterfaces = []; - $mockedCloneMethod = \false; - $unmockedCloneMethod = \false; - $isClass = \false; - $isInterface = \false; - $class = null; - $mockMethods = new \PHPUnit\Framework\MockObject\MockMethodSet(); - $_mockClassName = $this->generateClassName($type, $mockClassName, 'Mock_'); - if (class_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isClass = \true; - } elseif (interface_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isInterface = \true; - } - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; - if (!empty($_mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $_mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; - $epilogue = "\n\n}"; - } - $mockedCloneMethod = \true; - } else { - try { - $class = new ReflectionClass($_mockClassName['fullClassName']); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($class->isFinal()) { - throw new \PHPUnit\Framework\MockObject\ClassIsFinalException($_mockClassName['fullClassName']); - } - // @see https://github.com/sebastianbergmann/phpunit/issues/2995 - if ($isInterface && $class->implementsInterface(Throwable::class)) { - $actualClassName = Exception::class; - $additionalInterfaces[] = $class->getName(); - $isInterface = \false; - try { - $class = new ReflectionClass($actualClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { - $methodName = $method->getName(); - if ($class->hasMethod($methodName)) { - try { - $classMethod = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if (!$this->canMockMethod($classMethod)) { - continue; - } - } - $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments)); - } - $_mockClassName = $this->generateClassName($actualClassName, $_mockClassName['className'], 'Mock_'); - } - // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && !$class->implementsInterface(Iterator::class) && !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - $mockMethods->addMethods(...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments)); - } - if ($class->hasMethod('__clone')) { - try { - $cloneMethod = $class->getMethod('__clone'); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + $this->printHeader($result); + $this->printFooter($result); + } + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time) : void + { + $this->printEvent('testFailed', ['name' => $test->getName(), 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time)]); + } + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time) : void + { + $this->write(self::getMessage($e) . \PHP_EOL); + } + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void + { + $parameters = ['name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), 'duration' => self::toMilliseconds($time)]; + if ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + if ($comparisonFailure instanceof ComparisonFailure) { + $expectedString = $comparisonFailure->getExpectedAsString(); + if ($expectedString === null || empty($expectedString)) { + $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); } - // @codeCoverageIgnoreEnd - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $unmockedCloneMethod = \true; - } else { - $mockedCloneMethod = \true; - } + $actualString = $comparisonFailure->getActualAsString(); + if ($actualString === null || empty($actualString)) { + $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); } - } else { - $mockedCloneMethod = \true; - } - } - if ($isClass && $explicitMethods === []) { - $mockMethods->addMethods(...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments)); - } - if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { - $mockMethods->addMethods(...$this->mockInterfaceMethods($_mockClassName['fullClassName'], $cloneArguments)); - } - if (is_array($explicitMethods)) { - foreach ($explicitMethods as $methodName) { - if ($class !== null && $class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($this->canMockMethod($method)) { - $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments)); - } - } else { - $mockMethods->addMethods(\PHPUnit\Framework\MockObject\MockMethod::fromName($_mockClassName['fullClassName'], $methodName, $cloneArguments)); + if ($actualString !== null && $expectedString !== null) { + $parameters['type'] = 'comparisonFailure'; + $parameters['actual'] = $actualString; + $parameters['expected'] = $expectedString; } } } - $mockedMethods = ''; - $configurable = []; - foreach ($mockMethods->asArray() as $mockMethod) { - $mockedMethods .= $mockMethod->generateCode(); - $configurable[] = new \PHPUnit\Framework\MockObject\ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); - } - $method = ''; - if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { - $method = \PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\Method;'; - } - $cloneTrait = ''; - if ($mockedCloneMethod) { - $cloneTrait = \PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\MockedCloneMethod;'; - } - if ($unmockedCloneMethod) { - $cloneTrait = \PHP_EOL . ' use \\PHPUnit\\Framework\\MockObject\\UnmockedCloneMethod;'; - } - $classTemplate->setVar(['prologue' => $prologue ?? '', 'epilogue' => $epilogue ?? '', 'class_declaration' => $this->generateMockClassDeclaration($_mockClassName, $isInterface, $additionalInterfaces), 'clone' => $cloneTrait, 'mock_class_name' => $_mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $method]); - return new \PHPUnit\Framework\MockObject\MockClass($classTemplate->render(), $_mockClassName['className'], $configurable); + $this->printEvent('testFailed', $parameters); } - private function generateClassName(string $type, string $className, string $prefix) : array + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void { - if ($type[0] === '\\') { - $type = substr($type, 1); - } - $classNameParts = explode('\\', $type); - if (count($classNameParts) > 1) { - $type = array_pop($classNameParts); - $namespaceName = implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; + $this->printIgnoredTest($test->getName(), $t, $time); + } + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time) : void + { + $this->addError($test, $t, $time); + } + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time) : void + { + $testName = $test->getName(); + if ($this->startedTestName !== $testName) { + $this->startTest($test); + $this->printIgnoredTest($testName, $t, $time); + $this->endTest($test, $time); } else { - $namespaceName = ''; - $fullClassName = $type; - } - if ($className === '') { - do { - $className = $prefix . $type . '_' . substr(md5((string) mt_rand()), 0, 8); - } while (class_exists($className, \false)); + $this->printIgnoredTest($testName, $t, $time); } - return ['className' => $className, 'originalClassName' => $type, 'fullClassName' => $fullClassName, 'namespaceName' => $namespaceName]; } - private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []) : string + public function printIgnoredTest(string $testName, Throwable $t, float $time) : void { - $buffer = 'class '; - $additionalInterfaces[] = \PHPUnit\Framework\MockObject\MockObject::class; - $interfaces = implode(', ', $additionalInterfaces); - if ($isInterface) { - $buffer .= sprintf('%s implements %s', $mockClassName['className'], $interfaces); - if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, \true)) { - $buffer .= ', '; - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - $buffer .= $mockClassName['originalClassName']; - } + $this->printEvent('testIgnored', ['name' => $testName, 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time)]); + } + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite) : void + { + if (stripos(ini_get('disable_functions'), 'getmypid') === \false) { + $this->flowId = getmypid(); } else { - $buffer .= sprintf('%s extends %s%s implements %s', $mockClassName['className'], !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', $mockClassName['originalClassName'], $interfaces); + $this->flowId = \false; + } + if (!$this->isSummaryTestCountPrinted) { + $this->isSummaryTestCountPrinted = \true; + $this->printEvent('testCount', ['count' => count($suite)]); + } + $suiteName = $suite->getName(); + if (empty($suiteName)) { + return; + } + $parameters = ['name' => $suiteName]; + if (class_exists($suiteName, \false)) { + $fileName = self::getFileName($suiteName); + $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; + } else { + $split = explode('::', $suiteName); + if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { + $fileName = self::getFileName($split[0]); + $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; + $parameters['name'] = $split[1]; + } } - return $buffer; - } - private function canMockMethod(ReflectionMethod $method) : bool - { - return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameExcluded($method->getName())); + $this->printEvent('testSuiteStarted', $parameters); } - private function isMethodNameExcluded(string $name) : bool + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite) : void { - return isset(self::EXCLUDED_METHOD_NAMES[$name]); + $suiteName = $suite->getName(); + if (empty($suiteName)) { + return; + } + $parameters = ['name' => $suiteName]; + if (!class_exists($suiteName, \false)) { + $split = explode('::', $suiteName); + if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { + $parameters['name'] = $split[1]; + } + } + $this->printEvent('testSuiteFinished', $parameters); } /** - * @throws RuntimeException + * A test started. */ - private function getTemplate(string $template) : Template + public function startTest(Test $test) : void { - $filename = __DIR__ . \DIRECTORY_SEPARATOR . 'Generator' . \DIRECTORY_SEPARATOR . $template; - if (!isset(self::$templates[$filename])) { - try { - self::$templates[$filename] = new Template($filename); - } catch (TemplateException $e) { - throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - } + $testName = $test->getName(); + $this->startedTestName = $testName; + $params = ['name' => $testName]; + if ($test instanceof TestCase) { + $className = get_class($test); + $fileName = self::getFileName($className); + $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; } - return self::$templates[$filename]; + $this->printEvent('testStarted', $params); } /** - * @see https://github.com/sebastianbergmann/phpunit/issues/4139#issuecomment-605409765 + * A test ended. */ - private function isConstructor(ReflectionMethod $method) : bool + public function endTest(Test $test, float $time) : void { - $methodName = strtolower($method->getName()); - if ($methodName === '__construct') { - return \true; + parent::endTest($test, $time); + $this->printEvent('testFinished', ['name' => $test->getName(), 'duration' => self::toMilliseconds($time)]); + } + protected function writeProgress(string $progress) : void + { + } + private function printEvent(string $eventName, array $params = []) : void + { + $this->write("\n##teamcity[{$eventName}"); + if ($this->flowId) { + $params['flowId'] = $this->flowId; } - if (\PHP_MAJOR_VERSION >= 8) { - return \false; + foreach ($params as $key => $value) { + $escapedValue = self::escapeValue((string) $value); + $this->write(" {$key}='{$escapedValue}'"); } - $className = strtolower($method->getDeclaringClass()->getName()); - return $methodName === $className; + $this->write("]\n"); } -} - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + private static function getMessage(Throwable $t) : string + { + $message = ''; + if ($t instanceof ExceptionWrapper) { + if ($t->getClassName() !== '') { + $message .= $t->getClassName(); } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - } -declare(strict_types=1); - -{prologue}{class_declaration} -{ - use \PHPUnit\Framework\MockObject\Api;{method}{clone} -{mocked_methods}}{epilogue} - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + if ($message !== '' && $t->getMessage() !== '') { + $message .= ' : '; } } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - - return $__phpunit_result; + return $message . $t->getMessage(); } - - public function {method_name}({arguments}) + private static function getDetails(Throwable $t) : string { + $stackTrace = Filter::getFilteredStacktrace($t); + $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); + while ($previous) { + $stackTrace .= "\nCaused by\n" . TestFailure::exceptionToString($previous) . "\n" . Filter::getFilteredStacktrace($previous); + $previous = $previous instanceof ExceptionWrapper ? $previous->getPreviousWrapped() : $previous->getPrevious(); + } + return ' ' . str_replace("\n", "\n ", $stackTrace); } -declare(strict_types=1); - -{namespace}class {class_name} extends \SoapClient -{ - public function __construct($wsdl, array $options) + private static function getPrimitiveValueAsString($value) : ?string { - parent::__construct('{wsdl}', $options); + if ($value === null) { + return 'null'; + } + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + if (is_scalar($value)) { + return print_r($value, \true); + } + return null; } -{methods}} - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + private static function escapeValue(string $text) : string { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); + return str_replace(['|', "'", "\n", "\r", ']', '['], ['||', "|'", '|n', '|r', '|]', '|['], $text); } - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + /** + * @param string $className + */ + private static function getFileName($className) : string { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } + try { + return (new ReflectionClass($className))->getFileName(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); + // @codeCoverageIgnoreEnd } - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + /** + * @param float $time microseconds + */ + private static function toMilliseconds(float $time) : int { - throw new \PHPUnit\Framework\MockObject\BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object'); + return (int) round($time * 1000); } -declare(strict_types=1); - -{prologue}class {class_name} -{ - use {trait_name}; } - - @trigger_error({deprecation}, E_USER_DEPRECATED); */ - private $proxyTarget; + protected $env = []; /** - * @var bool + * @var int */ - private $allowMockingUnknownTypes = \true; + protected $timeout = 0; + public static function factory() : self + { + if (DIRECTORY_SEPARATOR === '\\') { + return new \PHPUnit\Util\PHP\WindowsPhpProcess(); + } + return new \PHPUnit\Util\PHP\DefaultPhpProcess(); + } + public function __construct() + { + $this->runtime = new Runtime(); + } /** - * @var bool + * Defines if should use STDERR redirection or not. + * + * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT. */ - private $returnValueGeneration = \true; + public function setUseStderrRedirection(bool $stderrRedirection) : void + { + $this->stderrRedirection = $stderrRedirection; + } /** - * @var Generator + * Returns TRUE if uses STDERR redirection or FALSE if not. */ - private $generator; + public function useStderrRedirection() : bool + { + return $this->stderrRedirection; + } /** - * @param string|string[] $type - * - * @psalm-param class-string|string|string[] $type + * Sets the input string to be sent via STDIN. */ - public function __construct(TestCase $testCase, $type) + public function setStdin(string $stdin) : void { - $this->testCase = $testCase; - $this->type = $type; - $this->generator = new \PHPUnit\Framework\MockObject\Generator(); + $this->stdin = $stdin; } /** - * Creates a mock object using a fluent interface. - * - * @throws \PHPUnit\Framework\InvalidArgumentException - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws DuplicateMethodException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownTypeException - * - * @psalm-return MockObject&MockedType + * Returns the input string to be sent via STDIN. */ - public function getMock() : \PHPUnit\Framework\MockObject\MockObject + public function getStdin() : string { - $object = $this->generator->getMock($this->type, !$this->emptyMethodsArray ? $this->methods : null, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->cloneArguments, $this->callOriginalMethods, $this->proxyTarget, $this->allowMockingUnknownTypes, $this->returnValueGeneration); - $this->testCase->registerMockObject($object); - return $object; + return $this->stdin; } /** - * Creates a mock object for an abstract class using a fluent interface. - * - * @psalm-return MockObject&MockedType - * - * @throws \PHPUnit\Framework\Exception - * @throws ReflectionException - * @throws RuntimeException + * Sets the string of arguments to pass to the php job. */ - public function getMockForAbstractClass() : \PHPUnit\Framework\MockObject\MockObject + public function setArgs(string $args) : void { - $object = $this->generator->getMockForAbstractClass($this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments); - $this->testCase->registerMockObject($object); - return $object; + $this->args = $args; } /** - * Creates a mock object for a trait using a fluent interface. - * - * @psalm-return MockObject&MockedType - * - * @throws \PHPUnit\Framework\Exception - * @throws ReflectionException - * @throws RuntimeException + * Returns the string of arguments to pass to the php job. */ - public function getMockForTrait() : \PHPUnit\Framework\MockObject\MockObject + public function getArgs() : string { - $object = $this->generator->getMockForTrait($this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments); - $this->testCase->registerMockObject($object); - return $object; + return $this->args; } /** - * Specifies the subset of methods to mock. Default is to mock none of them. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 + * Sets the array of environment variables to start the child process with. * - * @return $this + * @param array $env */ - public function setMethods(?array $methods = null) : self + public function setEnv(array $env) : void { - if ($methods === null) { - $this->methods = $methods; - } else { - $this->methods = array_merge($this->methods ?? [], $methods); - } - return $this; + $this->env = $env; } /** - * Specifies the subset of methods to mock, requiring each to exist in the class. - * - * @param string[] $methods - * - * @throws CannotUseOnlyMethodsException - * @throws ReflectionException - * - * @return $this + * Returns the array of environment variables to start the child process with. */ - public function onlyMethods(array $methods) : self + public function getEnv() : array { - if (empty($methods)) { - $this->emptyMethodsArray = \true; - return $this; - } - try { - $reflector = new ReflectionClass($this->type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - foreach ($methods as $method) { - if (!$reflector->hasMethod($method)) { - throw new \PHPUnit\Framework\MockObject\CannotUseOnlyMethodsException($this->type, $method); - } - } - $this->methods = array_merge($this->methods ?? [], $methods); - return $this; + return $this->env; } /** - * Specifies methods that don't exist in the class which you want to mock. - * - * @param string[] $methods - * - * @throws CannotUseAddMethodsException - * @throws ReflectionException - * @throws RuntimeException - * - * @return $this + * Sets the amount of seconds to wait before timing out. */ - public function addMethods(array $methods) : self + public function setTimeout(int $timeout) : void { - if (empty($methods)) { - $this->emptyMethodsArray = \true; - return $this; - } - try { - $reflector = new ReflectionClass($this->type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - foreach ($methods as $method) { - if ($reflector->hasMethod($method)) { - throw new \PHPUnit\Framework\MockObject\CannotUseAddMethodsException($this->type, $method); - } - } - $this->methods = array_merge($this->methods ?? [], $methods); - return $this; + $this->timeout = $timeout; } /** - * Specifies the subset of methods to not mock. Default is to mock all of them. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 - * - * @throws ReflectionException + * Returns the amount of seconds to wait before timing out. */ - public function setMethodsExcept(array $methods = []) : self + public function getTimeout() : int { - return $this->setMethods(array_diff($this->generator->getClassMethods($this->type), $methods)); + return $this->timeout; } /** - * Specifies the arguments for the constructor. + * Runs a single test in a separate PHP process. * - * @return $this + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function setConstructorArgs(array $args) : self + public function runTestJob(string $job, Test $test, TestResult $result) : void { - $this->constructorArgs = $args; - return $this; + $result->startTest($test); + $_result = $this->runJob($job); + $this->processChildResult($test, $result, $_result['stdout'], $_result['stderr']); } /** - * Specifies the name for the mock class. - * - * @return $this + * Returns the command based into the configurations. */ - public function setMockClassName(string $name) : self + public function getCommand(array $settings, string $file = null) : string { - $this->mockClassName = $name; - return $this; + $command = $this->runtime->getBinary(); + if ($this->runtime->hasPCOV()) { + $settings = array_merge($settings, $this->runtime->getCurrentSettings(array_keys(ini_get_all('pcov')))); + } elseif ($this->runtime->hasXdebug()) { + $settings = array_merge($settings, $this->runtime->getCurrentSettings(array_keys(ini_get_all('xdebug')))); + } + $command .= $this->settingsToParameters($settings); + if (PHP_SAPI === 'phpdbg') { + $command .= ' -qrr'; + if (!$file) { + $command .= 's='; + } + } + if ($file) { + $command .= ' ' . escapeshellarg($file); + } + if ($this->args) { + if (!$file) { + $command .= ' --'; + } + $command .= ' ' . $this->args; + } + if ($this->stderrRedirection) { + $command .= ' 2>&1'; + } + return $command; } /** - * Disables the invocation of the original constructor. - * - * @return $this + * Runs a single job (PHP code) using a separate PHP process. */ - public function disableOriginalConstructor() : self + public abstract function runJob(string $job, array $settings = []) : array; + protected function settingsToParameters(array $settings) : string { - $this->originalConstructor = \false; - return $this; + $buffer = ''; + foreach ($settings as $setting) { + $buffer .= ' -d ' . escapeshellarg($setting); + } + return $buffer; } /** - * Enables the invocation of the original constructor. + * Processes the TestResult object from an isolated process. * - * @return $this + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function enableOriginalConstructor() : self + private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr) : void { - $this->originalConstructor = \true; - return $this; + $time = 0; + if (!empty($stderr)) { + $result->addError($test, new Exception(trim($stderr)), $time); + } else { + set_error_handler( + /** + * @throws ErrorException + */ + static function ($errno, $errstr, $errfile, $errline) : void { + throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); + } + ); + try { + if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { + $stdout = substr($stdout, 19); + } + $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); + restore_error_handler(); + if ($childResult === \false) { + $result->addFailure($test, new AssertionFailedError('Test was run in child process and ended unexpectedly'), $time); + } + } catch (ErrorException $e) { + restore_error_handler(); + $childResult = \false; + $result->addError($test, new Exception(trim($stdout), 0, $e), $time); + } + if ($childResult !== \false) { + if (!empty($childResult['output'])) { + $output = $childResult['output']; + } + /* @var TestCase $test */ + $test->setResult($childResult['testResult']); + $test->addToAssertionCount($childResult['numAssertions']); + $childResult = $childResult['result']; + assert($childResult instanceof TestResult); + if ($result->getCollectCodeCoverageInformation()) { + $result->getCodeCoverage()->merge($childResult->getCodeCoverage()); + } + $time = $childResult->time(); + $notImplemented = $childResult->notImplemented(); + $risky = $childResult->risky(); + $skipped = $childResult->skipped(); + $errors = $childResult->errors(); + $warnings = $childResult->warnings(); + $failures = $childResult->failures(); + if (!empty($notImplemented)) { + $result->addError($test, $this->getException($notImplemented[0]), $time); + } elseif (!empty($risky)) { + $result->addError($test, $this->getException($risky[0]), $time); + } elseif (!empty($skipped)) { + $result->addError($test, $this->getException($skipped[0]), $time); + } elseif (!empty($errors)) { + $result->addError($test, $this->getException($errors[0]), $time); + } elseif (!empty($warnings)) { + $result->addWarning($test, $this->getException($warnings[0]), $time); + } elseif (!empty($failures)) { + $result->addFailure($test, $this->getException($failures[0]), $time); + } + } + } + $result->endTest($test, $time); + if (!empty($output)) { + print $output; + } } /** - * Disables the invocation of the original clone constructor. + * Gets the thrown exception from a PHPUnit\Framework\TestFailure. * - * @return $this + * @see https://github.com/sebastianbergmann/phpunit/issues/74 */ - public function disableOriginalClone() : self + private function getException(TestFailure $error) : Exception { - $this->originalClone = \false; - return $this; + $exception = $error->thrownException(); + if ($exception instanceof __PHP_Incomplete_Class) { + $exceptionArray = []; + foreach ((array) $exception as $key => $value) { + $key = substr($key, strrpos($key, "\x00") + 1); + $exceptionArray[$key] = $value; + } + $exception = new SyntheticError(sprintf('%s: %s', $exceptionArray['_PHP_Incomplete_Class_Name'], $exceptionArray['message']), $exceptionArray['code'], $exceptionArray['file'], $exceptionArray['line'], $exceptionArray['trace']); + } + return $exception; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use function array_merge; +use function fclose; +use function file_put_contents; +use function fread; +use function fwrite; +use function is_array; +use function is_resource; +use function proc_close; +use function proc_open; +use function proc_terminate; +use function rewind; +use function sprintf; +use function stream_get_contents; +use function stream_select; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Exception; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class DefaultPhpProcess extends \PHPUnit\Util\PHP\AbstractPhpProcess +{ /** - * Enables the invocation of the original clone constructor. - * - * @return $this + * @var string */ - public function enableOriginalClone() : self - { - $this->originalClone = \true; - return $this; - } + protected $tempFile; /** - * Disables the use of class autoloading while creating the mock object. + * Runs a single job (PHP code) using a separate PHP process. * - * @return $this + * @throws Exception */ - public function disableAutoload() : self + public function runJob(string $job, array $settings = []) : array { - $this->autoload = \false; - return $this; + if ($this->stdin || $this->useTemporaryFile()) { + if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || file_put_contents($this->tempFile, $job) === \false) { + throw new Exception('Unable to write temporary file'); + } + $job = $this->stdin; + } + return $this->runProcess($job, $settings); } /** - * Enables the use of class autoloading while creating the mock object. - * - * @return $this + * Returns an array of file handles to be used in place of pipes. */ - public function enableAutoload() : self + protected function getHandles() : array { - $this->autoload = \true; - return $this; + return []; } /** - * Disables the cloning of arguments passed to mocked methods. + * Handles creating the child process and returning the STDOUT and STDERR. * - * @return $this + * @throws Exception */ - public function disableArgumentCloning() : self + protected function runProcess(string $job, array $settings) : array { - $this->cloneArguments = \false; - return $this; + $handles = $this->getHandles(); + $env = null; + if ($this->env) { + $env = $_SERVER ?? []; + unset($env['argv'], $env['argc']); + $env = array_merge($env, $this->env); + foreach ($env as $envKey => $envVar) { + if (is_array($envVar)) { + unset($env[$envKey]); + } + } + } + $pipeSpec = [0 => $handles[0] ?? ['pipe', 'r'], 1 => $handles[1] ?? ['pipe', 'w'], 2 => $handles[2] ?? ['pipe', 'w']]; + $process = proc_open($this->getCommand($settings, $this->tempFile), $pipeSpec, $pipes, null, $env); + if (!is_resource($process)) { + throw new Exception('Unable to spawn worker process'); + } + if ($job) { + $this->process($pipes[0], $job); + } + fclose($pipes[0]); + $stderr = $stdout = ''; + if ($this->timeout) { + unset($pipes[0]); + while (\true) { + $r = $pipes; + $w = null; + $e = null; + $n = @stream_select($r, $w, $e, $this->timeout); + if ($n === \false) { + break; + } + if ($n === 0) { + proc_terminate($process, 9); + throw new Exception(sprintf('Job execution aborted after %d seconds', $this->timeout)); + } + if ($n > 0) { + foreach ($r as $pipe) { + $pipeOffset = 0; + foreach ($pipes as $i => $origPipe) { + if ($pipe === $origPipe) { + $pipeOffset = $i; + break; + } + } + if (!$pipeOffset) { + break; + } + $line = fread($pipe, 8192); + if ($line === '' || $line === \false) { + fclose($pipes[$pipeOffset]); + unset($pipes[$pipeOffset]); + } elseif ($pipeOffset === 1) { + $stdout .= $line; + } else { + $stderr .= $line; + } + } + if (empty($pipes)) { + break; + } + } + } + } else { + if (isset($pipes[1])) { + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + } + if (isset($pipes[2])) { + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + } + } + if (isset($handles[1])) { + rewind($handles[1]); + $stdout = stream_get_contents($handles[1]); + fclose($handles[1]); + } + if (isset($handles[2])) { + rewind($handles[2]); + $stderr = stream_get_contents($handles[2]); + fclose($handles[2]); + } + proc_close($process); + $this->cleanup(); + return ['stdout' => $stdout, 'stderr' => $stderr]; } /** - * Enables the cloning of arguments passed to mocked methods. - * - * @return $this + * @param resource $pipe */ - public function enableArgumentCloning() : self + protected function process($pipe, string $job) : void { - $this->cloneArguments = \true; - return $this; + fwrite($pipe, $job); } - /** - * Enables the invocation of the original methods. - * - * @return $this - */ - public function enableProxyingToOriginalMethods() : self + protected function cleanup() : void { - $this->callOriginalMethods = \true; - return $this; + if ($this->tempFile) { + unlink($this->tempFile); + } } - /** - * Disables the invocation of the original methods. - * - * @return $this - */ - public function disableProxyingToOriginalMethods() : self + protected function useTemporaryFile() : bool { - $this->callOriginalMethods = \false; - $this->proxyTarget = null; - return $this; + return \false; } - /** - * Sets the proxy target. - * - * @return $this - */ - public function setProxyTarget(object $object) : self - { - $this->proxyTarget = $object; - return $this; +} +{driverMethod}($filter), + $filter + ); + + if ({codeCoverageCacheDirectory}) { + $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); } - /** - * @return $this - */ - public function allowMockingUnknownTypes() : self - { - $this->allowMockingUnknownTypes = \true; - return $this; + + $coverage->start(__FILE__); +} + +register_shutdown_function( + function() use ($coverage) { + $output = null; + + if ($coverage) { + $output = $coverage->stop(); + } + + file_put_contents('{coverageFile}', serialize($output)); + } +); + +ob_end_clean(); + +require '{job}'; +{driverMethod}($filter), + $filter + ); + + if ({cachesStaticAnalysis}) { + $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); + } + + $result->setCodeCoverage($codeCoverage); + } + + $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); + $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); + $result->enforceTimeLimit({enforcesTimeLimit}); + $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); + $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); + + $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(TRUE); + + ob_end_clean(); + $test->run($result); + $output = ''; + if (!$test->hasExpectationOnOutput()) { + $output = $test->getActualOutput(); + } + + ini_set('xdebug.scream', '0'); + @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + print serialize( + [ + 'testResult' => $test->getResult(), + 'numAssertions' => $test->getNumAssertions(), + 'result' => $result, + 'output' => $output + ] + ); +} + +$configurationFilePath = '{configurationFilePath}'; + +if ('' !== $configurationFilePath) { + $configuration = (new Loader)->load($configurationFilePath); + + (new PhpHandler)->handle($configuration->php()); + + unset($configuration); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; + unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); +} + +__phpunit_run_isolated_test(); +allowMockingUnknownTypes = \false; - return $this; + + $result = new PHPUnit\Framework\TestResult; + + if ({collectCodeCoverageInformation}) { + $filter = unserialize('{codeCoverageFilter}'); + + $codeCoverage = new CodeCoverage( + (new Selector)->{driverMethod}($filter), + $filter + ); + + if ({cachesStaticAnalysis}) { + $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); + } + + $result->setCodeCoverage($codeCoverage); } - /** - * @return $this - */ - public function enableAutoReturnValueGeneration() : self - { - $this->returnValueGeneration = \true; - return $this; + + $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); + $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); + $result->enforceTimeLimit({enforcesTimeLimit}); + $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); + $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); + + $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); + \assert($test instanceof TestCase); + + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + $test->run($result); + $output = ''; + if (!$test->hasExpectationOnOutput()) { + $output = $test->getActualOutput(); } - /** - * @return $this - */ - public function disableAutoReturnValueGeneration() : self - { - $this->returnValueGeneration = \false; - return $this; + + ini_set('xdebug.scream', '0'); + @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } } + + print serialize( + [ + 'testResult' => $test->getResult(), + 'numAssertions' => $test->getNumAssertions(), + 'result' => $result, + 'output' => $output + ] + ); +} + +$configurationFilePath = '{configurationFilePath}'; + +if ('' !== $configurationFilePath) { + $configuration = (new Loader)->load($configurationFilePath); + + (new PhpHandler)->handle($configuration->php()); + + unset($configuration); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; + unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); } + +__phpunit_run_isolated_test(); methods[strtolower($method->getName())] = $method; + if (PHP_MAJOR_VERSION < 8) { + return '"' . parent::getCommand($settings, $file) . '"'; } + return parent::getCommand($settings, $file); } /** - * @return MockMethod[] + * @throws Exception */ - public function asArray() : array + protected function getHandles() : array { - return array_values($this->methods); + if (\false === ($stdout_handle = tmpfile())) { + throw new Exception('A temporary file could not be created; verify that your TEMP environment variable is writable'); + } + return [1 => $stdout_handle]; } - public function hasMethod(string $methodName) : bool + protected function useTemporaryFile() : bool { - return array_key_exists(strtolower($methodName), $this->methods); + return \true; } } className = $className; - $this->methodName = $methodName; - $this->parameters = $parameters; - $this->object = $object; - $this->proxiedCall = $proxiedCall; - if (strtolower($methodName) === '__tostring') { - $returnType = 'string'; - } - if (strpos($returnType, '?') === 0) { - $returnType = substr($returnType, 1); - $this->isReturnTypeNullable = \true; + if (is_resource($out)) { + $this->stream = $out; + return; } - $this->returnType = $returnType; - if (!$cloneObjects) { + if (!is_string($out)) { return; } - foreach ($this->parameters as $key => $value) { - if (is_object($value)) { - $this->parameters[$key] = $this->cloneObject($value); + if (strpos($out, 'socket://') === 0) { + $tmp = explode(':', str_replace('socket://', '', $out)); + if (count($tmp) !== 2) { + throw new \PHPUnit\Util\Exception(sprintf('"%s" does not match "socket://hostname:port" format', $out)); } + $this->stream = fsockopen($tmp[0], (int) $tmp[1]); + return; } + if (strpos($out, 'php://') === \false && !\PHPUnit\Util\Filesystem::createDirectory(dirname($out))) { + throw new \PHPUnit\Util\Exception(sprintf('Directory "%s" was not created', dirname($out))); + } + $this->stream = fopen($out, 'wb'); + $this->isPhpStream = strncmp($out, 'php://', 6) !== 0; } - public function getClassName() : string - { - return $this->className; - } - public function getMethodName() : string - { - return $this->methodName; - } - public function getParameters() : array - { - return $this->parameters; - } - /** - * @throws RuntimeException - * - * @return mixed Mocked return value - */ - public function generateReturnValue() + public function write(string $buffer) : void { - if ($this->isReturnTypeNullable || $this->proxiedCall) { - return null; - } - $union = \false; - if (strpos($this->returnType, '|') !== \false) { - $types = explode('|', $this->returnType); - $union = \true; + if ($this->stream) { + assert(is_resource($this->stream)); + fwrite($this->stream, $buffer); } else { - $types = [$this->returnType]; - } - $types = array_map('strtolower', $types); - if (\in_array('', $types, \true) || \in_array('null', $types, \true) || \in_array('mixed', $types, \true) || \in_array('void', $types, \true)) { - return null; - } - if (\in_array('false', $types, \true) || \in_array('bool', $types, \true)) { - return \false; - } - if (\in_array('float', $types, \true)) { - return 0.0; - } - if (\in_array('int', $types, \true)) { - return 0; - } - if (\in_array('string', $types, \true)) { - return ''; - } - if (\in_array('array', $types, \true)) { - return []; - } - if (\in_array('static', $types, \true)) { - try { - return (new Instantiator())->instantiate(get_class($this->object)); - } catch (Throwable $t) { - throw new \PHPUnit\Framework\MockObject\RuntimeException($t->getMessage(), (int) $t->getCode(), $t); - } - } - if (\in_array('object', $types, \true)) { - return new stdClass(); - } - if (\in_array('callable', $types, \true) || \in_array('closure', $types, \true)) { - return static function () : void { - }; - } - if (\in_array('traversable', $types, \true) || \in_array('generator', $types, \true) || \in_array('iterable', $types, \true)) { - $generator = static function () : \Generator { - yield from []; - }; - return $generator(); - } - if (!$union) { - try { - return (new \PHPUnit\Framework\MockObject\Generator())->getMock($this->returnType, [], [], '', \false); - } catch (Throwable $t) { - throw new \PHPUnit\Framework\MockObject\RuntimeException(sprintf('Return value for %s::%s() cannot be generated: %s', $this->className, $this->methodName, $t->getMessage()), (int) $t->getCode()); + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { + $buffer = htmlspecialchars($buffer, ENT_COMPAT | ENT_SUBSTITUTE); } + print $buffer; } - throw new \PHPUnit\Framework\MockObject\RuntimeException(sprintf('Return value for %s::%s() cannot be generated because the declared return type is a union, please configure a return value for this method', $this->className, $this->methodName)); - } - public function toString() : string - { - $exporter = new Exporter(); - return sprintf('%s::%s(%s)%s', $this->className, $this->methodName, implode(', ', array_map([$exporter, 'shortenedExport'], $this->parameters)), $this->returnType ? sprintf(': %s', $this->returnType) : ''); - } - public function getObject() : object - { - return $this->object; } - private function cloneObject(object $original) : object + public function flush() : void { - if (Type::isCloneable($original)) { - return clone $original; + if ($this->stream && $this->isPhpStream) { + assert(is_resource($this->stream)); + fclose($this->stream); } - return $original; } } isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - if ($method->isStatic()) { - $modifier .= ' static'; - } - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - $docComment = $method->getDocComment(); - if (is_string($docComment) && preg_match('#\\*[ \\t]*+@deprecated[ \\t]*+(.*?)\\r?+\\n[ \\t]*+\\*(?:[ \\t]*+@|/$)#s', $docComment, $deprecation)) { - $deprecation = trim(preg_replace('#[ \\t]*\\r?\\n[ \\t]*+\\*[ \\t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = null; - } - return new self($method->getDeclaringClass()->getName(), $method->getName(), $cloneArguments, $modifier, self::getMethodParametersForDeclaration($method), self::getMethodParametersForCall($method), (new ReflectionMapper())->fromMethodReturnType($method), $reference, $callOriginalMethod, $method->isStatic(), $deprecation); - } - public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments) : self - { - return new self($fullClassName, $methodName, $cloneArguments, 'public', '', '', new UnknownType(), '', \false, \false, null); - } - public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) - { - $this->className = $className; - $this->methodName = $methodName; - $this->cloneArguments = $cloneArguments; - $this->modifier = $modifier; - $this->argumentsForDeclaration = $argumentsForDeclaration; - $this->argumentsForCall = $argumentsForCall; - $this->returnType = $returnType; - $this->reference = $reference; - $this->callOriginalMethod = $callOriginalMethod; - $this->static = $static; - $this->deprecation = $deprecation; - } - public function getName() : string - { - return $this->methodName; - } - /** - * @throws RuntimeException - */ - public function generateCode() : string - { - if ($this->static) { - $templateFile = 'mocked_static_method.tpl'; - } elseif ($this->returnType instanceof VoidType) { - $templateFile = sprintf('%s_method_void.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked'); - } else { - $templateFile = sprintf('%s_method.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked'); - } - $deprecation = $this->deprecation; - if (null !== $this->deprecation) { - $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - $deprecationTemplate->setVar(['deprecation' => var_export($deprecation, \true)]); - $deprecation = $deprecationTemplate->render(); - } - $template = $this->getTemplate($templateFile); - $template->setVar(['arguments_decl' => $this->argumentsForDeclaration, 'arguments_call' => $this->argumentsForCall, 'return_declaration' => !empty($this->returnType->asString()) ? ': ' . $this->returnType->asString() : '', 'return_type' => $this->returnType->asString(), 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, 'class_name' => $this->className, 'method_name' => $this->methodName, 'modifier' => $this->modifier, 'reference' => $this->reference, 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', 'deprecation' => $deprecation]); - return $template->render(); - } - public function getReturnType() : Type - { - return $this->returnType; - } - /** - * @throws RuntimeException - */ - private function getTemplate(string $template) : Template - { - $filename = __DIR__ . \DIRECTORY_SEPARATOR . 'Generator' . \DIRECTORY_SEPARATOR . $template; - if (!isset(self::$templates[$filename])) { - try { - self::$templates[$filename] = new Template($filename); - } catch (TemplateException $e) { - throw new \PHPUnit\Framework\MockObject\RuntimeException($e->getMessage(), (int) $e->getCode(), $e); - } - } - return self::$templates[$filename]; - } - /** - * Returns the parameters of a function or method. - * - * @throws RuntimeException - */ - private static function getMethodParametersForDeclaration(ReflectionMethod $method) : string - { - $parameters = []; - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - $nullable = ''; - $default = ''; - $reference = ''; - $typeDeclaration = ''; - $type = null; - $typeName = null; - if ($parameter->hasType()) { - $type = $parameter->getType(); - if ($type instanceof ReflectionNamedType) { - $typeName = $type->getName(); - } - } - if ($parameter->isVariadic()) { - $name = '...' . $name; - } elseif ($parameter->isDefaultValueAvailable()) { - $default = ' = ' . self::exportDefaultValue($parameter); - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - if ($type !== null) { - if ($typeName !== 'mixed' && $parameter->allowsNull() && !$type instanceof ReflectionUnionType) { - $nullable = '?'; - } - if ($typeName === 'self') { - $typeDeclaration = $method->getDeclaringClass()->getName() . ' '; - } elseif ($typeName !== null) { - $typeDeclaration = $typeName . ' '; - } elseif ($type instanceof ReflectionUnionType) { - $typeDeclaration = self::unionTypeAsString($type, $method->getDeclaringClass()->getName()); - } - } - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; - } - return implode(', ', $parameters); - } +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Reflection +{ /** - * Returns the parameters of a function or method. - * - * @throws ReflectionException + * @psalm-return list */ - private static function getMethodParametersForCall(ReflectionMethod $method) : string + public function publicMethodsInTestClass(ReflectionClass $class) : array { - $parameters = []; - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - if ($parameter->isVariadic()) { - continue; - } - if ($parameter->isPassedByReference()) { - $parameters[] = '&' . $name; - } else { - $parameters[] = $name; - } - } - return implode(', ', $parameters); + return $this->filterMethods($class, ReflectionMethod::IS_PUBLIC); } /** - * @throws ReflectionException + * @psalm-return list */ - private static function exportDefaultValue(ReflectionParameter $parameter) : string + public function methodsInTestClass(ReflectionClass $class) : array { - try { - return (string) var_export($parameter->getDefaultValue(), \true); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new \PHPUnit\Framework\MockObject\ReflectionException($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd + return $this->filterMethods($class, null); } - private static function unionTypeAsString(ReflectionUnionType $union, string $self) : string + /** + * @psalm-return list + */ + private function filterMethods(ReflectionClass $class, ?int $filter) : array { - $types = []; - foreach ($union->getTypes() as $type) { - if ((string) $type === 'self') { - $types[] = $self; - } else { - $types[] = $type; + $methods = []; + // PHP <7.3.5 throw error when null is passed + // to ReflectionClass::getMethods() when strict_types is enabled. + $classMethods = $filter === null ? $class->getMethods() : $class->getMethods($filter); + foreach ($classMethods as $method) { + if ($method->getDeclaringClass()->getName() === TestCase::class) { + continue; + } + if ($method->getDeclaringClass()->getName() === Assert::class) { + continue; } + $methods[] = $method; } - return implode('|', $types) . ' '; + return $methods; } } methodName = $methodName; - } - public function toString() : string - { - return sprintf('is "%s"', $this->methodName); - } - protected function matches($other) : bool + public static function safeMatch(string $pattern, string $subject) { - if (!is_string($other)) { - return \false; - } - return strtolower($this->methodName) === strtolower($other); + return \PHPUnit\Util\ErrorHandler::invokeIgnoringWarnings(static function () use($pattern, $subject) { + return preg_match($pattern, $subject); + }); } } $parameters) { - if (!is_iterable($parameters)) { - throw new InvalidParameterGroupException(sprintf('Parameter group #%d must be an array or Traversable, got %s', $index, gettype($parameters))); - } - foreach ($parameters as $parameter) { - if (!$parameter instanceof Constraint) { - $parameter = new IsEqual($parameter); - } - $this->parameterGroups[$index][] = $parameter; - } + if ($test instanceof TestCase) { + return [get_class($test), $test->getName()]; } + if ($test instanceof SelfDescribing) { + return ['', $test->toString()]; + } + return ['', get_class($test)]; } - public function toString() : string - { - return 'with consecutive parameters'; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public function apply(BaseInvocation $invocation) : void + public static function describeAsString(\PHPUnit\Framework\Test $test) : string { - $this->invocations[] = $invocation; - $callIndex = count($this->invocations) - 1; - $this->verifyInvocation($invocation, $callIndex); + if ($test instanceof SelfDescribing) { + return $test->toString(); + } + return get_class($test); } /** - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * + * @return array|bool + * + * @psalm-param class-string $className */ - public function verify() : void + public static function getLinesToBeCovered(string $className, string $methodName) { - foreach ($this->invocations as $callIndex => $invocation) { - $this->verifyInvocation($invocation, $callIndex); + $annotations = self::parseTestMethodAnnotations($className, $methodName); + if (!self::shouldCoversAnnotationBeUsed($annotations)) { + return \false; } + return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); } /** - * Verify a single invocation. + * Returns lines of code specified with the @uses annotation. * - * @param int $callIndex + * @throws CodeCoverageException * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @psalm-param class-string $className */ - private function verifyInvocation(BaseInvocation $invocation, $callIndex) : void + public static function getLinesToBeUsed(string $className, string $methodName) : array { - if (!isset($this->parameterGroups[$callIndex])) { - // no parameter assertion for this call index - return; + return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); + } + public static function requiresCodeCoverageDataCollection(TestCase $test) : bool + { + $annotations = self::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); + // If there is no @covers annotation but a @coversNothing annotation on + // the test method then code coverage data does not need to be collected + if (isset($annotations['method']['coversNothing'])) { + // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 + // return false; } - $parameters = $this->parameterGroups[$callIndex]; - if (count($invocation->getParameters()) < count($parameters)) { - throw new ExpectationFailedException(sprintf('Parameter count for invocation %s is too low.', $invocation->toString())); + // If there is at least one @covers annotation then + // code coverage data needs to be collected + if (isset($annotations['method']['covers'])) { + return \true; } - foreach ($parameters as $i => $parameter) { - $parameter->evaluate($invocation->getParameters()[$i], sprintf('Parameter %s for invocation #%d %s does not match expected ' . 'value.', $i, $callIndex, $invocation->toString())); + // If there is no @covers annotation but a @coversNothing annotation + // then code coverage data does not need to be collected + if (isset($annotations['class']['coversNothing'])) { + // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 + // return false; } + // If there is no @coversNothing annotation then + // code coverage data may be collected + return \true; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function is_string; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\MethodNameConstraint; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodName -{ /** - * @var Constraint + * @throws Exception + * + * @psalm-param class-string $className */ - private $constraint; + public static function getRequirements(string $className, string $methodName) : array + { + return self::mergeArraysRecursively(Registry::getInstance()->forClassName($className)->requirements(), Registry::getInstance()->forMethod($className, $methodName)->requirements()); + } /** - * @param Constraint|string $constraint + * Returns the missing requirements for a test. * - * @throws InvalidArgumentException + * @throws Exception + * @throws Warning + * + * @psalm-param class-string $className */ - public function __construct($constraint) + public static function getMissingRequirements(string $className, string $methodName) : array { - if (is_string($constraint)) { - $constraint = new MethodNameConstraint($constraint); + $required = self::getRequirements($className, $methodName); + $missing = []; + $hint = null; + if (!empty($required['PHP'])) { + $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); + if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) { + $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); + $hint = 'PHP'; + } + } elseif (!empty($required['PHP_constraint'])) { + $version = new \PHPUnit\PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); + if (!$required['PHP_constraint']['constraint']->complies($version)) { + $missing[] = sprintf('PHP version does not match the required constraint %s.', $required['PHP_constraint']['constraint']->asString()); + $hint = 'PHP_constraint'; + } } - if (!$constraint instanceof Constraint) { - throw InvalidArgumentException::create(1, 'PHPUnit\\Framework\\Constraint\\Constraint object or string'); + if (!empty($required['PHPUnit'])) { + $phpunitVersion = Version::id(); + $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); + if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { + $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); + $hint = $hint ?? 'PHPUnit'; + } + } elseif (!empty($required['PHPUnit_constraint'])) { + $phpunitVersion = new \PHPUnit\PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); + if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { + $missing[] = sprintf('PHPUnit version does not match the required constraint %s.', $required['PHPUnit_constraint']['constraint']->asString()); + $hint = $hint ?? 'PHPUnit_constraint'; + } } - $this->constraint = $constraint; - } - public function toString() : string - { - return 'method name ' . $this->constraint->toString(); + if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem())->getFamily()) { + $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); + $hint = $hint ?? 'OSFAMILY'; + } + if (!empty($required['OS'])) { + $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); + if (!preg_match($requiredOsPattern, PHP_OS)) { + $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); + $hint = $hint ?? 'OS'; + } + } + if (!empty($required['functions'])) { + foreach ($required['functions'] as $function) { + $pieces = explode('::', $function); + if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { + continue; + } + if (function_exists($function)) { + continue; + } + $missing[] = sprintf('Function %s is required.', $function); + $hint = $hint ?? 'function_' . $function; + } + } + if (!empty($required['setting'])) { + foreach ($required['setting'] as $setting => $value) { + if (ini_get($setting) !== $value) { + $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); + $hint = $hint ?? '__SETTING_' . $setting; + } + } + } + if (!empty($required['extensions'])) { + foreach ($required['extensions'] as $extension) { + if (isset($required['extension_versions'][$extension])) { + continue; + } + if (!extension_loaded($extension)) { + $missing[] = sprintf('Extension %s is required.', $extension); + $hint = $hint ?? 'extension_' . $extension; + } + } + } + if (!empty($required['extension_versions'])) { + foreach ($required['extension_versions'] as $extension => $req) { + $actualVersion = phpversion($extension); + $operator = new \PHPUnit\Util\VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); + if ($actualVersion === \false || !version_compare($actualVersion, $req['version'], $operator->asString())) { + $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); + $hint = $hint ?? 'extension_' . $extension; + } + } + } + if ($hint && isset($required['__OFFSET'])) { + array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); + array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); + } + return $missing; } /** - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Returns the provided data for a method. + * + * @throws Exception + * + * @psalm-param class-string $className */ - public function matches(BaseInvocation $invocation) : bool + public static function getProvidedData(string $className, string $methodName) : ?array { - return $this->matchesName($invocation->getMethodName()); + return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); } /** - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @psalm-param class-string $className */ - public function matchesName(string $methodName) : bool + public static function parseTestMethodAnnotations(string $className, ?string $methodName = '') : array { - return (bool) $this->constraint->evaluate($methodName, '', \true); + $registry = Registry::getInstance(); + if ($methodName !== null) { + try { + return ['method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), 'class' => $registry->forClassName($className)->symbolAnnotations()]; + } catch (\PHPUnit\Util\Exception $methodNotFound) { + // ignored + } + } + return ['method' => null, 'class' => $registry->forClassName($className)->symbolAnnotations()]; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 - * @codeCoverageIgnore - */ -final class InvokedAtIndex extends \PHPUnit\Framework\MockObject\Rule\InvocationOrder -{ - /** - * @var int - */ - private $sequenceIndex; - /** - * @var int - */ - private $currentIndex = -1; /** - * @param int $sequenceIndex + * @psalm-param class-string $className */ - public function __construct($sequenceIndex) - { - $this->sequenceIndex = $sequenceIndex; - } - public function toString() : string + public static function getInlineAnnotations(string $className, string $methodName) : array { - return 'invoked at sequence index ' . $this->sequenceIndex; + return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); } - public function matches(BaseInvocation $invocation) : bool + /** @psalm-param class-string $className */ + public static function getBackupSettings(string $className, string $methodName) : array { - $this->currentIndex++; - return $this->currentIndex == $this->sequenceIndex; + return ['backupGlobals' => self::getBooleanAnnotationSetting($className, $methodName, 'backupGlobals'), 'backupStaticAttributes' => self::getBooleanAnnotationSetting($className, $methodName, 'backupStaticAttributes')]; } /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. + * @psalm-param class-string $className * - * @throws ExpectationFailedException + * @return ExecutionOrderDependency[] */ - public function verify() : void + public static function getDependencies(string $className, string $methodName) : array { - if ($this->currentIndex < $this->sequenceIndex) { - throw new ExpectationFailedException(sprintf('The expected invocation at index %s was never reached.', $this->sequenceIndex)); + $annotations = self::parseTestMethodAnnotations($className, $methodName); + $dependsAnnotations = $annotations['class']['depends'] ?? []; + if (isset($annotations['method']['depends'])) { + $dependsAnnotations = array_merge($dependsAnnotations, $annotations['method']['depends']); } + // Normalize dependency name to className::methodName + $dependencies = []; + foreach ($dependsAnnotations as $value) { + $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value); + } + return array_unique($dependencies); } - protected function invokedDo(BaseInvocation $invocation) : void - { - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class AnyParameters implements \PHPUnit\Framework\MockObject\Rule\ParametersRule -{ - public function toString() : string + /** @psalm-param class-string $className */ + public static function getGroups(string $className, ?string $methodName = '') : array { - return 'with any parameters'; + $annotations = self::parseTestMethodAnnotations($className, $methodName); + $groups = []; + if (isset($annotations['method']['author'])) { + $groups[] = $annotations['method']['author']; + } elseif (isset($annotations['class']['author'])) { + $groups[] = $annotations['class']['author']; + } + if (isset($annotations['class']['group'])) { + $groups[] = $annotations['class']['group']; + } + if (isset($annotations['method']['group'])) { + $groups[] = $annotations['method']['group']; + } + if (isset($annotations['class']['ticket'])) { + $groups[] = $annotations['class']['ticket']; + } + if (isset($annotations['method']['ticket'])) { + $groups[] = $annotations['method']['ticket']; + } + foreach (['method', 'class'] as $element) { + foreach (['small', 'medium', 'large'] as $size) { + if (isset($annotations[$element][$size])) { + $groups[] = [$size]; + break 2; + } + } + } + foreach (['method', 'class'] as $element) { + if (isset($annotations[$element]['covers'])) { + foreach ($annotations[$element]['covers'] as $coversTarget) { + $groups[] = ['__phpunit_covers_' . self::canonicalizeName($coversTarget)]; + } + } + if (isset($annotations[$element]['uses'])) { + foreach ($annotations[$element]['uses'] as $usesTarget) { + $groups[] = ['__phpunit_uses_' . self::canonicalizeName($usesTarget)]; + } + } + } + return array_unique(array_merge([], ...$groups)); } - public function apply(BaseInvocation $invocation) : void + /** @psalm-param class-string $className */ + public static function getSize(string $className, ?string $methodName) : int { + $groups = array_flip(self::getGroups($className, $methodName)); + if (isset($groups['large'])) { + return self::LARGE; + } + if (isset($groups['medium'])) { + return self::MEDIUM; + } + if (isset($groups['small'])) { + return self::SMALL; + } + return self::UNKNOWN; } - public function verify() : void + /** @psalm-param class-string $className */ + public static function getProcessIsolationSettings(string $className, string $methodName) : bool { + $annotations = self::parseTestMethodAnnotations($className, $methodName); + return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class InvocationOrder implements SelfDescribing, Verifiable -{ - /** - * @var BaseInvocation[] - */ - private $invocations = []; - public function getInvocationCount() : int + /** @psalm-param class-string $className */ + public static function getClassProcessIsolationSettings(string $className, string $methodName) : bool { - return count($this->invocations); + $annotations = self::parseTestMethodAnnotations($className, $methodName); + return isset($annotations['class']['runClassInSeparateProcess']); } - public function hasBeenInvoked() : bool + /** @psalm-param class-string $className */ + public static function getPreserveGlobalStateSettings(string $className, string $methodName) : ?bool { - return count($this->invocations) > 0; + return self::getBooleanAnnotationSetting($className, $methodName, 'preserveGlobalState'); } - public final function invoked(BaseInvocation $invocation) + /** @psalm-param class-string $className */ + public static function getHookMethods(string $className) : array { - $this->invocations[] = $invocation; - return $this->invokedDo($invocation); + if (!class_exists($className, \false)) { + return self::emptyHookMethodsArray(); + } + if (!isset(self::$hookMethods[$className])) { + self::$hookMethods[$className] = self::emptyHookMethodsArray(); + try { + foreach ((new \PHPUnit\Util\Reflection())->methodsInTestClass(new ReflectionClass($className)) as $method) { + $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); + if ($method->isStatic()) { + if ($docBlock->isHookToBeExecutedBeforeClass()) { + array_unshift(self::$hookMethods[$className]['beforeClass'], $method->getName()); + } + if ($docBlock->isHookToBeExecutedAfterClass()) { + self::$hookMethods[$className]['afterClass'][] = $method->getName(); + } + } + if ($docBlock->isToBeExecutedBeforeTest()) { + array_unshift(self::$hookMethods[$className]['before'], $method->getName()); + } + if ($docBlock->isToBeExecutedAsPreCondition()) { + array_unshift(self::$hookMethods[$className]['preCondition'], $method->getName()); + } + if ($docBlock->isToBeExecutedAsPostCondition()) { + self::$hookMethods[$className]['postCondition'][] = $method->getName(); + } + if ($docBlock->isToBeExecutedAfterTest()) { + self::$hookMethods[$className]['after'][] = $method->getName(); + } + } + } catch (ReflectionException $e) { + } + } + return self::$hookMethods[$className]; } - public abstract function matches(BaseInvocation $invocation) : bool; - protected abstract function invokedDo(BaseInvocation $invocation); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtLeastOnce extends \PHPUnit\Framework\MockObject\Rule\InvocationOrder -{ - public function toString() : string + public static function isTestMethod(ReflectionMethod $method) : bool { - return 'invoked at least once'; + if (!$method->isPublic()) { + return \false; + } + if (strpos($method->getName(), 'test') === 0) { + return \true; + } + return array_key_exists('test', Registry::getInstance()->forMethod($method->getDeclaringClass()->getName(), $method->getName())->symbolAnnotations()); } /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. + * @throws CodeCoverageException * - * @throws ExpectationFailedException + * @psalm-param class-string $className */ - public function verify() : void + private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode) : array { - $count = $this->getInvocationCount(); - if ($count < 1) { - throw new ExpectationFailedException('Expected invocation at least once but it never occurred.'); + $annotations = self::parseTestMethodAnnotations($className, $methodName); + $classShortcut = null; + if (!empty($annotations['class'][$mode . 'DefaultClass'])) { + if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { + throw new CodeCoverageException(sprintf('More than one @%sClass annotation in class or interface "%s".', $mode, $className)); + } + $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; + } + $list = $annotations['class'][$mode] ?? []; + if (isset($annotations['method'][$mode])) { + $list = array_merge($list, $annotations['method'][$mode]); + } + $codeUnits = CodeUnitCollection::fromArray([]); + $mapper = new Mapper(); + foreach (array_unique($list) as $element) { + if ($classShortcut && strncmp($element, '::', 2) === 0) { + $element = $classShortcut . $element; + } + $element = preg_replace('/[\\s()]+$/', '', $element); + $element = explode(' ', $element); + $element = $element[0]; + if ($mode === 'covers' && interface_exists($element)) { + throw new InvalidCoversTargetException(sprintf('Trying to @cover interface "%s".', $element)); + } + try { + $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element)); + } catch (InvalidCodeUnitException $e) { + throw new InvalidCoversTargetException(sprintf('"@%s %s" is invalid', $mode, $element), (int) $e->getCode(), $e); + } } + return $mapper->codeUnitsToSourceLines($codeUnits); } - public function matches(BaseInvocation $invocation) : bool + private static function emptyHookMethodsArray() : array { - return \true; + return ['beforeClass' => ['setUpBeforeClass'], 'before' => ['setUp'], 'preCondition' => ['assertPreConditions'], 'postCondition' => ['assertPostConditions'], 'after' => ['tearDown'], 'afterClass' => ['tearDownAfterClass']]; } - protected function invokedDo(BaseInvocation $invocation) : void + /** @psalm-param class-string $className */ + private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName) : ?bool { + $annotations = self::parseTestMethodAnnotations($className, $methodName); + if (isset($annotations['method'][$settingName])) { + if ($annotations['method'][$settingName][0] === 'enabled') { + return \true; + } + if ($annotations['method'][$settingName][0] === 'disabled') { + return \false; + } + } + if (isset($annotations['class'][$settingName])) { + if ($annotations['class'][$settingName][0] === 'enabled') { + return \true; + } + if ($annotations['class'][$settingName][0] === 'disabled') { + return \false; + } + } + return null; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtMostCount extends \PHPUnit\Framework\MockObject\Rule\InvocationOrder -{ - /** - * @var int - */ - private $allowedInvocations; /** - * @param int $allowedInvocations + * Trims any extensions from version string that follows after + * the .[.] format. */ - public function __construct($allowedInvocations) + private static function sanitizeVersionNumber(string $version) { - $this->allowedInvocations = $allowedInvocations; + return preg_replace('/^(\\d+\\.\\d+(?:.\\d+)?).*$/', '$1', $version); } - public function toString() : string + private static function shouldCoversAnnotationBeUsed(array $annotations) : bool { - return 'invoked at most ' . $this->allowedInvocations . ' times'; + if (isset($annotations['method']['coversNothing'])) { + return \false; + } + if (isset($annotations['method']['covers'])) { + return \true; + } + if (isset($annotations['class']['coversNothing'])) { + return \false; + } + return \true; } /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. + * Merge two arrays together. * - * @throws ExpectationFailedException + * If an integer key exists in both arrays and preserveNumericKeys is false, the value + * from the second array will be appended to the first array. If both values are arrays, they + * are merged together, else the value of the second array overwrites the one of the first array. + * + * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php + * + * Zend Framework (http://framework.zend.com/) + * + * @see http://github.com/zendframework/zf2 for the canonical source repository + * + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License */ - public function verify() : void + private static function mergeArraysRecursively(array $a, array $b) : array { - $count = $this->getInvocationCount(); - if ($count > $this->allowedInvocations) { - throw new ExpectationFailedException('Expected invocation at most ' . $this->allowedInvocations . ' times but it occurred ' . $count . ' time(s).'); + foreach ($b as $key => $value) { + if (array_key_exists($key, $a)) { + if (is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = self::mergeArraysRecursively($a[$key], $value); + } else { + $a[$key] = $value; + } + } else { + $a[$key] = $value; + } } + return $a; } - public function matches(BaseInvocation $invocation) : bool - { - return \true; - } - protected function invokedDo(BaseInvocation $invocation) : void + private static function canonicalizeName(string $name) : string { + return strtolower(trim($name, '\\')); } } '│', 'start' => '│', 'message' => '│', 'diff' => '│', 'trace' => '│', 'last' => '│']; /** - * @param int $requiredInvocations + * Colored Testdox use box-drawing for a more textured map of the message. */ - public function __construct($requiredInvocations) - { - $this->requiredInvocations = $requiredInvocations; - } - public function toString() : string - { - return 'invoked at least ' . $this->requiredInvocations . ' times'; - } + private const PREFIX_DECORATED = ['default' => '│', 'start' => '┐', 'message' => '├', 'diff' => '┊', 'trace' => '╵', 'last' => '┴']; + private const SPINNER_ICONS = [" \x1b[36m◐\x1b[0m running tests", " \x1b[36m◓\x1b[0m running tests", " \x1b[36m◑\x1b[0m running tests", " \x1b[36m◒\x1b[0m running tests"]; + private const STATUS_STYLES = [BaseTestRunner::STATUS_PASSED => ['symbol' => '✔', 'color' => 'fg-green'], BaseTestRunner::STATUS_ERROR => ['symbol' => '✘', 'color' => 'fg-yellow', 'message' => 'bg-yellow,fg-black'], BaseTestRunner::STATUS_FAILURE => ['symbol' => '✘', 'color' => 'fg-red', 'message' => 'bg-red,fg-white'], BaseTestRunner::STATUS_SKIPPED => ['symbol' => '↩', 'color' => 'fg-cyan', 'message' => 'fg-cyan'], BaseTestRunner::STATUS_RISKY => ['symbol' => '☢', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_INCOMPLETE => ['symbol' => '∅', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_WARNING => ['symbol' => '⚠', 'color' => 'fg-yellow', 'message' => 'fg-yellow'], BaseTestRunner::STATUS_UNKNOWN => ['symbol' => '?', 'color' => 'fg-blue', 'message' => 'fg-white,bg-blue']]; /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. + * @var int[] + */ + private $nonSuccessfulTestResults = []; + /** + * @var Timer + */ + private $timer; + /** + * @param null|resource|string $out + * @param int|string $numberOfColumns * - * @throws ExpectationFailedException + * @throws \PHPUnit\Framework\Exception */ - public function verify() : void + public function __construct($out = null, bool $verbose = \false, string $colors = self::COLOR_DEFAULT, bool $debug = \false, $numberOfColumns = 80, bool $reverse = \false) { - $count = $this->getInvocationCount(); - if ($count < $this->requiredInvocations) { - throw new ExpectationFailedException('Expected invocation at least ' . $this->requiredInvocations . ' times but it occurred ' . $count . ' time(s).'); - } + parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); + $this->timer = new Timer(); + $this->timer->start(); } - public function matches(BaseInvocation $invocation) : bool + public function printResult(TestResult $result) : void { - return \true; + $this->printHeader($result); + $this->printNonSuccessfulTestsSummary($result->count()); + $this->printFooter($result); } - protected function invokedDo(BaseInvocation $invocation) : void + protected function printHeader(TestResult $result) : void { + $this->write("\n" . (new ResourceUsageFormatter())->resourceUsage($this->timer->stop()) . "\n\n"); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class AnyInvokedCount extends \PHPUnit\Framework\MockObject\Rule\InvocationOrder -{ - public function toString() : string + protected function formatClassName(Test $test) : string { - return 'invoked zero or more times'; + if ($test instanceof TestCase) { + return $this->prettifier->prettifyTestClass(get_class($test)); + } + return get_class($test); } - public function verify() : void + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose) : void { + if ($status !== BaseTestRunner::STATUS_PASSED) { + $this->nonSuccessfulTestResults[] = $this->testIndex; + } + parent::registerTestResult($test, $t, $status, $time, $verbose); } - public function matches(BaseInvocation $invocation) : bool + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function formatTestName(Test $test) : string { - return \true; + if ($test instanceof TestCase) { + return $this->prettifier->prettifyTestCase($test); + } + return parent::formatTestName($test); } - protected function invokedDo(BaseInvocation $invocation) : void + protected function writeTestResult(array $prevResult, array $result) : void { + // spacer line for new suite headers and after verbose messages + if ($prevResult['testName'] !== '' && (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { + $this->write(PHP_EOL); + } + // suite header + if ($prevResult['className'] !== $result['className']) { + $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL); + } + // test result line + if ($this->colors && $result['className'] === PhptTestCase::class) { + $testName = Color::colorizePath($result['testName'], $prevResult['testName'], \true); + } else { + $testName = $result['testMethod']; + } + $style = self::STATUS_STYLES[$result['status']]; + $line = sprintf(' %s %s%s' . PHP_EOL, $this->colorizeTextBox($style['color'], $style['symbol']), $testName, $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : ''); + $this->write($line); + // additional information when verbose + $this->write($result['message']); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface ParametersRule extends SelfDescribing, Verifiable -{ - /** - * @throws ExpectationFailedException if the invocation violates the rule - */ - public function apply(BaseInvocation $invocation) : void; - public function verify() : void; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedCount extends \PHPUnit\Framework\MockObject\Rule\InvocationOrder -{ - /** - * @var int - */ - private $expectedCount; - /** - * @param int $expectedCount - */ - public function __construct($expectedCount) + protected function formatThrowable(Throwable $t, ?int $status = null) : string + { + return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); + } + protected function colorizeMessageAndDiff(string $style, string $buffer) : array + { + $lines = $buffer ? array_map('\\rtrim', explode(PHP_EOL, $buffer)) : []; + $message = []; + $diff = []; + $insideDiff = \false; + foreach ($lines as $line) { + if ($line === '--- Expected') { + $insideDiff = \true; + } + if (!$insideDiff) { + $message[] = $line; + } else { + if (strpos($line, '-') === 0) { + $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, \true)); + } elseif (strpos($line, '+') === 0) { + $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, \true)); + } elseif ($line === '@@ @@') { + $line = Color::colorize('fg-cyan', $line); + } + $diff[] = $line; + } + } + $diff = implode(PHP_EOL, $diff); + if (!empty($message)) { + $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message)); + } + return [$message, $diff]; + } + protected function formatStacktrace(Throwable $t) : string { - $this->expectedCount = $expectedCount; + $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t); + if (!$this->colors) { + return $trace; + } + $lines = []; + $prevPath = ''; + foreach (explode(PHP_EOL, $trace) as $line) { + if (preg_match('/^(.*):(\\d+)$/', $line, $matches)) { + $lines[] = Color::colorizePath($matches[1], $prevPath) . Color::dim(':') . Color::colorize('fg-blue', $matches[2]) . "\n"; + $prevPath = $matches[1]; + } else { + $lines[] = $line; + $prevPath = ''; + } + } + return implode('', $lines); } - public function isNever() : bool + protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null) : string { - return $this->expectedCount === 0; + $message = $this->formatThrowable($t, $result['status']); + $diff = ''; + if (!($this->verbose || $result['verbose'])) { + return ''; + } + if ($message && $this->colors) { + $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; + [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); + } + if ($prefix === null || !$this->colors) { + $prefix = self::PREFIX_SIMPLE; + } + if ($this->colors) { + $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; + $prefix = array_map(static function ($p) use($color) { + return Color::colorize($color, $p); + }, self::PREFIX_DECORATED); + } + $trace = $this->formatStacktrace($t); + $out = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL; + if ($message) { + $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL; + } + if ($diff) { + $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL; + } + if ($trace) { + if ($message || $diff) { + $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL; + } + $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL; + } + $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL; + return $out; } - public function toString() : string + protected function drawSpinner() : void { - return 'invoked ' . $this->expectedCount . ' time(s)'; + if ($this->colors) { + $id = $this->spinState % count(self::SPINNER_ICONS); + $this->write(self::SPINNER_ICONS[$id]); + } } - public function matches(BaseInvocation $invocation) : bool + protected function undrawSpinner() : void { - return \true; + if ($this->colors) { + $id = $this->spinState % count(self::SPINNER_ICONS); + $this->write("\x1b[1K\x1b[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); + } } - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify() : void + private function formatRuntime(float $time, string $color = '') : string { - $count = $this->getInvocationCount(); - if ($count !== $this->expectedCount) { - throw new ExpectationFailedException(sprintf('Method was expected to be called %d times, ' . 'actually called %d times.', $this->expectedCount, $count)); + if (!$this->colors) { + return sprintf('[%.2f ms]', $time * 1000); + } + if ($time > 1) { + $color = 'fg-magenta'; } + return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); } - /** - * @throws ExpectationFailedException - */ - protected function invokedDo(BaseInvocation $invocation) : void + private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests) : void { - $count = $this->getInvocationCount(); - if ($count > $this->expectedCount) { - $message = $invocation->toString() . ' '; - switch ($this->expectedCount) { - case 0: - $message .= 'was not expected to be called.'; - break; - case 1: - $message .= 'was not expected to be called more than once.'; - break; - default: - $message .= sprintf('was not expected to be called more than %d times.', $this->expectedCount); - } - throw new ExpectationFailedException($message); + if (empty($this->nonSuccessfulTestResults)) { + return; + } + if (count($this->nonSuccessfulTestResults) / $numberOfExecutedTests >= 0.7) { + return; + } + $this->write("Summary of non-successful tests:\n\n"); + $prevResult = $this->getEmptyTestResult(); + foreach ($this->nonSuccessfulTestResults as $testIndex) { + $result = $this->testResults[$testIndex]; + $this->writeTestResult($prevResult, $result); + $prevResult = $result; } } } @@ -79464,120 +82148,112 @@ declare (strict_types=1); * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\MockObject\Rule; +namespace PHPUnit\Util\TestDox; -use function count; -use function get_class; use function sprintf; -use Exception; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\Constraint\IsAnything; -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\TestResult; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Parameters implements \PHPUnit\Framework\MockObject\Rule\ParametersRule +final class HtmlResultPrinter extends \PHPUnit\Util\TestDox\ResultPrinter { /** - * @var Constraint[] + * @var string */ - private $parameters = []; + private const PAGE_HEADER = <<<'EOT' + + + + + Test Documentation + + + +EOT; /** - * @var BaseInvocation + * @var string */ - private $invocation; + private const CLASS_HEADER = <<<'EOT' + +

          %s

          +
            + +EOT; /** - * @var bool|ExpectationFailedException + * @var string */ - private $parameterVerificationResult; + private const CLASS_FOOTER = <<<'EOT' +
          +EOT; /** - * @throws \PHPUnit\Framework\Exception + * @var string */ - public function __construct(array $parameters) + private const PAGE_FOOTER = <<<'EOT' + + + +EOT; + public function printResult(TestResult $result) : void { - foreach ($parameters as $parameter) { - if (!$parameter instanceof Constraint) { - $parameter = new IsEqual($parameter); - } - $this->parameters[] = $parameter; - } } - public function toString() : string + /** + * Handler for 'start run' event. + */ + protected function startRun() : void { - $text = 'with parameter'; - foreach ($this->parameters as $index => $parameter) { - if ($index > 0) { - $text .= ' and'; - } - $text .= ' ' . $index . ' ' . $parameter->toString(); - } - return $text; + $this->write(self::PAGE_HEADER); } /** - * @throws Exception + * Handler for 'start class' event. */ - public function apply(BaseInvocation $invocation) : void + protected function startClass(string $name) : void { - $this->invocation = $invocation; - $this->parameterVerificationResult = null; - try { - $this->parameterVerificationResult = $this->doVerify(); - } catch (ExpectationFailedException $e) { - $this->parameterVerificationResult = $e; - throw $this->parameterVerificationResult; - } + $this->write(sprintf(self::CLASS_HEADER, $name, $this->currentTestClassPrettified)); } /** - * Checks if the invocation $invocation matches the current rules. If it - * does the rule will get the invoked() method called which should check - * if an expectation is met. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * Handler for 'on test' event. */ - public function verify() : void + protected function onTest(string $name, bool $success = \true) : void { - $this->doVerify(); + $this->write(sprintf("
        • %s %s
        • \n", $success ? '#555753' : '#ef2929', $success ? '✓' : '❌', $name)); } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * Handler for 'end class' event. */ - private function doVerify() : bool + protected function endClass(string $name) : void { - if (isset($this->parameterVerificationResult)) { - return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); - } - if ($this->invocation === null) { - throw new ExpectationFailedException('Mocked method does not exist.'); - } - if (count($this->invocation->getParameters()) < count($this->parameters)) { - $message = 'Parameter count for invocation %s is too low.'; - // The user called `->with($this->anything())`, but may have meant - // `->withAnyParameters()`. - // - // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 - if (count($this->parameters) === 1 && get_class($this->parameters[0]) === IsAnything::class) { - $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; - } - throw new ExpectationFailedException(sprintf($message, $this->invocation->toString())); - } - foreach ($this->parameters as $i => $parameter) { - $parameter->evaluate($this->invocation->getParameters()[$i], sprintf('Parameter %s for invocation %s does not match expected ' . 'value.', $i, $this->invocation->toString())); - } - return \true; + $this->write(self::CLASS_FOOTER); } /** - * @throws ExpectationFailedException + * Handler for 'end run' event. */ - private function guardAgainstDuplicateEvaluationOfParameterConstraints() : bool + protected function endRun() : void { - if ($this->parameterVerificationResult instanceof ExpectationFailedException) { - throw $this->parameterVerificationResult; - } - return (bool) $this->parameterVerificationResult; + $this->write(self::PAGE_FOOTER); } } useColor = $useColor; + } /** - * @psalm-param class-string $mockName + * Prettifies the name of a test class. + * + * @psalm-param class-string $className */ - public function __construct(string $classCode, string $mockName) + public function prettifyTestClass(string $className) : string { - $this->classCode = $classCode; - $this->mockName = $mockName; + try { + $annotations = Test::parseTestMethodAnnotations($className); + if (isset($annotations['class']['testdox'][0])) { + return $annotations['class']['testdox'][0]; + } + } catch (UtilException $e) { + // ignore, determine className by parsing the provided name + } + $parts = explode('\\', $className); + $className = array_pop($parts); + if (substr($className, -1 * strlen('Test')) === 'Test') { + $className = substr($className, 0, strlen($className) - strlen('Test')); + } + if (strpos($className, 'Tests') === 0) { + $className = substr($className, strlen('Tests')); + } elseif (strpos($className, 'Test') === 0) { + $className = substr($className, strlen('Test')); + } + if (empty($className)) { + $className = 'UnnamedTests'; + } + if (!empty($parts)) { + $parts[] = $className; + $fullyQualifiedName = implode('\\', $parts); + } else { + $fullyQualifiedName = $className; + } + $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); + if ($fullyQualifiedName !== $className) { + return $result . ' (' . $fullyQualifiedName . ')'; + } + return $result; } /** - * @psalm-return class-string + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function generate() : string + public function prettifyTestCase(TestCase $test) : string { - if (!class_exists($this->mockName, \false)) { - eval($this->classCode); + $annotations = Test::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); + $annotationWithPlaceholders = \false; + $callback = static function (string $variable) : string { + return sprintf('/%s(?=\\b)/', preg_quote($variable, '/')); + }; + if (isset($annotations['method']['testdox'][0])) { + $result = $annotations['method']['testdox'][0]; + if (strpos($result, '$') !== \false) { + $annotation = $annotations['method']['testdox'][0]; + $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); + $variables = array_map($callback, array_keys($providedData)); + $result = trim(preg_replace($variables, $providedData, $annotation)); + $annotationWithPlaceholders = \true; + } + } else { + $result = $this->prettifyTestMethod($test->getName(\false)); } - return $this->mockName; + if (!$annotationWithPlaceholders && $test->usesDataProvider()) { + $result .= $this->prettifyDataSet($test); + } + return $result; } - public function getClassCode() : string + public function prettifyDataSet(TestCase $test) : string { - return $this->classCode; + if (!$this->useColor) { + return $test->getDataSetAsString(\false); + } + if (is_int($test->dataName())) { + $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); + } else { + $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); + } + return $data; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\SebastianBergmann\Type\Type; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConfigurableMethod -{ - /** - * @var string - */ - private $name; /** - * @var Type + * Prettifies the name of a test method. */ - private $returnType; - public function __construct(string $name, Type $returnType) - { - $this->name = $name; - $this->returnType = $returnType; - } - public function getName() : string + public function prettifyTestMethod(string $name) : string { - return $this->name; + $buffer = ''; + if ($name === '') { + return $buffer; + } + $string = (string) preg_replace('#\\d+$#', '', $name, -1, $count); + if (in_array($string, $this->strings, \true)) { + $name = $string; + } elseif ($count === 0) { + $this->strings[] = $string; + } + if (strpos($name, 'test_') === 0) { + $name = substr($name, 5); + } elseif (strpos($name, 'test') === 0) { + $name = substr($name, 4); + } + if ($name === '') { + return $buffer; + } + $name[0] = strtoupper($name[0]); + if (strpos($name, '_') !== \false) { + return trim(str_replace('_', ' ', $name)); + } + $wasNumeric = \false; + foreach (range(0, strlen($name) - 1) as $i) { + if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { + $buffer .= ' ' . strtolower($name[$i]); + } else { + $isNumeric = is_numeric($name[$i]); + if (!$wasNumeric && $isNumeric) { + $buffer .= ' '; + $wasNumeric = \true; + } + if ($wasNumeric && !$isNumeric) { + $wasNumeric = \false; + } + $buffer .= $name[$i]; + } + } + return $buffer; } - public function mayReturn($value) : bool + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test) : array { - if ($value === null && $this->returnType->allowsNull()) { - return \true; + try { + $reflector = new ReflectionMethod(get_class($test), $test->getName(\false)); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new UtilException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + $providedData = []; + $providedDataValues = array_values($test->getProvidedData()); + $i = 0; + $providedData['$_dataName'] = $test->dataName(); + foreach ($reflector->getParameters() as $parameter) { + if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { + try { + $providedDataValues[$i] = $parameter->getDefaultValue(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new UtilException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + $value = $providedDataValues[$i++] ?? null; + if (is_object($value)) { + $reflector = new ReflectionObject($value); + if ($reflector->hasMethod('__toString')) { + $value = (string) $value; + } else { + $value = get_class($value); + } + } + if (!is_scalar($value)) { + $value = gettype($value); + } + if (is_bool($value) || is_int($value) || is_float($value)) { + $value = (new Exporter())->export($value); + } + if (is_string($value) && $value === '') { + if ($this->useColor) { + $value = Color::colorize('dim,underlined', 'empty'); + } else { + $value = "''"; + } + } + $providedData['$' . $parameter->getName()] = $value; } - return $this->returnType->isAssignable(Type::fromValue($value, \false)); - } - public function getReturnTypeDeclaration() : string - { - return $this->returnType->asString(); + if ($this->useColor) { + $providedData = array_map(static function ($value) { + return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, \true)); + }, $providedData); + } + return $providedData; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func_array; -use function func_get_args; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Method -{ - public function method() - { - $expects = $this->expects(new AnyInvokedCount()); - return call_user_func_array([$expects, 'method'], func_get_args()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Api -{ + protected $prettifier; /** - * @var ConfigurableMethod[] + * @var string */ - private static $__phpunit_configurableMethods; + protected $testClass = ''; /** - * @var object + * @var int */ - private $__phpunit_originalObject; + protected $testStatus; /** - * @var bool + * @var array */ - private $__phpunit_returnValueGeneration = \true; + protected $tests = []; /** - * @var InvocationHandler + * @var int */ - private $__phpunit_invocationMocker; - /** @noinspection MagicMethodsValidityInspection */ - public static function __phpunit_initConfigurableMethods(\PHPUnit\Framework\MockObject\ConfigurableMethod ...$configurableMethods) : void - { - if (isset(static::$__phpunit_configurableMethods)) { - throw new \PHPUnit\Framework\MockObject\ConfigurableMethodsAlreadyInitializedException('Configurable methods is already initialized and can not be reinitialized'); - } - static::$__phpunit_configurableMethods = $configurableMethods; - } - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setOriginalObject($originalObject) : void - { - $this->__phpunit_originalObject = $originalObject; - } - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration) : void - { - $this->__phpunit_returnValueGeneration = $returnValueGeneration; - } - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_getInvocationHandler() : \PHPUnit\Framework\MockObject\InvocationHandler - { - if ($this->__phpunit_invocationMocker === null) { - $this->__phpunit_invocationMocker = new \PHPUnit\Framework\MockObject\InvocationHandler(static::$__phpunit_configurableMethods, $this->__phpunit_returnValueGeneration); - } - return $this->__phpunit_invocationMocker; - } - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_hasMatchers() : bool - { - return $this->__phpunit_getInvocationHandler()->hasMatchers(); - } - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_verify(bool $unsetInvocationMocker = \true) : void - { - $this->__phpunit_getInvocationHandler()->verify(); - if ($unsetInvocationMocker) { - $this->__phpunit_invocationMocker = null; - } - } - public function expects(InvocationOrder $matcher) : InvocationMockerBuilder - { - return $this->__phpunit_getInvocationHandler()->expects($matcher); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait MockedCloneMethod -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait UnmockedCloneMethod -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - parent::__clone(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function assert; -use function implode; -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; -use PHPUnit\Framework\MockObject\Rule\AnyParameters; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use PHPUnit\Framework\MockObject\Rule\InvokedCount; -use PHPUnit\Framework\MockObject\Rule\MethodName; -use PHPUnit\Framework\MockObject\Rule\ParametersRule; -use PHPUnit\Framework\MockObject\Stub\Stub; -use PHPUnit\Framework\TestFailure; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Matcher -{ + protected $successful = 0; /** - * @var InvocationOrder + * @var int */ - private $invocationRule; + protected $warned = 0; /** - * @var mixed + * @var int */ - private $afterMatchBuilderId; + protected $failed = 0; /** - * @var bool + * @var int */ - private $afterMatchBuilderIsInvoked = \false; + protected $risky = 0; /** - * @var MethodName + * @var int */ - private $methodNameRule; + protected $skipped = 0; /** - * @var ParametersRule + * @var int */ - private $parametersRule; + protected $incomplete = 0; /** - * @var Stub + * @var null|string */ - private $stub; - public function __construct(InvocationOrder $rule) + protected $currentTestClassPrettified; + /** + * @var null|string + */ + protected $currentTestMethodPrettified; + /** + * @var array + */ + private $groups; + /** + * @var array + */ + private $excludeGroups; + /** + * @param resource $out + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct($out = null, array $groups = [], array $excludeGroups = []) { - $this->invocationRule = $rule; + parent::__construct($out); + $this->groups = $groups; + $this->excludeGroups = $excludeGroups; + $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier(); + $this->startRun(); } - public function hasMatchers() : bool + /** + * Flush buffer and close output. + */ + public function flush() : void { - return !$this->invocationRule instanceof AnyInvokedCount; + $this->doEndClass(); + $this->endRun(); + parent::flush(); } - public function hasMethodNameRule() : bool + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time) : void { - return $this->methodNameRule !== null; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_ERROR; + $this->failed++; } - public function getMethodNameRule() : MethodName + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time) : void { - return $this->methodNameRule; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_WARNING; + $this->warned++; } - public function setMethodNameRule(MethodName $rule) : void + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void { - $this->methodNameRule = $rule; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_FAILURE; + $this->failed++; } - public function hasParametersRule() : bool + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void { - return $this->parametersRule !== null; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; + $this->incomplete++; } - public function setParametersRule(ParametersRule $rule) : void + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time) : void { - $this->parametersRule = $rule; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_RISKY; + $this->risky++; } - public function setStub(Stub $stub) : void + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time) : void { - $this->stub = $stub; + if (!$this->isOfInterest($test)) { + return; + } + $this->testStatus = BaseTestRunner::STATUS_SKIPPED; + $this->skipped++; } - public function setAfterMatchBuilderId(string $id) : void + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite) : void { - $this->afterMatchBuilderId = $id; } /** - * @throws ExpectationFailedException - * @throws MatchBuilderNotFoundException - * @throws MethodNameNotConfiguredException - * @throws RuntimeException + * A testsuite ended. */ - public function invoked(\PHPUnit\Framework\MockObject\Invocation $invocation) + public function endTestSuite(TestSuite $suite) : void { - if ($this->methodNameRule === null) { - throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); - } - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject()->__phpunit_getInvocationHandler()->lookupMatcher($this->afterMatchBuilderId); - if (!$matcher) { - throw new \PHPUnit\Framework\MockObject\MatchBuilderNotFoundException($this->afterMatchBuilderId); - } - assert($matcher instanceof self); - if ($matcher->invocationRule->hasBeenInvoked()) { - $this->afterMatchBuilderIsInvoked = \true; - } - } - $this->invocationRule->invoked($invocation); - try { - if ($this->parametersRule !== null) { - $this->parametersRule->apply($invocation); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage()), $e->getComparisonFailure()); - } - if ($this->stub) { - return $this->stub->invoke($invocation); - } - return $invocation->generateReturnValue(); } /** + * A test started. + * * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * @throws MatchBuilderNotFoundException - * @throws MethodNameNotConfiguredException - * @throws RuntimeException */ - public function matches(\PHPUnit\Framework\MockObject\Invocation $invocation) : bool + public function startTest(Test $test) : void { - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject()->__phpunit_getInvocationHandler()->lookupMatcher($this->afterMatchBuilderId); - if (!$matcher) { - throw new \PHPUnit\Framework\MockObject\MatchBuilderNotFoundException($this->afterMatchBuilderId); - } - assert($matcher instanceof self); - if (!$matcher->invocationRule->hasBeenInvoked()) { - return \false; - } - } - if ($this->methodNameRule === null) { - throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); - } - if (!$this->invocationRule->matches($invocation)) { - return \false; + if (!$this->isOfInterest($test)) { + return; } - try { - if (!$this->methodNameRule->matches($invocation)) { - return \false; + $class = get_class($test); + if ($this->testClass !== $class) { + if ($this->testClass !== '') { + $this->doEndClass(); } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage()), $e->getComparisonFailure()); + $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); + $this->testClass = $class; + $this->tests = []; + $this->startClass($class); } - return \true; + if ($test instanceof TestCase) { + $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); + } + $this->testStatus = BaseTestRunner::STATUS_PASSED; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * @throws MethodNameNotConfiguredException + * A test ended. */ - public function verify() : void + public function endTest(Test $test, float $time) : void { - if ($this->methodNameRule === null) { - throw new \PHPUnit\Framework\MockObject\MethodNameNotConfiguredException(); - } - try { - $this->invocationRule->verify(); - if ($this->parametersRule === null) { - $this->parametersRule = new AnyParameters(); - } - $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; - $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); - if (!$invocationIsAny && !$invocationIsNever) { - $this->parametersRule->verify(); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException(sprintf("Expectation failed for %s when %s.\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), TestFailure::exceptionToString($e))); + if (!$this->isOfInterest($test)) { + return; } + $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; + $this->currentTestClassPrettified = null; + $this->currentTestMethodPrettified = null; } - public function toString() : string + protected function doEndClass() : void { - $list = []; - if ($this->invocationRule !== null) { - $list[] = $this->invocationRule->toString(); + foreach ($this->tests as $test) { + $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); } - if ($this->methodNameRule !== null) { - $list[] = 'where ' . $this->methodNameRule->toString(); + $this->endClass($this->testClass); + } + /** + * Handler for 'start run' event. + */ + protected function startRun() : void + { + } + /** + * Handler for 'start class' event. + */ + protected function startClass(string $name) : void + { + } + /** + * Handler for 'on test' event. + */ + protected function onTest(string $name, bool $success = \true) : void + { + } + /** + * Handler for 'end class' event. + */ + protected function endClass(string $name) : void + { + } + /** + * Handler for 'end run' event. + */ + protected function endRun() : void + { + } + private function isOfInterest(Test $test) : bool + { + if (!$test instanceof TestCase) { + return \false; } - if ($this->parametersRule !== null) { - $list[] = 'and ' . $this->parametersRule->toString(); + if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { + return \false; } - if ($this->afterMatchBuilderId !== null) { - $list[] = 'after ' . $this->afterMatchBuilderId; + if (!empty($this->groups)) { + foreach ($test->getGroups() as $group) { + if (in_array($group, $this->groups, \true)) { + return \true; + } + } + return \false; } - if ($this->stub !== null) { - $list[] = 'will ' . $this->stub->toString(); + if (!empty($this->excludeGroups)) { + foreach ($test->getGroups() as $group) { + if (in_array($group, $this->excludeGroups, \true)) { + return \false; + } + } + return \true; } - return implode(' ', $list); + return \true; } } Buffer for test results + */ + protected $testResults = []; + /** + * @var array Lookup table for testname to testResults[index] + */ + protected $testNameResultIndex = []; /** * @var bool */ - private $returnValueGeneration; + protected $enableOutputBuffer = \false; /** - * @var Throwable + * @var array array */ - private $deferredError; - public function __construct(array $configurableMethods, bool $returnValueGeneration) + protected $originalExecutionOrder = []; + /** + * @var int + */ + protected $spinState = 0; + /** + * @var bool + */ + protected $showProgress = \true; + /** + * @param null|resource|string $out + * @param int|string $numberOfColumns + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct($out = null, bool $verbose = \false, string $colors = self::COLOR_DEFAULT, bool $debug = \false, $numberOfColumns = 80, bool $reverse = \false) { - $this->configurableMethods = $configurableMethods; - $this->returnValueGeneration = $returnValueGeneration; + parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); + $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier($this->colors); } - public function hasMatchers() : bool + public function setOriginalExecutionOrder(array $order) : void + { + $this->originalExecutionOrder = $order; + $this->enableOutputBuffer = !empty($order); + } + public function setShowProgressAnimation(bool $showProgress) : void + { + $this->showProgress = $showProgress; + } + public function printResult(TestResult $result) : void { - foreach ($this->matchers as $matcher) { - if ($matcher->hasMatchers()) { - return \true; - } - } - return \false; } /** - * Looks up the match builder with identification $id and returns it. - * - * @param string $id The identification of the match builder + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function lookupMatcher(string $id) : ?\PHPUnit\Framework\MockObject\Matcher + public function endTest(Test $test, float $time) : void { - if (isset($this->matcherMap[$id])) { - return $this->matcherMap[$id]; + if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { + return; } - return null; + if ($this->testHasPassed()) { + $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, \false); + } + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + $this->testIndex++; + } + parent::endTest($test, $time); } /** - * Registers a matcher with the identification $id. The matcher can later be - * looked up using lookupMatcher() to figure out if it has been invoked. - * - * @param string $id The identification of the matcher - * @param Matcher $matcher The builder which is being registered - * - * @throws MatcherAlreadyRegisteredException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function registerMatcher(string $id, \PHPUnit\Framework\MockObject\Matcher $matcher) : void + public function addError(Test $test, Throwable $t, float $time) : void { - if (isset($this->matcherMap[$id])) { - throw new \PHPUnit\Framework\MockObject\MatcherAlreadyRegisteredException($id); - } - $this->matcherMap[$id] = $matcher; + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, \true); } - public function expects(InvocationOrder $rule) : InvocationMocker + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addWarning(Test $test, Warning $e, float $time) : void { - $matcher = new \PHPUnit\Framework\MockObject\Matcher($rule); - $this->addMatcher($matcher); - return new InvocationMocker($this, $matcher, ...$this->configurableMethods); + $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, \true); } /** - * @throws Exception - * @throws RuntimeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function invoke(\PHPUnit\Framework\MockObject\Invocation $invocation) + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void { - $exception = null; - $hasReturnValue = \false; - $returnValue = null; - foreach ($this->matchers as $match) { - try { - if ($match->matches($invocation)) { - $value = $match->invoked($invocation); - if (!$hasReturnValue) { - $returnValue = $value; - $hasReturnValue = \true; - } - } - } catch (Exception $e) { - $exception = $e; - } + $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, \true); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, \false); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addRiskyTest(Test $test, Throwable $t, float $time) : void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, \false); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addSkippedTest(Test $test, Throwable $t, float $time) : void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, \false); + } + public function writeProgress(string $progress) : void + { + $this->flushOutputBuffer(); + } + public function flush() : void + { + $this->flushOutputBuffer(\true); + } + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose) : void + { + $testName = $test instanceof Reorderable ? $test->sortId() : $test->getName(); + $result = ['className' => $this->formatClassName($test), 'testName' => $testName, 'testMethod' => $this->formatTestName($test), 'message' => '', 'status' => $status, 'time' => $time, 'verbose' => $verbose]; + if ($t !== null) { + $result['message'] = $this->formatTestResultMessage($t, $result); } - if ($exception !== null) { - throw $exception; + $this->testResults[$this->testIndex] = $result; + $this->testNameResultIndex[$testName] = $this->testIndex; + } + protected function formatTestName(Test $test) : string + { + return method_exists($test, 'getName') ? $test->getName() : ''; + } + protected function formatClassName(Test $test) : string + { + return get_class($test); + } + protected function testHasPassed() : bool + { + if (!isset($this->testResults[$this->testIndex]['status'])) { + return \true; } - if ($hasReturnValue) { - return $returnValue; + if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { + return \true; } - if (!$this->returnValueGeneration) { - $exception = new \PHPUnit\Framework\MockObject\ReturnValueNotConfiguredException($invocation); - if (strtolower($invocation->getMethodName()) === '__tostring') { - $this->deferredError = $exception; - return ''; + return \false; + } + protected function flushOutputBuffer(bool $forceFlush = \false) : void + { + if ($this->testFlushIndex === $this->testIndex) { + return; + } + if ($this->testFlushIndex > 0) { + if ($this->enableOutputBuffer && isset($this->originalExecutionOrder[$this->testFlushIndex - 1])) { + $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); + } else { + $prevResult = $this->testResults[$this->testFlushIndex - 1]; } - throw $exception; + } else { + $prevResult = $this->getEmptyTestResult(); + } + if (!$this->enableOutputBuffer) { + $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); + } else { + do { + $flushed = \false; + if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { + $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); + } else { + // This test(name) cannot found in original execution order, + // flush result to output stream right away + $result = $this->testResults[$this->testFlushIndex]; + } + if (!empty($result)) { + $this->hideSpinner(); + $this->writeTestResult($prevResult, $result); + $this->testFlushIndex++; + $prevResult = $result; + $flushed = \true; + } else { + $this->showSpinner(); + } + } while ($flushed && $this->testFlushIndex < $this->testIndex); } - return $invocation->generateReturnValue(); } - public function matches(\PHPUnit\Framework\MockObject\Invocation $invocation) : bool + protected function showSpinner() : void { - foreach ($this->matchers as $matcher) { - if (!$matcher->matches($invocation)) { - return \false; - } + if (!$this->showProgress) { + return; } - return \true; + if ($this->spinState) { + $this->undrawSpinner(); + } + $this->spinState++; + $this->drawSpinner(); } - /** - * @throws Throwable - */ - public function verify() : void + protected function hideSpinner() : void { - foreach ($this->matchers as $matcher) { - $matcher->verify(); + if (!$this->showProgress) { + return; } - if ($this->deferredError) { - throw $this->deferredError; + if ($this->spinState) { + $this->undrawSpinner(); } + $this->spinState = 0; } - private function addMatcher(\PHPUnit\Framework\MockObject\Matcher $matcher) : void + protected function drawSpinner() : void { - $this->matchers[] = $matcher; + // optional for CLI printers: show the user a 'buffering output' spinner + } + protected function undrawSpinner() : void + { + // remove the spinner from the current line + } + protected function writeTestResult(array $prevResult, array $result) : void + { + } + protected function getEmptyTestResult() : array + { + return ['className' => '', 'testName' => '', 'message' => '', 'failed' => '', 'verbose' => '']; + } + protected function getTestResultByName(?string $testName) : array + { + if (isset($this->testNameResultIndex[$testName])) { + return $this->testResults[$this->testNameResultIndex[$testName]]; + } + return []; + } + protected function formatThrowable(Throwable $t, ?int $status = null) : string + { + $message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); + if ($message) { + $message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t); + } else { + $message = $this->formatStacktrace($t); + } + return $message; + } + protected function formatStacktrace(Throwable $t) : string + { + return \PHPUnit\Util\Filter::getFilteredStacktrace($t); + } + protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│') : string + { + $message = $this->formatThrowable($t, $result['status']); + if ($message === '') { + return ''; + } + if (!($this->verbose || $result['verbose'])) { + return ''; + } + return $this->prefixLines($prefix, $message); + } + protected function prefixLines(string $prefix, string $message) : string + { + $message = trim($message); + return implode(PHP_EOL, array_map(static function (string $text) use($prefix) { + return ' ' . $prefix . ($text ? ' ' . $text : ''); + }, preg_split('/\\r\\n|\\r|\\n/', $message))); } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface SelfDescribing -{ - /** - * Returns a string representation of the object. - */ - public function toString() : string; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const LC_ALL; -use const LC_COLLATE; -use const LC_CTYPE; -use const LC_MONETARY; -use const LC_NUMERIC; -use const LC_TIME; -use const PATHINFO_FILENAME; -use const PHP_EOL; -use const PHP_URL_PATH; -use function array_filter; -use function array_flip; -use function array_keys; -use function array_merge; -use function array_pop; -use function array_search; -use function array_unique; -use function array_values; -use function basename; -use function call_user_func; -use function chdir; -use function class_exists; -use function clearstatcache; -use function count; -use function debug_backtrace; -use function defined; -use function explode; -use function get_class; -use function get_include_path; -use function getcwd; -use function implode; -use function in_array; -use function ini_set; -use function is_array; -use function is_callable; -use function is_int; -use function is_object; -use function is_string; -use function libxml_clear_errors; -use function method_exists; -use function ob_end_clean; -use function ob_get_contents; -use function ob_get_level; -use function ob_start; -use function parse_url; -use function pathinfo; -use function preg_replace; -use function serialize; -use function setlocale; -use function sprintf; -use function strpos; -use function substr; -use function trim; -use function var_export; -use PHPUnit\DeepCopy\DeepCopy; -use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint; -use PHPUnit\Framework\Constraint\ExceptionCode; -use PHPUnit\Framework\Constraint\ExceptionMessage; -use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression; -use PHPUnit\Framework\Constraint\LogicalOr; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning as WarningError; -use PHPUnit\Framework\MockObject\Generator as MockGenerator; -use PHPUnit\Framework\MockObject\MockBuilder; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; -use PHPUnit\Framework\MockObject\Stub; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; -use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\GlobalState; -use PHPUnit\Util\PHP\AbstractPhpProcess; -use PHPUnit\Util\Test as TestUtil; -use PHPUnit\Util\Type; -use Prophecy\Exception\Prediction\PredictionException; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophet; -use ReflectionClass; -use ReflectionException; -use PHPUnit\SebastianBergmann\Comparator\Comparator; -use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; -use PHPUnit\SebastianBergmann\Diff\Differ; -use PHPUnit\SebastianBergmann\Exporter\Exporter; -use PHPUnit\SebastianBergmann\GlobalState\ExcludeList; -use PHPUnit\SebastianBergmann\GlobalState\Restorer; -use PHPUnit\SebastianBergmann\GlobalState\Snapshot; -use PHPUnit\SebastianBergmann\ObjectEnumerator\Enumerator; -use PHPUnit\SebastianBergmann\Template\Template; -use SoapClient; -use Throwable; +namespace PHPUnit\Util\TestDox; + +use PHPUnit\Framework\TestResult; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -abstract class TestCase extends \PHPUnit\Framework\Assert implements \PHPUnit\Framework\Reorderable, \PHPUnit\Framework\SelfDescribing, \PHPUnit\Framework\Test +final class TextResultPrinter extends \PHPUnit\Util\TestDox\ResultPrinter { - private const LOCALE_CATEGORIES = [\LC_ALL, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY, \LC_NUMERIC, \LC_TIME]; - /** - * @var ?bool - */ - protected $backupGlobals; - /** - * @var string[] - */ - protected $backupGlobalsExcludeList = []; - /** - * @var string[] - * - * @deprecated Use $backupGlobalsExcludeList instead - */ - protected $backupGlobalsBlacklist = []; - /** - * @var bool - */ - protected $backupStaticAttributes; - /** - * @var array> - */ - protected $backupStaticAttributesExcludeList = []; - /** - * @var array> - * - * @deprecated Use $backupStaticAttributesExcludeList instead - */ - protected $backupStaticAttributesBlacklist = []; - /** - * @var bool - */ - protected $runTestInSeparateProcess; - /** - * @var bool - */ - protected $preserveGlobalState = \true; - /** - * @var list - */ - protected $providedTests = []; - /** - * @var bool - */ - private $runClassInSeparateProcess; - /** - * @var bool - */ - private $inIsolation = \false; - /** - * @var array - */ - private $data; - /** - * @var int|string - */ - private $dataName; - /** - * @var null|string - */ - private $expectedException; - /** - * @var null|string - */ - private $expectedExceptionMessage; - /** - * @var null|string - */ - private $expectedExceptionMessageRegExp; - /** - * @var null|int|string - */ - private $expectedExceptionCode; - /** - * @var string - */ - private $name = ''; - /** - * @var list - */ - private $dependencies = []; - /** - * @var array - */ - private $dependencyInput = []; - /** - * @var array - */ - private $iniSettings = []; - /** - * @var array - */ - private $locale = []; - /** - * @var MockObject[] - */ - private $mockObjects = []; - /** - * @var MockGenerator - */ - private $mockObjectGenerator; - /** - * @var int - */ - private $status = BaseTestRunner::STATUS_UNKNOWN; - /** - * @var string - */ - private $statusMessage = ''; - /** - * @var int - */ - private $numAssertions = 0; - /** - * @var TestResult - */ - private $result; - /** - * @var mixed - */ - private $testResult; - /** - * @var string - */ - private $output = ''; - /** - * @var ?string - */ - private $outputExpectedRegex; - /** - * @var ?string - */ - private $outputExpectedString; - /** - * @var mixed - */ - private $outputCallback = \false; - /** - * @var bool - */ - private $outputBufferingActive = \false; + public function printResult(TestResult $result) : void + { + } /** - * @var int + * Handler for 'start class' event. */ - private $outputBufferingLevel; + protected function startClass(string $name) : void + { + $this->write($this->currentTestClassPrettified . "\n"); + } /** - * @var bool + * Handler for 'on test' event. */ - private $outputRetrievedForAssertion = \false; + protected function onTest(string $name, bool $success = \true) : void + { + if ($success) { + $this->write(' [x] '); + } else { + $this->write(' [ ] '); + } + $this->write($name . "\n"); + } /** - * @var ?Snapshot + * Handler for 'end class' event. */ - private $snapshot; + protected function endClass(string $name) : void + { + $this->write("\n"); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use function array_filter; +use function get_class; +use function implode; +use function strpos; +use DOMDocument; +use DOMElement; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Framework\WarningTestCase; +use PHPUnit\Util\Printer; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use ReflectionException; +use Throwable; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlResultPrinter extends Printer implements TestListener +{ /** - * @var \Prophecy\Prophet + * @var DOMDocument */ - private $prophet; + private $document; /** - * @var bool + * @var DOMElement */ - private $beStrictAboutChangesToGlobalState = \false; + private $root; /** - * @var bool + * @var NamePrettifier */ - private $registerMockObjectsFromTestArgumentsRecursively = \false; + private $prettifier; /** - * @var string[] + * @var null|Throwable */ - private $warnings = []; + private $exception; /** - * @var string[] + * @param resource|string $out + * + * @throws Exception */ - private $groups = []; + public function __construct($out = null) + { + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = \true; + $this->root = $this->document->createElement('tests'); + $this->document->appendChild($this->root); + $this->prettifier = new \PHPUnit\Util\TestDox\NamePrettifier(); + parent::__construct($out); + } /** - * @var bool + * Flush buffer and close output. */ - private $doesNotPerformAssertions = \false; + public function flush() : void + { + $this->write($this->document->saveXML()); + parent::flush(); + } /** - * @var Comparator[] + * An error occurred. */ - private $customComparators = []; + public function addError(Test $test, Throwable $t, float $time) : void + { + $this->exception = $t; + } /** - * @var string[] + * A warning occurred. */ - private $doubledTypes = []; + public function addWarning(Test $test, Warning $e, float $time) : void + { + } /** - * Returns a matcher that matches when the method is executed - * zero or more times. + * A failure occurred. */ - public static function any() : AnyInvokedCountMatcher + public function addFailure(Test $test, AssertionFailedError $e, float $time) : void { - return new AnyInvokedCountMatcher(); + $this->exception = $e; } /** - * Returns a matcher that matches when the method is never executed. + * Incomplete test. */ - public static function never() : InvokedCountMatcher + public function addIncompleteTest(Test $test, Throwable $t, float $time) : void { - return new InvokedCountMatcher(0); } /** - * Returns a matcher that matches when the method is executed - * at least N times. + * Risky test. */ - public static function atLeast(int $requiredInvocations) : InvokedAtLeastCountMatcher + public function addRiskyTest(Test $test, Throwable $t, float $time) : void { - return new InvokedAtLeastCountMatcher($requiredInvocations); } /** - * Returns a matcher that matches when the method is executed at least once. + * Skipped test. */ - public static function atLeastOnce() : InvokedAtLeastOnceMatcher + public function addSkippedTest(Test $test, Throwable $t, float $time) : void { - return new InvokedAtLeastOnceMatcher(); } /** - * Returns a matcher that matches when the method is executed exactly once. + * A test suite started. */ - public static function once() : InvokedCountMatcher + public function startTestSuite(TestSuite $suite) : void { - return new InvokedCountMatcher(1); } /** - * Returns a matcher that matches when the method is executed - * exactly $count times. + * A test suite ended. */ - public static function exactly(int $count) : InvokedCountMatcher + public function endTestSuite(TestSuite $suite) : void { - return new InvokedCountMatcher($count); } /** - * Returns a matcher that matches when the method is executed - * at most N times. + * A test started. */ - public static function atMost(int $allowedInvocations) : InvokedAtMostCountMatcher + public function startTest(Test $test) : void { - return new InvokedAtMostCountMatcher($allowedInvocations); + $this->exception = null; } /** - * Returns a matcher that matches when the method is executed - * at the given index. + * A test ended. * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 - * @codeCoverageIgnore + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function at(int $index) : InvokedAtIndexMatcher + public function endTest(Test $test, float $time) : void { - $stack = debug_backtrace(); - while (!empty($stack)) { - $frame = array_pop($stack); - if (isset($frame['object']) && $frame['object'] instanceof self) { - $frame['object']->addWarning('The at() matcher has been deprecated. It will be removed in PHPUnit 10. Please refactor your test to not rely on the order in which methods are invoked.'); - break; + if (!$test instanceof TestCase || $test instanceof WarningTestCase) { + return; + } + $groups = array_filter($test->getGroups(), static function ($group) { + return !($group === 'small' || $group === 'medium' || $group === 'large' || strpos($group, '__phpunit_') === 0); + }); + $testNode = $this->document->createElement('test'); + $testNode->setAttribute('className', get_class($test)); + $testNode->setAttribute('methodName', $test->getName()); + $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); + $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); + $testNode->setAttribute('status', (string) $test->getStatus()); + $testNode->setAttribute('time', (string) $time); + $testNode->setAttribute('size', (string) $test->getSize()); + $testNode->setAttribute('groups', implode(',', $groups)); + foreach ($groups as $group) { + $groupNode = $this->document->createElement('group'); + $groupNode->setAttribute('name', $group); + $testNode->appendChild($groupNode); + } + $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); + foreach (['class', 'method'] as $type) { + foreach ($annotations[$type] as $annotation => $values) { + if ($annotation !== 'covers' && $annotation !== 'uses') { + continue; + } + foreach ($values as $value) { + $coversNode = $this->document->createElement($annotation); + $coversNode->setAttribute('target', $value); + $testNode->appendChild($coversNode); + } } } - return new InvokedAtIndexMatcher($index); - } - public static function returnValue($value) : ReturnStub - { - return new ReturnStub($value); - } - public static function returnValueMap(array $valueMap) : ReturnValueMapStub - { - return new ReturnValueMapStub($valueMap); + foreach ($test->doubledTypes() as $doubledType) { + $testDoubleNode = $this->document->createElement('testDouble'); + $testDoubleNode->setAttribute('type', $doubledType); + $testNode->appendChild($testDoubleNode); + } + $inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(get_class($test), $test->getName(\false)); + if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { + $testNode->setAttribute('given', $inlineAnnotations['given']['value']); + $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); + $testNode->setAttribute('when', $inlineAnnotations['when']['value']); + $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); + $testNode->setAttribute('then', $inlineAnnotations['then']['value']); + $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); + } + if ($this->exception !== null) { + if ($this->exception instanceof Exception) { + $steps = $this->exception->getSerializableTrace(); + } else { + $steps = $this->exception->getTrace(); + } + try { + $file = (new ReflectionClass($test))->getFileName(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + foreach ($steps as $step) { + if (isset($step['file']) && $step['file'] === $file) { + $testNode->setAttribute('exceptionLine', (string) $step['line']); + break; + } + } + $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); + } + $this->root->appendChild($testNode); } - public static function returnArgument(int $argumentIndex) : ReturnArgumentStub +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const PHP_EOL; +use function get_class; +use function sprintf; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\PhptTestCase; +use RecursiveIteratorIterator; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TextTestListRenderer +{ + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function render(TestSuite $suite) : string { - return new ReturnArgumentStub($argumentIndex); + $buffer = 'Available test(s):' . PHP_EOL; + foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { + if ($test instanceof TestCase) { + $name = sprintf('%s::%s', get_class($test), str_replace(' with data set ', '', $test->getName())); + } elseif ($test instanceof PhptTestCase) { + $name = $test->getName(); + } else { + continue; + } + $buffer .= sprintf(' - %s' . PHP_EOL, $name); + } + return $buffer; } - public static function returnCallback($callback) : ReturnCallbackStub +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Type +{ + public static function isType(string $type) : bool { - return new ReturnCallbackStub($callback); + switch ($type) { + case 'numeric': + case 'integer': + case 'int': + case 'iterable': + case 'float': + case 'string': + case 'boolean': + case 'bool': + case 'null': + case 'array': + case 'object': + case 'resource': + case 'scalar': + return \true; + default: + return \false; + } } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function in_array; +use function sprintf; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class VersionComparisonOperator +{ /** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. - */ - public static function returnSelf() : ReturnSelfStub - { - return new ReturnSelfStub(); - } - public static function throwException(Throwable $exception) : ExceptionStub + * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + */ + private $operator; + public function __construct(string $operator) { - return new ExceptionStub($exception); + $this->ensureOperatorIsValid($operator); + $this->operator = $operator; } - public static function onConsecutiveCalls(...$args) : ConsecutiveCallsStub + /** + * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' + */ + public function asString() : string { - return new ConsecutiveCallsStub($args); + return $this->operator; } /** - * @param int|string $dataName + * @throws Exception * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @psalm-assert '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator */ - public function __construct(?string $name = null, array $data = [], $dataName = '') + private function ensureOperatorIsValid(string $operator) : void { - if ($name !== null) { - $this->setName($name); + if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], \true)) { + throw new \PHPUnit\Util\Exception(sprintf('"%s" is not a valid version_compare() operator', $operator)); } - $this->data = $data; - $this->dataName = $dataName; } - /** - * This method is called before the first test of this test class is run. - */ - public static function setUpBeforeClass() : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function addslashes; +use function array_map; +use function implode; +use function is_string; +use function realpath; +use function sprintf; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage as FilterConfiguration; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated + */ +final class XdebugFilterScriptGenerator +{ + public function generate(FilterConfiguration $filter) : string { + $files = array_map(static function ($item) { + return sprintf(" '%s'", $item); + }, $this->getItems($filter)); + $files = implode(",\n", $files); + return <<directories() as $directory) { + $path = realpath($directory->path()); + if (is_string($path)) { + $files[] = sprintf(addslashes('%s' . DIRECTORY_SEPARATOR), $path); + } + } + foreach ($filter->files() as $file) { + $files[] = $file->path(); + } + return $files; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const ENT_QUOTES; +use function assert; +use function class_exists; +use function htmlspecialchars; +use function mb_convert_encoding; +use function ord; +use function preg_replace; +use function settype; +use function strlen; +use DOMCharacterData; +use DOMDocument; +use DOMElement; +use DOMNode; +use DOMText; +use ReflectionClass; +use ReflectionException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Xml +{ /** - * This method is called before each test. + * @deprecated Only used by assertEqualXMLStructure() */ - protected function setUp() : void + public static function import(DOMElement $element) : DOMElement { + return (new DOMDocument())->importNode($element, \true); } /** - * This method is called after each test. + * @deprecated Only used by assertEqualXMLStructure() */ - protected function tearDown() : void + public static function removeCharacterDataNodes(DOMNode $node) : void { + if ($node->hasChildNodes()) { + for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { + if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { + $node->removeChild($child); + } + } + } } /** - * Returns a string representation of the test case. + * Escapes a string for the use in XML documents. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, + * and FFFF (not even as character reference). + * + * @see https://www.w3.org/TR/xml/#charsets */ - public function toString() : string - { - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $buffer = sprintf('%s::%s', $class->name, $this->getName(\false)); - return $buffer . $this->getDataSetAsString(); - } - public function count() : int - { - return 1; - } - public function getActualOutputForAssertion() : string - { - $this->outputRetrievedForAssertion = \true; - return $this->getActualOutput(); - } - public function expectOutputRegex(string $expectedRegex) : void - { - $this->outputExpectedRegex = $expectedRegex; - } - public function expectOutputString(string $expectedString) : void + public static function prepareString(string $string) : string { - $this->outputExpectedString = $expectedString; + return preg_replace('/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', htmlspecialchars(self::convertToUtf8($string), ENT_QUOTES)); } /** - * @psalm-param class-string<\Throwable> $exception + * "Convert" a DOMElement object into a PHP variable. */ - public function expectException(string $exception) : void + public static function xmlToVariable(DOMElement $element) { - // @codeCoverageIgnoreStart - switch ($exception) { - case Deprecated::class: - $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Deprecated is deprecated and will be removed in PHPUnit 10. Use expectDeprecation() instead.'); + $variable = null; + switch ($element->tagName) { + case 'array': + $variable = []; + foreach ($element->childNodes as $entry) { + if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { + continue; + } + $item = $entry->childNodes->item(0); + if ($item instanceof DOMText) { + $item = $entry->childNodes->item(1); + } + $value = self::xmlToVariable($item); + if ($entry->hasAttribute('key')) { + $variable[(string) $entry->getAttribute('key')] = $value; + } else { + $variable[] = $value; + } + } break; - case Error::class: - $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Error is deprecated and will be removed in PHPUnit 10. Use expectError() instead.'); + case 'object': + $className = $element->getAttribute('class'); + if ($element->hasChildNodes()) { + $arguments = $element->childNodes->item(0)->childNodes; + $constructorArgs = []; + foreach ($arguments as $argument) { + if ($argument instanceof DOMElement) { + $constructorArgs[] = self::xmlToVariable($argument); + } + } + try { + assert(class_exists($className)); + $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new \PHPUnit\Util\Exception($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } else { + $variable = new $className(); + } break; - case Notice::class: - $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Notice is deprecated and will be removed in PHPUnit 10. Use expectNotice() instead.'); + case 'boolean': + $variable = $element->textContent === 'true'; break; - case WarningError::class: - $this->addWarning('Support for using expectException() with PHPUnit\\Framework\\Error\\Warning is deprecated and will be removed in PHPUnit 10. Use expectWarning() instead.'); + case 'integer': + case 'double': + case 'string': + $variable = $element->textContent; + settype($variable, $element->tagName); break; } - // @codeCoverageIgnoreEnd - $this->expectedException = $exception; - } - /** - * @param int|string $code - */ - public function expectExceptionCode($code) : void - { - $this->expectedExceptionCode = $code; + return $variable; } - public function expectExceptionMessage(string $message) : void + private static function convertToUtf8(string $string) : string { - $this->expectedExceptionMessage = $message; + if (!self::isUtf8($string)) { + $string = mb_convert_encoding($string, 'UTF-8'); + } + return $string; } - public function expectExceptionMessageMatches(string $regularExpression) : void + private static function isUtf8(string $string) : bool { - $this->expectedExceptionMessageRegExp = $regularExpression; + $length = strlen($string); + for ($i = 0; $i < $length; $i++) { + if (ord($string[$i]) < 0x80) { + $n = 0; + } elseif ((ord($string[$i]) & 0xe0) === 0xc0) { + $n = 1; + } elseif ((ord($string[$i]) & 0xf0) === 0xe0) { + $n = 2; + } elseif ((ord($string[$i]) & 0xf0) === 0xf0) { + $n = 3; + } else { + return \false; + } + for ($j = 0; $j < $n; $j++) { + if (++$i === $length || (ord($string[$i]) & 0xc0) !== 0x80) { + return \false; + } + } + } + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use RuntimeException; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class FailedSchemaDetectionResult extends \PHPUnit\Util\Xml\SchemaDetectionResult +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function chdir; +use function dirname; +use function error_reporting; +use function file_get_contents; +use function getcwd; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Loader +{ /** - * Sets up an expectation for an exception to be raised by the code under test. - * Information for expected exception class, expected exception message, and - * expected exception code are retrieved from a given Exception object. + * @throws Exception */ - public function expectExceptionObject(\Exception $exception) : void + public function loadFile(string $filename, bool $isHtml = \false, bool $xinclude = \false, bool $strict = \false) : DOMDocument { - $this->expectException(get_class($exception)); - $this->expectExceptionMessage($exception->getMessage()); - $this->expectExceptionCode($exception->getCode()); + $reporting = error_reporting(0); + $contents = file_get_contents($filename); + error_reporting($reporting); + if ($contents === \false) { + throw new \PHPUnit\Util\Xml\Exception(sprintf('Could not read "%s".', $filename)); + } + return $this->load($contents, $isHtml, $filename, $xinclude, $strict); } - public function expectNotToPerformAssertions() : void + /** + * @throws Exception + */ + public function load(string $actual, bool $isHtml = \false, string $filename = '', bool $xinclude = \false, bool $strict = \false) : DOMDocument { - $this->doesNotPerformAssertions = \true; + if ($actual === '') { + throw new \PHPUnit\Util\Xml\Exception('Could not load XML from empty string'); + } + // Required for XInclude on Windows. + if ($xinclude) { + $cwd = getcwd(); + @chdir(dirname($filename)); + } + $document = new DOMDocument(); + $document->preserveWhiteSpace = \false; + $internal = libxml_use_internal_errors(\true); + $message = ''; + $reporting = error_reporting(0); + if ($filename !== '') { + // Required for XInclude + $document->documentURI = $filename; + } + if ($isHtml) { + $loaded = $document->loadHTML($actual); + } else { + $loaded = $document->loadXML($actual); + } + if (!$isHtml && $xinclude) { + $document->xinclude(); + } + foreach (libxml_get_errors() as $error) { + $message .= "\n" . $error->message; + } + libxml_use_internal_errors($internal); + error_reporting($reporting); + if (isset($cwd)) { + @chdir($cwd); + } + if ($loaded === \false || $strict && $message !== '') { + if ($filename !== '') { + throw new \PHPUnit\Util\Xml\Exception(sprintf('Could not load "%s".%s', $filename, $message !== '' ? "\n" . $message : '')); + } + if ($message === '') { + $message = 'Could not load XML for unknown reason'; + } + throw new \PHPUnit\Util\Xml\Exception($message); + } + return $document; } - public function expectDeprecation() : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +abstract class SchemaDetectionResult +{ + public function detected() : bool { - $this->expectedException = Deprecated::class; + return \false; } - public function expectDeprecationMessage(string $message) : void + /** + * @throws Exception + */ + public function version() : string { - $this->expectExceptionMessage($message); + throw new \PHPUnit\Util\Xml\Exception('No supported schema was detected'); } - public function expectDeprecationMessageMatches(string $regularExpression) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SchemaDetector +{ + /** + * @throws Exception + */ + public function detect(string $filename) : \PHPUnit\Util\Xml\SchemaDetectionResult { - $this->expectExceptionMessageMatches($regularExpression); + $document = (new \PHPUnit\Util\Xml\Loader())->loadFile($filename, \false, \true, \true); + foreach (['9.2', '8.5'] as $candidate) { + $schema = (new \PHPUnit\Util\Xml\SchemaFinder())->find($candidate); + if (!(new \PHPUnit\Util\Xml\Validator())->validate($document, $schema)->hasValidationErrors()) { + return new \PHPUnit\Util\Xml\SuccessfulSchemaDetectionResult($candidate); + } + } + return new \PHPUnit\Util\Xml\FailedSchemaDetectionResult(); } - public function expectNotice() : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function defined; +use function is_file; +use function sprintf; +use PHPUnit\Runner\Version; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SchemaFinder +{ + /** + * @throws Exception + */ + public function find(string $version) : string { - $this->expectedException = Notice::class; + if ($version === Version::series()) { + $filename = $this->path() . 'phpunit.xsd'; + } else { + $filename = $this->path() . 'schema/' . $version . '.xsd'; + } + if (!is_file($filename)) { + throw new \PHPUnit\Util\Xml\Exception(sprintf('Schema for PHPUnit %s is not available', $version)); + } + return $filename; } - public function expectNoticeMessage(string $message) : void + private function path() : string { - $this->expectExceptionMessage($message); + if (defined('__PHPUNIT_PHAR_ROOT__')) { + return \__PHPUNIT_PHAR_ROOT__ . '/'; + } + return __DIR__ . '/../../../'; } - public function expectNoticeMessageMatches(string $regularExpression) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function count; +use ArrayIterator; +use Countable; +use DOMNode; +use DOMNodeList; +use IteratorAggregate; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SnapshotNodeList implements Countable, IteratorAggregate +{ + /** + * @var DOMNode[] + */ + private $nodes = []; + public static function fromNodeList(DOMNodeList $list) : self { - $this->expectExceptionMessageMatches($regularExpression); + $snapshot = new self(); + foreach ($list as $node) { + $snapshot->nodes[] = $node; + } + return $snapshot; } - public function expectWarning() : void + public function count() : int { - $this->expectedException = WarningError::class; + return count($this->nodes); } - public function expectWarningMessage(string $message) : void + public function getIterator() : ArrayIterator { - $this->expectExceptionMessage($message); + return new ArrayIterator($this->nodes); } - public function expectWarningMessageMatches(string $regularExpression) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class SuccessfulSchemaDetectionResult extends \PHPUnit\Util\Xml\SchemaDetectionResult +{ + /** + * @var string + */ + private $version; + public function __construct(string $version) { - $this->expectExceptionMessageMatches($regularExpression); + $this->version = $version; } - public function expectError() : void + public function detected() : bool { - $this->expectedException = Error::class; + return \true; } - public function expectErrorMessage(string $message) : void + public function version() : string { - $this->expectExceptionMessage($message); + return $this->version; } - public function expectErrorMessageMatches(string $regularExpression) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function sprintf; +use function trim; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class ValidationResult +{ + /** + * @psalm-var array> + */ + private $validationErrors = []; + /** + * @psalm-param array $errors + */ + public static function fromArray(array $errors) : self { - $this->expectExceptionMessageMatches($regularExpression); + $validationErrors = []; + foreach ($errors as $error) { + if (!isset($validationErrors[$error->line])) { + $validationErrors[$error->line] = []; + } + $validationErrors[$error->line][] = trim($error->message); + } + return new self($validationErrors); } - public function getStatus() : int + private function __construct(array $validationErrors) { - return $this->status; + $this->validationErrors = $validationErrors; } - public function markAsRisky() : void + public function hasValidationErrors() : bool { - $this->status = BaseTestRunner::STATUS_RISKY; + return !empty($this->validationErrors); } - public function getStatusMessage() : string + public function asString() : string { - return $this->statusMessage; + $buffer = ''; + foreach ($this->validationErrors as $line => $validationErrorsOnLine) { + $buffer .= sprintf(\PHP_EOL . ' Line %d:' . \PHP_EOL, $line); + foreach ($validationErrorsOnLine as $validationError) { + $buffer .= sprintf(' - %s' . \PHP_EOL, $validationError); + } + } + return $buffer; } - public function hasFailed() : bool +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function file_get_contents; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use DOMDocument; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Validator +{ + public function validate(DOMDocument $document, string $xsdFilename) : \PHPUnit\Util\Xml\ValidationResult { - $status = $this->getStatus(); - return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; + $originalErrorHandling = libxml_use_internal_errors(\true); + $document->schemaValidateSource(file_get_contents($xsdFilename)); + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($originalErrorHandling); + return \PHPUnit\Util\Xml\ValidationResult::fromArray($errors); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function get_class; +use function implode; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\PhptTestCase; +use RecursiveIteratorIterator; +use XMLWriter; +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlTestListRenderer +{ /** - * Runs the test case and collects the results in a TestResult object. - * If no TestResult object is passed a new one will be created. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws CodeCoverageException - * @throws UtilException */ - public function run(\PHPUnit\Framework\TestResult $result = null) : \PHPUnit\Framework\TestResult + public function render(TestSuite $suite) : string { - if ($result === null) { - $result = $this->createResult(); - } - if (!$this instanceof \PHPUnit\Framework\ErrorTestCase && !$this instanceof \PHPUnit\Framework\WarningTestCase) { - $this->setTestResultObject($result); - } - if (!$this instanceof \PHPUnit\Framework\ErrorTestCase && !$this instanceof \PHPUnit\Framework\WarningTestCase && !$this instanceof \PHPUnit\Framework\SkippedTestCase && !$this->handleDependencies()) { - return $result; - } - if ($this->runInSeparateProcess()) { - $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($runEntireClass) { - $template = new Template(__DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl'); - } else { - $template = new Template(__DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl'); - } - if ($this->preserveGlobalState) { - $constants = GlobalState::getConstantsAsString(); - $globals = GlobalState::getGlobalsAsString(); - $includedFiles = GlobalState::getIncludedFilesAsString(); - $iniSettings = GlobalState::getIniSettingsAsString(); - } else { - $constants = ''; - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], \true) . ";\n"; - } else { - $globals = ''; + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent(\true); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('tests'); + $currentTestCase = null; + foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { + if ($test instanceof TestCase) { + if (get_class($test) !== $currentTestCase) { + if ($currentTestCase !== null) { + $writer->endElement(); + } + $writer->startElement('testCaseClass'); + $writer->writeAttribute('name', get_class($test)); + $currentTestCase = get_class($test); } - $includedFiles = ''; - $iniSettings = ''; - } - $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; - $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; - $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; - $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; - $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; - $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, \true); - } else { - $composerAutoload = '\'\''; - } - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, \true); - } else { - $phar = '\'\''; - } - $codeCoverage = $result->getCodeCoverage(); - $codeCoverageFilter = null; - $cachesStaticAnalysis = 'false'; - $codeCoverageCacheDirectory = null; - $driverMethod = 'forLineCoverage'; - if ($codeCoverage) { - $codeCoverageFilter = $codeCoverage->filter(); - if ($codeCoverage->collectsBranchAndPathCoverage()) { - $driverMethod = 'forLineAndPathCoverage'; + $writer->startElement('testCaseMethod'); + $writer->writeAttribute('name', $test->getName(\false)); + $writer->writeAttribute('groups', implode(',', $test->getGroups())); + if (!empty($test->getDataSetAsString(\false))) { + $writer->writeAttribute('dataSet', str_replace(' with data set ', '', $test->getDataSetAsString(\false))); } - if ($codeCoverage->cachesStaticAnalysis()) { - $cachesStaticAnalysis = 'true'; - $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); + $writer->endElement(); + } elseif ($test instanceof PhptTestCase) { + if ($currentTestCase !== null) { + $writer->endElement(); + $currentTestCase = null; } + $writer->startElement('phptFile'); + $writer->writeAttribute('path', $test->getName()); + $writer->endElement(); } - $data = var_export(serialize($this->data), \true); - $dataName = var_export($this->dataName, \true); - $dependencyInput = var_export(serialize($this->dependencyInput), \true); - $includePath = var_export(get_include_path(), \true); - $codeCoverageFilter = var_export(serialize($codeCoverageFilter), \true); - $codeCoverageCacheDirectory = var_export(serialize($codeCoverageCacheDirectory), \true); - // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC - // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences - $data = "'." . $data . ".'"; - $dataName = "'.(" . $dataName . ").'"; - $dependencyInput = "'." . $dependencyInput . ".'"; - $includePath = "'." . $includePath . ".'"; - $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; - $codeCoverageCacheDirectory = "'." . $codeCoverageCacheDirectory . ".'"; - $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; - $var = ['composerAutoload' => $composerAutoload, 'phar' => $phar, 'filename' => $class->getFileName(), 'className' => $class->getName(), 'collectCodeCoverageInformation' => $coverage, 'cachesStaticAnalysis' => $cachesStaticAnalysis, 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, 'driverMethod' => $driverMethod, 'data' => $data, 'dataName' => $dataName, 'dependencyInput' => $dependencyInput, 'constants' => $constants, 'globals' => $globals, 'include_path' => $includePath, 'included_files' => $includedFiles, 'iniSettings' => $iniSettings, 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, 'enforcesTimeLimit' => $enforcesTimeLimit, 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, 'codeCoverageFilter' => $codeCoverageFilter, 'configurationFilePath' => $configurationFilePath, 'name' => $this->getName(\false)]; - if (!$runEntireClass) { - $var['methodName'] = $this->name; - } - $template->setVar($var); - $php = AbstractPhpProcess::factory(); - $php->runTestJob($template->render(), $this, $result); - } else { - $result->run($this); } - $this->result = null; - return $result; - } - /** - * Returns a builder object to create mock objects using a fluent interface. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $className - * @psalm-return MockBuilder - */ - public function getMockBuilder(string $className) : MockBuilder - { - $this->recordDoubledType($className); - return new MockBuilder($this, $className); - } - public function registerComparator(Comparator $comparator) : void - { - ComparatorFactory::getInstance()->register($comparator); - $this->customComparators[] = $comparator; - } - /** - * @return string[] - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function doubledTypes() : array - { - return array_unique($this->doubledTypes); - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getGroups() : array - { - return $this->groups; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setGroups(array $groups) : void - { - $this->groups = $groups; + if ($currentTestCase !== null) { + $writer->endElement(); + } + $writer->endElement(); + $writer->endDocument(); + return $writer->outputMemory(); } +} + + + + + phpunit + phpunit + 9.5.26 + The PHP Unit Testing framework. + + + BSD-3-Clause + + + pkg:composer/phpunit/phpunit@9.5.26 + + + doctrine + instantiator + 1.4.1 + A small, lightweight utility to instantiate objects in PHP without invoking their constructors + + + MIT + + + pkg:composer/doctrine/instantiator@1.4.1 + + + myclabs + deep-copy + 1.11.0 + Create deep copies (clones) of your objects + + + MIT + + + pkg:composer/myclabs/deep-copy@1.11.0 + + + nikic + php-parser + v4.15.1 + A PHP parser written in PHP + + + BSD-3-Clause + + + pkg:composer/nikic/php-parser@v4.15.1 + + + phar-io + manifest + 2.0.3 + Component for reading phar.io manifest information from a PHP Archive (PHAR) + + + BSD-3-Clause + + + pkg:composer/phar-io/manifest@2.0.3 + + + phar-io + version + 3.2.1 + Library for handling version information and constraints + + + BSD-3-Clause + + + pkg:composer/phar-io/version@3.2.1 + + + phpdocumentor + reflection-common + 2.2.0 + Common reflection classes used by phpdocumentor to reflect the code structure + + + MIT + + + pkg:composer/phpdocumentor/reflection-common@2.2.0 + + + phpdocumentor + reflection-docblock + 5.3.0 + With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock. + + + MIT + + + pkg:composer/phpdocumentor/reflection-docblock@5.3.0 + + + phpdocumentor + type-resolver + 1.6.1 + A PSR-5 based resolver of Class names, Types and Structural Element Names + + + MIT + + + pkg:composer/phpdocumentor/type-resolver@1.6.1 + + + phpspec + prophecy + v1.15.0 + Highly opinionated mocking framework for PHP 5.3+ + + + MIT + + + pkg:composer/phpspec/prophecy@v1.15.0 + + + phpunit + php-code-coverage + 9.2.18 + Library that provides collection, processing, and rendering functionality for PHP code coverage information. + + + BSD-3-Clause + + + pkg:composer/phpunit/php-code-coverage@9.2.18 + + + phpunit + php-file-iterator + 3.0.6 + FilterIterator implementation that filters files based on a list of suffixes. + + + BSD-3-Clause + + + pkg:composer/phpunit/php-file-iterator@3.0.6 + + + phpunit + php-invoker + 3.1.1 + Invoke callables with a timeout + + + BSD-3-Clause + + + pkg:composer/phpunit/php-invoker@3.1.1 + + + phpunit + php-text-template + 2.0.4 + Simple template engine. + + + BSD-3-Clause + + + pkg:composer/phpunit/php-text-template@2.0.4 + + + phpunit + php-timer + 5.0.3 + Utility class for timing + + + BSD-3-Clause + + + pkg:composer/phpunit/php-timer@5.0.3 + + + sebastian + cli-parser + 1.0.1 + Library for parsing CLI options + + + BSD-3-Clause + + + pkg:composer/sebastian/cli-parser@1.0.1 + + + sebastian + code-unit + 1.0.8 + Collection of value objects that represent the PHP code units + + + BSD-3-Clause + + + pkg:composer/sebastian/code-unit@1.0.8 + + + sebastian + code-unit-reverse-lookup + 2.0.3 + Looks up which function or method a line of code belongs to + + + BSD-3-Clause + + + pkg:composer/sebastian/code-unit-reverse-lookup@2.0.3 + + + sebastian + comparator + 4.0.8 + Provides the functionality to compare PHP values for equality + + + BSD-3-Clause + + + pkg:composer/sebastian/comparator@4.0.8 + + + sebastian + complexity + 2.0.2 + Library for calculating the complexity of PHP code units + + + BSD-3-Clause + + + pkg:composer/sebastian/complexity@2.0.2 + + + sebastian + diff + 4.0.4 + Diff implementation + + + BSD-3-Clause + + + pkg:composer/sebastian/diff@4.0.4 + + + sebastian + environment + 5.1.4 + Provides functionality to handle HHVM/PHP environments + + + BSD-3-Clause + + + pkg:composer/sebastian/environment@5.1.4 + + + sebastian + exporter + 4.0.5 + Provides the functionality to export PHP variables for visualization + + + BSD-3-Clause + + + pkg:composer/sebastian/exporter@4.0.5 + + + sebastian + global-state + 5.0.5 + Snapshotting of global state + + + BSD-3-Clause + + + pkg:composer/sebastian/global-state@5.0.5 + + + sebastian + lines-of-code + 1.0.3 + Library for counting the lines of code in PHP source code + + + BSD-3-Clause + + + pkg:composer/sebastian/lines-of-code@1.0.3 + + + sebastian + object-enumerator + 4.0.4 + Traverses array structures and object graphs to enumerate all referenced objects + + + BSD-3-Clause + + + pkg:composer/sebastian/object-enumerator@4.0.4 + + + sebastian + object-reflector + 2.0.4 + Allows reflection of object attributes, including inherited and non-public ones + + + BSD-3-Clause + + + pkg:composer/sebastian/object-reflector@2.0.4 + + + sebastian + recursion-context + 4.0.4 + Provides functionality to recursively process PHP variables + + + BSD-3-Clause + + + pkg:composer/sebastian/recursion-context@4.0.4 + + + sebastian + resource-operations + 3.0.3 + Provides a list of PHP built-in functions that operate on resources + + + BSD-3-Clause + + + pkg:composer/sebastian/resource-operations@3.0.3 + + + sebastian + type + 3.2.0 + Collection of value objects that represent the types of the PHP type system + + + BSD-3-Clause + + + pkg:composer/sebastian/type@3.2.0 + + + sebastian + version + 3.0.2 + Library that helps with managing the version number of Git-hosted PHP projects + + + BSD-3-Clause + + + pkg:composer/sebastian/version@3.0.2 + + + theseer + tokenizer + 1.2.1 + A small library for converting tokenized PHP source code into XML and potentially other formats + + + BSD-3-Clause + + + pkg:composer/theseer/tokenizer@1.2.1 + + + webmozart + assert + 1.11.0 + Assertions to validate method input/output with nice error messages. + + + MIT + + + pkg:composer/webmozart/assert@1.11.0 + + + + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 8.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +sebastian/cli-parser + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use function array_map; +use function array_merge; +use function array_shift; +use function array_slice; +use function assert; +use function count; +use function current; +use function explode; +use function is_array; +use function is_int; +use function is_string; +use function key; +use function next; +use function preg_replace; +use function reset; +use function sort; +use function strlen; +use function strpos; +use function strstr; +use function substr; +final class Parser +{ /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @psalm-param list $argv + * @psalm-param list $longOptions * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws AmbiguousOptionException + * @throws RequiredOptionArgumentMissingException + * @throws OptionDoesNotAllowArgumentException + * @throws UnknownOptionException */ - public function getName(bool $withDataSet = \true) : string + public function parse(array $argv, string $shortOptions, array $longOptions = null) : array { - if ($withDataSet) { - return $this->name . $this->getDataSetAsString(\false); + if (empty($argv)) { + return [[], []]; } - return $this->name; - } - /** - * Returns the size of the test. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getSize() : int - { - return TestUtil::getSize(static::class, $this->getName(\false)); - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function hasSize() : bool - { - return $this->getSize() !== TestUtil::UNKNOWN; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function isSmall() : bool - { - return $this->getSize() === TestUtil::SMALL; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function isMedium() : bool - { - return $this->getSize() === TestUtil::MEDIUM; - } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function isLarge() : bool - { - return $this->getSize() === TestUtil::LARGE; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getActualOutput() : string - { - if (!$this->outputBufferingActive) { - return $this->output; + $options = []; + $nonOptions = []; + if ($longOptions) { + sort($longOptions); } - return (string) ob_get_contents(); - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function hasOutput() : bool - { - if ($this->output === '') { - return \false; + if (isset($argv[0][0]) && $argv[0][0] !== '-') { + array_shift($argv); } - if ($this->hasExpectationOnOutput()) { - return \false; + reset($argv); + $argv = array_map('trim', $argv); + while (\false !== ($arg = current($argv))) { + $i = key($argv); + assert(is_int($i)); + next($argv); + if ($arg === '') { + continue; + } + if ($arg === '--') { + $nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1)); + break; + } + if ($arg[0] !== '-' || strlen($arg) > 1 && $arg[1] === '-' && !$longOptions) { + $nonOptions[] = $arg; + continue; + } + if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) { + $this->parseLongOption(substr($arg, 2), $longOptions, $options, $argv); + } else { + $this->parseShortOption(substr($arg, 1), $shortOptions, $options, $argv); + } } - return \true; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function doesNotPerformAssertions() : bool - { - return $this->doesNotPerformAssertions; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function hasExpectationOnOutput() : bool - { - return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedException() : ?string - { - return $this->expectedException; - } - /** - * @return null|int|string - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionCode() - { - return $this->expectedExceptionCode; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionMessage() : ?string - { - return $this->expectedExceptionMessage; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionMessageRegExp() : ?string - { - return $this->expectedExceptionMessageRegExp; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag) : void - { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + return [$options, $nonOptions]; } /** - * @throws Throwable - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws RequiredOptionArgumentMissingException */ - public function runBare() : void + private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args) : void { - $this->numAssertions = 0; - $this->snapshotGlobalState(); - $this->startOutputBuffering(); - clearstatcache(); - $currentWorkingDirectory = getcwd(); - $hookMethods = TestUtil::getHookMethods(static::class); - $hasMetRequirements = \false; - try { - $this->checkRequirements(); - $hasMetRequirements = \true; - if ($this->inIsolation) { - foreach ($hookMethods['beforeClass'] as $method) { - $this->{$method}(); - } - } - $this->setDoesNotPerformAssertionsFromAnnotation(); - foreach ($hookMethods['before'] as $method) { - $this->{$method}(); - } - foreach ($hookMethods['preCondition'] as $method) { - $this->{$method}(); - } - $this->testResult = $this->runTest(); - $this->verifyMockObjects(); - foreach ($hookMethods['postCondition'] as $method) { - $this->{$method}(); - } - if (!empty($this->warnings)) { - throw new \PHPUnit\Framework\Warning(implode("\n", array_unique($this->warnings))); + $argLength = strlen($arg); + for ($i = 0; $i < $argLength; $i++) { + $option = $arg[$i]; + $optionArgument = null; + if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === \false) { + throw new UnknownOptionException('-' . $option); } - $this->status = BaseTestRunner::STATUS_PASSED; - } catch (\PHPUnit\Framework\IncompleteTest $e) { - $this->status = BaseTestRunner::STATUS_INCOMPLETE; - $this->statusMessage = $e->getMessage(); - } catch (\PHPUnit\Framework\SkippedTest $e) { - $this->status = BaseTestRunner::STATUS_SKIPPED; - $this->statusMessage = $e->getMessage(); - } catch (\PHPUnit\Framework\Warning $e) { - $this->status = BaseTestRunner::STATUS_WARNING; - $this->statusMessage = $e->getMessage(); - } catch (\PHPUnit\Framework\AssertionFailedError $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (PredictionException $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (Throwable $_e) { - $e = $_e; - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - $this->mockObjects = []; - $this->prophet = null; - // Tear down the fixture. An exception raised in tearDown() will be - // caught and passed on when no exception was raised before. - try { - if ($hasMetRequirements) { - foreach ($hookMethods['after'] as $method) { - $this->{$method}(); + assert(is_string($spec)); + if (strlen($spec) > 1 && $spec[1] === ':') { + if ($i + 1 < $argLength) { + $opts[] = [$option, substr($arg, $i + 1)]; + break; } - if ($this->inIsolation) { - foreach ($hookMethods['afterClass'] as $method) { - $this->{$method}(); + if (!(strlen($spec) > 2 && $spec[2] === ':')) { + $optionArgument = current($args); + if (!$optionArgument) { + throw new RequiredOptionArgumentMissingException('-' . $option); } + assert(is_string($optionArgument)); + next($args); } } - } catch (Throwable $_e) { - $e = $e ?? $_e; - } - try { - $this->stopOutputBuffering(); - } catch (\PHPUnit\Framework\RiskyTestError $_e) { - $e = $e ?? $_e; - } - if (isset($_e)) { - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - clearstatcache(); - if ($currentWorkingDirectory !== getcwd()) { - chdir($currentWorkingDirectory); - } - $this->restoreGlobalState(); - $this->unregisterCustomComparators(); - $this->cleanupIniSettings(); - $this->cleanupLocaleSettings(); - libxml_clear_errors(); - // Perform assertion on output. - if (!isset($e)) { - try { - if ($this->outputExpectedRegex !== null) { - $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); - } elseif ($this->outputExpectedString !== null) { - $this->assertEquals($this->outputExpectedString, $this->output); - } - } catch (Throwable $_e) { - $e = $_e; - } - } - // Workaround for missing "finally". - if (isset($e)) { - if ($e instanceof PredictionException) { - $e = new \PHPUnit\Framework\AssertionFailedError($e->getMessage()); - } - $this->onNotSuccessfulTest($e); - } - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setName(string $name) : void - { - $this->name = $name; - if (is_callable($this->sortId(), \true)) { - $this->providedTests = [new \PHPUnit\Framework\ExecutionOrderDependency($this->sortId())]; + $opts[] = [$option, $optionArgument]; } } /** - * @param list $dependencies + * @psalm-param list $longOptions * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setDependencies(array $dependencies) : void - { - $this->dependencies = $dependencies; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setDependencyInput(array $dependencyInput) : void - { - $this->dependencyInput = $dependencyInput; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState) : void - { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setBackupGlobals(?bool $backupGlobals) : void - { - if ($this->backupGlobals === null && $backupGlobals !== null) { - $this->backupGlobals = $backupGlobals; - } - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setBackupStaticAttributes(?bool $backupStaticAttributes) : void - { - if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { - $this->backupStaticAttributes = $backupStaticAttributes; - } - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws AmbiguousOptionException + * @throws RequiredOptionArgumentMissingException + * @throws OptionDoesNotAllowArgumentException + * @throws UnknownOptionException */ - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess) : void + private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args) : void { - if ($this->runTestInSeparateProcess === null) { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; + $count = count($longOptions); + $list = explode('=', $arg); + $option = $list[0]; + $optionArgument = null; + if (count($list) > 1) { + $optionArgument = $list[1]; } - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess) : void - { - if ($this->runClassInSeparateProcess === null) { - $this->runClassInSeparateProcess = $runClassInSeparateProcess; + $optionLength = strlen($option); + foreach ($longOptions as $i => $longOption) { + $opt_start = substr($longOption, 0, $optionLength); + if ($opt_start !== $option) { + continue; + } + $opt_rest = substr($longOption, $optionLength); + if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) { + throw new AmbiguousOptionException('--' . $option); + } + if (substr($longOption, -1) === '=') { + /* @noinspection StrlenInEmptyStringCheckContextInspection */ + if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) { + if (\false === ($optionArgument = current($args))) { + throw new RequiredOptionArgumentMissingException('--' . $option); + } + next($args); + } + } elseif ($optionArgument) { + throw new OptionDoesNotAllowArgumentException('--' . $option); + } + $fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption); + $opts[] = [$fullOption, $optionArgument]; + return; } + throw new UnknownOptionException('--' . $option); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setPreserveGlobalState(bool $preserveGlobalState) : void - { - $this->preserveGlobalState = $preserveGlobalState; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setInIsolation(bool $inIsolation) : void - { - $this->inIsolation = $inIsolation; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function isInIsolation() : bool - { - return $this->inIsolation; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getResult() - { - return $this->testResult; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setResult($result) : void - { - $this->testResult = $result; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setOutputCallback(callable $callback) : void - { - $this->outputCallback = $callback; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getTestResultObject() : ?\PHPUnit\Framework\TestResult - { - return $this->result; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setTestResultObject(\PHPUnit\Framework\TestResult $result) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; +final class AmbiguousOptionException extends RuntimeException implements Exception +{ + public function __construct(string $option) { - $this->result = $result; + parent::__construct(sprintf('Option "%s" is ambiguous', $option)); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function registerMockObject(MockObject $mockObject) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; +final class OptionDoesNotAllowArgumentException extends RuntimeException implements Exception +{ + public function __construct(string $option) { - $this->mockObjects[] = $mockObject; + parent::__construct(sprintf('Option "%s" does not allow an argument', $option)); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function addToAssertionCount(int $count) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; +final class RequiredOptionArgumentMissingException extends RuntimeException implements Exception +{ + public function __construct(string $option) { - $this->numAssertions += $count; + parent::__construct(sprintf('Required argument for option "%s" is missing', $option)); } - /** - * Returns the number of assertions performed by this test. - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getNumAssertions() : int +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; +final class UnknownOptionException extends RuntimeException implements Exception +{ + public function __construct(string $option) { - return $this->numAssertions; + parent::__construct(sprintf('Unknown option "%s"', $option)); } +} +code-unit-reverse-lookup + +Copyright (c) 2016-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnitReverseLookup; + +use function array_merge; +use function assert; +use function get_declared_classes; +use function get_declared_traits; +use function get_defined_functions; +use function is_array; +use function range; +use ReflectionClass; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; +/** + * @since Class available since Release 1.0.0 + */ +class Wizard +{ /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @var array */ - public function usesDataProvider() : bool - { - return !empty($this->data); - } + private $lookupTable = []; /** - * @return int|string - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @var array */ - public function dataName() - { - return $this->dataName; - } + private $processedClasses = []; /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @var array */ - public function getDataSetAsString(bool $includeData = \true) : string - { - $buffer = ''; - if (!empty($this->data)) { - if (is_int($this->dataName)) { - $buffer .= sprintf(' with data set #%d', $this->dataName); - } else { - $buffer .= sprintf(' with data set "%s"', $this->dataName); - } - if ($includeData) { - $exporter = new Exporter(); - $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); - } - } - return $buffer; - } + private $processedFunctions = []; /** - * Gets the data set of a TestCase. + * @param string $filename + * @param int $lineNumber * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getProvidedData() : array - { - return $this->data; - } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @return string */ - public function addWarning(string $warning) : void - { - $this->warnings[] = $warning; - } - public function sortId() : string + public function lookup($filename, $lineNumber) { - $id = $this->name; - if (strpos($id, '::') === \false) { - $id = static::class . '::' . $id; + if (!isset($this->lookupTable[$filename][$lineNumber])) { + $this->updateLookupTable(); } - if ($this->usesDataProvider()) { - $id .= $this->getDataSetAsString(\false); + if (isset($this->lookupTable[$filename][$lineNumber])) { + return $this->lookupTable[$filename][$lineNumber]; } - return $id; - } - /** - * Returns the normalized test name as class::method. - * - * @return list - */ - public function provides() : array - { - return $this->providedTests; + return $filename . ':' . $lineNumber; } - /** - * Returns a list of normalized dependency names, class::method. - * - * This list can differ from the raw dependencies as the resolver has - * no need for the [!][shallow]clone prefix that is filtered out - * during normalization. - * - * @return list - */ - public function requires() : array + private function updateLookupTable() : void { - return $this->dependencies; + $this->processClassesAndTraits(); + $this->processFunctions(); } - /** - * Override to run the test and assert its state. - * - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - * @throws AssertionFailedError - * @throws Exception - * @throws ExpectationFailedException - * @throws Throwable - */ - protected function runTest() + private function processClassesAndTraits() : void { - if (trim($this->name) === '') { - throw new \PHPUnit\Framework\Exception('PHPUnit\\Framework\\TestCase::$name must be a non-blank string.'); - } - $testArguments = array_merge($this->data, $this->dependencyInput); - $this->registerMockObjectsFromTestArguments($testArguments); - try { - $testResult = $this->{$this->name}(...array_values($testArguments)); - } catch (Throwable $exception) { - if (!$this->checkExceptionExpectations($exception)) { - throw $exception; - } - if ($this->expectedException !== null) { - if ($this->expectedException === Error::class) { - $this->assertThat($exception, LogicalOr::fromConstraints(new ExceptionConstraint(Error::class), new ExceptionConstraint(\Error::class))); - } else { - $this->assertThat($exception, new ExceptionConstraint($this->expectedException)); - } - } - if ($this->expectedExceptionMessage !== null) { - $this->assertThat($exception, new ExceptionMessage($this->expectedExceptionMessage)); - } - if ($this->expectedExceptionMessageRegExp !== null) { - $this->assertThat($exception, new ExceptionMessageRegularExpression($this->expectedExceptionMessageRegExp)); + $classes = get_declared_classes(); + $traits = get_declared_traits(); + assert(is_array($classes)); + assert(is_array($traits)); + foreach (array_merge($classes, $traits) as $classOrTrait) { + if (isset($this->processedClasses[$classOrTrait])) { + continue; } - if ($this->expectedExceptionCode !== null) { - $this->assertThat($exception, new ExceptionCode($this->expectedExceptionCode)); + $reflector = new ReflectionClass($classOrTrait); + foreach ($reflector->getMethods() as $method) { + $this->processFunctionOrMethod($method); } - return; - } - if ($this->expectedException !== null) { - $this->assertThat(null, new ExceptionConstraint($this->expectedException)); - } elseif ($this->expectedExceptionMessage !== null) { - $this->numAssertions++; - throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with message "%s" is thrown', $this->expectedExceptionMessage)); - } elseif ($this->expectedExceptionMessageRegExp !== null) { - $this->numAssertions++; - throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with message matching "%s" is thrown', $this->expectedExceptionMessageRegExp)); - } elseif ($this->expectedExceptionCode !== null) { - $this->numAssertions++; - throw new \PHPUnit\Framework\AssertionFailedError(sprintf('Failed asserting that exception with code "%s" is thrown', $this->expectedExceptionCode)); + $this->processedClasses[$classOrTrait] = \true; } - return $testResult; } - /** - * This method is a wrapper for the ini_set() function that automatically - * resets the modified php.ini setting to its original value after the - * test is run. - * - * @throws Exception - */ - protected function iniSet(string $varName, string $newValue) : void + private function processFunctions() : void { - $currentValue = ini_set($varName, $newValue); - if ($currentValue !== \false) { - $this->iniSettings[$varName] = $currentValue; - } else { - throw new \PHPUnit\Framework\Exception(sprintf('INI setting "%s" could not be set to "%s".', $varName, $newValue)); + foreach (get_defined_functions()['user'] as $function) { + if (isset($this->processedFunctions[$function])) { + continue; + } + $this->processFunctionOrMethod(new ReflectionFunction($function)); + $this->processedFunctions[$function] = \true; } } - /** - * This method is a wrapper for the setlocale() function that automatically - * resets the locale to its original value after the test is run. - * - * @throws Exception - */ - protected function setLocale(...$args) : void + private function processFunctionOrMethod(ReflectionFunctionAbstract $functionOrMethod) : void { - if (count($args) < 2) { - throw new \PHPUnit\Framework\Exception(); + if ($functionOrMethod->isInternal()) { + return; } - [$category, $locale] = $args; - if (!in_array($category, self::LOCALE_CATEGORIES, \true)) { - throw new \PHPUnit\Framework\Exception(); + $name = $functionOrMethod->getName(); + if ($functionOrMethod instanceof ReflectionMethod) { + $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name; } - if (!is_array($locale) && !is_string($locale)) { - throw new \PHPUnit\Framework\Exception(); + if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) { + $this->lookupTable[$functionOrMethod->getFileName()] = []; } - $this->locale[$category] = setlocale($category, 0); - $result = setlocale(...$args); - if ($result === \false) { - throw new \PHPUnit\Framework\Exception('The locale functionality is not implemented on your platform, ' . 'the specified locale does not exist or the category name is ' . 'invalid.'); + foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) { + $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name; } } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class ClassMethodUnit extends CodeUnit +{ /** - * Makes configurable stub for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return Stub&RealInstanceType - */ - protected function createStub(string $originalClassName) : Stub - { - return $this->createMockObject($originalClassName); - } - /** - * Returns a mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function createMock(string $originalClassName) : MockObject - { - return $this->createMockObject($originalClassName); - } - /** - * Returns a configured mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @psalm-assert-if-true ClassMethodUnit $this */ - protected function createConfiguredMock(string $originalClassName, array $configuration) : MockObject + public function isClassMethod() : bool { - $o = $this->createMockObject($originalClassName); - foreach ($configuration as $method => $return) { - $o->method($method)->willReturn($return); - } - return $o; + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class ClassUnit extends CodeUnit +{ /** - * Returns a partial mock object for the specified class. - * - * @param string[] $methods - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @psalm-assert-if-true ClassUnit $this */ - protected function createPartialMock(string $originalClassName, array $methods) : MockObject + public function isClass() : bool { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $mockedMethodsThatDontExist = array_filter($methods, static function (string $method) use($reflector) { - return !$reflector->hasMethod($method); - }); - if ($mockedMethodsThatDontExist) { - $this->addWarning(sprintf('createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', implode(', ', $mockedMethodsThatDontExist), $originalClassName)); - } - return $this->getMockBuilder($originalClassName)->disableOriginalConstructor()->disableOriginalClone()->disableArgumentCloning()->disallowMockingUnknownTypes()->setMethods(empty($methods) ? null : $methods)->getMock(); + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +use function range; +use function sprintf; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +/** + * @psalm-immutable + */ +abstract class CodeUnit +{ /** - * Returns a test proxy for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @var string */ - protected function createTestProxy(string $originalClassName, array $constructorArguments = []) : MockObject - { - return $this->getMockBuilder($originalClassName)->setConstructorArgs($constructorArguments)->enableProxyingToOriginalMethods()->getMock(); - } + private $name; /** - * Mocks the specified class and returns the name of the mocked class. - * - * @param null|array $methods $methods - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string|string $originalClassName - * @psalm-return class-string + * @var string */ - protected function getMockClass(string $originalClassName, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \false, bool $callOriginalClone = \true, bool $callAutoload = \true, bool $cloneArguments = \false) : string - { - $this->recordDoubledType($originalClassName); - $mock = $this->getMockObjectGenerator()->getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments); - return get_class($mock); - } + private $sourceFileName; /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods are not mocked by default. - * To mock concrete methods, use the 7th parameter ($mockedMethods). - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @var array + * @psalm-var list */ - protected function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = [], bool $cloneArguments = \false) : MockObject - { - $this->recordDoubledType($originalClassName); - $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass($originalClassName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - $this->registerMockObject($mockObject); - return $mockObject; - } + private $sourceLines; /** - * Returns a mock object based on the given WSDL file. + * @psalm-param class-string $className * - * @psalm-template RealInstanceType of object - * @psalm-param class-string|string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function getMockFromWsdl(string $wsdlFile, string $originalClassName = '', string $mockClassName = '', array $methods = [], bool $callOriginalConstructor = \true, array $options = []) : MockObject + public static function forClass(string $className) : ClassUnit { - $this->recordDoubledType(SoapClient::class); - if ($originalClassName === '') { - $fileName = pathinfo(basename(parse_url($wsdlFile, \PHP_URL_PATH)), \PATHINFO_FILENAME); - $originalClassName = preg_replace('/\\W/', '', $fileName); - } - if (!class_exists($originalClassName)) { - eval($this->getMockObjectGenerator()->generateClassFromWsdl($wsdlFile, $originalClassName, $methods, $options)); - } - $mockObject = $this->getMockObjectGenerator()->getMock($originalClassName, $methods, ['', $options], $mockClassName, $callOriginalConstructor, \false, \false); - $this->registerMockObject($mockObject); - return $mockObject; + self::ensureUserDefinedClass($className); + $reflector = self::reflectorForClass($className); + return new ClassUnit($className, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. + * @psalm-param class-string $className * - * @psalm-param trait-string $traitName + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true, array $mockedMethods = [], bool $cloneArguments = \false) : MockObject + public static function forClassMethod(string $className, string $methodName) : ClassMethodUnit { - $this->recordDoubledType($traitName); - $mockObject = $this->getMockObjectGenerator()->getMockForTrait($traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - $this->registerMockObject($mockObject); - return $mockObject; + self::ensureUserDefinedClass($className); + $reflector = self::reflectorForClassMethod($className, $methodName); + return new ClassMethodUnit($className . '::' . $methodName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * Returns an object for the specified trait. + * @psalm-param class-string $interfaceName * - * @psalm-param trait-string $traitName + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function getObjectForTrait(string $traitName, array $arguments = [], string $traitClassName = '', bool $callOriginalConstructor = \true, bool $callOriginalClone = \true, bool $callAutoload = \true) : object + public static function forInterface(string $interfaceName) : InterfaceUnit { - $this->recordDoubledType($traitName); - return $this->getMockObjectGenerator()->getObjectForTrait($traitName, $traitClassName, $callAutoload, $callOriginalConstructor, $arguments); + self::ensureUserDefinedInterface($interfaceName); + $reflector = self::reflectorForClass($interfaceName); + return new InterfaceUnit($interfaceName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * @throws \Prophecy\Exception\Doubler\ClassNotFoundException - * @throws \Prophecy\Exception\Doubler\DoubleException - * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * @psalm-param class-string $interfaceName * - * @psalm-param class-string|null $classOrInterface + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function prophesize(?string $classOrInterface = null) : ObjectProphecy + public static function forInterfaceMethod(string $interfaceName, string $methodName) : InterfaceMethodUnit { - $this->addWarning('PHPUnit\\Framework\\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.'); - if (is_string($classOrInterface)) { - $this->recordDoubledType($classOrInterface); - } - return $this->getProphet()->prophesize($classOrInterface); + self::ensureUserDefinedInterface($interfaceName); + $reflector = self::reflectorForClassMethod($interfaceName, $methodName); + return new InterfaceMethodUnit($interfaceName . '::' . $methodName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * Creates a default TestResult object. + * @psalm-param class-string $traitName * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function createResult() : \PHPUnit\Framework\TestResult + public static function forTrait(string $traitName) : TraitUnit { - return new \PHPUnit\Framework\TestResult(); + self::ensureUserDefinedTrait($traitName); + $reflector = self::reflectorForClass($traitName); + return new TraitUnit($traitName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * Performs assertions shared by all tests of a test case. + * @psalm-param class-string $traitName * - * This method is called between setUp() and test. + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function assertPreConditions() : void + public static function forTraitMethod(string $traitName, string $methodName) : TraitMethodUnit { + self::ensureUserDefinedTrait($traitName); + $reflector = self::reflectorForClassMethod($traitName, $methodName); + return new TraitMethodUnit($traitName . '::' . $methodName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * Performs assertions shared by all tests of a test case. + * @psalm-param callable-string $functionName * - * This method is called between test and tearDown(). + * @throws InvalidCodeUnitException + * @throws ReflectionException */ - protected function assertPostConditions() : void + public static function forFunction(string $functionName) : FunctionUnit { + $reflector = self::reflectorForFunction($functionName); + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined function', $functionName)); + } + return new FunctionUnit($functionName, $reflector->getFileName(), range($reflector->getStartLine(), $reflector->getEndLine())); } /** - * This method is called when a test method did not execute successfully. - * - * @throws Throwable + * @psalm-param list $sourceLines */ - protected function onNotSuccessfulTest(Throwable $t) : void + private function __construct(string $name, string $sourceFileName, array $sourceLines) { - throw $t; + $this->name = $name; + $this->sourceFileName = $sourceFileName; + $this->sourceLines = $sourceLines; } - protected function recordDoubledType(string $originalClassName) : void + public function name() : string { - $this->doubledTypes[] = $originalClassName; + return $this->name; } - /** - * @throws Throwable - */ - private function verifyMockObjects() : void + public function sourceFileName() : string { - foreach ($this->mockObjects as $mockObject) { - if ($mockObject->__phpunit_hasMatchers()) { - $this->numAssertions++; - } - $mockObject->__phpunit_verify($this->shouldInvocationMockerBeReset($mockObject)); - } - if ($this->prophet !== null) { - try { - $this->prophet->checkPredictions(); - } finally { - foreach ($this->prophet->getProphecies() as $objectProphecy) { - foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { - foreach ($methodProphecies as $methodProphecy) { - /* @var MethodProphecy $methodProphecy */ - $this->numAssertions += count($methodProphecy->getCheckedPredictions()); - } - } - } - } - } + return $this->sourceFileName; } /** - * @throws SkippedTestError - * @throws SyntheticSkippedError - * @throws Warning + * @psalm-return list */ - private function checkRequirements() : void + public function sourceLines() : array { - if (!$this->name || !method_exists($this, $this->name)) { - return; - } - $missingRequirements = TestUtil::getMissingRequirements(static::class, $this->name); - if (!empty($missingRequirements)) { - $this->markTestSkipped(implode(\PHP_EOL, $missingRequirements)); - } + return $this->sourceLines; } - private function handleDependencies() : bool + public function isClass() : bool { - if ([] === $this->dependencies || $this->inIsolation) { - return \true; - } - $passed = $this->result->passed(); - $passedKeys = array_keys($passed); - $numKeys = count($passedKeys); - for ($i = 0; $i < $numKeys; $i++) { - $pos = strpos($passedKeys[$i], ' with data set'); - if ($pos !== \false) { - $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); - } - } - $passedKeys = array_flip(array_unique($passedKeys)); - foreach ($this->dependencies as $dependency) { - if (!$dependency->isValid()) { - $this->markSkippedForNotSpecifyingDependency(); - return \false; - } - if ($dependency->targetIsClass()) { - $dependencyClassName = $dependency->getTargetClassName(); - if (array_search($dependencyClassName, $this->result->passedClasses(), \true) === \false) { - $this->markSkippedForMissingDependency($dependency); - return \false; - } - continue; - } - $dependencyTarget = $dependency->getTarget(); - if (!isset($passedKeys[$dependencyTarget])) { - if (!$this->isCallableTestMethod($dependencyTarget)) { - $this->markWarningForUncallableDependency($dependency); - } else { - $this->markSkippedForMissingDependency($dependency); - } - return \false; - } - if (isset($passed[$dependencyTarget])) { - if ($passed[$dependencyTarget]['size'] != \PHPUnit\Util\Test::UNKNOWN && $this->getSize() != \PHPUnit\Util\Test::UNKNOWN && $passed[$dependencyTarget]['size'] > $this->getSize()) { - $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError('This test depends on a test that is larger than itself.'), 0); - return \false; - } - if ($dependency->useDeepClone()) { - $deepCopy = new DeepCopy(); - $deepCopy->skipUncloneable(\false); - $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($passed[$dependencyTarget]['result']); - } elseif ($dependency->useShallowClone()) { - $this->dependencyInput[$dependencyTarget] = clone $passed[$dependencyTarget]['result']; - } else { - $this->dependencyInput[$dependencyTarget] = $passed[$dependencyTarget]['result']; - } - } else { - $this->dependencyInput[$dependencyTarget] = null; - } - } - return \true; + return \false; } - private function markSkippedForNotSpecifyingDependency() : void + public function isClassMethod() : bool { - $this->status = BaseTestRunner::STATUS_SKIPPED; - $this->result->startTest($this); - $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError('This method has an invalid @depends annotation.'), 0); - $this->result->endTest($this, 0); + return \false; } - private function markSkippedForMissingDependency(\PHPUnit\Framework\ExecutionOrderDependency $dependency) : void + public function isInterface() : bool { - $this->status = BaseTestRunner::STATUS_SKIPPED; - $this->result->startTest($this); - $this->result->addError($this, new \PHPUnit\Framework\SkippedTestError(sprintf('This test depends on "%s" to pass.', $dependency->getTarget())), 0); - $this->result->endTest($this, 0); + return \false; } - private function markWarningForUncallableDependency(\PHPUnit\Framework\ExecutionOrderDependency $dependency) : void + public function isInterfaceMethod() : bool { - $this->status = BaseTestRunner::STATUS_WARNING; - $this->result->startTest($this); - $this->result->addWarning($this, new \PHPUnit\Framework\Warning(sprintf('This test depends on "%s" which does not exist.', $dependency->getTarget())), 0); - $this->result->endTest($this, 0); + return \false; } - /** - * Get the mock object generator, creating it if it doesn't exist. - */ - private function getMockObjectGenerator() : MockGenerator + public function isTrait() : bool { - if ($this->mockObjectGenerator === null) { - $this->mockObjectGenerator = new MockGenerator(); - } - return $this->mockObjectGenerator; + return \false; } - private function startOutputBuffering() : void + public function isTraitMethod() : bool { - ob_start(); - $this->outputBufferingActive = \true; - $this->outputBufferingLevel = ob_get_level(); + return \false; + } + public function isFunction() : bool + { + return \false; } /** - * @throws RiskyTestError + * @psalm-param class-string $className + * + * @throws InvalidCodeUnitException */ - private function stopOutputBuffering() : void + private static function ensureUserDefinedClass(string $className) : void { - if (ob_get_level() !== $this->outputBufferingLevel) { - while (ob_get_level() >= $this->outputBufferingLevel) { - ob_end_clean(); + try { + $reflector = new ReflectionClass($className); + if ($reflector->isInterface()) { + throw new InvalidCodeUnitException(sprintf('"%s" is an interface and not a class', $className)); } - throw new \PHPUnit\Framework\RiskyTestError('Test code or tested code did not (only) close its own output buffers'); - } - $this->output = ob_get_contents(); - if ($this->outputCallback !== \false) { - $this->output = (string) call_user_func($this->outputCallback, $this->output); - } - ob_end_clean(); - $this->outputBufferingActive = \false; - $this->outputBufferingLevel = ob_get_level(); - } - private function snapshotGlobalState() : void - { - if ($this->runTestInSeparateProcess || $this->inIsolation || !$this->backupGlobals && !$this->backupStaticAttributes) { - return; + if ($reflector->isTrait()) { + throw new InvalidCodeUnitException(sprintf('"%s" is a trait and not a class', $className)); + } + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined class', $className)); + } + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === \true); + // @codeCoverageIgnoreEnd } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws RiskyTestError + * @psalm-param class-string $interfaceName + * + * @throws InvalidCodeUnitException */ - private function restoreGlobalState() : void + private static function ensureUserDefinedInterface(string $interfaceName) : void { - if (!$this->snapshot instanceof Snapshot) { - return; - } - if ($this->beStrictAboutChangesToGlobalState) { - try { - $this->compareGlobalStateSnapshots($this->snapshot, $this->createGlobalStateSnapshot($this->backupGlobals === \true)); - } catch (\PHPUnit\Framework\RiskyTestError $rte) { - // Intentionally left empty + try { + $reflector = new ReflectionClass($interfaceName); + if (!$reflector->isInterface()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not an interface', $interfaceName)); } + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined interface', $interfaceName)); + } + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - $restorer = new Restorer(); - if ($this->backupGlobals) { - $restorer->restoreGlobalVariables($this->snapshot); - } - if ($this->backupStaticAttributes) { - $restorer->restoreStaticAttributes($this->snapshot); - } - $this->snapshot = null; - if (isset($rte)) { - throw $rte; - } + // @codeCoverageIgnoreEnd } - private function createGlobalStateSnapshot(bool $backupGlobals) : Snapshot + /** + * @psalm-param class-string $traitName + * + * @throws InvalidCodeUnitException + */ + private static function ensureUserDefinedTrait(string $traitName) : void { - $excludeList = new ExcludeList(); - foreach ($this->backupGlobalsExcludeList as $globalVariable) { - $excludeList->addGlobalVariable($globalVariable); - } - if (!empty($this->backupGlobalsBlacklist)) { - $this->addWarning('PHPUnit\\Framework\\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\\Framework\\TestCase::$backupGlobalsExcludeList instead.'); - foreach ($this->backupGlobalsBlacklist as $globalVariable) { - $excludeList->addGlobalVariable($globalVariable); - } - } - if (!defined('PHPUNIT_TESTSUITE')) { - $excludeList->addClassNamePrefix('PHPUnit'); - $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\CodeCoverage'); - $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\FileIterator'); - $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Invoker'); - $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Template'); - $excludeList->addClassNamePrefix('PHPUnit\\SebastianBergmann\\Timer'); - $excludeList->addClassNamePrefix('Symfony'); - $excludeList->addClassNamePrefix('PHPUnit\\Doctrine\\Instantiator'); - $excludeList->addClassNamePrefix('Prophecy'); - $excludeList->addStaticAttribute(ComparatorFactory::class, 'instance'); - foreach ($this->backupStaticAttributesExcludeList as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } + try { + $reflector = new ReflectionClass($traitName); + if (!$reflector->isTrait()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not a trait', $traitName)); } - if (!empty($this->backupStaticAttributesBlacklist)) { - $this->addWarning('PHPUnit\\Framework\\TestCase::$backupStaticAttributesBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\\Framework\\TestCase::$backupStaticAttributesExcludeList instead.'); - foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } - } + // @codeCoverageIgnoreStart + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException(sprintf('"%s" is not a user-defined trait', $traitName)); } + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - return new Snapshot($excludeList, $backupGlobals, (bool) $this->backupStaticAttributes, \false, \false, \false, \false, \false, \false, \false); + // @codeCoverageIgnoreEnd } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws RiskyTestError + * @psalm-param class-string $className + * + * @throws ReflectionException */ - private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after) : void + private static function reflectorForClass(string $className) : ReflectionClass { - $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; - if ($backupGlobals) { - $this->compareGlobalStateSnapshotPart($before->globalVariables(), $after->globalVariables(), "--- Global variables before the test\n+++ Global variables after the test\n"); - $this->compareGlobalStateSnapshotPart($before->superGlobalVariables(), $after->superGlobalVariables(), "--- Super-global variables before the test\n+++ Super-global variables after the test\n"); - } - if ($this->backupStaticAttributes) { - $this->compareGlobalStateSnapshotPart($before->staticAttributes(), $after->staticAttributes(), "--- Static attributes before the test\n+++ Static attributes after the test\n"); + try { + return new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } + // @codeCoverageIgnoreEnd } /** - * @throws RiskyTestError + * @psalm-param class-string $className + * + * @throws ReflectionException */ - private function compareGlobalStateSnapshotPart(array $before, array $after, string $header) : void - { - if ($before != $after) { - $differ = new Differ($header); - $exporter = new Exporter(); - $diff = $differ->diff($exporter->export($before), $exporter->export($after)); - throw new \PHPUnit\Framework\RiskyTestError($diff); - } - } - private function getProphet() : Prophet + private static function reflectorForClassMethod(string $className, string $methodName) : ReflectionMethod { - if ($this->prophet === null) { - $this->prophet = new Prophet(); + try { + return new ReflectionMethod($className, $methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - return $this->prophet; + // @codeCoverageIgnoreEnd } /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @psalm-param callable-string $functionName + * + * @throws ReflectionException */ - private function shouldInvocationMockerBeReset(MockObject $mock) : bool + private static function reflectorForFunction(string $functionName) : ReflectionFunction { - $enumerator = new Enumerator(); - foreach ($enumerator->enumerate($this->dependencyInput) as $object) { - if ($mock === $object) { - return \false; - } - } - if (!is_array($this->testResult) && !is_object($this->testResult)) { - return \true; + try { + return new ReflectionFunction($functionName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); } - return !in_array($mock, $enumerator->enumerate($this->testResult), \true); + // @codeCoverageIgnoreEnd } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +use function array_merge; +use function count; +use Countable; +use IteratorAggregate; +final class CodeUnitCollection implements Countable, IteratorAggregate +{ /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @psalm-var list */ - private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []) : void - { - if ($this->registerMockObjectsFromTestArgumentsRecursively) { - foreach ((new Enumerator())->enumerate($testArguments) as $object) { - if ($object instanceof MockObject) { - $this->registerMockObject($object); - } - } - } else { - foreach ($testArguments as $testArgument) { - if ($testArgument instanceof MockObject) { - if (Type::isCloneable($testArgument)) { - $testArgument = clone $testArgument; - } - $this->registerMockObject($testArgument); - } elseif (is_array($testArgument) && !in_array($testArgument, $visited, \true)) { - $visited[] = $testArgument; - $this->registerMockObjectsFromTestArguments($testArgument, $visited); - } - } - } - } - private function setDoesNotPerformAssertionsFromAnnotation() : void - { - $annotations = TestUtil::parseTestMethodAnnotations(static::class, $this->name); - if (isset($annotations['method']['doesNotPerformAssertions'])) { - $this->doesNotPerformAssertions = \true; - } - } - private function unregisterCustomComparators() : void + private $codeUnits = []; + /** + * @psalm-param list $items + */ + public static function fromArray(array $items) : self { - $factory = ComparatorFactory::getInstance(); - foreach ($this->customComparators as $comparator) { - $factory->unregister($comparator); + $collection = new self(); + foreach ($items as $item) { + $collection->add($item); } - $this->customComparators = []; + return $collection; } - private function cleanupIniSettings() : void + public static function fromList(CodeUnit ...$items) : self { - foreach ($this->iniSettings as $varName => $oldValue) { - ini_set($varName, $oldValue); - } - $this->iniSettings = []; + return self::fromArray($items); } - private function cleanupLocaleSettings() : void + private function __construct() { - foreach ($this->locale as $category => $locale) { - setlocale($category, $locale); - } - $this->locale = []; } /** - * @throws Exception + * @psalm-return list */ - private function checkExceptionExpectations(Throwable $throwable) : bool + public function asArray() : array { - $result = \false; - if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { - $result = \true; - } - if ($throwable instanceof \PHPUnit\Framework\Exception) { - $result = \false; - } - if (is_string($this->expectedException)) { - try { - $reflector = new ReflectionClass($this->expectedException); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - if ($this->expectedException === 'PHPUnit\\Framework\\Exception' || $this->expectedException === '\\PHPUnit\\Framework\\Exception' || $reflector->isSubclassOf(\PHPUnit\Framework\Exception::class)) { - $result = \true; - } - } - return $result; + return $this->codeUnits; } - private function runInSeparateProcess() : bool + public function getIterator() : CodeUnitCollectionIterator { - return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && !$this->inIsolation && !$this instanceof PhptTestCase; + return new CodeUnitCollectionIterator($this); } - private function isCallableTestMethod(string $dependency) : bool + public function count() : int { - [$className, $methodName] = explode('::', $dependency); - if (!class_exists($className)) { - return \false; - } - try { - $class = new ReflectionClass($className); - } catch (ReflectionException $e) { - return \false; - } - if (!$class->isSubclassOf(__CLASS__)) { - return \false; - } - if (!$class->hasMethod($methodName)) { - return \false; - } - try { - $method = $class->getMethod($methodName); - } catch (ReflectionException $e) { - return \false; - } - return TestUtil::isTestMethod($method); + return count($this->codeUnits); } - /** - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - private function createMockObject(string $originalClassName) : MockObject + public function isEmpty() : bool { - return $this->getMockBuilder($originalClassName)->disableOriginalConstructor()->disableOriginalClone()->disableArgumentCloning()->disallowMockingUnknownTypes()->getMock(); + return empty($this->codeUnits); + } + public function mergeWith(self $other) : self + { + return self::fromArray(array_merge($this->asArray(), $other->asArray())); + } + private function add(CodeUnit $item) : void + { + $this->codeUnits[] = $item; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\CodeUnit; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class WarningTestCase extends \PHPUnit\Framework\TestCase +use Iterator; +final class CodeUnitCollectionIterator implements Iterator { /** - * @var bool - */ - protected $backupGlobals = \false; - /** - * @var bool - */ - protected $backupStaticAttributes = \false; - /** - * @var bool + * @psalm-var list */ - protected $runTestInSeparateProcess = \false; + private $codeUnits; /** - * @var string + * @var int */ - private $message; - public function __construct(string $message = '') + private $position = 0; + public function __construct(CodeUnitCollection $collection) { - $this->message = $message; - parent::__construct('Warning'); + $this->codeUnits = $collection->asArray(); } - public function getMessage() : string + public function rewind() : void { - return $this->message; + $this->position = 0; + } + public function valid() : bool + { + return isset($this->codeUnits[$this->position]); + } + public function key() : int + { + return $this->position; + } + public function current() : CodeUnit + { + return $this->codeUnits[$this->position]; + } + public function next() : void + { + $this->position++; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class FunctionUnit extends CodeUnit +{ /** - * Returns a string representation of the test case. + * @psalm-assert-if-true FunctionUnit $this */ - public function toString() : string + public function isFunction() : bool { - return 'Warning'; + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class InterfaceMethodUnit extends CodeUnit +{ /** - * @throws Exception - * - * @psalm-return never-return + * @psalm-assert-if-true InterfaceMethod $this */ - protected function runTest() : void + public function isInterfaceMethod() : bool { - throw new \PHPUnit\Framework\Warning($this->message); + return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\CodeUnit; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class InvalidParameterGroupException extends \PHPUnit\Framework\Exception +final class InterfaceUnit extends CodeUnit { + /** + * @psalm-assert-if-true InterfaceUnit $this + */ + public function isInterface() : bool + { + return \true; + } } +sebastian/code-unit + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; +use function array_keys; +use function array_merge; +use function array_unique; +use function array_values; +use function class_exists; +use function explode; +use function function_exists; +use function interface_exists; +use function ksort; +use function method_exists; +use function sort; use function sprintf; +use function str_replace; use function strpos; -use Throwable; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ExceptionMessage extends \PHPUnit\Framework\Constraint\Constraint +use function trait_exists; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +final class Mapper { /** - * @var string + * @psalm-return array> */ - private $expectedMessage; - public function __construct(string $expected) + public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits) : array { - $this->expectedMessage = $expected; + $result = []; + foreach ($codeUnits as $codeUnit) { + $sourceFileName = $codeUnit->sourceFileName(); + if (!isset($result[$sourceFileName])) { + $result[$sourceFileName] = []; + } + $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines()); + } + foreach (array_keys($result) as $sourceFileName) { + $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName])); + sort($result[$sourceFileName]); + } + ksort($result); + return $result; } - public function toString() : string + /** + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public function stringToCodeUnits(string $unit) : CodeUnitCollection + { + if (strpos($unit, '::') !== \false) { + [$firstPart, $secondPart] = explode('::', $unit); + if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) { + return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart)); + } + if ($this->isUserDefinedClass($firstPart)) { + if ($secondPart === '') { + return $this->publicMethodsOfClass($firstPart); + } + if ($secondPart === '') { + return $this->protectedAndPrivateMethodsOfClass($firstPart); + } + if ($secondPart === '') { + return $this->protectedMethodsOfClass($firstPart); + } + if ($secondPart === '') { + return $this->publicAndPrivateMethodsOfClass($firstPart); + } + if ($secondPart === '') { + return $this->privateMethodsOfClass($firstPart); + } + if ($secondPart === '') { + return $this->publicAndProtectedMethodsOfClass($firstPart); + } + if ($this->isUserDefinedMethod($firstPart, $secondPart)) { + return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart)); + } + } + if ($this->isUserDefinedInterface($firstPart)) { + return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart)); + } + if ($this->isUserDefinedTrait($firstPart)) { + return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart)); + } + } else { + if ($this->isUserDefinedClass($unit)) { + $units = [CodeUnit::forClass($unit)]; + foreach ($this->reflectorForClass($unit)->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $units[] = CodeUnit::forTrait($trait->getName()); + } + return CodeUnitCollection::fromArray($units); + } + if ($this->isUserDefinedInterface($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forInterface($unit)); + } + if ($this->isUserDefinedTrait($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forTrait($unit)); + } + if ($this->isUserDefinedFunction($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forFunction($unit)); + } + $unit = str_replace('', '', $unit); + if ($this->isUserDefinedClass($unit)) { + return $this->classAndParentClassesAndTraits($unit); + } + } + throw new InvalidCodeUnitException(sprintf('"%s" is not a valid code unit', $unit)); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicAndProtectedMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicAndPrivateMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function protectedMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function protectedAndPrivateMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function privateMethodsOfClass(string $className) : CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PRIVATE); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function methodsOfClass(string $className, int $filter) : CodeUnitCollection + { + $units = []; + foreach ($this->reflectorForClass($className)->getMethods($filter) as $method) { + if (!$method->isUserDefined()) { + continue; + } + $units[] = CodeUnit::forClassMethod($className, $method->getName()); + } + return CodeUnitCollection::fromArray($units); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function classAndParentClassesAndTraits(string $className) : CodeUnitCollection + { + $units = [CodeUnit::forClass($className)]; + $reflector = $this->reflectorForClass($className); + foreach ($this->reflectorForClass($className)->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $units[] = CodeUnit::forTrait($trait->getName()); + } + while ($reflector = $reflector->getParentClass()) { + if (!$reflector->isUserDefined()) { + break; + } + $units[] = CodeUnit::forClass($reflector->getName()); + foreach ($reflector->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $units[] = CodeUnit::forTrait($trait->getName()); + } + } + return CodeUnitCollection::fromArray($units); + } + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function reflectorForClass(string $className) : ReflectionClass + { + try { + return new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + /** + * @throws ReflectionException + */ + private function isUserDefinedFunction(string $functionName) : bool + { + if (!function_exists($functionName)) { + return \false; + } + try { + return (new ReflectionFunction($functionName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + /** + * @throws ReflectionException + */ + private function isUserDefinedClass(string $className) : bool + { + if (!class_exists($className)) { + return \false; + } + try { + return (new ReflectionClass($className))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd + } + /** + * @throws ReflectionException + */ + private function isUserDefinedInterface(string $interfaceName) : bool { - if ($this->expectedMessage === '') { - return 'exception message is empty'; + if (!interface_exists($interfaceName)) { + return \false; } - return 'exception message contains '; + try { + return (new ReflectionClass($interfaceName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param Throwable $other + * @throws ReflectionException */ - protected function matches($other) : bool + private function isUserDefinedTrait(string $traitName) : bool { - if ($this->expectedMessage === '') { - return $other->getMessage() === ''; + if (!trait_exists($traitName)) { + return \false; } - return strpos((string) $other->getMessage(), $this->expectedMessage) !== \false; + try { + return (new ReflectionClass($traitName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd } /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object + * @throws ReflectionException */ - protected function failureDescription($other) : string + private function isUserDefinedMethod(string $className, string $methodName) : bool { - if ($this->expectedMessage === '') { - return sprintf("exception message is empty but is '%s'", $other->getMessage()); + if (!class_exists($className)) { + // @codeCoverageIgnoreStart + return \false; + // @codeCoverageIgnoreEnd } - return sprintf("exception message '%s' contains '%s'", $other->getMessage(), $this->expectedMessage); + if (!method_exists($className, $methodName)) { + // @codeCoverageIgnoreStart + return \false; + // @codeCoverageIgnoreEnd + } + try { + return (new ReflectionMethod($className, $methodName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException($e->getMessage(), (int) $e->getCode(), $e); + } + // @codeCoverageIgnoreEnd } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; -use function sprintf; -use Throwable; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class ExceptionCode extends \PHPUnit\Framework\Constraint\Constraint +final class TraitMethodUnit extends CodeUnit { /** - * @var int|string - */ - private $expectedCode; - /** - * @param int|string $expected - */ - public function __construct($expected) - { - $this->expectedCode = $expected; - } - public function toString() : string - { - return 'exception code is '; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param Throwable $other - */ - protected function matches($other) : bool - { - return (string) $other->getCode() === (string) $this->expectedCode; - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @psalm-assert-if-true TraitMethodUnit $this */ - protected function failureDescription($other) : string + public function isTraitMethod() : bool { - return sprintf('%s is equal to expected exception code %s', $this->exporter()->export($other->getCode()), $this->exporter()->export($this->expectedCode)); + return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; -use function sprintf; -use Exception; -use PHPUnit\Util\RegularExpression as RegularExpressionUtil; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class ExceptionMessageRegularExpression extends \PHPUnit\Framework\Constraint\Constraint +final class TraitUnit extends CodeUnit { /** - * @var string - */ - private $expectedMessageRegExp; - public function __construct(string $expected) - { - $this->expectedMessageRegExp = $expected; - } - public function toString() : string - { - return 'exception message matches '; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param \PHPUnit\Framework\Exception $other - * - * @throws \PHPUnit\Framework\Exception - * @throws Exception - */ - protected function matches($other) : bool - { - $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); - if ($match === \false) { - throw new \PHPUnit\Framework\Exception("Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'"); - } - return $match === 1; - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object + * @psalm-assert-if-true TraitUnit $this */ - protected function failureDescription($other) : string + public function isTrait() : bool { - return sprintf("exception message '%s' matches '%s'", $other->getMessage(), $this->expectedMessageRegExp); + return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; -use function get_class; -use function sprintf; -use PHPUnit\Util\Filter; use Throwable; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends \PHPUnit\Framework\Constraint\Constraint +interface Exception extends Throwable { - /** - * @var string - */ - private $className; - public function __construct(string $className) - { - $this->className = $className; - } - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return sprintf('exception of type "%s"', $this->className); - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - return $other instanceof $this->className; - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other) : string - { - if ($other !== null) { - $message = ''; - if ($other instanceof Throwable) { - $message = '. Message was: "' . $other->getMessage() . '" at' . "\n" . Filter::getFilteredStacktrace($other); - } - return sprintf('exception of type "%s" matches expected exception "%s"%s', get_class($other), $this->className, $message); - } - return sprintf('exception of type "%s" is thrown', $this->className); - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; -use function array_map; -use function array_values; -use function count; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -abstract class BinaryOperator extends \PHPUnit\Framework\Constraint\Operator +use RuntimeException; +final class InvalidCodeUnitException extends RuntimeException implements Exception { - /** - * @var Constraint[] - */ - private $constraints = []; - public static function fromConstraints(\PHPUnit\Framework\Constraint\Constraint ...$constraints) : self - { - $constraint = new static(); - $constraint->constraints = $constraints; - return $constraint; - } - /** - * @param mixed[] $constraints - */ - public function setConstraints(array $constraints) : void - { - $this->constraints = array_map(function ($constraint) : Constraint { - return $this->checkConstraint($constraint); - }, array_values($constraints)); - } - /** - * Returns the number of operands (constraints). - */ - public final function arity() : int - { - return count($this->constraints); - } - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - $reduced = $this->reduce(); - if ($reduced !== $this) { - return $reduced->toString(); - } - $text = ''; - foreach ($this->constraints as $key => $constraint) { - $constraint = $constraint->reduce(); - $text .= $this->constraintToString($constraint, $key); - } - return $text; - } - /** - * Counts the number of constraint elements. - */ - public function count() : int - { - $count = 0; - foreach ($this->constraints as $constraint) { - $count += count($constraint); - } - return $count; - } - /** - * Returns the nested constraints. - */ - protected final function constraints() : array - { - return $this->constraints; - } - /** - * Returns true if the $constraint needs to be wrapped with braces. - */ - protected final function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool - { - return $this->arity() > 1 && parent::constraintNeedsParentheses($constraint); - } - /** - * Reduces the sub-expression starting at $this by skipping degenerate - * sub-expression and returns first descendant constraint that starts - * a non-reducible sub-expression. - * - * See Constraint::reduce() for more. - */ - protected function reduce() : \PHPUnit\Framework\Constraint\Constraint - { - if ($this->arity() === 1 && $this->constraints[0] instanceof \PHPUnit\Framework\Constraint\Operator) { - return $this->constraints[0]->reduce(); - } - return parent::reduce(); - } - /** - * Returns string representation of given operand in context of this operator. - * - * @param Constraint $constraint operand constraint - * @param int $position position of $constraint in this expression - */ - private function constraintToString(\PHPUnit\Framework\Constraint\Constraint $constraint, int $position) : string - { - $prefix = ''; - if ($position > 0) { - $prefix = ' ' . $this->operator() . ' '; - } - if ($this->constraintNeedsParentheses($constraint)) { - return $prefix . '( ' . $constraint->toString() . ' )'; - } - $string = $constraint->toStringInContext($this, $position); - if ($string === '') { - $string = $constraint->toString(); - } - return $prefix . $string; - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\CodeUnit; -use function array_reduce; -use function array_shift; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit +use RuntimeException; +final class NoTraitException extends RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -final class LogicalXor extends \PHPUnit\Framework\Constraint\BinaryOperator +namespace PHPUnit\SebastianBergmann\CodeUnit; + +use RuntimeException; +final class ReflectionException extends RuntimeException implements Exception { - /** - * Returns the name of this operator. - */ - public function operator() : string - { - return 'xor'; - } - /** - * Returns this operator's precedence. - * - * @see https://www.php.net/manual/en/language.operators.precedence.php. - */ - public function precedence() : int - { - return 23; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - public function matches($other) : bool - { - $constraints = $this->constraints(); - $initial = array_shift($constraints); - if ($initial === null) { - return \false; - } - return array_reduce($constraints, static function (bool $matches, \PHPUnit\Framework\Constraint\Constraint $constraint) use($other) : bool { - return $matches xor $constraint->evaluate($other, '', \true); - }, $initial->evaluate($other, '', \true)); - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function array_map; -use function count; -use function preg_match; -use function preg_quote; -use function preg_replace; +use function array_key_exists; +use function is_array; +use function sort; +use function sprintf; +use function str_replace; +use function trim; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares arrays for equality. + * + * Arrays are equal if they contain the same key-value pairs. + * The order of the keys does not matter. + * The types of key-value pairs do not matter. */ -final class LogicalNot extends \PHPUnit\Framework\Constraint\UnaryOperator +class ArrayComparator extends Comparator { - public static function negate(string $string) : string - { - $positives = ['contains ', 'exists', 'has ', 'is ', 'are ', 'matches ', 'starts with ', 'ends with ', 'reference ', 'not not ']; - $negatives = ['does not contain ', 'does not exist', 'does not have ', 'is not ', 'are not ', 'does not match ', 'starts not with ', 'ends not with ', 'don\'t reference ', 'not ']; - preg_match('/(\'[\\w\\W]*\')([\\w\\W]*)("[\\w\\W]*")/i', $string, $matches); - $positives = array_map(static function (string $s) { - return '/\\b' . preg_quote($s, '/') . '/'; - }, $positives); - if (count($matches) > 0) { - $nonInput = $matches[2]; - $negatedString = preg_replace('/' . preg_quote($nonInput, '/') . '/', preg_replace($positives, $negatives, $nonInput), $string); - } else { - $negatedString = preg_replace($positives, $negatives, $string); - } - return $negatedString; - } - /** - * Returns the name of this operator. - */ - public function operator() : string - { - return 'not'; - } /** - * Returns this operator's precedence. + * Returns whether the comparator can compare two values. * - * @see https://www.php.net/manual/en/language.operators.precedence.php - */ - public function precedence() : int - { - return 5; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - return !$this->constraint()->evaluate($other, '', \true); - } - /** - * Applies additional transformation to strings returned by toString() or - * failureDescription(). + * @return bool */ - protected function transformString(string $string) : string + public function accepts($expected, $actual) { - return self::negate($string); + return is_array($expected) && is_array($actual); } /** - * Reduces the sub-expression starting at $this by skipping degenerate - * sub-expression and returns first descendant constraint that starts - * a non-reducible sub-expression. + * Asserts that two arrays are equal. * - * See Constraint::reduce() for more. + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws ComparisonFailure */ - protected function reduce() : \PHPUnit\Framework\Constraint\Constraint + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) { - $constraint = $this->constraint(); - if ($constraint instanceof self) { - return $constraint->constraint()->reduce(); + if ($canonicalize) { + sort($expected); + sort($actual); + } + $remaining = $actual; + $actualAsString = "Array (\n"; + $expectedAsString = "Array (\n"; + $equal = \true; + foreach ($expected as $key => $value) { + unset($remaining[$key]); + if (!array_key_exists($key, $actual)) { + $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); + $equal = \false; + continue; + } + try { + $comparator = $this->factory->getComparatorFor($value, $actual[$key]); + $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); + $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); + $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($actual[$key])); + } catch (ComparisonFailure $e) { + $expectedAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected())); + $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual())); + $equal = \false; + } + } + foreach ($remaining as $key => $value) { + $actualAsString .= sprintf(" %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value)); + $equal = \false; + } + $expectedAsString .= ')'; + $actualAsString .= ')'; + if (!$equal) { + throw new ComparisonFailure($expected, $actual, $expectedAsString, $actualAsString, \false, 'Failed asserting that two arrays are equal.'); } - return parent::reduce(); + } + protected function indent($lines) + { + return trim(str_replace("\n", "\n ", $lines)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function count; +use PHPUnit\SebastianBergmann\Exporter\Exporter; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Abstract base class for comparators which compare values for equality. */ -abstract class UnaryOperator extends \PHPUnit\Framework\Constraint\Operator +abstract class Comparator { /** - * @var Constraint - */ - private $constraint; - /** - * @param Constraint|mixed $constraint - */ - public function __construct($constraint) - { - $this->constraint = $this->checkConstraint($constraint); - } - /** - * Returns the number of operands (constraints). + * @var Factory */ - public function arity() : int - { - return 1; - } + protected $factory; /** - * Returns a string representation of the constraint. + * @var Exporter */ - public function toString() : string + protected $exporter; + public function __construct() { - $reduced = $this->reduce(); - if ($reduced !== $this) { - return $reduced->toString(); - } - $constraint = $this->constraint->reduce(); - if ($this->constraintNeedsParentheses($constraint)) { - return $this->operator() . '( ' . $constraint->toString() . ' )'; - } - $string = $constraint->toStringInContext($this, 0); - if ($string === '') { - return $this->transformString($constraint->toString()); - } - return $string; + $this->exporter = new Exporter(); } - /** - * Counts the number of constraint elements. - */ - public function count() : int + public function setFactory(Factory $factory) { - return count($this->constraint); + $this->factory = $factory; } /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. + * Returns whether the comparator can compare two values. * - * @param mixed $other evaluated value or object + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @return bool */ - protected function failureDescription($other) : string - { - $reduced = $this->reduce(); - if ($reduced !== $this) { - return $reduced->failureDescription($other); - } - $constraint = $this->constraint->reduce(); - if ($this->constraintNeedsParentheses($constraint)) { - return $this->operator() . '( ' . $constraint->failureDescription($other) . ' )'; - } - $string = $constraint->failureDescriptionInContext($this, 0, $other); - if ($string === '') { - return $this->transformString($constraint->failureDescription($other)); - } - return $string; - } + public abstract function accepts($expected, $actual); /** - * Transforms string returned by the memeber constraint's toString() or - * failureDescription() such that it reflects constraint's participation in - * this expression. + * Asserts that two values are equal. * - * The method may be overwritten in a subclass to apply default - * transformation in case the operand constraint does not provide its own - * custom strings via toStringInContext() or failureDescriptionInContext(). + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true * - * @param string $string the string to be transformed - */ - protected function transformString(string $string) : string - { - return $string; - } - /** - * Provides access to $this->constraint for subclasses. - */ - protected final function constraint() : \PHPUnit\Framework\Constraint\Constraint - { - return $this->constraint; - } - /** - * Returns true if the $constraint needs to be wrapped with parentheses. + * @throws ComparisonFailure */ - protected function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool - { - $constraint = $constraint->reduce(); - return $constraint instanceof self || parent::constraintNeedsParentheses($constraint); - } + public abstract function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use RuntimeException; +use PHPUnit\SebastianBergmann\Diff\Differ; +use PHPUnit\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Thrown when an assertion for string equality failed. */ -abstract class Operator extends \PHPUnit\Framework\Constraint\Constraint +class ComparisonFailure extends RuntimeException { /** - * Returns the name of this operator. + * Expected value of the retrieval which does not match $actual. + * + * @var mixed */ - public abstract function operator() : string; + protected $expected; /** - * Returns this operator's precedence. + * Actually retrieved value which does not match $expected. * - * @see https://www.php.net/manual/en/language.operators.precedence.php + * @var mixed */ - public abstract function precedence() : int; + protected $actual; /** - * Returns the number of operands. + * The string representation of the expected value. + * + * @var string */ - public abstract function arity() : int; + protected $expectedAsString; /** - * Validates $constraint argument. + * The string representation of the actual value. + * + * @var string */ - protected function checkConstraint($constraint) : \PHPUnit\Framework\Constraint\Constraint + protected $actualAsString; + /** + * @var bool + */ + protected $identical; + /** + * Optional message which is placed in front of the first line + * returned by toString(). + * + * @var string + */ + protected $message; + /** + * Initialises with the expected value and the actual value. + * + * @param mixed $expected expected value retrieved + * @param mixed $actual actual value retrieved + * @param string $expectedAsString + * @param string $actualAsString + * @param bool $identical + * @param string $message a string which is prefixed on all returned lines + * in the difference output + */ + public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = \false, $message = '') { - if (!$constraint instanceof \PHPUnit\Framework\Constraint\Constraint) { - return new \PHPUnit\Framework\Constraint\IsEqual($constraint); - } - return $constraint; + $this->expected = $expected; + $this->actual = $actual; + $this->expectedAsString = $expectedAsString; + $this->actualAsString = $actualAsString; + $this->message = $message; + } + public function getActual() + { + return $this->actual; + } + public function getExpected() + { + return $this->expected; } /** - * Returns true if the $constraint needs to be wrapped with braces. + * @return string */ - protected function constraintNeedsParentheses(\PHPUnit\Framework\Constraint\Constraint $constraint) : bool + public function getActualAsString() { - return $constraint instanceof self && $constraint->arity() > 1 && $this->precedence() <= $constraint->precedence(); + return $this->actualAsString; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class LogicalOr extends \PHPUnit\Framework\Constraint\BinaryOperator -{ /** - * Returns the name of this operator. + * @return string */ - public function operator() : string + public function getExpectedAsString() { - return 'or'; + return $this->expectedAsString; } /** - * Returns this operator's precedence. - * - * @see https://www.php.net/manual/en/language.operators.precedence.php + * @return string */ - public function precedence() : int + public function getDiff() { - return 24; + if (!$this->actualAsString && !$this->expectedAsString) { + return ''; + } + $differ = new Differ(new UnifiedDiffOutputBuilder("\n--- Expected\n+++ Actual\n")); + return $differ->diff($this->expectedAsString, $this->actualAsString); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @return string */ - public function matches($other) : bool + public function toString() { - foreach ($this->constraints() as $constraint) { - if ($constraint->evaluate($other, '', \true)) { - return \true; - } - } - return \false; + return $this->message . $this->getDiff(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use function sprintf; +use function strtolower; +use DOMDocument; +use DOMNode; +use ValueError; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares DOMNode instances for equality. */ -final class LogicalAnd extends \PHPUnit\Framework\Constraint\BinaryOperator +class DOMNodeComparator extends ObjectComparator { /** - * Returns the name of this operator. + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - public function operator() : string + public function accepts($expected, $actual) { - return 'and'; + return $expected instanceof DOMNode && $actual instanceof DOMNode; } /** - * Returns this operator's precedence. + * Asserts that two values are equal. * - * @see https://www.php.net/manual/en/language.operators.precedence.php + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws ComparisonFailure */ - public function precedence() : int + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) { - return 22; + $expectedAsString = $this->nodeToText($expected, \true, $ignoreCase); + $actualAsString = $this->nodeToText($actual, \true, $ignoreCase); + if ($expectedAsString !== $actualAsString) { + $type = $expected instanceof DOMDocument ? 'documents' : 'nodes'; + throw new ComparisonFailure($expected, $actual, $expectedAsString, $actualAsString, \false, sprintf("Failed asserting that two DOM %s are equal.\n", $type)); + } } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * Returns the normalized, whitespace-cleaned, and indented textual + * representation of a DOMNode. */ - protected function matches($other) : bool + private function nodeToText(DOMNode $node, bool $canonicalize, bool $ignoreCase) : string { - foreach ($this->constraints() as $constraint) { - if (!$constraint->evaluate($other, '', \true)) { - return \false; + if ($canonicalize) { + $document = new DOMDocument(); + try { + @$document->loadXML($node->C14N()); + } catch (ValueError $e) { } + $node = $document; } - return [] !== $this->constraints(); + $document = $node instanceof DOMDocument ? $node : $node->ownerDocument; + $document->formatOutput = \true; + $document->normalizeDocument(); + $text = $node instanceof DOMDocument ? $node->saveXML() : $document->saveXML($node); + return $ignoreCase ? strtolower($text) : $text; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function file_exists; +use function abs; +use function floor; use function sprintf; +use DateInterval; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use Exception; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares DateTimeInterface instances for equality. */ -final class FileExists extends \PHPUnit\Framework\Constraint\Constraint +class DateTimeComparator extends ObjectComparator { /** - * Returns a string representation of the constraint. + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - public function toString() : string + public function accepts($expected, $actual) { - return 'file exists'; + return ($expected instanceof DateTime || $expected instanceof DateTimeInterface) && ($actual instanceof DateTime || $actual instanceof DateTimeInterface); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Asserts that two values are equal. * - * @param mixed $other value or object to evaluate + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws Exception + * @throws ComparisonFailure */ - protected function matches($other) : bool + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) { - return file_exists($other); + /** @var DateTimeInterface $expected */ + /** @var DateTimeInterface $actual */ + $absDelta = abs($delta); + $delta = new DateInterval(sprintf('PT%dS', $absDelta)); + $delta->f = $absDelta - floor($absDelta); + $actualClone = (clone $actual)->setTimezone(new DateTimeZone('UTC')); + $expectedLower = (clone $expected)->setTimezone(new DateTimeZone('UTC'))->sub($delta); + $expectedUpper = (clone $expected)->setTimezone(new DateTimeZone('UTC'))->add($delta); + if ($actualClone < $expectedLower || $actualClone > $expectedUpper) { + throw new ComparisonFailure($expected, $actual, $this->dateTimeToString($expected), $this->dateTimeToString($actual), \false, 'Failed asserting that two DateTime objects are equal.'); + } } /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object + * Returns an ISO 8601 formatted string representation of a datetime or + * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly + * initialized. */ - protected function failureDescription($other) : string + private function dateTimeToString(DateTimeInterface $datetime) : string { - return sprintf('file "%s" exists', $other); + $string = $datetime->format('Y-m-d\\TH:i:s.uO'); + return $string ?: 'Invalid DateTimeInterface object'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function is_writable; -use function sprintf; +use function is_float; +use function is_numeric; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares doubles for equality. + * + * @deprecated since v3.0.5 and v4.0.8 */ -final class IsWritable extends \PHPUnit\Framework\Constraint\Constraint +class DoubleComparator extends NumericComparator { /** - * Returns a string representation of the constraint. + * Smallest value available in PHP. + * + * @var float */ - public function toString() : string - { - return 'is writable'; - } + public const EPSILON = 1.0E-10; /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Returns whether the comparator can compare two values. * - * @param mixed $other value or object to evaluate + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - protected function matches($other) : bool + public function accepts($expected, $actual) { - return is_writable($other); + return (is_float($expected) || is_float($actual)) && is_numeric($expected) && is_numeric($actual); } /** - * Returns the description of the failure. + * Asserts that two values are equal. * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true * - * @param mixed $other evaluated value or object + * @throws ComparisonFailure */ - protected function failureDescription($other) : string + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - return sprintf('"%s" is writable', $other); + if ($delta == 0) { + $delta = self::EPSILON; + } + parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function is_dir; -use function sprintf; +use Exception; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares Exception instances for equality. */ -final class DirectoryExists extends \PHPUnit\Framework\Constraint\Constraint +class ExceptionComparator extends ObjectComparator { /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'directory exists'; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Returns whether the comparator can compare two values. * - * @param mixed $other value or object to evaluate + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - protected function matches($other) : bool + public function accepts($expected, $actual) { - return is_dir($other); + return $expected instanceof Exception && $actual instanceof Exception; } /** - * Returns the description of the failure. + * Converts an object to an array containing all of its private, protected + * and public properties. * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. + * @param object $object * - * @param mixed $other evaluated value or object + * @return array */ - protected function failureDescription($other) : string + protected function toArray($object) { - return sprintf('directory "%s" exists', $other); + $array = parent::toArray($object); + unset($array['file'], $array['line'], $array['trace'], $array['string'], $array['xdebug_message']); + return $array; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function is_readable; -use function sprintf; +use function array_unshift; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Factory for comparators which compare values for equality. */ -final class IsReadable extends \PHPUnit\Framework\Constraint\Constraint +class Factory { /** - * Returns a string representation of the constraint. + * @var Factory + */ + private static $instance; + /** + * @var Comparator[] + */ + private $customComparators = []; + /** + * @var Comparator[] + */ + private $defaultComparators = []; + /** + * @return Factory + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self(); + // @codeCoverageIgnore + } + return self::$instance; + } + /** + * Constructs a new factory. */ - public function toString() : string + public function __construct() { - return 'is readable'; + $this->registerDefaultComparators(); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Returns the correct comparator for comparing two values. * - * @param mixed $other value or object to evaluate + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return Comparator */ - protected function matches($other) : bool + public function getComparatorFor($expected, $actual) { - return is_readable($other); + foreach ($this->customComparators as $comparator) { + if ($comparator->accepts($expected, $actual)) { + return $comparator; + } + } + foreach ($this->defaultComparators as $comparator) { + if ($comparator->accepts($expected, $actual)) { + return $comparator; + } + } + throw new RuntimeException('No suitable Comparator implementation found'); } /** - * Returns the description of the failure. + * Registers a new comparator. * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. + * This comparator will be returned by getComparatorFor() if its accept() method + * returns TRUE for the compared values. It has higher priority than the + * existing comparators, meaning that its accept() method will be invoked + * before those of the other comparators. * - * @param mixed $other evaluated value or object + * @param Comparator $comparator The comparator to be registered */ - protected function failureDescription($other) : string + public function register(Comparator $comparator) { - return sprintf('"%s" is readable', $other); + array_unshift($this->customComparators, $comparator); + $comparator->setFactory($this); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsTrue extends \PHPUnit\Framework\Constraint\Constraint -{ /** - * Returns a string representation of the constraint. + * Unregisters a comparator. + * + * This comparator will no longer be considered by getComparatorFor(). + * + * @param Comparator $comparator The comparator to be unregistered */ - public function toString() : string + public function unregister(Comparator $comparator) { - return 'is true'; + foreach ($this->customComparators as $key => $_comparator) { + if ($comparator === $_comparator) { + unset($this->customComparators[$key]); + } + } } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * Unregisters all non-default comparators. */ - protected function matches($other) : bool + public function reset() { - return $other === \true; + $this->customComparators = []; + } + private function registerDefaultComparators() : void + { + $this->registerDefaultComparator(new MockObjectComparator()); + $this->registerDefaultComparator(new DateTimeComparator()); + $this->registerDefaultComparator(new DOMNodeComparator()); + $this->registerDefaultComparator(new SplObjectStorageComparator()); + $this->registerDefaultComparator(new ExceptionComparator()); + $this->registerDefaultComparator(new ObjectComparator()); + $this->registerDefaultComparator(new ResourceComparator()); + $this->registerDefaultComparator(new ArrayComparator()); + $this->registerDefaultComparator(new NumericComparator()); + $this->registerDefaultComparator(new ScalarComparator()); + $this->registerDefaultComparator(new TypeComparator()); + } + private function registerDefaultComparator(Comparator $comparator) : void + { + $this->defaultComparators[] = $comparator; + $comparator->setFactory($this); } } +Comparator + +Copyright (c) 2002-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use PHPUnit\Framework\MockObject\MockObject; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares PHPUnit\Framework\MockObject\MockObject instances for equality. */ -final class IsFalse extends \PHPUnit\Framework\Constraint\Constraint +class MockObjectComparator extends ObjectComparator { /** - * Returns a string representation of the constraint. + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - public function toString() : string + public function accepts($expected, $actual) { - return 'is false'; + return $expected instanceof MockObject && $actual instanceof MockObject; } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Converts an object to an array containing all of its private, protected + * and public properties. * - * @param mixed $other value or object to evaluate + * @param object $object + * + * @return array */ - protected function matches($other) : bool + protected function toArray($object) { - return $other === \false; + $array = parent::toArray($object); + unset($array['__phpunit_invocationMocker']); + return $array; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use function abs; +use function is_float; +use function is_infinite; +use function is_nan; +use function is_numeric; use function is_string; use function sprintf; -use function strpos; -use function trim; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares numerical values for equality. */ -final class IsEqualCanonicalizing extends \PHPUnit\Framework\Constraint\Constraint +class NumericComparator extends ScalarComparator { /** - * @var mixed + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - private $value; - public function __construct($value) + public function accepts($expected, $actual) { - $this->value = $value; + // all numerical values, but not if both of them are strings + return is_numeric($expected) && is_numeric($actual) && !(is_string($expected) && is_string($actual)); } /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. + * Asserts that two values are equal. * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true * - * @throws ExpectationFailedException + * @throws ComparisonFailure */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - // If $this->value and $other are identical, they are also equal. - // This is the most common path and will allow us to skip - // initialization of all the comparators. - if ($this->value === $other) { - return \true; + if ($this->isInfinite($actual) && $this->isInfinite($expected)) { + return; } - $comparatorFactory = ComparatorFactory::getInstance(); - try { - $comparator = $comparatorFactory->getComparatorFor($this->value, $other); - $comparator->assertEquals($this->value, $other, 0.0, \true, \false); - } catch (ComparisonFailure $f) { - if ($returnResult) { - return \false; - } - throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); + if (($this->isInfinite($actual) xor $this->isInfinite($expected)) || ($this->isNan($actual) || $this->isNan($expected)) || abs($actual - $expected) > $delta) { + throw new ComparisonFailure($expected, $actual, '', '', \false, sprintf('Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected))); } - return \true; } - /** - * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string + private function isInfinite($value) : bool { - if (is_string($this->value)) { - if (strpos($this->value, "\n") !== \false) { - return 'is equal to '; - } - return sprintf("is equal to '%s'", $this->value); - } - return sprintf('is equal to %s', $this->exporter()->export($this->value)); + return is_float($value) && is_infinite($value); + } + private function isNan($value) : bool + { + return is_float($value) && is_nan($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function is_string; +use function get_class; +use function in_array; +use function is_object; use function sprintf; -use function strpos; -use function trim; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; +use function substr_replace; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares objects for equality. */ -final class IsEqualIgnoringCase extends \PHPUnit\Framework\Constraint\Constraint +class ObjectComparator extends ArrayComparator { /** - * @var mixed + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - private $value; - public function __construct($value) + public function accepts($expected, $actual) { - $this->value = $value; + return is_object($expected) && is_object($actual); } /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. + * Asserts that two values are equal. * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) * - * @throws ExpectationFailedException + * @throws ComparisonFailure */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = []) { - // If $this->value and $other are identical, they are also equal. - // This is the most common path and will allow us to skip - // initialization of all the comparators. - if ($this->value === $other) { - return \true; + if (get_class($actual) !== get_class($expected)) { + throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, sprintf('%s is not instance of expected class "%s".', $this->exporter->export($actual), get_class($expected))); } - $comparatorFactory = ComparatorFactory::getInstance(); - try { - $comparator = $comparatorFactory->getComparatorFor($this->value, $other); - $comparator->assertEquals($this->value, $other, 0.0, \false, \true); - } catch (ComparisonFailure $f) { - if ($returnResult) { - return \false; + // don't compare twice to allow for cyclic dependencies + if (in_array([$actual, $expected], $processed, \true) || in_array([$expected, $actual], $processed, \true)) { + return; + } + $processed[] = [$actual, $expected]; + // don't compare objects if they are identical + // this helps to avoid the error "maximum function nesting level reached" + // CAUTION: this conditional clause is not tested + if ($actual !== $expected) { + try { + parent::assertEquals($this->toArray($expected), $this->toArray($actual), $delta, $canonicalize, $ignoreCase, $processed); + } catch (ComparisonFailure $e) { + throw new ComparisonFailure( + $expected, + $actual, + // replace "Array" with "MyClass object" + substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), + substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), + \false, + 'Failed asserting that two objects are equal.' + ); } - throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); } - return \true; } /** - * Returns a string representation of the constraint. + * Converts an object to an array containing all of its private, protected + * and public properties. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param object $object + * + * @return array */ - public function toString() : string + protected function toArray($object) { - if (is_string($this->value)) { - if (strpos($this->value, "\n") !== \false) { - return 'is equal to '; - } - return sprintf("is equal to '%s'", $this->value); - } - return sprintf('is equal to %s', $this->exporter()->export($this->value)); + return $this->exporter->toArray($object); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function sprintf; -use function trim; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; +use function is_resource; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares resources for equality. */ -final class IsEqualWithDelta extends \PHPUnit\Framework\Constraint\Constraint +class ResourceComparator extends Comparator { /** - * @var mixed - */ - private $value; - /** - * @var float - */ - private $delta; - public function __construct($value, float $delta) - { - $this->value = $value; - $this->delta = $delta; - } - /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. + * Returns whether the comparator can compare two values. * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare * - * @throws ExpectationFailedException + * @return bool */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool + public function accepts($expected, $actual) { - // If $this->value and $other are identical, they are also equal. - // This is the most common path and will allow us to skip - // initialization of all the comparators. - if ($this->value === $other) { - return \true; - } - $comparatorFactory = ComparatorFactory::getInstance(); - try { - $comparator = $comparatorFactory->getComparatorFor($this->value, $other); - $comparator->assertEquals($this->value, $other, $this->delta); - } catch (ComparisonFailure $f) { - if ($returnResult) { - return \false; - } - throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); - } - return \true; + return is_resource($expected) && is_resource($actual); } /** - * Returns a string representation of the constraint. + * Asserts that two values are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure */ - public function toString() : string + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - return sprintf('is equal to %s with delta <%F>>', $this->exporter()->export($this->value), $this->delta); + if ($actual != $expected) { + throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual)); + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use function is_bool; +use function is_object; +use function is_scalar; use function is_string; +use function method_exists; use function sprintf; -use function strpos; -use function trim; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use PHPUnit\SebastianBergmann\Comparator\Factory as ComparatorFactory; +use function strtolower; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares scalar or NULL values for equality. */ -final class IsEqual extends \PHPUnit\Framework\Constraint\Constraint +class ScalarComparator extends Comparator { /** - * @var mixed - */ - private $value; - /** - * @var float - */ - private $delta; - /** - * @var bool - */ - private $canonicalize; - /** - * @var bool - */ - private $ignoreCase; - public function __construct($value, float $delta = 0.0, bool $canonicalize = \false, bool $ignoreCase = \false) - { - $this->value = $value; - $this->delta = $delta; - $this->canonicalize = $canonicalize; - $this->ignoreCase = $ignoreCase; - } - /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. - * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * Returns whether the comparator can compare two values. * - * @throws ExpectationFailedException + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare * * @return bool - */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool - { - // If $this->value and $other are identical, they are also equal. - // This is the most common path and will allow us to skip - // initialization of all the comparators. - if ($this->value === $other) { - return \true; - } - $comparatorFactory = ComparatorFactory::getInstance(); - try { - $comparator = $comparatorFactory->getComparatorFor($this->value, $other); - $comparator->assertEquals($this->value, $other, $this->delta, $this->canonicalize, $this->ignoreCase); - } catch (ComparisonFailure $f) { - if ($returnResult) { - return \false; - } - throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); - } - return \true; + * + * @since Method available since Release 3.6.0 + */ + public function accepts($expected, $actual) + { + return (is_scalar($expected) xor null === $expected) && (is_scalar($actual) xor null === $actual) || is_string($expected) && is_object($actual) && method_exists($actual, '__toString') || is_object($expected) && method_exists($expected, '__toString') && is_string($actual); } /** - * Returns a string representation of the constraint. + * Asserts that two values are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure */ - public function toString() : string + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - $delta = ''; - if (is_string($this->value)) { - if (strpos($this->value, "\n") !== \false) { - return 'is equal to '; + $expectedToCompare = $expected; + $actualToCompare = $actual; + // always compare as strings to avoid strange behaviour + // otherwise 0 == 'Foobar' + if (is_string($expected) && !is_bool($actual) || is_string($actual) && !is_bool($expected)) { + $expectedToCompare = (string) $expectedToCompare; + $actualToCompare = (string) $actualToCompare; + if ($ignoreCase) { + $expectedToCompare = strtolower($expectedToCompare); + $actualToCompare = strtolower($actualToCompare); } - return sprintf("is equal to '%s'", $this->value); } - if ($this->delta != 0) { - $delta = sprintf(' with delta <%F>', $this->delta); + if ($expectedToCompare !== $actualToCompare && is_string($expected) && is_string($actual)) { + throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two strings are equal.'); + } + if ($expectedToCompare != $actualToCompare) { + throw new ComparisonFailure( + $expected, + $actual, + // no diff is required + '', + '', + \false, + sprintf('Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected)) + ); } - return sprintf('is equal to %s%s', $this->exporter()->export($this->value), $delta); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; +use SplObjectStorage; /** - * @psalm-template CallbackInput of mixed - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares \SplObjectStorage instances for equality. */ -final class Callback extends \PHPUnit\Framework\Constraint\Constraint +class SplObjectStorageComparator extends Comparator { /** - * @var callable + * Returns whether the comparator can compare two values. * - * @psalm-var callable(CallbackInput $input): bool - */ - private $callback; - /** @psalm-param callable(CallbackInput $input): bool $callback */ - public function __construct(callable $callback) - { - $this->callback = $callback; - } - /** - * Returns a string representation of the constraint. + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool */ - public function toString() : string + public function accepts($expected, $actual) { - return 'is accepted by specified callback'; + return $expected instanceof SplObjectStorage && $actual instanceof SplObjectStorage; } /** - * Evaluates the constraint for parameter $value. Returns true if the - * constraint is met, false otherwise. + * Asserts that two values are equal. * - * @param mixed $other value or object to evaluate + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true * - * @psalm-param CallbackInput $other + * @throws ComparisonFailure */ - protected function matches($other) : bool + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - return ($this->callback)($other); + foreach ($actual as $object) { + if (!$expected->contains($object)) { + throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two objects are equal.'); + } + } + foreach ($expected as $object) { + if (!$actual->contains($object)) { + throw new ComparisonFailure($expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), \false, 'Failed asserting that two objects are equal.'); + } + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use PHPUnit\Framework\ExpectationFailedException; +use function gettype; +use function sprintf; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Compares values for type equality. */ -final class IsAnything extends \PHPUnit\Framework\Constraint\Constraint +class TypeComparator extends Comparator { /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. + * Returns whether the comparator can compare two values. * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare * - * @throws ExpectationFailedException - */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool - { - return $returnResult ? \true : null; - } - /** - * Returns a string representation of the constraint. + * @return bool */ - public function toString() : string + public function accepts($expected, $actual) { - return 'is anything'; + return \true; } /** - * Counts the number of constraint elements. + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure */ - public function count() : int + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false) { - return 0; + if (gettype($expected) != gettype($actual)) { + throw new ComparisonFailure( + $expected, + $actual, + // we don't need a diff + '', + '', + \false, + sprintf('%s does not match expected type "%s".', $this->exporter->shortenedExport($actual), gettype($expected)) + ); + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Comparator; -use function is_nan; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -final class IsNan extends \PHPUnit\Framework\Constraint\Constraint +namespace PHPUnit\SebastianBergmann\Comparator; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Complexity; + +use PHPUnit\PhpParser\Error; +use PHPUnit\PhpParser\Lexer; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\NodeTraverser; +use PHPUnit\PhpParser\NodeVisitor\NameResolver; +use PHPUnit\PhpParser\NodeVisitor\ParentConnectingVisitor; +use PHPUnit\PhpParser\Parser; +use PHPUnit\PhpParser\ParserFactory; +final class Calculator { /** - * Returns a string representation of the constraint. + * @throws RuntimeException */ - public function toString() : string + public function calculateForSourceFile(string $sourceFile) : ComplexityCollection { - return 'is nan'; + return $this->calculateForSourceString(\file_get_contents($sourceFile)); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * @throws RuntimeException + */ + public function calculateForSourceString(string $source) : ComplexityCollection + { + try { + $nodes = $this->parser()->parse($source); + \assert($nodes !== null); + return $this->calculateForAbstractSyntaxTree($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + } + // @codeCoverageIgnoreEnd + } + /** + * @param Node[] $nodes * - * @param mixed $other value or object to evaluate + * @throws RuntimeException */ - protected function matches($other) : bool + public function calculateForAbstractSyntaxTree(array $nodes) : ComplexityCollection { - return is_nan($other); + $traverser = new NodeTraverser(); + $complexityCalculatingVisitor = new ComplexityCalculatingVisitor(\true); + $traverser->addVisitor(new NameResolver()); + $traverser->addVisitor(new ParentConnectingVisitor()); + $traverser->addVisitor($complexityCalculatingVisitor); + try { + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + } + // @codeCoverageIgnoreEnd + return $complexityCalculatingVisitor->result(); + } + private function parser() : Parser + { + return (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Complexity; -use function is_finite; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class IsFinite extends \PHPUnit\Framework\Constraint\Constraint +final class Complexity { /** - * Returns a string representation of the constraint. + * @var string */ - public function toString() : string - { - return 'is finite'; - } + private $name; /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @var int */ - protected function matches($other) : bool + private $cyclomaticComplexity; + public function __construct(string $name, int $cyclomaticComplexity) { - return is_finite($other); + $this->name = $name; + $this->cyclomaticComplexity = $cyclomaticComplexity; + } + public function name() : string + { + return $this->name; + } + public function cyclomaticComplexity() : int + { + return $this->cyclomaticComplexity; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Complexity; -use function is_infinite; +use function count; +use Countable; +use IteratorAggregate; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * @psalm-immutable */ -final class IsInfinite extends \PHPUnit\Framework\Constraint\Constraint +final class ComplexityCollection implements Countable, IteratorAggregate { /** - * Returns a string representation of the constraint. + * @psalm-var list */ - public function toString() : string + private $items = []; + public static function fromList(Complexity ...$items) : self { - return 'is infinite'; + return new self($items); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @psalm-param list $items */ - protected function matches($other) : bool + private function __construct(array $items) { - return is_infinite($other); + $this->items = $items; + } + /** + * @psalm-return list + */ + public function asArray() : array + { + return $this->items; + } + public function getIterator() : ComplexityCollectionIterator + { + return new ComplexityCollectionIterator($this); + } + public function count() : int + { + return count($this->items); + } + public function isEmpty() : bool + { + return empty($this->items); + } + public function cyclomaticComplexity() : int + { + $cyclomaticComplexity = 0; + foreach ($this as $item) { + $cyclomaticComplexity += $item->cyclomaticComplexity(); + } + return $cyclomaticComplexity; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Complexity; -use function count; -use function is_array; -use function iterator_count; -use function sprintf; -use Countable; -use EmptyIterator; -use Generator; use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\Exception; -use Traversable; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -class Count extends \PHPUnit\Framework\Constraint\Constraint +final class ComplexityCollectionIterator implements Iterator { + /** + * @psalm-var list + */ + private $items; /** * @var int */ - private $expectedCount; - public function __construct(int $expected) + private $position = 0; + public function __construct(ComplexityCollection $items) { - $this->expectedCount = $expected; + $this->items = $items->asArray(); } - public function toString() : string + public function rewind() : void { - return sprintf('count matches %d', $this->expectedCount); + $this->position = 0; } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @throws Exception - */ - protected function matches($other) : bool + public function valid() : bool { - return $this->expectedCount === $this->getCountOf($other); + return isset($this->items[$this->position]); } - /** - * @throws Exception - */ - protected function getCountOf($other) : ?int + public function key() : int { - if ($other instanceof Countable || is_array($other)) { - return count($other); - } - if ($other instanceof EmptyIterator) { - return 0; - } - if ($other instanceof Traversable) { - while ($other instanceof IteratorAggregate) { - try { - $other = $other->getIterator(); - } catch (\Exception $e) { - throw new Exception($e->getMessage(), $e->getCode(), $e); - } - } - $iterator = $other; - if ($iterator instanceof Generator) { - return $this->getCountOfGenerator($iterator); - } - if (!$iterator instanceof Iterator) { - return iterator_count($iterator); - } - $key = $iterator->key(); - $count = iterator_count($iterator); - // Manually rewind $iterator to previous key, since iterator_count - // moves pointer. - if ($key !== null) { - $iterator->rewind(); - while ($iterator->valid() && $key !== $iterator->key()) { - $iterator->next(); - } - } - return $count; - } - return null; + return $this->position; } - /** - * Returns the total number of iterations from a generator. - * This will fully exhaust the generator. - */ - protected function getCountOfGenerator(Generator $generator) : int + public function current() : Complexity { - for ($count = 0; $generator->valid(); $generator->next()) { - $count++; - } - return $count; + return $this->items[$this->position]; } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other) : string + public function next() : void { - return sprintf('actual size %d matches expected size %d', (int) $this->getCountOf($other), $this->expectedCount); + $this->position++; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Complexity; + +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Complexity; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} +sebastian/complexity + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Complexity; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class GreaterThan extends \PHPUnit\Framework\Constraint\Constraint +use function assert; +use function is_array; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\Name; +use PHPUnit\PhpParser\Node\Stmt; +use PHPUnit\PhpParser\Node\Stmt\Class_; +use PHPUnit\PhpParser\Node\Stmt\ClassMethod; +use PHPUnit\PhpParser\Node\Stmt\Function_; +use PHPUnit\PhpParser\Node\Stmt\Trait_; +use PHPUnit\PhpParser\NodeTraverser; +use PHPUnit\PhpParser\NodeVisitorAbstract; +final class ComplexityCalculatingVisitor extends NodeVisitorAbstract { /** - * @var float|int + * @psalm-var list */ - private $value; + private $result = []; /** - * @param float|int $value + * @var bool */ - public function __construct($value) + private $shortCircuitTraversal; + public function __construct(bool $shortCircuitTraversal) { - $this->value = $value; + $this->shortCircuitTraversal = $shortCircuitTraversal; } - /** - * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string + public function enterNode(Node $node) : ?int { - return 'is greater than ' . $this->exporter()->export($this->value); + if (!$node instanceof ClassMethod && !$node instanceof Function_) { + return null; + } + if ($node instanceof ClassMethod) { + $name = $this->classMethodName($node); + } else { + $name = $this->functionName($node); + } + $statements = $node->getStmts(); + assert(is_array($statements)); + $this->result[] = new Complexity($name, $this->cyclomaticComplexity($statements)); + if ($this->shortCircuitTraversal) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + return null; + } + public function result() : ComplexityCollection + { + return ComplexityCollection::fromList(...$this->result); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @param Stmt[] $statements */ - protected function matches($other) : bool + private function cyclomaticComplexity(array $statements) : int { - return $this->value < $other; + $traverser = new NodeTraverser(); + $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor(); + $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($statements); + return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + } + private function classMethodName(ClassMethod $node) : string + { + $parent = $node->getAttribute('parent'); + assert($parent instanceof Class_ || $parent instanceof Trait_); + assert(isset($parent->namespacedName)); + assert($parent->namespacedName instanceof Name); + return $parent->namespacedName->toString() . '::' . $node->name->toString(); + } + private function functionName(Function_ $node) : string + { + assert(isset($node->namespacedName)); + assert($node->namespacedName instanceof Name); + return $node->namespacedName->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Complexity; -use function count; -use function gettype; -use function sprintf; -use function strpos; -use Countable; -use EmptyIterator; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsEmpty extends \PHPUnit\Framework\Constraint\Constraint +use function get_class; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\Expr\BinaryOp\BooleanAnd; +use PHPUnit\PhpParser\Node\Expr\BinaryOp\BooleanOr; +use PHPUnit\PhpParser\Node\Expr\BinaryOp\LogicalAnd; +use PHPUnit\PhpParser\Node\Expr\BinaryOp\LogicalOr; +use PHPUnit\PhpParser\Node\Expr\Ternary; +use PHPUnit\PhpParser\Node\Stmt\Case_; +use PHPUnit\PhpParser\Node\Stmt\Catch_; +use PHPUnit\PhpParser\Node\Stmt\ElseIf_; +use PHPUnit\PhpParser\Node\Stmt\For_; +use PHPUnit\PhpParser\Node\Stmt\Foreach_; +use PHPUnit\PhpParser\Node\Stmt\If_; +use PHPUnit\PhpParser\Node\Stmt\While_; +use PHPUnit\PhpParser\NodeVisitorAbstract; +final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract { /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'is empty'; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @var int */ - protected function matches($other) : bool + private $cyclomaticComplexity = 1; + public function enterNode(Node $node) : void { - if ($other instanceof EmptyIterator) { - return \true; - } - if ($other instanceof Countable) { - return count($other) === 0; + /* @noinspection GetClassMissUseInspection */ + switch (get_class($node)) { + case BooleanAnd::class: + case BooleanOr::class: + case Case_::class: + case Catch_::class: + case ElseIf_::class: + case For_::class: + case Foreach_::class: + case If_::class: + case LogicalAnd::class: + case LogicalOr::class: + case Ternary::class: + case While_::class: + $this->cyclomaticComplexity++; } - return empty($other); } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other) : string + public function cyclomaticComplexity() : int { - $type = gettype($other); - return sprintf('%s %s %s', strpos($type, 'a') === 0 || strpos($type, 'o') === 0 ? 'an' : 'a', $type, $this->toString()); + return $this->cyclomaticComplexity; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class LessThan extends \PHPUnit\Framework\Constraint\Constraint +final class Chunk { /** - * @var float|int + * @var int */ - private $value; + private $start; /** - * @param float|int $value + * @var int */ - public function __construct($value) + private $startRange; + /** + * @var int + */ + private $end; + /** + * @var int + */ + private $endRange; + /** + * @var Line[] + */ + private $lines; + public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) { - $this->value = $value; + $this->start = $start; + $this->startRange = $startRange; + $this->end = $end; + $this->endRange = $endRange; + $this->lines = $lines; + } + public function getStart() : int + { + return $this->start; + } + public function getStartRange() : int + { + return $this->startRange; + } + public function getEnd() : int + { + return $this->end; + } + public function getEndRange() : int + { + return $this->endRange; } /** - * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @return Line[] */ - public function toString() : string + public function getLines() : array { - return 'is less than ' . $this->exporter()->export($this->value); + return $this->lines; } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @param Line[] $lines */ - protected function matches($other) : bool + public function setLines(array $lines) : void { - return $this->value > $other; + foreach ($lines as $line) { + if (!$line instanceof Line) { + throw new InvalidArgumentException(); + } + } + $this->lines = $lines; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class SameSize extends \PHPUnit\Framework\Constraint\Count +final class Diff { - public function __construct(iterable $expected) + /** + * @var string + */ + private $from; + /** + * @var string + */ + private $to; + /** + * @var Chunk[] + */ + private $chunks; + /** + * @param Chunk[] $chunks + */ + public function __construct(string $from, string $to, array $chunks = []) { - parent::__construct((int) $this->getCountOf($expected)); + $this->from = $from; + $this->to = $to; + $this->chunks = $chunks; + } + public function getFrom() : string + { + return $this->from; + } + public function getTo() : string + { + return $this->to; + } + /** + * @return Chunk[] + */ + public function getChunks() : array + { + return $this->chunks; + } + /** + * @param Chunk[] $chunks + */ + public function setChunks(array $chunks) : void + { + $this->chunks = $chunks; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function abs; +use const PHP_INT_SIZE; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; +use function array_shift; +use function array_unshift; +use function array_values; +use function count; +use function current; +use function end; use function get_class; +use function gettype; use function is_array; -use function is_float; -use function is_infinite; -use function is_nan; use function is_object; use function is_string; +use function key; +use function min; +use function preg_split; +use function prev; +use function reset; use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsIdentical extends \PHPUnit\Framework\Constraint\Constraint +use function substr; +use PHPUnit\SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; +use PHPUnit\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; +final class Differ { + public const OLD = 0; + public const ADDED = 1; + public const REMOVED = 2; + public const DIFF_LINE_END_WARNING = 3; + public const NO_LINE_END_EOF_WARNING = 4; /** - * @var float - */ - private const EPSILON = 1.0E-10; - /** - * @var mixed + * @var DiffOutputBuilderInterface */ - private $value; - public function __construct($value) - { - $this->value = $value; - } + private $outputBuilder; /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. - * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. + * @param DiffOutputBuilderInterface $outputBuilder * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @throws InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool + public function __construct($outputBuilder = null) { - if (is_float($this->value) && is_float($other) && !is_infinite($this->value) && !is_infinite($other) && !is_nan($this->value) && !is_nan($other)) { - $success = abs($this->value - $other) < self::EPSILON; + if ($outputBuilder instanceof DiffOutputBuilderInterface) { + $this->outputBuilder = $outputBuilder; + } elseif (null === $outputBuilder) { + $this->outputBuilder = new UnifiedDiffOutputBuilder(); + } elseif (is_string($outputBuilder)) { + // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support + // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 + // @deprecated + $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { - $success = $this->value === $other; - } - if ($returnResult) { - return $success; - } - if (!$success) { - $f = null; - // if both values are strings, make sure a diff is generated - if (is_string($this->value) && is_string($other)) { - $f = new ComparisonFailure($this->value, $other, sprintf("'%s'", $this->value), sprintf("'%s'", $other)); - } - // if both values are array, make sure a diff is generated - if (is_array($this->value) && is_array($other)) { - $f = new ComparisonFailure($this->value, $other, $this->exporter()->export($this->value), $this->exporter()->export($other)); - } - $this->fail($other, $description, $f); + throw new InvalidArgumentException(sprintf('Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', is_object($outputBuilder) ? 'instance of "' . get_class($outputBuilder) . '"' : gettype($outputBuilder) . ' "' . $outputBuilder . '"')); } - return null; } /** - * Returns a string representation of the constraint. + * Returns the diff between two arrays or strings as string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param array|string $from + * @param array|string $to */ - public function toString() : string + public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) : string { - if (is_object($this->value)) { - return 'is identical to an object of class "' . get_class($this->value) . '"'; - } - return 'is identical to ' . $this->exporter()->export($this->value); + $diff = $this->diffToArray($this->normalizeDiffInput($from), $this->normalizeDiffInput($to), $lcs); + return $this->outputBuilder->getDiff($diff); } /** - * Returns the description of the failure. + * Returns the diff between two arrays or strings as array. * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. + * Each array element contains two elements: + * - [0] => mixed $token + * - [1] => 2|1|0 * - * @param mixed $other evaluated value or object + * - 2: REMOVED: $token was removed from $from + * - 1: ADDED: $token was added to $from + * - 0: OLD: $token is not changed in $to * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param array|string $from + * @param array|string $to + * @param LongestCommonSubsequenceCalculator $lcs */ - protected function failureDescription($other) : string + public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) : array { - if (is_object($this->value) && is_object($other)) { - return 'two variables reference the same object'; + if (is_string($from)) { + $from = $this->splitStringByLines($from); + } elseif (!is_array($from)) { + throw new InvalidArgumentException('"from" must be an array or string.'); } - if (is_string($this->value) && is_string($other)) { - return 'two strings are identical'; + if (is_string($to)) { + $to = $this->splitStringByLines($to); + } elseif (!is_array($to)) { + throw new InvalidArgumentException('"to" must be an array or string.'); } - if (is_array($this->value) && is_array($other)) { - return 'two arrays are identical'; + [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); + if ($lcs === null) { + $lcs = $this->selectLcsImplementation($from, $to); } - return parent::failureDescription($other); + $common = $lcs->calculate(array_values($from), array_values($to)); + $diff = []; + foreach ($start as $token) { + $diff[] = [$token, self::OLD]; + } + reset($from); + reset($to); + foreach ($common as $token) { + while (($fromToken = reset($from)) !== $token) { + $diff[] = [array_shift($from), self::REMOVED]; + } + while (($toToken = reset($to)) !== $token) { + $diff[] = [array_shift($to), self::ADDED]; + } + $diff[] = [$token, self::OLD]; + array_shift($from); + array_shift($to); + } + while (($token = array_shift($from)) !== null) { + $diff[] = [$token, self::REMOVED]; + } + while (($token = array_shift($to)) !== null) { + $diff[] = [$token, self::ADDED]; + } + foreach ($end as $token) { + $diff[] = [$token, self::OLD]; + } + if ($this->detectUnmatchedLineEndings($diff)) { + array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); + } + return $diff; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function get_class; -use function is_object; -use PHPUnit\Framework\ActualValueIsNotAnObjectException; -use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException; -use PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException; -use PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException; -use PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException; -use PHPUnit\Framework\ComparisonMethodDoesNotExistException; -use ReflectionNamedType; -use ReflectionObject; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ObjectEquals extends \PHPUnit\Framework\Constraint\Constraint -{ /** - * @var object + * Casts variable to string if it is not a string or array. + * + * @return array|string */ - private $expected; + private function normalizeDiffInput($input) + { + if (!is_array($input) && !is_string($input)) { + return (string) $input; + } + return $input; + } /** - * @var string + * Checks if input is string, if so it will split it line-by-line. */ - private $method; - public function __construct(object $object, string $method = 'equals') + private function splitStringByLines(string $input) : array { - $this->expected = $object; - $this->method = $method; + return preg_split('/(.*\\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } - public function toString() : string + private function selectLcsImplementation(array $from, array $to) : LongestCommonSubsequenceCalculator { - return 'two objects are equal'; + // We do not want to use the time-efficient implementation if its memory + // footprint will probably exceed this value. Note that the footprint + // calculation is only an estimation for the matrix and the LCS method + // will typically allocate a bit more memory than this. + $memoryLimit = 100 * 1024 * 1024; + if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { + return new MemoryEfficientLongestCommonSubsequenceCalculator(); + } + return new TimeEfficientLongestCommonSubsequenceCalculator(); } /** - * @throws ActualValueIsNotAnObjectException - * @throws ComparisonMethodDoesNotAcceptParameterTypeException - * @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException - * @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException - * @throws ComparisonMethodDoesNotDeclareParameterTypeException - * @throws ComparisonMethodDoesNotExistException + * Calculates the estimated memory footprint for the DP-based method. + * + * @return float|int */ - protected function matches($other) : bool + private function calculateEstimatedFootprint(array $from, array $to) { - if (!is_object($other)) { - throw new ActualValueIsNotAnObjectException(); - } - $object = new ReflectionObject($other); - if (!$object->hasMethod($this->method)) { - throw new ComparisonMethodDoesNotExistException(get_class($other), $this->method); - } - /** @noinspection PhpUnhandledExceptionInspection */ - $method = $object->getMethod($this->method); - if (!$method->hasReturnType()) { - throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); - } - $returnType = $method->getReturnType(); - if (!$returnType instanceof ReflectionNamedType) { - throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; + return $itemSize * min(count($from), count($to)) ** 2; + } + /** + * Returns true if line ends don't match in a diff. + */ + private function detectUnmatchedLineEndings(array $diff) : bool + { + $newLineBreaks = ['' => \true]; + $oldLineBreaks = ['' => \true]; + foreach ($diff as $entry) { + if (self::OLD === $entry[1]) { + $ln = $this->getLinebreak($entry[0]); + $oldLineBreaks[$ln] = \true; + $newLineBreaks[$ln] = \true; + } elseif (self::ADDED === $entry[1]) { + $newLineBreaks[$this->getLinebreak($entry[0])] = \true; + } elseif (self::REMOVED === $entry[1]) { + $oldLineBreaks[$this->getLinebreak($entry[0])] = \true; + } } - if ($returnType->allowsNull()) { - throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + // if either input or output is a single line without breaks than no warning should be raised + if (['' => \true] === $newLineBreaks || ['' => \true] === $oldLineBreaks) { + return \false; } - if ($returnType->getName() !== 'bool') { - throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(get_class($other), $this->method); + // two way compare + foreach ($newLineBreaks as $break => $set) { + if (!isset($oldLineBreaks[$break])) { + return \true; + } } - if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) { - throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException(get_class($other), $this->method); + foreach ($oldLineBreaks as $break => $set) { + if (!isset($newLineBreaks[$break])) { + return \true; + } } - $parameter = $method->getParameters()[0]; - if (!$parameter->hasType()) { - throw new ComparisonMethodDoesNotDeclareParameterTypeException(get_class($other), $this->method); + return \false; + } + private function getLinebreak($line) : string + { + if (!is_string($line)) { + return ''; } - $type = $parameter->getType(); - if (!$type instanceof ReflectionNamedType) { - throw new ComparisonMethodDoesNotDeclareParameterTypeException(get_class($other), $this->method); + $lc = substr($line, -1); + if ("\r" === $lc) { + return "\r"; } - $typeName = $type->getName(); - if ($typeName === 'self') { - $typeName = get_class($other); + if ("\n" !== $lc) { + return ''; } - if (!$this->expected instanceof $typeName) { - throw new ComparisonMethodDoesNotAcceptParameterTypeException(get_class($other), $this->method, get_class($this->expected)); + if ("\r\n" === substr($line, -2)) { + return "\r\n"; } - return $other->{$this->method}($this->expected); + return "\n"; } - protected function failureDescription($other) : string + private static function getArrayDiffParted(array &$from, array &$to) : array { - return $this->toString(); + $start = []; + $end = []; + reset($to); + foreach ($from as $k => $v) { + $toK = key($to); + if ($toK === $k && $v === $to[$k]) { + $start[$k] = $v; + unset($from[$k], $to[$k]); + } else { + break; + } + } + end($from); + end($to); + do { + $fromK = key($from); + $toK = key($to); + if (null === $fromK || null === $toK || current($from) !== current($to)) { + break; + } + prev($from); + prev($to); + $end = [$fromK => $from[$fromK]] + $end; + unset($from[$fromK], $to[$toK]); + } while (\true); + return [$from, $to, $start, $end]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use ReflectionObject; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ObjectHasAttribute extends \PHPUnit\Framework\Constraint\ClassHasAttribute +use function get_class; +use function gettype; +use function is_object; +use function sprintf; +use Exception; +final class ConfigurationException extends InvalidArgumentException { - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool + public function __construct(string $option, string $expected, $value, int $code = 0, Exception $previous = null) { - return (new ReflectionObject($other))->hasProperty($this->attributeName()); + parent::__construct(sprintf('Option "%s" must be %s, got "%s".', $option, $expected, is_object($value) ? get_class($value) : (null === $value ? '' : gettype($value) . '#' . $value)), $code, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function get_class; -use function is_object; -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -class ClassHasAttribute extends \PHPUnit\Framework\Constraint\Constraint +use Throwable; +interface Exception extends Throwable { - /** - * @var string - */ - private $attributeName; - public function __construct(string $attributeName) - { - $this->attributeName = $attributeName; - } - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return sprintf('has attribute "%s"', $this->attributeName); - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - try { - return (new ReflectionClass($other))->hasProperty($this->attributeName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other) : string - { - return sprintf('%sclass "%s" %s', is_object($other) ? 'object of ' : '', is_object($other) ? get_class($other) : $other, $this->toString()); - } - protected function attributeName() : string - { - return $this->attributeName; - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ClassHasStaticAttribute extends \PHPUnit\Framework\Constraint\ClassHasAttribute +class InvalidArgumentException extends \InvalidArgumentException implements Exception { - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return sprintf('has static attribute "%s"', $this->attributeName()); - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - try { - $class = new ReflectionClass($other); - if ($class->hasProperty($this->attributeName())) { - return $class->getProperty($this->attributeName())->isStatic(); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - return \false; - } } +sebastian/diff + +Copyright (c) 2002-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function array_key_exists; -use function is_array; -use ArrayAccess; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ArrayHasKey extends \PHPUnit\Framework\Constraint\Constraint +final class Line { + public const ADDED = 1; + public const REMOVED = 2; + public const UNCHANGED = 3; /** - * @var int|string + * @var int */ - private $key; + private $type; /** - * @param int|string $key + * @var string */ - public function __construct($key) + private $content; + public function __construct(int $type = self::UNCHANGED, string $content = '') { - $this->key = $key; + $this->type = $type; + $this->content = $content; } - /** - * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string + public function getContent() : string { - return 'has the key ' . $this->exporter()->export($this->key); + return $this->content; } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool + public function getType() : int { - if (is_array($other)) { - return array_key_exists($this->key, $other); - } - if ($other instanceof ArrayAccess) { - return $other->offsetExists($this->key); - } - return \false; + return $this->type; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Diff; + +interface LongestCommonSubsequenceCalculator +{ /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Calculates the longest common subsequence of two arrays. */ - protected function failureDescription($other) : string - { - return 'an array ' . $this->toString(); - } + public function calculate(array $from, array $to) : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use SplObjectStorage; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class TraversableContainsIdentical extends \PHPUnit\Framework\Constraint\TraversableContains +use function array_fill; +use function array_merge; +use function array_reverse; +use function array_slice; +use function count; +use function in_array; +use function max; +final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * {@inheritdoc} */ - protected function matches($other) : bool + public function calculate(array $from, array $to) : array { - if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + $cFrom = count($from); + $cTo = count($to); + if ($cFrom === 0) { + return []; } - foreach ($other as $element) { - if ($this->value() === $element) { - return \true; + if ($cFrom === 1) { + if (in_array($from[0], $to, \true)) { + return [$from[0]]; } + return []; } - return \false; + $i = (int) ($cFrom / 2); + $fromStart = array_slice($from, 0, $i); + $fromEnd = array_slice($from, $i); + $llB = $this->length($fromStart, $to); + $llE = $this->length(array_reverse($fromEnd), array_reverse($to)); + $jMax = 0; + $max = 0; + for ($j = 0; $j <= $cTo; $j++) { + $m = $llB[$j] + $llE[$cTo - $j]; + if ($m >= $max) { + $max = $m; + $jMax = $j; + } + } + $toStart = array_slice($to, 0, $jMax); + $toEnd = array_slice($to, $jMax); + return array_merge($this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd)); + } + private function length(array $from, array $to) : array + { + $current = array_fill(0, count($to) + 1, 0); + $cFrom = count($from); + $cTo = count($to); + for ($i = 0; $i < $cFrom; $i++) { + $prev = $current; + for ($j = 0; $j < $cTo; $j++) { + if ($from[$i] === $to[$j]) { + $current[$j + 1] = $prev[$j] + 1; + } else { + $current[$j + 1] = max($current[$j], $prev[$j + 1]); + } + } + } + return $current; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff\Output; -use function is_array; -use function sprintf; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -abstract class TraversableContains extends \PHPUnit\Framework\Constraint\Constraint +use function count; +abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface { /** - * @var mixed - */ - private $value; - public function __construct($value) - { - $this->value = $value; - } - /** - * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string - { - return 'contains ' . $this->exporter()->export($this->value); - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Takes input of the diff array and returns the common parts. + * Iterates through diff line by line. */ - protected function failureDescription($other) : string - { - return sprintf('%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString()); - } - protected function value() + protected function getCommonChunks(array $diff, int $lineThreshold = 5) : array { - return $this->value; + $diffSize = count($diff); + $capturing = \false; + $chunkStart = 0; + $chunkSize = 0; + $commonChunks = []; + for ($i = 0; $i < $diffSize; ++$i) { + if ($diff[$i][1] === 0) { + if ($capturing === \false) { + $capturing = \true; + $chunkStart = $i; + $chunkSize = 0; + } else { + ++$chunkSize; + } + } elseif ($capturing !== \false) { + if ($chunkSize >= $lineThreshold) { + $commonChunks[$chunkStart] = $chunkStart + $chunkSize; + } + $capturing = \false; + } + } + if ($capturing !== \false && $chunkSize >= $lineThreshold) { + $commonChunks[$chunkStart] = $chunkStart + $chunkSize; + } + return $commonChunks; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff\Output; -use SplObjectStorage; +use function fclose; +use function fopen; +use function fwrite; +use function stream_get_contents; +use function substr; +use PHPUnit\SebastianBergmann\Diff\Differ; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Builds a diff string representation in a loose unified diff format + * listing only changes lines. Does not include line numbers. */ -final class TraversableContainsEqual extends \PHPUnit\Framework\Constraint\TraversableContains +final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface { /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @var string */ - protected function matches($other) : bool + private $header; + public function __construct(string $header = "--- Original\n+++ New\n") { - if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + $this->header = $header; + } + public function getDiff(array $diff) : string + { + $buffer = fopen('php://memory', 'r+b'); + if ('' !== $this->header) { + fwrite($buffer, $this->header); + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); + } } - foreach ($other as $element) { - /* @noinspection TypeUnsafeComparisonInspection */ - if ($this->value() == $element) { - return \true; + foreach ($diff as $diffEntry) { + if ($diffEntry[1] === Differ::ADDED) { + fwrite($buffer, '+' . $diffEntry[0]); + } elseif ($diffEntry[1] === Differ::REMOVED) { + fwrite($buffer, '-' . $diffEntry[0]); + } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { + fwrite($buffer, ' ' . $diffEntry[0]); + continue; + // Warnings should not be tested for line break, it will always be there + } else { + /* Not changed (old) 0 */ + continue; + // we didn't write the non changs line, so do not add a line break either + } + $lc = substr($diffEntry[0], -1); + if ($lc !== "\n" && $lc !== "\r") { + fwrite($buffer, "\n"); + // \No newline at end of file } } - return \false; + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); + return $diff; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff\Output; -use PHPUnit\Framework\ExpectationFailedException; -use Traversable; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Defines how an output builder should take a generated + * diff array and return a string representation of that diff. */ -final class TraversableContainsOnly extends \PHPUnit\Framework\Constraint\Constraint +interface DiffOutputBuilderInterface { - /** - * @var Constraint - */ - private $constraint; - /** - * @var string - */ - private $type; - /** - * @throws \PHPUnit\Framework\Exception - */ - public function __construct(string $type, bool $isNativeType = \true) - { - if ($isNativeType) { - $this->constraint = new \PHPUnit\Framework\Constraint\IsType($type); - } else { - $this->constraint = new \PHPUnit\Framework\Constraint\IsInstanceOf($type); - } - $this->type = $type; - } - /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. - * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. - * - * @param mixed|Traversable $other - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool - { - $success = \true; - foreach ($other as $item) { - if (!$this->constraint->evaluate($item, '', \true)) { - $success = \false; - break; - } - } - if ($returnResult) { - return $success; - } - if (!$success) { - $this->fail($other, $description); - } - return null; - } - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'contains only values of type "' . $this->type . '"'; - } + public function getDiff(array $diff) : string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff\Output; +use function array_merge; +use function array_splice; +use function count; +use function fclose; +use function fopen; +use function fwrite; +use function is_bool; +use function is_int; +use function is_string; +use function max; +use function min; use function sprintf; -use Countable; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use PHPUnit\SebastianBergmann\Exporter\Exporter; +use function stream_get_contents; +use function substr; +use PHPUnit\SebastianBergmann\Diff\ConfigurationException; +use PHPUnit\SebastianBergmann\Diff\Differ; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Strict Unified diff output builder. + * + * Generates (strict) Unified diff's (unidiffs) with hunks. */ -abstract class Constraint implements Countable, SelfDescribing +final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface { + private static $default = [ + 'collapseRanges' => \true, + // ranges of length one are rendered with the trailing `,1` + 'commonLineThreshold' => 6, + // number of same lines before ending a new hunk and creating a new one (if needed) + 'contextLines' => 3, + // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, + ]; /** - * @var ?Exporter + * @var bool */ - private $exporter; + private $changed; /** - * Evaluates the constraint for parameter $other. - * - * If $returnResult is set to false (the default), an exception is thrown - * in case of a failure. null is returned otherwise. - * - * If $returnResult is true, the result of the evaluation is returned as - * a boolean value instead: true in case of success, false in case of a - * failure. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @var bool */ - public function evaluate($other, string $description = '', bool $returnResult = \false) : ?bool - { - $success = \false; - if ($this->matches($other)) { - $success = \true; - } - if ($returnResult) { - return $success; - } - if (!$success) { - $this->fail($other, $description); - } - return null; - } + private $collapseRanges; /** - * Counts the number of constraint elements. + * @var int >= 0 */ - public function count() : int - { - return 1; - } - protected function exporter() : Exporter - { - if ($this->exporter === null) { - $this->exporter = new Exporter(); - } - return $this->exporter; - } + private $commonLineThreshold; /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate - * @codeCoverageIgnore + * @var string */ - protected function matches($other) : bool - { - return \false; - } + private $header; /** - * Throws an exception for the given compared value and test description. - * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * @param ComparisonFailure $comparisonFailure - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-return never-return + * @var int >= 0 */ - protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) : void + private $contextLines; + public function __construct(array $options = []) { - $failureDescription = sprintf('Failed asserting that %s.', $this->failureDescription($other)); - $additionalFailureDescription = $this->additionalFailureDescription($other); - if ($additionalFailureDescription) { - $failureDescription .= "\n" . $additionalFailureDescription; + $options = array_merge(self::$default, $options); + if (!is_bool($options['collapseRanges'])) { + throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); } - if (!empty($description)) { - $failureDescription = $description . "\n" . $failureDescription; + if (!is_int($options['contextLines']) || $options['contextLines'] < 0) { + throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); } - throw new ExpectationFailedException($failureDescription, $comparisonFailure); - } - /** - * Return additional failure description where needed. - * - * The function can be overridden to provide additional failure - * information like a diff - * - * @param mixed $other evaluated value or object - */ - protected function additionalFailureDescription($other) : string - { - return ''; - } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * To provide additional failure information additionalFailureDescription - * can be used. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function failureDescription($other) : string - { - return $this->exporter()->export($other) . ' ' . $this->toString(); + if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { + throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); + } + $this->assertString($options, 'fromFile'); + $this->assertString($options, 'toFile'); + $this->assertStringOrNull($options, 'fromFileDate'); + $this->assertStringOrNull($options, 'toFileDate'); + $this->header = sprintf("--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], $options['toFile'], null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate']); + $this->collapseRanges = $options['collapseRanges']; + $this->commonLineThreshold = $options['commonLineThreshold']; + $this->contextLines = $options['contextLines']; } - /** - * Returns a custom string representation of the constraint object when it - * appears in context of an $operator expression. - * - * The purpose of this method is to provide meaningful descriptive string - * in context of operators such as LogicalNot. Native PHPUnit constraints - * are supported out of the box by LogicalNot, but externally developed - * ones had no way to provide correct strings in this context. - * - * The method shall return empty string, when it does not handle - * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression - */ - protected function toStringInContext(\PHPUnit\Framework\Constraint\Operator $operator, $role) : string + public function getDiff(array $diff) : string { - return ''; + if (0 === count($diff)) { + return ''; + } + $this->changed = \false; + $buffer = fopen('php://memory', 'r+b'); + fwrite($buffer, $this->header); + $this->writeDiffHunks($buffer, $diff); + if (!$this->changed) { + fclose($buffer); + return ''; + } + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); + // If the last char is not a linebreak: add it. + // This might happen when both the `from` and `to` do not have a trailing linebreak + $last = substr($diff, -1); + return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff; } - /** - * Returns the description of the failure when this constraint appears in - * context of an $operator expression. - * - * The purpose of this method is to provide meaningful failue description - * in context of operators such as LogicalNot. Native PHPUnit constraints - * are supported out of the box by LogicalNot, but externally developed - * ones had no way to provide correct messages in this context. - * - * The method shall return empty string, when it does not handle - * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression - * @param mixed $other evaluated value or object - */ - protected function failureDescriptionInContext(\PHPUnit\Framework\Constraint\Operator $operator, $role, $other) : string + private function writeDiffHunks($output, array $diff) : void { - $string = $this->toStringInContext($operator, $role); - if ($string === '') { - return ''; + // detect "No newline at end of file" and insert into `$diff` if needed + $upperLimit = count($diff); + if (0 === $diff[$upperLimit - 1][1]) { + $lc = substr($diff[$upperLimit - 1][0], -1); + if ("\n" !== $lc) { + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + } else { + // search back for the last `+` and `-` line, + // check if has trailing linebreak, else add under it warning under it + $toFind = [1 => \true, 2 => \true]; + for ($i = $upperLimit - 1; $i >= 0; --$i) { + if (isset($toFind[$diff[$i][1]])) { + unset($toFind[$diff[$i][1]]); + $lc = substr($diff[$i][0], -1); + if ("\n" !== $lc) { + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + if (!count($toFind)) { + break; + } + } + } } - return $this->exporter()->export($other) . ' ' . $string; + // write hunks to output buffer + $cutOff = max($this->commonLineThreshold, $this->contextLines); + $hunkCapture = \false; + $sameCount = $toRange = $fromRange = 0; + $toStart = $fromStart = 1; + $i = 0; + /** @var int $i */ + foreach ($diff as $i => $entry) { + if (0 === $entry[1]) { + // same + if (\false === $hunkCapture) { + ++$fromStart; + ++$toStart; + continue; + } + ++$sameCount; + ++$toRange; + ++$fromRange; + if ($sameCount === $cutOff) { + $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; + // note: $contextEndOffset = $this->contextLines; + // + // because we never go beyond the end of the diff. + // with the cutoff/contextlines here the follow is never true; + // + // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { + // $contextEndOffset = count($diff) - 1; + // } + // + // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop + $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output); + $fromStart += $fromRange; + $toStart += $toRange; + $hunkCapture = \false; + $sameCount = $toRange = $fromRange = 0; + } + continue; + } + $sameCount = 0; + if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { + continue; + } + $this->changed = \true; + if (\false === $hunkCapture) { + $hunkCapture = $i; + } + if (Differ::ADDED === $entry[1]) { + // added + ++$toRange; + } + if (Differ::REMOVED === $entry[1]) { + // removed + ++$fromRange; + } + } + if (\false === $hunkCapture) { + return; + } + // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold + $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; + // prevent trying to write out more common lines than there are in the diff _and_ + // do not write more than configured through the context lines + $contextEndOffset = min($sameCount, $this->contextLines); + $fromRange -= $sameCount; + $toRange -= $sameCount; + $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output); } - /** - * Reduces the sub-expression starting at $this by skipping degenerate - * sub-expression and returns first descendant constraint that starts - * a non-reducible sub-expression. - * - * Returns $this for terminal constraints and for operators that start - * non-reducible sub-expression, or the nearest descendant of $this that - * starts a non-reducible sub-expression. - * - * A constraint expression may be modelled as a tree with non-terminal - * nodes (operators) and terminal nodes. For example: - * - * LogicalOr (operator, non-terminal) - * + LogicalAnd (operator, non-terminal) - * | + IsType('int') (terminal) - * | + GreaterThan(10) (terminal) - * + LogicalNot (operator, non-terminal) - * + IsType('array') (terminal) - * - * A degenerate sub-expression is a part of the tree, that effectively does - * not contribute to the evaluation of the expression it appears in. An example - * of degenerate sub-expression is a BinaryOperator constructed with single - * operand or nested BinaryOperators, each with single operand. An - * expression involving a degenerate sub-expression is equivalent to a - * reduced expression with the degenerate sub-expression removed, for example - * - * LogicalAnd (operator) - * + LogicalOr (degenerate operator) - * | + LogicalAnd (degenerate operator) - * | + IsType('int') (terminal) - * + GreaterThan(10) (terminal) - * - * is equivalent to - * - * LogicalAnd (operator) - * + IsType('int') (terminal) - * + GreaterThan(10) (terminal) - * - * because the subexpression - * - * + LogicalOr - * + LogicalAnd - * + - - * - * is degenerate. Calling reduce() on the LogicalOr object above, as well - * as on LogicalAnd, shall return the IsType('int') instance. - * - * Other specific reductions can be implemented, for example cascade of - * LogicalNot operators - * - * + LogicalNot - * + LogicalNot - * +LogicalNot - * + IsTrue - * - * can be reduced to - * - * LogicalNot - * + IsTrue - */ - protected function reduce() : self + private function writeHunk(array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output) : void { - return $this; + fwrite($output, '@@ -' . $fromStart); + if (!$this->collapseRanges || 1 !== $fromRange) { + fwrite($output, ',' . $fromRange); + } + fwrite($output, ' +' . $toStart); + if (!$this->collapseRanges || 1 !== $toRange) { + fwrite($output, ',' . $toRange); + } + fwrite($output, " @@\n"); + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === Differ::ADDED) { + $this->changed = \true; + fwrite($output, '+' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::REMOVED) { + $this->changed = \true; + fwrite($output, '-' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::OLD) { + fwrite($output, ' ' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { + $this->changed = \true; + fwrite($output, $diff[$i][0]); + } + //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package + // skip + //} else { + // unknown/invalid + //} + } } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const JSON_ERROR_CTRL_CHAR; -use const JSON_ERROR_DEPTH; -use const JSON_ERROR_NONE; -use const JSON_ERROR_STATE_MISMATCH; -use const JSON_ERROR_SYNTAX; -use const JSON_ERROR_UTF8; -use function strtolower; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class JsonMatchesErrorMessageProvider -{ - /** - * Translates JSON error to a human readable string. - */ - public static function determineJsonError(string $error, string $prefix = '') : ?string + private function assertString(array $options, string $option) : void { - switch ($error) { - case \JSON_ERROR_NONE: - return null; - case \JSON_ERROR_DEPTH: - return $prefix . 'Maximum stack depth exceeded'; - case \JSON_ERROR_STATE_MISMATCH: - return $prefix . 'Underflow or the modes mismatch'; - case \JSON_ERROR_CTRL_CHAR: - return $prefix . 'Unexpected control character found'; - case \JSON_ERROR_SYNTAX: - return $prefix . 'Syntax error, malformed JSON'; - case \JSON_ERROR_UTF8: - return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; - default: - return $prefix . 'Unknown error'; + if (!is_string($options[$option])) { + throw new ConfigurationException($option, 'a string', $options[$option]); } } - /** - * Translates a given type to a human readable message prefix. - */ - public static function translateTypeToPrefix(string $type) : string + private function assertStringOrNull(array $options, string $option) : void { - switch (strtolower($type)) { - case 'expected': - $prefix = 'Expected value JSON decode error - '; - break; - case 'actual': - $prefix = 'Actual value JSON decode error - '; - break; - default: - $prefix = ''; - break; + if (null !== $options[$option] && !is_string($options[$option])) { + throw new ConfigurationException($option, 'a string or ', $options[$option]); } - return $prefix; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff\Output; -use function json_decode; -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Util\Json; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use function array_splice; +use function count; +use function fclose; +use function fopen; +use function fwrite; +use function max; +use function min; +use function stream_get_contents; +use function strlen; +use function substr; +use PHPUnit\SebastianBergmann\Diff\Differ; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Builds a diff string representation in unified diff format in chunks. */ -final class JsonMatches extends \PHPUnit\Framework\Constraint\Constraint +final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder { /** - * @var string - */ - private $value; - public function __construct(string $value) - { - $this->value = $value; - } - /** - * Returns a string representation of the object. + * @var bool */ - public function toString() : string - { - return sprintf('matches JSON string "%s"', $this->value); - } + private $collapseRanges = \true; /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate + * @var int >= 0 */ - protected function matches($other) : bool - { - [$error, $recodedOther] = Json::canonicalize($other); - if ($error) { - return \false; - } - [$error, $recodedValue] = Json::canonicalize($this->value); - if ($error) { - return \false; - } - return $recodedOther == $recodedValue; - } + private $commonLineThreshold = 6; /** - * Throws an exception for the given compared value and test description. - * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * @param ComparisonFailure $comparisonFailure - * - * @throws \PHPUnit\Framework\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-return never-return + * @var int >= 0 */ - protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) : void - { - if ($comparisonFailure === null) { - [$error, $recodedOther] = Json::canonicalize($other); - if ($error) { - parent::fail($other, $description); - } - [$error, $recodedValue] = Json::canonicalize($this->value); - if ($error) { - parent::fail($other, $description); - } - $comparisonFailure = new ComparisonFailure(json_decode($this->value), json_decode($other), Json::prettify($recodedValue), Json::prettify($recodedOther), \false, 'Failed asserting that two json values are equal.'); - } - parent::fail($other, $description, $comparisonFailure); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const DIRECTORY_SEPARATOR; -use function explode; -use function implode; -use function preg_match; -use function preg_quote; -use function preg_replace; -use function strtr; -use PHPUnit\SebastianBergmann\Diff\Differ; -use PHPUnit\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class StringMatchesFormatDescription extends \PHPUnit\Framework\Constraint\RegularExpression -{ + private $contextLines = 3; /** * @var string */ - private $string; - public function __construct(string $string) - { - parent::__construct($this->createPatternFromFormat($this->convertNewlines($string))); - $this->string = $string; - } + private $header; /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @var bool */ - protected function matches($other) : bool + private $addLineNumbers; + public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = \false) { - return parent::matches($this->convertNewlines($other)); + $this->header = $header; + $this->addLineNumbers = $addLineNumbers; } - protected function failureDescription($other) : string + public function getDiff(array $diff) : string { - return 'string matches format description'; + $buffer = fopen('php://memory', 'r+b'); + if ('' !== $this->header) { + fwrite($buffer, $this->header); + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); + } + } + if (0 !== count($diff)) { + $this->writeDiffHunks($buffer, $diff); + } + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); + // If the diff is non-empty and last char is not a linebreak: add it. + // This might happen when both the `from` and `to` do not have a trailing linebreak + $last = substr($diff, -1); + return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff; } - protected function additionalFailureDescription($other) : string + private function writeDiffHunks($output, array $diff) : void { - $from = explode("\n", $this->string); - $to = explode("\n", $this->convertNewlines($other)); - foreach ($from as $index => $line) { - if (isset($to[$index]) && $line !== $to[$index]) { - $line = $this->createPatternFromFormat($line); - if (preg_match($line, $to[$index]) > 0) { - $from[$index] = $to[$index]; + // detect "No newline at end of file" and insert into `$diff` if needed + $upperLimit = count($diff); + if (0 === $diff[$upperLimit - 1][1]) { + $lc = substr($diff[$upperLimit - 1][0], -1); + if ("\n" !== $lc) { + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + } else { + // search back for the last `+` and `-` line, + // check if has trailing linebreak, else add under it warning under it + $toFind = [1 => \true, 2 => \true]; + for ($i = $upperLimit - 1; $i >= 0; --$i) { + if (isset($toFind[$diff[$i][1]])) { + unset($toFind[$diff[$i][1]]); + $lc = substr($diff[$i][0], -1); + if ("\n" !== $lc) { + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + if (!count($toFind)) { + break; + } } } } - $this->string = implode("\n", $from); - $other = implode("\n", $to); - return (new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")))->diff($this->string, $other); - } - private function createPatternFromFormat(string $string) : string - { - $string = strtr(preg_quote($string, '/'), ['%%' => '%', '%e' => '\\' . \DIRECTORY_SEPARATOR, '%s' => '[^\\r\\n]+', '%S' => '[^\\r\\n]*', '%a' => '.+', '%A' => '.*', '%w' => '\\s*', '%i' => '[+-]?\\d+', '%d' => '\\d+', '%x' => '[0-9a-fA-F]+', '%f' => '[+-]?\\.?\\d+\\.?\\d*(?:[Ee][+-]?\\d+)?', '%c' => '.']); - return '/^' . $string . '$/s'; - } - private function convertNewlines(string $text) : string - { - return preg_replace('/\\r\\n/', "\n", $text); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function json_decode; -use function json_last_error; -use function sprintf; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsJson extends \PHPUnit\Framework\Constraint\Constraint -{ - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'is valid JSON'; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - if ($other === '') { - return \false; + // write hunks to output buffer + $cutOff = max($this->commonLineThreshold, $this->contextLines); + $hunkCapture = \false; + $sameCount = $toRange = $fromRange = 0; + $toStart = $fromStart = 1; + $i = 0; + /** @var int $i */ + foreach ($diff as $i => $entry) { + if (0 === $entry[1]) { + // same + if (\false === $hunkCapture) { + ++$fromStart; + ++$toStart; + continue; + } + ++$sameCount; + ++$toRange; + ++$fromRange; + if ($sameCount === $cutOff) { + $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; + // note: $contextEndOffset = $this->contextLines; + // + // because we never go beyond the end of the diff. + // with the cutoff/contextlines here the follow is never true; + // + // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { + // $contextEndOffset = count($diff) - 1; + // } + // + // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop + $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output); + $fromStart += $fromRange; + $toStart += $toRange; + $hunkCapture = \false; + $sameCount = $toRange = $fromRange = 0; + } + continue; + } + $sameCount = 0; + if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { + continue; + } + if (\false === $hunkCapture) { + $hunkCapture = $i; + } + if (Differ::ADDED === $entry[1]) { + ++$toRange; + } + if (Differ::REMOVED === $entry[1]) { + ++$fromRange; + } } - json_decode($other); - if (json_last_error()) { - return \false; + if (\false === $hunkCapture) { + return; } - return \true; + // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold + $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; + // prevent trying to write out more common lines than there are in the diff _and_ + // do not write more than configured through the context lines + $contextEndOffset = min($sameCount, $this->contextLines); + $fromRange -= $sameCount; + $toRange -= $sameCount; + $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output); } - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function failureDescription($other) : string + private function writeHunk(array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output) : void { - if ($other === '') { - return 'an empty string is valid JSON'; + if ($this->addLineNumbers) { + fwrite($output, '@@ -' . $fromStart); + if (!$this->collapseRanges || 1 !== $fromRange) { + fwrite($output, ',' . $fromRange); + } + fwrite($output, ' +' . $toStart); + if (!$this->collapseRanges || 1 !== $toRange) { + fwrite($output, ',' . $toRange); + } + fwrite($output, " @@\n"); + } else { + fwrite($output, "@@ @@\n"); + } + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === Differ::ADDED) { + fwrite($output, '+' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::REMOVED) { + fwrite($output, '-' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::OLD) { + fwrite($output, ' ' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { + fwrite($output, "\n"); + // $diff[$i][0] + } else { + /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */ + fwrite($output, ' ' . $diff[$i][0]); + } } - json_decode($other); - $error = (string) \PHPUnit\Framework\Constraint\JsonMatchesErrorMessageProvider::determineJsonError((string) json_last_error()); - return sprintf('%s is valid JSON (%s)', $this->exporter()->shortenedExport($other), $error); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function strlen; -use function strpos; -use PHPUnit\Framework\InvalidArgumentException; +use function array_pop; +use function count; +use function max; +use function preg_match; +use function preg_split; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Unified diff parser. */ -final class StringStartsWith extends \PHPUnit\Framework\Constraint\Constraint +final class Parser { /** - * @var string + * @return Diff[] */ - private $prefix; - public function __construct(string $prefix) + public function parse(string $string) : array { - if (strlen($prefix) === 0) { - throw InvalidArgumentException::create(1, 'non-empty string'); + $lines = preg_split('(\\r\\n|\\r|\\n)', $string); + if (!empty($lines) && $lines[count($lines) - 1] === '') { + array_pop($lines); } - $this->prefix = $prefix; - } - /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'starts with "' . $this->prefix . '"'; + $lineCount = count($lines); + $diffs = []; + $diff = null; + $collected = []; + for ($i = 0; $i < $lineCount; ++$i) { + if (preg_match('#^---\\h+"?(?P[^\\v\\t"]+)#', $lines[$i], $fromMatch) && preg_match('#^\\+\\+\\+\\h+"?(?P[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) { + if ($diff !== null) { + $this->parseFileDiff($diff, $collected); + $diffs[] = $diff; + $collected = []; + } + $diff = new Diff($fromMatch['file'], $toMatch['file']); + ++$i; + } else { + if (preg_match('/^(?:diff --git |index [\\da-f\\.]+|[+-]{3} [ab])/', $lines[$i])) { + continue; + } + $collected[] = $lines[$i]; + } + } + if ($diff !== null && count($collected)) { + $this->parseFileDiff($diff, $collected); + $diffs[] = $diff; + } + return $diffs; } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool + private function parseFileDiff(Diff $diff, array $lines) : void { - return strpos((string) $other, $this->prefix) === 0; + $chunks = []; + $chunk = null; + $diffLines = []; + foreach ($lines as $line) { + if (preg_match('/^@@\\s+-(?P\\d+)(?:,\\s*(?P\\d+))?\\s+\\+(?P\\d+)(?:,\\s*(?P\\d+))?\\s+@@/', $line, $match)) { + $chunk = new Chunk((int) $match['start'], isset($match['startrange']) ? max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? max(1, (int) $match['endrange']) : 1); + $chunks[] = $chunk; + $diffLines = []; + continue; + } + if (preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { + $type = Line::UNCHANGED; + if ($match['type'] === '+') { + $type = Line::ADDED; + } elseif ($match['type'] === '-') { + $type = Line::REMOVED; + } + $diffLines[] = new Line($type, $match['line']); + if (null !== $chunk) { + $chunk->setLines($diffLines); + } + } + } + $diff->setChunks($chunks); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Diff; -use function mb_stripos; -use function mb_strtolower; -use function sprintf; -use function strpos; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class StringContains extends \PHPUnit\Framework\Constraint\Constraint +use function array_reverse; +use function count; +use function max; +use SplFixedArray; +final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** - * @var string - */ - private $string; - /** - * @var bool - */ - private $ignoreCase; - public function __construct(string $string, bool $ignoreCase = \false) - { - $this->string = $string; - $this->ignoreCase = $ignoreCase; - } - /** - * Returns a string representation of the constraint. + * {@inheritdoc} */ - public function toString() : string + public function calculate(array $from, array $to) : array { - if ($this->ignoreCase) { - $string = mb_strtolower($this->string, 'UTF-8'); - } else { - $string = $this->string; + $common = []; + $fromLength = count($from); + $toLength = count($to); + $width = $fromLength + 1; + $matrix = new SplFixedArray($width * ($toLength + 1)); + for ($i = 0; $i <= $fromLength; ++$i) { + $matrix[$i] = 0; } - return sprintf('contains "%s"', $string); - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other) : bool - { - if ('' === $this->string) { - return \true; + for ($j = 0; $j <= $toLength; ++$j) { + $matrix[$j * $width] = 0; } - if ($this->ignoreCase) { - /* - * We must use the multi byte safe version so we can accurately compare non latin upper characters with - * their lowercase equivalents. - */ - return mb_stripos($other, $this->string, 0, 'UTF-8') !== \false; + for ($i = 1; $i <= $fromLength; ++$i) { + for ($j = 1; $j <= $toLength; ++$j) { + $o = $j * $width + $i; + $matrix[$o] = max($matrix[$o - 1], $matrix[$o - $width], $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0); + } } - /* - * Use the non multi byte safe functions to see if the string is contained in $other. - * - * This function is very fast and we don't care about the character position in the string. - * - * Additionally, we want this method to be binary safe so we can check if some binary data is in other binary - * data. - */ - return strpos($other, $this->string) !== \false; + $i = $fromLength; + $j = $toLength; + while ($i > 0 && $j > 0) { + if ($from[$i - 1] === $to[$j - 1]) { + $common[] = $from[$i - 1]; + --$i; + --$j; + } else { + $o = $j * $width + $i; + if ($matrix[$o - $width] > $matrix[$o - 1]) { + --$j; + } else { + --$i; + } + } + } + return array_reverse($common); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Environment; +use const DIRECTORY_SEPARATOR; +use const STDIN; +use const STDOUT; +use function defined; +use function fclose; +use function fstat; +use function function_exists; +use function getenv; +use function is_resource; +use function is_string; +use function posix_isatty; use function preg_match; -use function sprintf; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -class RegularExpression extends \PHPUnit\Framework\Constraint\Constraint +use function proc_close; +use function proc_open; +use function sapi_windows_vt100_support; +use function shell_exec; +use function stream_get_contents; +use function stream_isatty; +use function trim; +final class Console { /** - * @var string + * @var int */ - private $pattern; - public function __construct(string $pattern) - { - $this->pattern = $pattern; - } + public const STDIN = 0; /** - * Returns a string representation of the constraint. + * @var int */ - public function toString() : string + public const STDOUT = 1; + /** + * @var int + */ + public const STDERR = 2; + /** + * Returns true if STDOUT supports colorization. + * + * This code has been copied and adapted from + * Symfony\Component\Console\Output\StreamOutput. + */ + public function hasColorSupport() : bool { - return sprintf('matches PCRE pattern "%s"', $this->pattern); + if ('Hyper' === getenv('TERM_PROGRAM')) { + return \true; + } + if ($this->isWindows()) { + // @codeCoverageIgnoreStart + return defined('STDOUT') && function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT) || \false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); + // @codeCoverageIgnoreEnd + } + if (!defined('STDOUT')) { + // @codeCoverageIgnoreStart + return \false; + // @codeCoverageIgnoreEnd + } + return $this->isInteractive(STDOUT); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. + * Returns the number of columns of the terminal. * - * @param mixed $other value or object to evaluate + * @codeCoverageIgnore */ - protected function matches($other) : bool + public function getNumberOfColumns() : int { - return preg_match($this->pattern, $other) > 0; + if (!$this->isInteractive(defined('STDIN') ? STDIN : self::STDIN)) { + return 80; + } + if ($this->isWindows()) { + return $this->getNumberOfColumnsWindows(); + } + return $this->getNumberOfColumnsInteractive(); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function strlen; -use function substr; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class StringEndsWith extends \PHPUnit\Framework\Constraint\Constraint -{ /** - * @var string + * Returns if the file descriptor is an interactive terminal or not. + * + * Normally, we want to use a resource as a parameter, yet sadly it's not always awailable, + * eg when running code in interactive console (`php -a`), STDIN/STDOUT/STDERR constants are not defined. + * + * @param int|resource $fileDescriptor */ - private $suffix; - public function __construct(string $suffix) + public function isInteractive($fileDescriptor = self::STDOUT) : bool { - $this->suffix = $suffix; + if (is_resource($fileDescriptor)) { + // These functions require a descriptor that is a real resource, not a numeric ID of it + if (function_exists('stream_isatty') && @stream_isatty($fileDescriptor)) { + return \true; + } + // Check if formatted mode is S_IFCHR + if (function_exists('fstat') && @stream_isatty($fileDescriptor)) { + $stat = @fstat(STDOUT); + return $stat ? 020000 === ($stat['mode'] & 0170000) : \false; + } + return \false; + } + return function_exists('posix_isatty') && @posix_isatty($fileDescriptor); + } + private function isWindows() : bool + { + return DIRECTORY_SEPARATOR === '\\'; } /** - * Returns a string representation of the constraint. + * @codeCoverageIgnore */ - public function toString() : string + private function getNumberOfColumnsInteractive() : int { - return 'ends with "' . $this->suffix . '"'; + if (function_exists('shell_exec') && preg_match('#\\d+ (\\d+)#', shell_exec('stty size') ?: '', $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } + } + if (function_exists('shell_exec') && preg_match('#columns = (\\d+);#', shell_exec('stty') ?: '', $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } + } + return 80; } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * @codeCoverageIgnore */ - protected function matches($other) : bool + private function getNumberOfColumnsWindows() : int { - return substr($other, 0 - strlen($this->suffix)) === $this->suffix; + $ansicon = getenv('ANSICON'); + $columns = 80; + if (is_string($ansicon) && preg_match('/^(\\d+)x\\d+ \\(\\d+x(\\d+)\\)$/', trim($ansicon), $matches)) { + $columns = (int) $matches[1]; + } elseif (function_exists('proc_open')) { + $process = proc_open('mode CON', [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, null, ['suppress_errors' => \true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + if (preg_match('/--------+\\r?\\n.+?(\\d+)\\r?\\n.+?(\\d+)\\r?\\n/', $info, $matches)) { + $columns = (int) $matches[2]; + } + } + } + return $columns - 1; } } +sebastian/environment + +Copyright (c) 2014-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Environment; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsNull extends \PHPUnit\Framework\Constraint\Constraint +use const DIRECTORY_SEPARATOR; +use const PHP_OS; +use const PHP_OS_FAMILY; +use function defined; +final class OperatingSystem { /** - * Returns a string representation of the constraint. - */ - public function toString() : string - { - return 'is null'; - } - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * Returns PHP_OS_FAMILY (if defined (which it is on PHP >= 7.2)). + * Returns a string (compatible with PHP_OS_FAMILY) derived from PHP_OS otherwise. */ - protected function matches($other) : bool + public function getFamily() : string { - return $other === null; + if (defined('PHP_OS_FAMILY')) { + return PHP_OS_FAMILY; + } + if (DIRECTORY_SEPARATOR === '\\') { + return 'Windows'; + } + switch (PHP_OS) { + case 'Darwin': + return 'Darwin'; + case 'DragonFly': + case 'FreeBSD': + case 'NetBSD': + case 'OpenBSD': + return 'BSD'; + case 'Linux': + return 'Linux'; + case 'SunOS': + return 'Solaris'; + default: + return 'Unknown'; + } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Constraint; +namespace PHPUnit\SebastianBergmann\Environment; -use function gettype; -use function is_array; -use function is_bool; -use function is_callable; -use function is_float; -use function is_int; -use function is_iterable; -use function is_numeric; -use function is_object; -use function is_scalar; -use function is_string; +use const PHP_BINARY; +use const PHP_BINDIR; +use const PHP_MAJOR_VERSION; +use const PHP_SAPI; +use const PHP_VERSION; +use function array_map; +use function array_merge; +use function defined; +use function escapeshellarg; +use function explode; +use function extension_loaded; +use function getenv; +use function ini_get; +use function is_readable; +use function parse_ini_file; +use function php_ini_loaded_file; +use function php_ini_scanned_files; +use function phpversion; use function sprintf; +use function strpos; /** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * Utility class for HHVM/PHP environment handling. */ -final class IsType extends \PHPUnit\Framework\Constraint\Constraint +final class Runtime { /** * @var string */ - public const TYPE_ARRAY = 'array'; - /** - * @var string - */ - public const TYPE_BOOL = 'bool'; - /** - * @var string - */ - public const TYPE_FLOAT = 'float'; - /** - * @var string - */ - public const TYPE_INT = 'int'; - /** - * @var string - */ - public const TYPE_NULL = 'null'; - /** - * @var string - */ - public const TYPE_NUMERIC = 'numeric'; - /** - * @var string - */ - public const TYPE_OBJECT = 'object'; - /** - * @var string - */ - public const TYPE_RESOURCE = 'resource'; - /** - * @var string - */ - public const TYPE_CLOSED_RESOURCE = 'resource (closed)'; - /** - * @var string - */ - public const TYPE_STRING = 'string'; - /** - * @var string - */ - public const TYPE_SCALAR = 'scalar'; + private static $binary; /** - * @var string + * Returns true when Xdebug or PCOV is available or + * the runtime used is PHPDBG. */ - public const TYPE_CALLABLE = 'callable'; + public function canCollectCodeCoverage() : bool + { + return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage(); + } /** - * @var string + * Returns true when Zend OPcache is loaded, enabled, + * and is configured to discard comments. */ - public const TYPE_ITERABLE = 'iterable'; + public function discardsComments() : bool + { + if (!$this->isOpcacheActive()) { + return \false; + } + if (ini_get('opcache.save_comments') !== '0') { + return \false; + } + return \true; + } /** - * @var array + * Returns true when Zend OPcache is loaded, enabled, + * and is configured to perform just-in-time compilation. */ - private const KNOWN_TYPES = ['array' => \true, 'boolean' => \true, 'bool' => \true, 'double' => \true, 'float' => \true, 'integer' => \true, 'int' => \true, 'null' => \true, 'numeric' => \true, 'object' => \true, 'real' => \true, 'resource' => \true, 'resource (closed)' => \true, 'string' => \true, 'scalar' => \true, 'callable' => \true, 'iterable' => \true]; + public function performsJustInTimeCompilation() : bool + { + if (PHP_MAJOR_VERSION < 8) { + return \false; + } + if (!$this->isOpcacheActive()) { + return \false; + } + if (strpos(ini_get('opcache.jit'), '0') === 0) { + return \false; + } + return \true; + } /** - * @var string + * Returns the path to the binary of the current runtime. + * Appends ' --php' to the path when the runtime is HHVM. */ - private $type; + public function getBinary() : string + { + // HHVM + if (self::$binary === null && $this->isHHVM()) { + // @codeCoverageIgnoreStart + if ((self::$binary = getenv('PHP_BINARY')) === \false) { + self::$binary = PHP_BINARY; + } + self::$binary = escapeshellarg(self::$binary) . ' --php' . ' -d hhvm.php7.all=1'; + // @codeCoverageIgnoreEnd + } + if (self::$binary === null && PHP_BINARY !== '') { + self::$binary = escapeshellarg(PHP_BINARY); + } + if (self::$binary === null) { + // @codeCoverageIgnoreStart + $possibleBinaryLocations = [PHP_BINDIR . '/php', PHP_BINDIR . '/php-cli.exe', PHP_BINDIR . '/php.exe']; + foreach ($possibleBinaryLocations as $binary) { + if (is_readable($binary)) { + self::$binary = escapeshellarg($binary); + break; + } + } + // @codeCoverageIgnoreEnd + } + if (self::$binary === null) { + // @codeCoverageIgnoreStart + self::$binary = 'php'; + // @codeCoverageIgnoreEnd + } + return self::$binary; + } + public function getNameWithVersion() : string + { + return $this->getName() . ' ' . $this->getVersion(); + } + public function getNameWithVersionAndCodeCoverageDriver() : string + { + if (!$this->canCollectCodeCoverage() || $this->hasPHPDBGCodeCoverage()) { + return $this->getNameWithVersion(); + } + if ($this->hasPCOV()) { + return sprintf('%s with PCOV %s', $this->getNameWithVersion(), phpversion('pcov')); + } + if ($this->hasXdebug()) { + return sprintf('%s with Xdebug %s', $this->getNameWithVersion(), phpversion('xdebug')); + } + } + public function getName() : string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return 'HHVM'; + // @codeCoverageIgnoreEnd + } + if ($this->isPHPDBG()) { + // @codeCoverageIgnoreStart + return 'PHPDBG'; + // @codeCoverageIgnoreEnd + } + return 'PHP'; + } + public function getVendorUrl() : string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return 'http://hhvm.com/'; + // @codeCoverageIgnoreEnd + } + return 'https://secure.php.net/'; + } + public function getVersion() : string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return HHVM_VERSION; + // @codeCoverageIgnoreEnd + } + return PHP_VERSION; + } /** - * @throws \PHPUnit\Framework\Exception + * Returns true when the runtime used is PHP and Xdebug is loaded. */ - public function __construct(string $type) + public function hasXdebug() : bool { - if (!isset(self::KNOWN_TYPES[$type])) { - throw new \PHPUnit\Framework\Exception(sprintf('Type specified for PHPUnit\\Framework\\Constraint\\IsType <%s> ' . 'is not a valid type.', $type)); - } - $this->type = $type; + return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug'); } /** - * Returns a string representation of the constraint. + * Returns true when the runtime used is HHVM. */ - public function toString() : string + public function isHHVM() : bool { - return sprintf('is of type "%s"', $this->type); + return defined('PHPUnit\\HHVM_VERSION'); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * Returns true when the runtime used is PHP without the PHPDBG SAPI. */ - protected function matches($other) : bool + public function isPHP() : bool { - switch ($this->type) { - case 'numeric': - return is_numeric($other); - case 'integer': - case 'int': - return is_int($other); - case 'double': - case 'float': - case 'real': - return is_float($other); - case 'string': - return is_string($other); - case 'boolean': - case 'bool': - return is_bool($other); - case 'null': - return null === $other; - case 'array': - return is_array($other); - case 'object': - return is_object($other); - case 'resource': - $type = gettype($other); - return $type === 'resource' || $type === 'resource (closed)'; - case 'resource (closed)': - return gettype($other) === 'resource (closed)'; - case 'scalar': - return is_scalar($other); - case 'callable': - return is_callable($other); - case 'iterable': - return is_iterable($other); - default: - return \false; - } + return !$this->isHHVM() && !$this->isPHPDBG(); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use ReflectionClass; -use ReflectionException; -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class IsInstanceOf extends \PHPUnit\Framework\Constraint\Constraint -{ /** - * @var string + * Returns true when the runtime used is PHP with the PHPDBG SAPI. */ - private $className; - public function __construct(string $className) + public function isPHPDBG() : bool { - $this->className = $className; + return PHP_SAPI === 'phpdbg' && !$this->isHHVM(); } /** - * Returns a string representation of the constraint. + * Returns true when the runtime used is PHP with the PHPDBG SAPI + * and the phpdbg_*_oplog() functions are available (PHP >= 7.0). */ - public function toString() : string + public function hasPHPDBGCodeCoverage() : bool { - return sprintf('is instance of %s "%s"', $this->getType(), $this->className); + return $this->isPHPDBG(); } /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate + * Returns true when the runtime used is PHP with PCOV loaded and enabled. */ - protected function matches($other) : bool + public function hasPCOV() : bool { - return $other instanceof $this->className; + return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled'); } /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object + * Parses the loaded php.ini file (if any) as well as all + * additional php.ini files from the additional ini dir for + * a list of all configuration settings loaded from files + * at startup. Then checks for each php.ini setting passed + * via the `$values` parameter whether this setting has + * been changed at runtime. Returns an array of strings + * where each string has the format `key=value` denoting + * the name of a changed php.ini setting with its new value. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @return string[] */ - protected function failureDescription($other) : string + public function getCurrentSettings(array $values) : array { - return sprintf('%s is an instance of %s "%s"', $this->exporter()->shortenedExport($other), $this->getType(), $this->className); + $diff = []; + $files = []; + if ($file = php_ini_loaded_file()) { + $files[] = $file; + } + if ($scanned = php_ini_scanned_files()) { + $files = array_merge($files, array_map('trim', explode(",\n", $scanned))); + } + foreach ($files as $ini) { + $config = parse_ini_file($ini, \true); + foreach ($values as $value) { + $set = ini_get($value); + if (empty($set)) { + continue; + } + if (!isset($config[$value]) || $set !== $config[$value]) { + $diff[$value] = sprintf('%s=%s', $value, $set); + } + } + } + return $diff; } - private function getType() : string + private function isOpcacheActive() : bool { - try { - $reflection = new ReflectionClass($this->className); - if ($reflection->isInterface()) { - return 'interface'; - } - } catch (ReflectionException $e) { + if (!extension_loaded('Zend OPcache')) { + return \false; } - return 'class'; + if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ini_get('opcache.enable_cli') === '1') { + return \true; + } + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && ini_get('opcache.enable') === '1') { + return \true; + } + return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\Exporter; -use function array_filter; -use function array_map; -use function array_values; +use function bin2hex; use function count; -use function explode; -use function in_array; -use function strpos; -use function trim; +use function function_exists; +use function get_class; +use function get_resource_type; +use function gettype; +use function implode; +use function ini_get; +use function ini_set; +use function is_array; +use function is_float; +use function is_object; +use function is_resource; +use function is_string; +use function mb_strlen; +use function mb_substr; +use function preg_match; +use function spl_object_hash; +use function sprintf; +use function str_repeat; +use function str_replace; +use function strlen; +use function substr; +use function var_export; +use PHPUnit\SebastianBergmann\RecursionContext\Context; +use SplObjectStorage; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * A nifty utility for visualizing PHP variables. + * + * + * export(new Exception); + * */ -final class ExecutionOrderDependency +class Exporter { /** - * @var string - */ - private $className = ''; - /** - * @var string - */ - private $methodName = ''; - /** - * @var bool + * Exports a value as a string. + * + * The output of this method is similar to the output of print_r(), but + * improved in various aspects: + * + * - NULL is rendered as "null" (instead of "") + * - TRUE is rendered as "true" (instead of "1") + * - FALSE is rendered as "false" (instead of "") + * - Strings are always quoted with single quotes + * - Carriage returns and newlines are normalized to \n + * - Recursion and repeated rendering is treated properly + * + * @param int $indentation The indentation level of the 2nd+ line + * + * @return string */ - private $useShallowClone = \false; + public function export($value, $indentation = 0) + { + return $this->recursiveExport($value, $indentation); + } /** - * @var bool + * @param array $data + * @param Context $context + * + * @return string */ - private $useDeepClone = \false; - public static function createFromDependsAnnotation(string $className, string $annotation) : self + public function shortenedRecursiveExport(&$data, Context $context = null) { - // Split clone option and target - $parts = explode(' ', trim($annotation), 2); - if (count($parts) === 1) { - $cloneOption = ''; - $target = $parts[0]; - } else { - $cloneOption = $parts[0]; - $target = $parts[1]; + $result = []; + $exporter = new self(); + if (!$context) { + $context = new Context(); } - // Prefix provided class for targets assumed to be in scope - if ($target !== '' && strpos($target, '::') === \false) { - $target = $className . '::' . $target; + $array = $data; + $context->add($data); + foreach ($array as $key => $value) { + if (is_array($value)) { + if ($context->contains($data[$key]) !== \false) { + $result[] = '*RECURSION*'; + } else { + $result[] = sprintf('array(%s)', $this->shortenedRecursiveExport($data[$key], $context)); + } + } else { + $result[] = $exporter->shortenedExport($value); + } } - return new self($target, null, $cloneOption); + return implode(', ', $result); } /** - * @psalm-param list $dependencies + * Exports a value into a single-line string. * - * @psalm-return list + * The output of this method is similar to the output of + * SebastianBergmann\Exporter\Exporter::export(). + * + * Newlines are replaced by the visible string '\n'. + * Contents of arrays and objects (if any) are replaced by '...'. + * + * @return string + * + * @see SebastianBergmann\Exporter\Exporter::export */ - public static function filterInvalid(array $dependencies) : array + public function shortenedExport($value) { - return array_values(array_filter($dependencies, static function (self $d) { - return $d->isValid(); - })); + if (is_string($value)) { + $string = str_replace("\n", '', $this->export($value)); + if (function_exists('mb_strlen')) { + if (mb_strlen($string) > 40) { + $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); + } + } else { + if (strlen($string) > 40) { + $string = substr($string, 0, 30) . '...' . substr($string, -7); + } + } + return $string; + } + if (is_object($value)) { + return sprintf('%s Object (%s)', get_class($value), count($this->toArray($value)) > 0 ? '...' : ''); + } + if (is_array($value)) { + return sprintf('Array (%s)', count($value) > 0 ? '...' : ''); + } + return $this->export($value); } /** - * @psalm-param list $existing - * @psalm-param list $additional + * Converts an object to an array containing all of its private, protected + * and public properties. * - * @psalm-return list + * @return array */ - public static function mergeUnique(array $existing, array $additional) : array + public function toArray($value) { - $existingTargets = array_map(static function ($dependency) { - return $dependency->getTarget(); - }, $existing); - foreach ($additional as $dependency) { - if (in_array($dependency->getTarget(), $existingTargets, \true)) { + if (!is_object($value)) { + return (array) $value; + } + $array = []; + foreach ((array) $value as $key => $val) { + // Exception traces commonly reference hundreds to thousands of + // objects currently loaded in memory. Including them in the result + // has a severe negative performance impact. + if ("\x00Error\x00trace" === $key || "\x00Exception\x00trace" === $key) { continue; } - $existingTargets[] = $dependency->getTarget(); - $existing[] = $dependency; + // properties are transformed to keys in the following way: + // private $property => "\0Classname\0property" + // protected $property => "\0*\0property" + // public $property => "property" + if (preg_match('/^\\0.+\\0(.+)$/', (string) $key, $matches)) { + $key = $matches[1]; + } + // See https://github.com/php/php-src/commit/5721132 + if ($key === "\x00gcdata") { + continue; + } + $array[$key] = $val; } - return $existing; + // Some internal classes like SplObjectStorage don't work with the + // above (fast) mechanism nor with reflection in Zend. + // Format the output similarly to print_r() in this case + if ($value instanceof SplObjectStorage) { + foreach ($value as $key => $val) { + $array[spl_object_hash($val)] = ['obj' => $val, 'inf' => $value->getInfo()]; + } + } + return $array; } /** - * @psalm-param list $left - * @psalm-param list $right + * Recursive implementation of export. * - * @psalm-return list + * @param mixed $value The value to export + * @param int $indentation The indentation level of the 2nd+ line + * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects + * + * @return string + * + * @see SebastianBergmann\Exporter\Exporter::export */ - public static function diff(array $left, array $right) : array + protected function recursiveExport(&$value, $indentation, $processed = null) { - if ($right === []) { - return $left; + if ($value === null) { + return 'null'; } - if ($left === []) { - return []; + if ($value === \true) { + return 'true'; } - $diff = []; - $rightTargets = array_map(static function ($dependency) { - return $dependency->getTarget(); - }, $right); - foreach ($left as $dependency) { - if (in_array($dependency->getTarget(), $rightTargets, \true)) { - continue; + if ($value === \false) { + return 'false'; + } + if (is_float($value)) { + $precisionBackup = ini_get('precision'); + ini_set('precision', '-1'); + try { + $valueStr = (string) $value; + if ((string) (int) $value === $valueStr) { + return $valueStr . '.0'; + } + return $valueStr; + } finally { + ini_set('precision', $precisionBackup); } - $diff[] = $dependency; } - return $diff; - } - public function __construct(string $classOrCallableName, ?string $methodName = null, ?string $option = null) - { - if ($classOrCallableName === '') { - return; + if (gettype($value) === 'resource (closed)') { + return 'resource (closed)'; } - if (strpos($classOrCallableName, '::') !== \false) { - [$this->className, $this->methodName] = explode('::', $classOrCallableName); - } else { - $this->className = $classOrCallableName; - $this->methodName = !empty($methodName) ? $methodName : 'class'; + if (is_resource($value)) { + return sprintf('resource(%d) of type (%s)', $value, get_resource_type($value)); } - if ($option === 'clone') { - $this->useDeepClone = \true; - } elseif ($option === 'shallowClone') { - $this->useShallowClone = \true; + if (is_string($value)) { + // Match for most non printable chars somewhat taking multibyte chars into account + if (preg_match('/[^\\x09-\\x0d\\x1b\\x20-\\xff]/', $value)) { + return 'Binary String: 0x' . bin2hex($value); + } + return "'" . str_replace('', "\n", str_replace(["\r\n", "\n\r", "\r", "\n"], ['\\r\\n', '\\n\\r', '\\r', '\\n'], $value)) . "'"; } - } - public function __toString() : string - { - return $this->getTarget(); - } - public function isValid() : bool - { - // Invalid dependencies can be declared and are skipped by the runner - return $this->className !== '' && $this->methodName !== ''; - } - public function useShallowClone() : bool - { - return $this->useShallowClone; - } - public function useDeepClone() : bool - { - return $this->useDeepClone; - } - public function targetIsClass() : bool - { - return $this->methodName === 'class'; - } - public function getTarget() : string - { - return $this->isValid() ? $this->className . '::' . $this->methodName : ''; - } - public function getTargetClassName() : string - { - return $this->className; + $whitespace = str_repeat(' ', 4 * $indentation); + if (!$processed) { + $processed = new Context(); + } + if (is_array($value)) { + if (($key = $processed->contains($value)) !== \false) { + return 'Array &' . $key; + } + $array = $value; + $key = $processed->add($value); + $values = ''; + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf('%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($value[$k], $indentation + 1, $processed)); + } + $values = "\n" . $values . $whitespace; + } + return sprintf('Array &%s (%s)', $key, $values); + } + if (is_object($value)) { + $class = get_class($value); + if ($hash = $processed->contains($value)) { + return sprintf('%s Object &%s', $class, $hash); + } + $hash = $processed->add($value); + $values = ''; + $array = $this->toArray($value); + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf('%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($v, $indentation + 1, $processed)); + } + $values = "\n" . $values . $whitespace; + } + return sprintf('%s Object &%s (%s)', $class, $hash, $values); + } + return var_export($value, \true); } } - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; +Copyright (c) 2002-2021, Sebastian Bergmann . +All rights reserved. -use Throwable; -/** - * @deprecated Use the `TestHook` interfaces instead - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface TestListener -{ - /** - * An error occurred. - * - * @deprecated Use `AfterTestErrorHook::executeAfterTestError` instead - */ - public function addError(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; - /** - * A warning occurred. - * - * @deprecated Use `AfterTestWarningHook::executeAfterTestWarning` instead - */ - public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void; - /** - * A failure occurred. - * - * @deprecated Use `AfterTestFailureHook::executeAfterTestFailure` instead - */ - public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void; - /** - * Incomplete test. - * - * @deprecated Use `AfterIncompleteTestHook::executeAfterIncompleteTest` instead - */ - public function addIncompleteTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; - /** - * Risky test. - * - * @deprecated Use `AfterRiskyTestHook::executeAfterRiskyTest` instead - */ - public function addRiskyTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; - /** - * Skipped test. - * - * @deprecated Use `AfterSkippedTestHook::executeAfterSkippedTest` instead - */ - public function addSkippedTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void; - /** - * A test suite started. - */ - public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void; - /** - * A test suite ended. - */ - public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void; - /** - * A test started. - * - * @deprecated Use `BeforeTestHook::executeBeforeTest` instead - */ - public function startTest(\PHPUnit\Framework\Test $test) : void; - /** - * A test ended. - * - * @deprecated Use `AfterTestHook::executeAfterTest` instead - */ - public function endTest(\PHPUnit\Framework\Test $test, float $time) : void; -} +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\GlobalState; -use function assert; -use function count; -use function get_class; +use const PHP_EOL; +use function is_array; +use function is_scalar; +use function serialize; use function sprintf; -use function trim; -use PHPUnit\Util\Filter; -use PHPUnit\Util\InvalidDataSetException; -use PHPUnit\Util\Test as TestUtil; -use ReflectionClass; -use Throwable; +use function var_export; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * Exports parts of a Snapshot as PHP code. */ -final class TestBuilder +final class CodeExporter { - public function build(ReflectionClass $theClass, string $methodName) : \PHPUnit\Framework\Test + public function constants(Snapshot $snapshot) : string { - $className = $theClass->getName(); - if (!$theClass->isInstantiable()) { - return new \PHPUnit\Framework\ErrorTestCase(sprintf('Cannot instantiate class "%s".', $className)); - } - $backupSettings = TestUtil::getBackupSettings($className, $methodName); - $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings($className, $methodName); - $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings($className, $methodName); - $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings($className, $methodName); - $constructor = $theClass->getConstructor(); - if ($constructor === null) { - throw new \PHPUnit\Framework\Exception('No valid test provided.'); + $result = ''; + foreach ($snapshot->constants() as $name => $value) { + $result .= sprintf('if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, $this->exportVariable($value)); } - $parameters = $constructor->getParameters(); - // TestCase() or TestCase($name) - if (count($parameters) < 2) { - $test = $this->buildTestWithoutData($className); - } else { - try { - $data = TestUtil::getProvidedData($className, $methodName); - } catch (\PHPUnit\Framework\IncompleteTestError $e) { - $message = sprintf("Test for %s::%s marked incomplete by data provider\n%s", $className, $methodName, $this->throwableToString($e)); - $data = new \PHPUnit\Framework\IncompleteTestCase($className, $methodName, $message); - } catch (\PHPUnit\Framework\SkippedTestError $e) { - $message = sprintf("Test for %s::%s skipped by data provider\n%s", $className, $methodName, $this->throwableToString($e)); - $data = new \PHPUnit\Framework\SkippedTestCase($className, $methodName, $message); - } catch (Throwable $t) { - $message = sprintf("The data provider specified for %s::%s is invalid.\n%s", $className, $methodName, $this->throwableToString($t)); - $data = new \PHPUnit\Framework\ErrorTestCase($message); - } - // Test method with @dataProvider. - if (isset($data)) { - $test = $this->buildDataProviderTestSuite($methodName, $className, $data, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); - } else { - $test = $this->buildTestWithoutData($className); - } + return $result; + } + public function globalVariables(Snapshot $snapshot) : string + { + $result = <<<'EOT' +call_user_func( + function () + { + foreach (array_keys($GLOBALS) as $key) { + unset($GLOBALS[$key]); } - if ($test instanceof \PHPUnit\Framework\TestCase) { - $test->setName($methodName); - $this->configureTestCase($test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); + } +); + + +EOT; + foreach ($snapshot->globalVariables() as $name => $value) { + $result .= sprintf('$GLOBALS[%s] = %s;' . PHP_EOL, $this->exportVariable($name), $this->exportVariable($value)); } - return $test; + return $result; } - /** @psalm-param class-string $className */ - private function buildTestWithoutData(string $className) + public function iniSettings(Snapshot $snapshot) : string { - return new $className(); + $result = ''; + foreach ($snapshot->iniSettings() as $key => $value) { + $result .= sprintf('@ini_set(%s, %s);' . "\n", $this->exportVariable($key), $this->exportVariable($value)); + } + return $result; } - /** @psalm-param class-string $className */ - private function buildDataProviderTestSuite(string $methodName, string $className, $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings) : \PHPUnit\Framework\DataProviderTestSuite + private function exportVariable($variable) : string { - $dataProviderTestSuite = new \PHPUnit\Framework\DataProviderTestSuite($className . '::' . $methodName); - $groups = TestUtil::getGroups($className, $methodName); - if ($data instanceof \PHPUnit\Framework\ErrorTestCase || $data instanceof \PHPUnit\Framework\SkippedTestCase || $data instanceof \PHPUnit\Framework\IncompleteTestCase) { - $dataProviderTestSuite->addTest($data, $groups); - } else { - foreach ($data as $_dataName => $_data) { - $_test = new $className($methodName, $_data, $_dataName); - assert($_test instanceof \PHPUnit\Framework\TestCase); - $this->configureTestCase($_test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings); - $dataProviderTestSuite->addTest($_test, $groups); - } + if (is_scalar($variable) || null === $variable || is_array($variable) && $this->arrayOnlyContainsScalars($variable)) { + return var_export($variable, \true); } - return $dataProviderTestSuite; + return 'unserialize(' . var_export(serialize($variable), \true) . ')'; } - private function configureTestCase(\PHPUnit\Framework\TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings) : void + private function arrayOnlyContainsScalars(array $array) : bool { - if ($runTestInSeparateProcess) { - $test->setRunTestInSeparateProcess(\true); - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + $result = \true; + foreach ($array as $element) { + if (is_array($element)) { + $result = $this->arrayOnlyContainsScalars($element); + } elseif (!is_scalar($element) && null !== $element) { + $result = \false; } - } - if ($runClassInSeparateProcess) { - $test->setRunClassInSeparateProcess(\true); - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + if ($result === \false) { + break; } } - if ($backupSettings['backupGlobals'] !== null) { - $test->setBackupGlobals($backupSettings['backupGlobals']); - } - if ($backupSettings['backupStaticAttributes'] !== null) { - $test->setBackupStaticAttributes($backupSettings['backupStaticAttributes']); - } - } - private function throwableToString(Throwable $t) : string - { - $message = $t->getMessage(); - if (empty(trim($message))) { - $message = ''; - } - if ($t instanceof InvalidDataSetException) { - return sprintf("%s\n%s", $message, Filter::getFilteredStacktrace($t)); - } - return sprintf("%s: %s\n%s", get_class($t), $message, Filter::getFilteredStacktrace($t)); + return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\GlobalState; -use const PHP_EOL; -use function count; -use function function_exists; -use function get_class; -use function sprintf; -use AssertionError; -use Countable; -use Error; -use PHPUnit\Util\ErrorHandler; -use PHPUnit\Util\ExcludeList; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Test as TestUtil; +use function in_array; +use function strpos; use ReflectionClass; -use ReflectionException; -use PHPUnit\SebastianBergmann\CodeCoverage\CodeCoverage; -use PHPUnit\SebastianBergmann\CodeCoverage\Exception as OriginalCodeCoverageException; -use PHPUnit\SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; -use PHPUnit\SebastianBergmann\Invoker\Invoker; -use PHPUnit\SebastianBergmann\Invoker\TimeoutException; -use PHPUnit\SebastianBergmann\ResourceOperations\ResourceOperations; -use PHPUnit\SebastianBergmann\Timer\Timer; -use Throwable; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestResult implements Countable +final class ExcludeList { /** * @var array */ - private $passed = []; - /** - * @var array - */ - private $passedTestClasses = []; - /** - * @var bool - */ - private $currentTestSuiteFailed = \false; - /** - * @var TestFailure[] - */ - private $errors = []; - /** - * @var TestFailure[] - */ - private $failures = []; - /** - * @var TestFailure[] - */ - private $warnings = []; - /** - * @var TestFailure[] - */ - private $notImplemented = []; - /** - * @var TestFailure[] - */ - private $risky = []; - /** - * @var TestFailure[] - */ - private $skipped = []; - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @var TestListener[] - */ - private $listeners = []; - /** - * @var int - */ - private $runTests = 0; - /** - * @var float - */ - private $time = 0; - /** - * Code Coverage information. - * - * @var CodeCoverage - */ - private $codeCoverage; - /** - * @var bool - */ - private $convertDeprecationsToExceptions = \false; - /** - * @var bool - */ - private $convertErrorsToExceptions = \true; - /** - * @var bool - */ - private $convertNoticesToExceptions = \true; - /** - * @var bool - */ - private $convertWarningsToExceptions = \true; - /** - * @var bool - */ - private $stop = \false; - /** - * @var bool - */ - private $stopOnError = \false; - /** - * @var bool - */ - private $stopOnFailure = \false; - /** - * @var bool - */ - private $stopOnWarning = \false; - /** - * @var bool - */ - private $beStrictAboutTestsThatDoNotTestAnything = \true; - /** - * @var bool - */ - private $beStrictAboutOutputDuringTests = \false; - /** - * @var bool - */ - private $beStrictAboutTodoAnnotatedTests = \false; - /** - * @var bool - */ - private $beStrictAboutResourceUsageDuringSmallTests = \false; - /** - * @var bool - */ - private $enforceTimeLimit = \false; - /** - * @var bool - */ - private $forceCoversAnnotation = \false; - /** - * @var int - */ - private $timeoutForSmallTests = 1; - /** - * @var int - */ - private $timeoutForMediumTests = 10; - /** - * @var int - */ - private $timeoutForLargeTests = 60; - /** - * @var bool - */ - private $stopOnRisky = \false; - /** - * @var bool - */ - private $stopOnIncomplete = \false; - /** - * @var bool - */ - private $stopOnSkipped = \false; + private $globalVariables = []; /** - * @var bool + * @var string[] */ - private $lastTestFailed = \false; + private $classes = []; /** - * @var int + * @var string[] */ - private $defaultTimeLimit = 0; + private $classNamePrefixes = []; /** - * @var bool + * @var string[] */ - private $stopOnDefect = \false; + private $parentClasses = []; /** - * @var bool + * @var string[] */ - private $registerMockObjectsFromTestArgumentsRecursively = \false; + private $interfaces = []; /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Registers a TestListener. + * @var array */ - public function addListener(\PHPUnit\Framework\TestListener $listener) : void + private $staticAttributes = []; + public function addGlobalVariable(string $variableName) : void { - $this->listeners[] = $listener; + $this->globalVariables[$variableName] = \true; } - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Unregisters a TestListener. - */ - public function removeListener(\PHPUnit\Framework\TestListener $listener) : void + public function addClass(string $className) : void { - foreach ($this->listeners as $key => $_listener) { - if ($listener === $_listener) { - unset($this->listeners[$key]); - } - } + $this->classes[] = $className; } - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Flushes all flushable TestListeners. - */ - public function flushListeners() : void + public function addSubclassesOf(string $className) : void { - foreach ($this->listeners as $listener) { - if ($listener instanceof Printer) { - $listener->flush(); - } - } + $this->parentClasses[] = $className; } - /** - * Adds an error to the list of errors. - */ - public function addError(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void + public function addImplementorsOf(string $interfaceName) : void { - if ($t instanceof \PHPUnit\Framework\RiskyTestError) { - $this->recordRisky($test, $t); - $notifyMethod = 'addRiskyTest'; - if ($test instanceof \PHPUnit\Framework\TestCase) { - $test->markAsRisky(); - } - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($t instanceof \PHPUnit\Framework\IncompleteTest) { - $this->recordNotImplemented($test, $t); - $notifyMethod = 'addIncompleteTest'; - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($t instanceof \PHPUnit\Framework\SkippedTest) { - $this->recordSkipped($test, $t); - $notifyMethod = 'addSkippedTest'; - if ($this->stopOnSkipped) { - $this->stop(); - } - } else { - $this->recordError($test, $t); - $notifyMethod = 'addError'; - if ($this->stopOnError || $this->stopOnFailure) { - $this->stop(); - } - } - // @see https://github.com/sebastianbergmann/phpunit/issues/1953 - if ($t instanceof Error) { - $t = new \PHPUnit\Framework\ExceptionWrapper($t); - } - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $t, $time); + $this->interfaces[] = $interfaceName; + } + public function addClassNamePrefix(string $classNamePrefix) : void + { + $this->classNamePrefixes[] = $classNamePrefix; + } + public function addStaticAttribute(string $className, string $attributeName) : void + { + if (!isset($this->staticAttributes[$className])) { + $this->staticAttributes[$className] = []; } - $this->lastTestFailed = \true; - $this->time += $time; + $this->staticAttributes[$className][$attributeName] = \true; } - /** - * Adds a warning to the list of warnings. - * The passed in exception caused the warning. - */ - public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void + public function isGlobalVariableExcluded(string $variableName) : bool { - if ($this->stopOnWarning || $this->stopOnDefect) { - $this->stop(); - } - $this->recordWarning($test, $e); - foreach ($this->listeners as $listener) { - $listener->addWarning($test, $e, $time); - } - $this->time += $time; + return isset($this->globalVariables[$variableName]); } - /** - * Adds a failure to the list of failures. - * The passed in exception caused the failure. - */ - public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void + public function isStaticAttributeExcluded(string $className, string $attributeName) : bool { - if ($e instanceof \PHPUnit\Framework\RiskyTestError || $e instanceof \PHPUnit\Framework\OutputError) { - $this->recordRisky($test, $e); - $notifyMethod = 'addRiskyTest'; - if ($test instanceof \PHPUnit\Framework\TestCase) { - $test->markAsRisky(); - } - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($e instanceof \PHPUnit\Framework\IncompleteTest) { - $this->recordNotImplemented($test, $e); - $notifyMethod = 'addIncompleteTest'; - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($e instanceof \PHPUnit\Framework\SkippedTest) { - $this->recordSkipped($test, $e); - $notifyMethod = 'addSkippedTest'; - if ($this->stopOnSkipped) { - $this->stop(); + if (in_array($className, $this->classes, \true)) { + return \true; + } + foreach ($this->classNamePrefixes as $prefix) { + if (strpos($className, $prefix) === 0) { + return \true; } - } else { - $this->failures[] = new \PHPUnit\Framework\TestFailure($test, $e); - $notifyMethod = 'addFailure'; - if ($this->stopOnFailure || $this->stopOnDefect) { - $this->stop(); + } + $class = new ReflectionClass($className); + foreach ($this->parentClasses as $type) { + if ($class->isSubclassOf($type)) { + return \true; } } - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $e, $time); + foreach ($this->interfaces as $type) { + if ($class->implementsInterface($type)) { + return \true; + } } - $this->lastTestFailed = \true; - $this->time += $time; - } - /** - * Informs the result that a test suite will be started. - */ - public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void - { - $this->currentTestSuiteFailed = \false; - foreach ($this->listeners as $listener) { - $listener->startTestSuite($suite); + if (isset($this->staticAttributes[$className][$attributeName])) { + return \true; } + return \false; } +} +sebastian/global-state + +Copyright (c) 2001-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\GlobalState; + +use function array_diff; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function function_exists; +use function get_defined_functions; +use function in_array; +use function is_array; +use ReflectionClass; +use ReflectionProperty; +/** + * Restorer of snapshots of global state. + */ +class Restorer +{ /** - * Informs the result that a test suite was completed. + * Deletes function definitions that are not defined in a snapshot. + * + * @throws RuntimeException when the uopz_delete() function is not available + * + * @see https://github.com/krakjoe/uopz */ - public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void + public function restoreFunctions(Snapshot $snapshot) : void { - if (!$this->currentTestSuiteFailed) { - $this->passedTestClasses[] = $suite->getName(); + if (!function_exists('PHPUnit\\uopz_delete')) { + throw new RuntimeException('The uopz_delete() function is required for this operation'); } - foreach ($this->listeners as $listener) { - $listener->endTestSuite($suite); + $functions = get_defined_functions(); + foreach (array_diff($functions['user'], $snapshot->functions()) as $function) { + uopz_delete($function); } } /** - * Informs the result that a test will be started. + * Restores all global and super-global variables from a snapshot. */ - public function startTest(\PHPUnit\Framework\Test $test) : void + public function restoreGlobalVariables(Snapshot $snapshot) : void { - $this->lastTestFailed = \false; - $this->runTests += count($test); - foreach ($this->listeners as $listener) { - $listener->startTest($test); + $superGlobalArrays = $snapshot->superGlobalArrays(); + foreach ($superGlobalArrays as $superGlobalArray) { + $this->restoreSuperGlobalArray($snapshot, $superGlobalArray); + } + $globalVariables = $snapshot->globalVariables(); + foreach (array_keys($GLOBALS) as $key) { + if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, \true) && !$snapshot->excludeList()->isGlobalVariableExcluded($key)) { + if (array_key_exists($key, $globalVariables)) { + $GLOBALS[$key] = $globalVariables[$key]; + } else { + unset($GLOBALS[$key]); + } + } } } /** - * Informs the result that a test was completed. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Restores all static attributes in user-defined classes from this snapshot. */ - public function endTest(\PHPUnit\Framework\Test $test, float $time) : void + public function restoreStaticAttributes(Snapshot $snapshot) : void { - foreach ($this->listeners as $listener) { - $listener->endTest($test, $time); - } - if (!$this->lastTestFailed && $test instanceof \PHPUnit\Framework\TestCase) { - $class = get_class($test); - $key = $class . '::' . $test->getName(); - $this->passed[$key] = ['result' => $test->getResult(), 'size' => \PHPUnit\Util\Test::getSize($class, $test->getName(\false))]; - $this->time += $time; + $current = new Snapshot($snapshot->excludeList(), \false, \false, \false, \false, \true, \false, \false, \false, \false); + $newClasses = array_diff($current->classes(), $snapshot->classes()); + unset($current); + foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { + foreach ($staticAttributes as $name => $value) { + $reflector = new ReflectionProperty($className, $name); + $reflector->setAccessible(\true); + $reflector->setValue($value); + } } - if ($this->lastTestFailed && $test instanceof \PHPUnit\Framework\TestCase) { - $this->currentTestSuiteFailed = \true; + foreach ($newClasses as $className) { + $class = new ReflectionClass($className); + $defaults = $class->getDefaultProperties(); + foreach ($class->getProperties() as $attribute) { + if (!$attribute->isStatic()) { + continue; + } + $name = $attribute->getName(); + if ($snapshot->excludeList()->isStaticAttributeExcluded($className, $name)) { + continue; + } + if (!isset($defaults[$name])) { + continue; + } + $attribute->setAccessible(\true); + $attribute->setValue($defaults[$name]); + } } } /** - * Returns true if no risky test occurred. - */ - public function allHarmless() : bool - { - return $this->riskyCount() === 0; - } - /** - * Gets the number of risky tests. - */ - public function riskyCount() : int - { - return count($this->risky); - } - /** - * Returns true if no incomplete test occurred. - */ - public function allCompletelyImplemented() : bool - { - return $this->notImplementedCount() === 0; - } - /** - * Gets the number of incomplete tests. - */ - public function notImplementedCount() : int - { - return count($this->notImplemented); - } - /** - * Returns an array of TestFailure objects for the risky tests. - * - * @return TestFailure[] - */ - public function risky() : array - { - return $this->risky; - } - /** - * Returns an array of TestFailure objects for the incomplete tests. - * - * @return TestFailure[] + * Restores a super-global variable array from this snapshot. */ - public function notImplemented() : array + private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray) : void { - return $this->notImplemented; + $superGlobalVariables = $snapshot->superGlobalVariables(); + if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray]) && isset($superGlobalVariables[$superGlobalArray])) { + $keys = array_keys(array_merge($GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray])); + foreach ($keys as $key) { + if (isset($superGlobalVariables[$superGlobalArray][$key])) { + $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; + } else { + unset($GLOBALS[$superGlobalArray][$key]); + } + } + } } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\GlobalState; + +use const PHP_VERSION_ID; +use function array_keys; +use function array_merge; +use function array_reverse; +use function func_get_args; +use function get_declared_classes; +use function get_declared_interfaces; +use function get_declared_traits; +use function get_defined_constants; +use function get_defined_functions; +use function get_included_files; +use function in_array; +use function ini_get_all; +use function is_array; +use function is_object; +use function is_resource; +use function is_scalar; +use function serialize; +use function unserialize; +use ReflectionClass; +use PHPUnit\SebastianBergmann\ObjectReflector\ObjectReflector; +use PHPUnit\SebastianBergmann\RecursionContext\Context; +use Throwable; +/** + * A snapshot of global state. + */ +class Snapshot +{ /** - * Returns true if no test has been skipped. + * @var ExcludeList */ - public function noneSkipped() : bool - { - return $this->skippedCount() === 0; - } + private $excludeList; /** - * Gets the number of skipped tests. + * @var array */ - public function skippedCount() : int - { - return count($this->skipped); - } + private $globalVariables = []; /** - * Returns an array of TestFailure objects for the skipped tests. - * - * @return TestFailure[] + * @var array */ - public function skipped() : array - { - return $this->skipped; - } + private $superGlobalArrays = []; /** - * Gets the number of detected errors. + * @var array */ - public function errorCount() : int - { - return count($this->errors); - } + private $superGlobalVariables = []; /** - * Returns an array of TestFailure objects for the errors. - * - * @return TestFailure[] + * @var array */ - public function errors() : array - { - return $this->errors; - } + private $staticAttributes = []; /** - * Gets the number of detected failures. + * @var array */ - public function failureCount() : int - { - return count($this->failures); - } + private $iniSettings = []; /** - * Returns an array of TestFailure objects for the failures. - * - * @return TestFailure[] + * @var array */ - public function failures() : array - { - return $this->failures; - } + private $includedFiles = []; /** - * Gets the number of detected warnings. + * @var array */ - public function warningCount() : int - { - return count($this->warnings); - } + private $constants = []; /** - * Returns an array of TestFailure objects for the warnings. - * - * @return TestFailure[] + * @var array */ - public function warnings() : array - { - return $this->warnings; - } + private $functions = []; /** - * Returns the names of the tests that have passed. + * @var array */ - public function passed() : array - { - return $this->passed; - } + private $interfaces = []; /** - * Returns the names of the TestSuites that have passed. - * - * This enables @depends-annotations for TestClassName::class + * @var array */ - public function passedClasses() : array - { - return $this->passedTestClasses; - } + private $classes = []; /** - * Returns whether code coverage information should be collected. + * @var array */ - public function getCollectCodeCoverageInformation() : bool - { - return $this->codeCoverage !== null; - } + private $traits = []; /** - * Runs a TestCase. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws CodeCoverageException - * @throws UnintentionallyCoveredCodeException + * Creates a snapshot of the current global state. */ - public function run(\PHPUnit\Framework\Test $test) : void + public function __construct(ExcludeList $excludeList = null, bool $includeGlobalVariables = \true, bool $includeStaticAttributes = \true, bool $includeConstants = \true, bool $includeFunctions = \true, bool $includeClasses = \true, bool $includeInterfaces = \true, bool $includeTraits = \true, bool $includeIniSettings = \true, bool $includeIncludedFiles = \true) { - \PHPUnit\Framework\Assert::resetCount(); - if ($test instanceof \PHPUnit\Framework\TestCase) { - $test->setRegisterMockObjectsFromTestArgumentsRecursively($this->registerMockObjectsFromTestArgumentsRecursively); - $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); - } - $error = \false; - $failure = \false; - $warning = \false; - $incomplete = \false; - $risky = \false; - $skipped = \false; - $this->startTest($test); - if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { - $errorHandler = new ErrorHandler($this->convertDeprecationsToExceptions, $this->convertErrorsToExceptions, $this->convertNoticesToExceptions, $this->convertWarningsToExceptions); - $errorHandler->register(); - } - $collectCodeCoverage = $this->codeCoverage !== null && !$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $isAnyCoverageRequired; - if ($collectCodeCoverage) { - $this->codeCoverage->start($test); - } - $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && !$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $test->getSize() === \PHPUnit\Util\Test::SMALL && function_exists('xdebug_start_function_monitor'); - if ($monitorFunctions) { - /* @noinspection ForgottenDebugOutputInspection */ - \xdebug_start_function_monitor(ResourceOperations::getFunctions()); + $this->excludeList = $excludeList ?: new ExcludeList(); + if ($includeConstants) { + $this->snapshotConstants(); } - $timer = new Timer(); - $timer->start(); - try { - $invoker = new Invoker(); - if (!$test instanceof \PHPUnit\Framework\ErrorTestCase && !$test instanceof \PHPUnit\Framework\WarningTestCase && $this->enforceTimeLimit && ($this->defaultTimeLimit || $test->getSize() != \PHPUnit\Util\Test::UNKNOWN) && $invoker->canInvokeWithTimeout()) { - switch ($test->getSize()) { - case \PHPUnit\Util\Test::SMALL: - $_timeout = $this->timeoutForSmallTests; - break; - case \PHPUnit\Util\Test::MEDIUM: - $_timeout = $this->timeoutForMediumTests; - break; - case \PHPUnit\Util\Test::LARGE: - $_timeout = $this->timeoutForLargeTests; - break; - case \PHPUnit\Util\Test::UNKNOWN: - $_timeout = $this->defaultTimeLimit; - break; - } - $invoker->invoke([$test, 'runBare'], [], $_timeout); - } else { - $test->runBare(); - } - } catch (TimeoutException $e) { - $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError($e->getMessage()), $_timeout); - $risky = \true; - } catch (\PHPUnit\Framework\AssertionFailedError $e) { - $failure = \true; - if ($e instanceof \PHPUnit\Framework\RiskyTestError) { - $risky = \true; - } elseif ($e instanceof \PHPUnit\Framework\IncompleteTestError) { - $incomplete = \true; - } elseif ($e instanceof \PHPUnit\Framework\SkippedTestError) { - $skipped = \true; - } - } catch (AssertionError $e) { - $test->addToAssertionCount(1); - $failure = \true; - $frame = $e->getTrace()[0]; - $e = new \PHPUnit\Framework\AssertionFailedError(sprintf('%s in %s:%s', $e->getMessage(), $frame['file'], $frame['line'])); - } catch (\PHPUnit\Framework\Warning $e) { - $warning = \true; - } catch (\PHPUnit\Framework\Exception $e) { - $error = \true; - } catch (Throwable $e) { - $e = new \PHPUnit\Framework\ExceptionWrapper($e); - $error = \true; + if ($includeFunctions) { + $this->snapshotFunctions(); } - $time = $timer->stop()->asSeconds(); - $test->addToAssertionCount(\PHPUnit\Framework\Assert::getCount()); - if ($monitorFunctions) { - $excludeList = new ExcludeList(); - /** @noinspection ForgottenDebugOutputInspection */ - $functions = \xdebug_get_monitored_functions(); - /* @noinspection ForgottenDebugOutputInspection */ - \xdebug_stop_function_monitor(); - foreach ($functions as $function) { - if (!$excludeList->isExcluded($function['filename'])) { - $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf('%s() used in %s:%s', $function['function'], $function['filename'], $function['lineno'])), $time); - } - } + if ($includeClasses || $includeStaticAttributes) { + $this->snapshotClasses(); } - if ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() === 0) { - $risky = \true; + if ($includeInterfaces) { + $this->snapshotInterfaces(); } - if ($this->forceCoversAnnotation && !$error && !$failure && !$warning && !$incomplete && !$skipped && !$risky) { - $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); - if (!isset($annotations['class']['covers']) && !isset($annotations['method']['covers']) && !isset($annotations['class']['coversNothing']) && !isset($annotations['method']['coversNothing'])) { - $this->addFailure($test, new \PHPUnit\Framework\MissingCoversAnnotationException('This test does not have a @covers annotation but is expected to have one'), $time); - $risky = \true; - } + if ($includeGlobalVariables) { + $this->setupSuperGlobalArrays(); + $this->snapshotGlobals(); } - if ($collectCodeCoverage) { - $append = !$risky && !$incomplete && !$skipped; - $linesToBeCovered = []; - $linesToBeUsed = []; - if ($append && $test instanceof \PHPUnit\Framework\TestCase) { - try { - $linesToBeCovered = \PHPUnit\Util\Test::getLinesToBeCovered(get_class($test), $test->getName(\false)); - $linesToBeUsed = \PHPUnit\Util\Test::getLinesToBeUsed(get_class($test), $test->getName(\false)); - } catch (\PHPUnit\Framework\InvalidCoversTargetException $cce) { - $this->addWarning($test, new \PHPUnit\Framework\Warning($cce->getMessage()), $time); - } - } - try { - $this->codeCoverage->stop($append, $linesToBeCovered, $linesToBeUsed); - } catch (UnintentionallyCoveredCodeException $cce) { - $unintentionallyCoveredCodeError = new \PHPUnit\Framework\UnintentionallyCoveredCodeError('This test executed code that is not listed as code to be covered or used:' . \PHP_EOL . $cce->getMessage()); - } catch (OriginalCodeCoverageException $cce) { - $error = \true; - $e = $e ?? $cce; - } + if ($includeStaticAttributes) { + $this->snapshotStaticAttributes(); } - if (isset($errorHandler)) { - $errorHandler->unregister(); - unset($errorHandler); + if ($includeIniSettings) { + $this->iniSettings = ini_get_all(null, \false); } - if ($error) { - $this->addError($test, $e, $time); - } elseif ($failure) { - $this->addFailure($test, $e, $time); - } elseif ($warning) { - $this->addWarning($test, $e, $time); - } elseif (isset($unintentionallyCoveredCodeError)) { - $this->addFailure($test, $unintentionallyCoveredCodeError, $time); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && !$test->doesNotPerformAssertions() && $test->getNumAssertions() === 0) { - try { - $reflected = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - $name = $test->getName(\false); - if ($name && $reflected->hasMethod($name)) { - try { - $reflected = $reflected->getMethod($name); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new \PHPUnit\Framework\Exception($e->getMessage(), (int) $e->getCode(), $e); - } - // @codeCoverageIgnoreEnd - } - $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf("This test did not perform any assertions\n\n%s:%d", $reflected->getFileName(), $reflected->getStartLine())), $time); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && $test->doesNotPerformAssertions() && $test->getNumAssertions() > 0) { - $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError(sprintf('This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', $test->getNumAssertions())), $time); - } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { - $this->addFailure($test, new \PHPUnit\Framework\OutputError(sprintf('This test printed output: %s', $test->getActualOutput())), $time); - } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof \PHPUnit\Framework\TestCase) { - $annotations = TestUtil::parseTestMethodAnnotations(get_class($test), $test->getName(\false)); - if (isset($annotations['method']['todo'])) { - $this->addFailure($test, new \PHPUnit\Framework\RiskyTestError('Test method is annotated with @todo'), $time); - } + if ($includeIncludedFiles) { + $this->includedFiles = get_included_files(); + } + if ($includeTraits) { + $this->traits = get_declared_traits(); } - $this->endTest($test, $time); } - /** - * Gets the number of run tests. - */ - public function count() : int + public function excludeList() : ExcludeList { - return $this->runTests; + return $this->excludeList; } - /** - * Checks whether the test run should stop. - */ - public function shouldStop() : bool + public function globalVariables() : array { - return $this->stop; + return $this->globalVariables; } - /** - * Marks that the test run should stop. - */ - public function stop() : void + public function superGlobalVariables() : array { - $this->stop = \true; + return $this->superGlobalVariables; } - /** - * Returns the code coverage object. - */ - public function getCodeCoverage() : ?CodeCoverage + public function superGlobalArrays() : array { - return $this->codeCoverage; + return $this->superGlobalArrays; } - /** - * Sets the code coverage object. - */ - public function setCodeCoverage(CodeCoverage $codeCoverage) : void + public function staticAttributes() : array { - $this->codeCoverage = $codeCoverage; + return $this->staticAttributes; } - /** - * Enables or disables the deprecation-to-exception conversion. - */ - public function convertDeprecationsToExceptions(bool $flag) : void + public function iniSettings() : array { - $this->convertDeprecationsToExceptions = $flag; + return $this->iniSettings; } - /** - * Returns the deprecation-to-exception conversion setting. - */ - public function getConvertDeprecationsToExceptions() : bool + public function includedFiles() : array { - return $this->convertDeprecationsToExceptions; + return $this->includedFiles; } - /** - * Enables or disables the error-to-exception conversion. - */ - public function convertErrorsToExceptions(bool $flag) : void + public function constants() : array { - $this->convertErrorsToExceptions = $flag; + return $this->constants; } - /** - * Returns the error-to-exception conversion setting. - */ - public function getConvertErrorsToExceptions() : bool + public function functions() : array { - return $this->convertErrorsToExceptions; + return $this->functions; } - /** - * Enables or disables the notice-to-exception conversion. - */ - public function convertNoticesToExceptions(bool $flag) : void + public function interfaces() : array { - $this->convertNoticesToExceptions = $flag; + return $this->interfaces; } - /** - * Returns the notice-to-exception conversion setting. - */ - public function getConvertNoticesToExceptions() : bool + public function classes() : array { - return $this->convertNoticesToExceptions; + return $this->classes; } - /** - * Enables or disables the warning-to-exception conversion. - */ - public function convertWarningsToExceptions(bool $flag) : void + public function traits() : array { - $this->convertWarningsToExceptions = $flag; + return $this->traits; } /** - * Returns the warning-to-exception conversion setting. + * Creates a snapshot user-defined constants. */ - public function getConvertWarningsToExceptions() : bool + private function snapshotConstants() : void { - return $this->convertWarningsToExceptions; + $constants = get_defined_constants(\true); + if (isset($constants['user'])) { + $this->constants = $constants['user']; + } } /** - * Enables or disables the stopping when an error occurs. + * Creates a snapshot user-defined functions. */ - public function stopOnError(bool $flag) : void + private function snapshotFunctions() : void { - $this->stopOnError = $flag; + $functions = get_defined_functions(); + $this->functions = $functions['user']; } /** - * Enables or disables the stopping when a failure occurs. + * Creates a snapshot user-defined classes. */ - public function stopOnFailure(bool $flag) : void + private function snapshotClasses() : void { - $this->stopOnFailure = $flag; + foreach (array_reverse(get_declared_classes()) as $className) { + $class = new ReflectionClass($className); + if (!$class->isUserDefined()) { + break; + } + $this->classes[] = $className; + } + $this->classes = array_reverse($this->classes); } /** - * Enables or disables the stopping when a warning occurs. + * Creates a snapshot user-defined interfaces. */ - public function stopOnWarning(bool $flag) : void - { - $this->stopOnWarning = $flag; - } - public function beStrictAboutTestsThatDoNotTestAnything(bool $flag) : void - { - $this->beStrictAboutTestsThatDoNotTestAnything = $flag; - } - public function isStrictAboutTestsThatDoNotTestAnything() : bool + private function snapshotInterfaces() : void { - return $this->beStrictAboutTestsThatDoNotTestAnything; + foreach (array_reverse(get_declared_interfaces()) as $interfaceName) { + $class = new ReflectionClass($interfaceName); + if (!$class->isUserDefined()) { + break; + } + $this->interfaces[] = $interfaceName; + } + $this->interfaces = array_reverse($this->interfaces); } - public function beStrictAboutOutputDuringTests(bool $flag) : void + /** + * Creates a snapshot of all global and super-global variables. + */ + private function snapshotGlobals() : void { - $this->beStrictAboutOutputDuringTests = $flag; + $superGlobalArrays = $this->superGlobalArrays(); + foreach ($superGlobalArrays as $superGlobalArray) { + $this->snapshotSuperGlobalArray($superGlobalArray); + } + foreach (array_keys($GLOBALS) as $key) { + if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, \true) && $this->canBeSerialized($GLOBALS[$key]) && !$this->excludeList->isGlobalVariableExcluded($key)) { + /* @noinspection UnserializeExploitsInspection */ + $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key])); + } + } } - public function isStrictAboutOutputDuringTests() : bool + /** + * Creates a snapshot a super-global variable array. + */ + private function snapshotSuperGlobalArray(string $superGlobalArray) : void { - return $this->beStrictAboutOutputDuringTests; + $this->superGlobalVariables[$superGlobalArray] = []; + if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { + foreach ($GLOBALS[$superGlobalArray] as $key => $value) { + /* @noinspection UnserializeExploitsInspection */ + $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value)); + } + } } - public function beStrictAboutResourceUsageDuringSmallTests(bool $flag) : void + /** + * Creates a snapshot of all static attributes in user-defined classes. + */ + private function snapshotStaticAttributes() : void { - $this->beStrictAboutResourceUsageDuringSmallTests = $flag; + foreach ($this->classes as $className) { + $class = new ReflectionClass($className); + $snapshot = []; + foreach ($class->getProperties() as $attribute) { + if ($attribute->isStatic()) { + $name = $attribute->getName(); + if ($this->excludeList->isStaticAttributeExcluded($className, $name)) { + continue; + } + $attribute->setAccessible(\true); + if (PHP_VERSION_ID >= 70400 && !$attribute->isInitialized()) { + continue; + } + $value = $attribute->getValue(); + if ($this->canBeSerialized($value)) { + /* @noinspection UnserializeExploitsInspection */ + $snapshot[$name] = unserialize(serialize($value)); + } + } + } + if (!empty($snapshot)) { + $this->staticAttributes[$className] = $snapshot; + } + } } - public function isStrictAboutResourceUsageDuringSmallTests() : bool + /** + * Returns a list of all super-global variable arrays. + */ + private function setupSuperGlobalArrays() : void { - return $this->beStrictAboutResourceUsageDuringSmallTests; + $this->superGlobalArrays = ['_ENV', '_POST', '_GET', '_COOKIE', '_SERVER', '_FILES', '_REQUEST']; } - public function enforceTimeLimit(bool $flag) : void + private function canBeSerialized($variable) : bool { - $this->enforceTimeLimit = $flag; + if (is_scalar($variable) || $variable === null) { + return \true; + } + if (is_resource($variable)) { + return \false; + } + foreach ($this->enumerateObjectsAndResources($variable) as $value) { + if (is_resource($value)) { + return \false; + } + if (is_object($value)) { + $class = new ReflectionClass($value); + if ($class->isAnonymous()) { + return \false; + } + try { + @serialize($value); + } catch (Throwable $t) { + return \false; + } + } + } + return \true; } - public function enforcesTimeLimit() : bool + private function enumerateObjectsAndResources($variable) : array { - return $this->enforceTimeLimit; + if (isset(func_get_args()[1])) { + $processed = func_get_args()[1]; + } else { + $processed = new Context(); + } + $result = []; + if ($processed->contains($variable)) { + return $result; + } + $array = $variable; + $processed->add($variable); + if (is_array($variable)) { + foreach ($array as $element) { + if (!is_array($element) && !is_object($element) && !is_resource($element)) { + continue; + } + if (!is_resource($element)) { + /** @noinspection SlowArrayOperationsInLoopInspection */ + $result = array_merge($result, $this->enumerateObjectsAndResources($element, $processed)); + } else { + $result[] = $element; + } + } + } else { + $result[] = $variable; + foreach ((new ObjectReflector())->getAttributes($variable) as $value) { + if (!is_array($value) && !is_object($value) && !is_resource($value)) { + continue; + } + if (!is_resource($value)) { + /** @noinspection SlowArrayOperationsInLoopInspection */ + $result = array_merge($result, $this->enumerateObjectsAndResources($value, $processed)); + } else { + $result[] = $value; + } + } + } + return $result; } - public function beStrictAboutTodoAnnotatedTests(bool $flag) : void +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\GlobalState; + +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\GlobalState; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +use function substr_count; +use PHPUnit\PhpParser\Error; +use PHPUnit\PhpParser\Lexer; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\NodeTraverser; +use PHPUnit\PhpParser\Parser; +use PHPUnit\PhpParser\ParserFactory; +final class Counter +{ + /** + * @throws RuntimeException + */ + public function countInSourceFile(string $sourceFile) : LinesOfCode { - $this->beStrictAboutTodoAnnotatedTests = $flag; + return $this->countInSourceString(\file_get_contents($sourceFile)); } - public function isStrictAboutTodoAnnotatedTests() : bool + /** + * @throws RuntimeException + */ + public function countInSourceString(string $source) : LinesOfCode { - return $this->beStrictAboutTodoAnnotatedTests; + $linesOfCode = substr_count($source, "\n"); + if ($linesOfCode === 0 && !empty($source)) { + $linesOfCode = 1; + } + try { + $nodes = $this->parser()->parse($source); + \assert($nodes !== null); + return $this->countInAbstractSyntaxTree($linesOfCode, $nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + } + // @codeCoverageIgnoreEnd } - public function forceCoversAnnotation() : void + /** + * @param Node[] $nodes + * + * @throws RuntimeException + */ + public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes) : LinesOfCode { - $this->forceCoversAnnotation = \true; + $traverser = new NodeTraverser(); + $visitor = new LineCountingVisitor($linesOfCode); + $traverser->addVisitor($visitor); + try { + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException($error->getMessage(), (int) $error->getCode(), $error); + } + // @codeCoverageIgnoreEnd + return $visitor->result(); } - public function forcesCoversAnnotation() : bool + private function parser() : Parser { - return $this->forceCoversAnnotation; + return (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer()); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +use Throwable; +interface Exception extends Throwable +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +use LogicException; +final class IllogicalValuesException extends LogicException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +use InvalidArgumentException; +final class NegativeValueException extends InvalidArgumentException implements Exception +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} +sebastian/lines-of-code + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +use function array_merge; +use function array_unique; +use function count; +use PHPUnit\PhpParser\Comment; +use PHPUnit\PhpParser\Node; +use PHPUnit\PhpParser\Node\Expr; +use PHPUnit\PhpParser\NodeVisitorAbstract; +final class LineCountingVisitor extends NodeVisitorAbstract +{ /** - * Enables or disables the stopping for risky tests. + * @var int */ - public function stopOnRisky(bool $flag) : void - { - $this->stopOnRisky = $flag; - } + private $linesOfCode; /** - * Enables or disables the stopping for incomplete tests. + * @var Comment[] */ - public function stopOnIncomplete(bool $flag) : void - { - $this->stopOnIncomplete = $flag; - } + private $comments = []; /** - * Enables or disables the stopping for skipped tests. + * @var int[] */ - public function stopOnSkipped(bool $flag) : void + private $linesWithStatements = []; + public function __construct(int $linesOfCode) { - $this->stopOnSkipped = $flag; + $this->linesOfCode = $linesOfCode; } - /** - * Enables or disables the stopping for defects: error, failure, warning. - */ - public function stopOnDefect(bool $flag) : void + public function enterNode(Node $node) : void { - $this->stopOnDefect = $flag; + $this->comments = array_merge($this->comments, $node->getComments()); + if (!$node instanceof Expr) { + return; + } + $this->linesWithStatements[] = $node->getStartLine(); } - /** - * Returns the time spent running the tests. - */ - public function time() : float + public function result() : LinesOfCode { - return $this->time; + $commentLinesOfCode = 0; + foreach ($this->comments() as $comment) { + $commentLinesOfCode += $comment->getEndLine() - $comment->getStartLine() + 1; + } + return new LinesOfCode($this->linesOfCode, $commentLinesOfCode, $this->linesOfCode - $commentLinesOfCode, count(array_unique($this->linesWithStatements))); } /** - * Returns whether the entire test was successful or not. + * @return Comment[] */ - public function wasSuccessful() : bool - { - return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); - } - public function wasSuccessfulIgnoringWarnings() : bool - { - return empty($this->errors) && empty($this->failures); - } - public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete() : bool + private function comments() : array { - return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); + $comments = []; + foreach ($this->comments as $comment) { + $comments[$comment->getStartLine() . '_' . $comment->getStartTokenPos() . '_' . $comment->getEndLine() . '_' . $comment->getEndTokenPos()] = $comment; + } + return $comments; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\LinesOfCode; + +/** + * @psalm-immutable + */ +final class LinesOfCode +{ /** - * Sets the default timeout for tests. + * @var int */ - public function setDefaultTimeLimit(int $timeout) : void - { - $this->defaultTimeLimit = $timeout; - } + private $linesOfCode; /** - * Sets the timeout for small tests. + * @var int */ - public function setTimeoutForSmallTests(int $timeout) : void - { - $this->timeoutForSmallTests = $timeout; - } + private $commentLinesOfCode; /** - * Sets the timeout for medium tests. + * @var int */ - public function setTimeoutForMediumTests(int $timeout) : void - { - $this->timeoutForMediumTests = $timeout; - } + private $nonCommentLinesOfCode; /** - * Sets the timeout for large tests. + * @var int */ - public function setTimeoutForLargeTests(int $timeout) : void - { - $this->timeoutForLargeTests = $timeout; - } + private $logicalLinesOfCode; /** - * Returns the set timeout for large tests. + * @throws IllogicalValuesException + * @throws NegativeValueException */ - public function getTimeoutForLargeTests() : int - { - return $this->timeoutForLargeTests; - } - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag) : void + public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode, int $logicalLinesOfCode) { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + if ($linesOfCode < 0) { + throw new NegativeValueException('$linesOfCode must not be negative'); + } + if ($commentLinesOfCode < 0) { + throw new NegativeValueException('$commentLinesOfCode must not be negative'); + } + if ($nonCommentLinesOfCode < 0) { + throw new NegativeValueException('$nonCommentLinesOfCode must not be negative'); + } + if ($logicalLinesOfCode < 0) { + throw new NegativeValueException('$logicalLinesOfCode must not be negative'); + } + if ($linesOfCode - $commentLinesOfCode !== $nonCommentLinesOfCode) { + throw new IllogicalValuesException('$linesOfCode !== $commentLinesOfCode + $nonCommentLinesOfCode'); + } + $this->linesOfCode = $linesOfCode; + $this->commentLinesOfCode = $commentLinesOfCode; + $this->nonCommentLinesOfCode = $nonCommentLinesOfCode; + $this->logicalLinesOfCode = $logicalLinesOfCode; } - private function recordError(\PHPUnit\Framework\Test $test, Throwable $t) : void + public function linesOfCode() : int { - $this->errors[] = new \PHPUnit\Framework\TestFailure($test, $t); + return $this->linesOfCode; } - private function recordNotImplemented(\PHPUnit\Framework\Test $test, Throwable $t) : void + public function commentLinesOfCode() : int { - $this->notImplemented[] = new \PHPUnit\Framework\TestFailure($test, $t); + return $this->commentLinesOfCode; } - private function recordRisky(\PHPUnit\Framework\Test $test, Throwable $t) : void + public function nonCommentLinesOfCode() : int { - $this->risky[] = new \PHPUnit\Framework\TestFailure($test, $t); + return $this->nonCommentLinesOfCode; } - private function recordSkipped(\PHPUnit\Framework\Test $test, Throwable $t) : void + public function logicalLinesOfCode() : int { - $this->skipped[] = new \PHPUnit\Framework\TestFailure($test, $t); + return $this->logicalLinesOfCode; } - private function recordWarning(\PHPUnit\Framework\Test $test, Throwable $t) : void + public function plus(self $other) : self { - $this->warnings[] = new \PHPUnit\Framework\TestFailure($test, $t); + return new self($this->linesOfCode() + $other->linesOfCode(), $this->commentLinesOfCode() + $other->commentLinesOfCode(), $this->nonCommentLinesOfCode() + $other->nonCommentLinesOfCode(), $this->logicalLinesOfCode() + $other->logicalLinesOfCode()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\ObjectEnumerator; +use function array_merge; +use function func_get_args; +use function is_array; +use function is_object; +use PHPUnit\SebastianBergmann\ObjectReflector\ObjectReflector; +use PHPUnit\SebastianBergmann\RecursionContext\Context; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * Traverses array structures and object graphs + * to enumerate all referenced objects. */ -interface Reorderable +class Enumerator { - public function sortId() : string; - /** - * @return list - */ - public function provides() : array; /** - * @return list + * Returns an array of all objects referenced either + * directly or indirectly by a variable. + * + * @param array|object $variable + * + * @return object[] */ - public function requires() : array; + public function enumerate($variable) + { + if (!is_array($variable) && !is_object($variable)) { + throw new InvalidArgumentException(); + } + if (isset(func_get_args()[1])) { + if (!func_get_args()[1] instanceof Context) { + throw new InvalidArgumentException(); + } + $processed = func_get_args()[1]; + } else { + $processed = new Context(); + } + $objects = []; + if ($processed->contains($variable)) { + return $objects; + } + $array = $variable; + $processed->add($variable); + if (is_array($variable)) { + foreach ($array as $element) { + if (!is_array($element) && !is_object($element)) { + continue; + } + $objects = array_merge($objects, $this->enumerate($element, $processed)); + } + } else { + $objects[] = $variable; + $reflector = new ObjectReflector(); + foreach ($reflector->getAttributes($variable) as $value) { + if (!is_array($value) && !is_object($value)) { + continue; + } + $objects = array_merge($objects, $this->enumerate($value, $processed)); + } + } + return $objects; + } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\ObjectEnumerator; use Throwable; -/** - * @deprecated The `TestListener` interface is deprecated - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -trait TestListenerDefaultImplementation +interface Exception extends Throwable { - public function addError(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void - { - } - public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void - { - } - public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void - { - } - public function addIncompleteTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void - { - } - public function addRiskyTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void - { - } - public function addSkippedTest(\PHPUnit\Framework\Test $test, Throwable $t, float $time) : void - { - } - public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void - { - } - public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void - { - } - public function startTest(\PHPUnit\Framework\Test $test) : void - { - } - public function endTest(\PHPUnit\Framework\Test $test, float $time) : void - { - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Error; +namespace PHPUnit\SebastianBergmann\ObjectEnumerator; -use PHPUnit\Framework\Exception; -/** - * @internal - */ -class Error extends Exception +class InvalidArgumentException extends \InvalidArgumentException implements Exception { - public function __construct(string $message, int $code, string $file, int $line, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); - $this->file = $file; - $this->line = $line; - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Error; +namespace PHPUnit\SebastianBergmann\ObjectReflector; -/** - * @internal - */ -final class Warning extends \PHPUnit\Framework\Error\Error +use Throwable; +interface Exception extends Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Error; +namespace PHPUnit\SebastianBergmann\ObjectReflector; -/** - * @internal - */ -final class Deprecated extends \PHPUnit\Framework\Error\Error +class InvalidArgumentException extends \InvalidArgumentException implements Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\Error; +namespace PHPUnit\SebastianBergmann\ObjectReflector; -/** - * @internal - */ -final class Notice extends \PHPUnit\Framework\Error\Error +use function count; +use function explode; +use function get_class; +use function is_object; +class ObjectReflector { + /** + * @param object $object + * + * @throws InvalidArgumentException + */ + public function getAttributes($object) : array + { + if (!is_object($object)) { + throw new InvalidArgumentException(); + } + $attributes = []; + $className = get_class($object); + foreach ((array) $object as $name => $value) { + $name = explode("\x00", (string) $name); + if (count($name) === 1) { + $name = $name[0]; + } else { + if ($name[1] !== $className) { + $name = $name[1] . '::' . $name[2]; + } else { + $name = $name[2]; + } + } + $attributes[$name] = $value; + } + return $attributes; + } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\RecursionContext; -use function array_keys; -use function get_class; +use const PHP_INT_MAX; +use const PHP_INT_MIN; +use function array_pop; +use function array_slice; +use function count; +use function is_array; +use function is_object; +use function random_int; use function spl_object_hash; -use PHPUnit\Util\Filter; -use Throwable; +use SplObjectStorage; /** - * Wraps Exceptions thrown by code under test. - * - * Re-instantiates Exceptions thrown by user-space code to retain their original - * class names, properties, and stack traces (but without arguments). - * - * Unlike PHPUnit\Framework\Exception, the complete stack of previous Exceptions - * is processed. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * A context containing previously processed arrays and objects + * when recursively processing a value. */ -final class ExceptionWrapper extends \PHPUnit\Framework\Exception +final class Context { /** - * @var string + * @var array[] */ - protected $className; + private $arrays; /** - * @var null|ExceptionWrapper + * @var SplObjectStorage */ - protected $previous; - public function __construct(Throwable $t) + private $objects; + /** + * Initialises the context. + */ + public function __construct() { - // PDOException::getCode() is a string. - // @see https://php.net/manual/en/class.pdoexception.php#95812 - parent::__construct($t->getMessage(), (int) $t->getCode()); - $this->setOriginalException($t); + $this->arrays = []; + $this->objects = new SplObjectStorage(); } - public function __toString() : string + /** + * @codeCoverageIgnore + */ + public function __destruct() { - $string = \PHPUnit\Framework\TestFailure::exceptionToString($this); - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - if ($this->previous) { - $string .= "\nCaused by\n" . $this->previous; + foreach ($this->arrays as &$array) { + if (is_array($array)) { + array_pop($array); + array_pop($array); + } } - return $string; } - public function getClassName() : string + /** + * Adds a value to the context. + * + * @param array|object $value the value to add + * + * @throws InvalidArgumentException Thrown if $value is not an array or object + * + * @return bool|int|string the ID of the stored value, either as a string or integer + * + * @psalm-template T + * @psalm-param T $value + * @param-out T $value + */ + public function add(&$value) { - return $this->className; + if (is_array($value)) { + return $this->addArray($value); + } + if (is_object($value)) { + return $this->addObject($value); + } + throw new InvalidArgumentException('Only arrays and objects are supported'); } - public function getPreviousWrapped() : ?self + /** + * Checks if the given value exists within the context. + * + * @param array|object $value the value to check + * + * @throws InvalidArgumentException Thrown if $value is not an array or object + * + * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored + * + * @psalm-template T + * @psalm-param T $value + * @param-out T $value + */ + public function contains(&$value) { - return $this->previous; + if (is_array($value)) { + return $this->containsArray($value); + } + if (is_object($value)) { + return $this->containsObject($value); + } + throw new InvalidArgumentException('Only arrays and objects are supported'); } - public function setClassName(string $className) : void + /** + * @return bool|int + */ + private function addArray(array &$array) { - $this->className = $className; + $key = $this->containsArray($array); + if ($key !== \false) { + return $key; + } + $key = count($this->arrays); + $this->arrays[] =& $array; + if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) { + $array[] = $key; + $array[] = $this->objects; + } else { + /* cover the improbable case too */ + do { + $key = random_int(PHP_INT_MIN, PHP_INT_MAX); + } while (isset($array[$key])); + $array[$key] = $key; + do { + $key = random_int(PHP_INT_MIN, PHP_INT_MAX); + } while (isset($array[$key])); + $array[$key] = $this->objects; + } + return $key; } - public function setOriginalException(Throwable $t) : void + /** + * @param object $object + */ + private function addObject($object) : string { - $this->originalException($t); - $this->className = get_class($t); - $this->file = $t->getFile(); - $this->line = $t->getLine(); - $this->serializableTrace = $t->getTrace(); - foreach (array_keys($this->serializableTrace) as $key) { - unset($this->serializableTrace[$key]['args']); - } - if ($t->getPrevious()) { - $this->previous = new self($t->getPrevious()); + if (!$this->objects->contains($object)) { + $this->objects->attach($object); } + return spl_object_hash($object); } - public function getOriginalException() : ?Throwable + /** + * @return false|int + */ + private function containsArray(array &$array) { - return $this->originalException(); + $end = array_slice($array, -2); + return isset($end[1]) && $end[1] === $this->objects ? $end[0] : \false; } /** - * Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents, - * which can be quite big, from being garbage-collected, thus blocking memory until shutdown. + * @param object $value * - * Approach works both for var_dump() and var_export() and print_r(). + * @return false|string */ - private function originalException(Throwable $exceptionToStore = null) : ?Throwable + private function containsObject($value) { - static $originalExceptions; - $instanceId = spl_object_hash($this); - if ($exceptionToStore) { - $originalExceptions[$instanceId] = $exceptionToStore; + if ($this->objects->contains($value)) { + return spl_object_hash($value); } - return $originalExceptions[$instanceId] ?? null; + return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework; +namespace PHPUnit\SebastianBergmann\RecursionContext; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IncompleteTestCase extends \PHPUnit\Framework\TestCase +use Throwable; +interface Exception extends Throwable { - /** - * @var bool - */ - protected $backupGlobals = \false; - /** - * @var bool - */ - protected $backupStaticAttributes = \false; - /** - * @var bool - */ - protected $runTestInSeparateProcess = \false; - /** - * @var string - */ - private $message; - public function __construct(string $className, string $methodName, string $message = '') - { - parent::__construct($className . '::' . $methodName); - $this->message = $message; - } - public function getMessage() : string - { - return $this->message; - } - /** - * Returns a string representation of the test case. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString() : string - { - return $this->getName(); - } - /** - * @throws Exception - */ - protected function runTest() : void - { - $this->markTestIncomplete($this->message); - } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit; +namespace PHPUnit\SebastianBergmann\RecursionContext; -use Throwable; -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Exception extends Throwable +final class InvalidArgumentException extends \InvalidArgumentException implements Exception { } +Recursion Context + +Copyright (c) 2002-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +Resource Operations + +Copyright (c) 2015-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\Exporter; +namespace PHPUnit\SebastianBergmann\ResourceOperations; -use function bin2hex; -use function count; -use function function_exists; -use function get_class; -use function get_resource_type; -use function implode; -use function is_array; -use function is_float; -use function is_object; -use function is_resource; -use function is_string; -use function mb_strlen; -use function mb_substr; -use function preg_match; -use function spl_object_hash; -use function sprintf; -use function str_repeat; -use function str_replace; -use function strlen; -use function substr; -use function var_export; -use PHPUnit\SebastianBergmann\RecursionContext\Context; -use SplObjectStorage; -/** - * A nifty utility for visualizing PHP variables. - * - * - * export(new Exception); - * - */ -class Exporter +final class ResourceOperations { /** - * Exports a value as a string. - * - * The output of this method is similar to the output of print_r(), but - * improved in various aspects: - * - * - NULL is rendered as "null" (instead of "") - * - TRUE is rendered as "true" (instead of "1") - * - FALSE is rendered as "false" (instead of "") - * - Strings are always quoted with single quotes - * - Carriage returns and newlines are normalized to \n - * - Recursion and repeated rendering is treated properly - * - * @param int $indentation The indentation level of the 2nd+ line - * - * @return string - */ - public function export($value, $indentation = 0) - { - return $this->recursiveExport($value, $indentation); - } - /** - * @param array $data - * @param Context $context - * - * @return string - */ - public function shortenedRecursiveExport(&$data, Context $context = null) - { - $result = []; - $exporter = new self(); - if (!$context) { - $context = new Context(); - } - $array = $data; - $context->add($data); - foreach ($array as $key => $value) { - if (is_array($value)) { - if ($context->contains($data[$key]) !== \false) { - $result[] = '*RECURSION*'; - } else { - $result[] = sprintf('array(%s)', $this->shortenedRecursiveExport($data[$key], $context)); - } - } else { - $result[] = $exporter->shortenedExport($value); - } - } - return implode(', ', $result); - } - /** - * Exports a value into a single-line string. - * - * The output of this method is similar to the output of - * SebastianBergmann\Exporter\Exporter::export(). - * - * Newlines are replaced by the visible string '\n'. - * Contents of arrays and objects (if any) are replaced by '...'. - * - * @return string - * - * @see SebastianBergmann\Exporter\Exporter::export - */ - public function shortenedExport($value) - { - if (is_string($value)) { - $string = str_replace("\n", '', $this->export($value)); - if (function_exists('mb_strlen')) { - if (mb_strlen($string) > 40) { - $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); - } - } else { - if (strlen($string) > 40) { - $string = substr($string, 0, 30) . '...' . substr($string, -7); - } - } - return $string; - } - if (is_object($value)) { - return sprintf('%s Object (%s)', get_class($value), count($this->toArray($value)) > 0 ? '...' : ''); - } - if (is_array($value)) { - return sprintf('Array (%s)', count($value) > 0 ? '...' : ''); - } - return $this->export($value); - } - /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @return array - */ - public function toArray($value) - { - if (!is_object($value)) { - return (array) $value; - } - $array = []; - foreach ((array) $value as $key => $val) { - // Exception traces commonly reference hundreds to thousands of - // objects currently loaded in memory. Including them in the result - // has a severe negative performance impact. - if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) { - continue; - } - // properties are transformed to keys in the following way: - // private $property => "\0Classname\0property" - // protected $property => "\0*\0property" - // public $property => "property" - if (preg_match('/^\\0.+\\0(.+)$/', (string) $key, $matches)) { - $key = $matches[1]; - } - // See https://github.com/php/php-src/commit/5721132 - if ($key === "\0gcdata") { - continue; - } - $array[$key] = $val; - } - // Some internal classes like SplObjectStorage don't work with the - // above (fast) mechanism nor with reflection in Zend. - // Format the output similarly to print_r() in this case - if ($value instanceof SplObjectStorage) { - foreach ($value as $key => $val) { - $array[spl_object_hash($val)] = ['obj' => $val, 'inf' => $value->getInfo()]; - } - } - return $array; - } - /** - * Recursive implementation of export. - * - * @param mixed $value The value to export - * @param int $indentation The indentation level of the 2nd+ line - * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects - * - * @return string - * - * @see SebastianBergmann\Exporter\Exporter::export + * @return string[] */ - protected function recursiveExport(&$value, $indentation, $processed = null) + public static function getFunctions() : array { - if ($value === null) { - return 'null'; - } - if ($value === \true) { - return 'true'; - } - if ($value === \false) { - return 'false'; - } - if (is_float($value) && (float) (int) $value === $value) { - return "{$value}.0"; - } - if (is_resource($value)) { - return sprintf('resource(%d) of type (%s)', $value, get_resource_type($value)); - } - if (is_string($value)) { - // Match for most non printable chars somewhat taking multibyte chars into account - if (preg_match('/[^\\x09-\\x0d\\x1b\\x20-\\xff]/', $value)) { - return 'Binary String: 0x' . bin2hex($value); - } - return "'" . str_replace('', "\n", str_replace(["\r\n", "\n\r", "\r", "\n"], ['\\r\\n', '\\n\\r', '\\r', '\\n'], $value)) . "'"; - } - $whitespace = str_repeat(' ', (int) (4 * $indentation)); - if (!$processed) { - $processed = new Context(); - } - if (is_array($value)) { - if (($key = $processed->contains($value)) !== \false) { - return 'Array &' . $key; - } - $array = $value; - $key = $processed->add($value); - $values = ''; - if (count($array) > 0) { - foreach ($array as $k => $v) { - $values .= sprintf('%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($value[$k], $indentation + 1, $processed)); - } - $values = "\n" . $values . $whitespace; - } - return sprintf('Array &%s (%s)', $key, $values); - } - if (is_object($value)) { - $class = get_class($value); - if ($hash = $processed->contains($value)) { - return sprintf('%s Object &%s', $class, $hash); - } - $hash = $processed->add($value); - $values = ''; - $array = $this->toArray($value); - if (count($array) > 0) { - foreach ($array as $k => $v) { - $values .= sprintf('%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($v, $indentation + 1, $processed)); - } - $values = "\n" . $values . $whitespace; - } - return sprintf('%s Object &%s (%s)', $class, $hash, $values); - } - return var_export($value, \true); + return ['Directory::close', 'Directory::read', 'Directory::rewind', 'DirectoryIterator::openFile', 'FilesystemIterator::openFile', 'Gmagick::readimagefile', 'HttpResponse::getRequestBodyStream', 'HttpResponse::getStream', 'HttpResponse::setStream', 'Imagick::pingImageFile', 'Imagick::readImageFile', 'Imagick::writeImageFile', 'Imagick::writeImagesFile', 'MongoGridFSCursor::__construct', 'MongoGridFSFile::getResource', 'MysqlndUhConnection::stmtInit', 'MysqlndUhConnection::storeResult', 'MysqlndUhConnection::useResult', 'PDF_activate_item', 'PDF_add_launchlink', 'PDF_add_locallink', 'PDF_add_nameddest', 'PDF_add_note', 'PDF_add_pdflink', 'PDF_add_table_cell', 'PDF_add_textflow', 'PDF_add_thumbnail', 'PDF_add_weblink', 'PDF_arc', 'PDF_arcn', 'PDF_attach_file', 'PDF_begin_document', 'PDF_begin_font', 'PDF_begin_glyph', 'PDF_begin_item', 'PDF_begin_layer', 'PDF_begin_page', 'PDF_begin_page_ext', 'PDF_begin_pattern', 'PDF_begin_template', 'PDF_begin_template_ext', 'PDF_circle', 'PDF_clip', 'PDF_close', 'PDF_close_image', 'PDF_close_pdi', 'PDF_close_pdi_page', 'PDF_closepath', 'PDF_closepath_fill_stroke', 'PDF_closepath_stroke', 'PDF_concat', 'PDF_continue_text', 'PDF_create_3dview', 'PDF_create_action', 'PDF_create_annotation', 'PDF_create_bookmark', 'PDF_create_field', 'PDF_create_fieldgroup', 'PDF_create_gstate', 'PDF_create_pvf', 'PDF_create_textflow', 'PDF_curveto', 'PDF_define_layer', 'PDF_delete', 'PDF_delete_pvf', 'PDF_delete_table', 'PDF_delete_textflow', 'PDF_encoding_set_char', 'PDF_end_document', 'PDF_end_font', 'PDF_end_glyph', 'PDF_end_item', 'PDF_end_layer', 'PDF_end_page', 'PDF_end_page_ext', 'PDF_end_pattern', 'PDF_end_template', 'PDF_endpath', 'PDF_fill', 'PDF_fill_imageblock', 'PDF_fill_pdfblock', 'PDF_fill_stroke', 'PDF_fill_textblock', 'PDF_findfont', 'PDF_fit_image', 'PDF_fit_pdi_page', 'PDF_fit_table', 'PDF_fit_textflow', 'PDF_fit_textline', 'PDF_get_apiname', 'PDF_get_buffer', 'PDF_get_errmsg', 'PDF_get_errnum', 'PDF_get_parameter', 'PDF_get_pdi_parameter', 'PDF_get_pdi_value', 'PDF_get_value', 'PDF_info_font', 'PDF_info_matchbox', 'PDF_info_table', 'PDF_info_textflow', 'PDF_info_textline', 'PDF_initgraphics', 'PDF_lineto', 'PDF_load_3ddata', 'PDF_load_font', 'PDF_load_iccprofile', 'PDF_load_image', 'PDF_makespotcolor', 'PDF_moveto', 'PDF_new', 'PDF_open_ccitt', 'PDF_open_file', 'PDF_open_image', 'PDF_open_image_file', 'PDF_open_memory_image', 'PDF_open_pdi', 'PDF_open_pdi_document', 'PDF_open_pdi_page', 'PDF_pcos_get_number', 'PDF_pcos_get_stream', 'PDF_pcos_get_string', 'PDF_place_image', 'PDF_place_pdi_page', 'PDF_process_pdi', 'PDF_rect', 'PDF_restore', 'PDF_resume_page', 'PDF_rotate', 'PDF_save', 'PDF_scale', 'PDF_set_border_color', 'PDF_set_border_dash', 'PDF_set_border_style', 'PDF_set_gstate', 'PDF_set_info', 'PDF_set_layer_dependency', 'PDF_set_parameter', 'PDF_set_text_pos', 'PDF_set_value', 'PDF_setcolor', 'PDF_setdash', 'PDF_setdashpattern', 'PDF_setflat', 'PDF_setfont', 'PDF_setgray', 'PDF_setgray_fill', 'PDF_setgray_stroke', 'PDF_setlinecap', 'PDF_setlinejoin', 'PDF_setlinewidth', 'PDF_setmatrix', 'PDF_setmiterlimit', 'PDF_setrgbcolor', 'PDF_setrgbcolor_fill', 'PDF_setrgbcolor_stroke', 'PDF_shading', 'PDF_shading_pattern', 'PDF_shfill', 'PDF_show', 'PDF_show_boxed', 'PDF_show_xy', 'PDF_skew', 'PDF_stringwidth', 'PDF_stroke', 'PDF_suspend_page', 'PDF_translate', 'PDF_utf16_to_utf8', 'PDF_utf32_to_utf16', 'PDF_utf8_to_utf16', 'PDO::pgsqlLOBOpen', 'RarEntry::getStream', 'SQLite3::openBlob', 'SWFMovie::saveToFile', 'SplFileInfo::openFile', 'SplFileObject::openFile', 'SplTempFileObject::openFile', 'V8Js::compileString', 'V8Js::executeScript', 'Vtiful\\Kernel\\Excel::setColumn', 'Vtiful\\Kernel\\Excel::setRow', 'Vtiful\\Kernel\\Format::align', 'Vtiful\\Kernel\\Format::bold', 'Vtiful\\Kernel\\Format::italic', 'Vtiful\\Kernel\\Format::underline', 'XMLWriter::openMemory', 'XMLWriter::openURI', 'ZipArchive::getStream', 'Zookeeper::setLogStream', 'apc_bin_dumpfile', 'apc_bin_loadfile', 'bbcode_add_element', 'bbcode_add_smiley', 'bbcode_create', 'bbcode_destroy', 'bbcode_parse', 'bbcode_set_arg_parser', 'bbcode_set_flags', 'bcompiler_read', 'bcompiler_write_class', 'bcompiler_write_constant', 'bcompiler_write_exe_footer', 'bcompiler_write_file', 'bcompiler_write_footer', 'bcompiler_write_function', 'bcompiler_write_functions_from_file', 'bcompiler_write_header', 'bcompiler_write_included_filename', 'bzclose', 'bzerrno', 'bzerror', 'bzerrstr', 'bzflush', 'bzopen', 'bzread', 'bzwrite', 'cairo_surface_write_to_png', 'closedir', 'copy', 'crack_closedict', 'crack_opendict', 'cubrid_bind', 'cubrid_close_prepare', 'cubrid_close_request', 'cubrid_col_get', 'cubrid_col_size', 'cubrid_column_names', 'cubrid_column_types', 'cubrid_commit', 'cubrid_connect', 'cubrid_connect_with_url', 'cubrid_current_oid', 'cubrid_db_parameter', 'cubrid_disconnect', 'cubrid_drop', 'cubrid_fetch', 'cubrid_free_result', 'cubrid_get', 'cubrid_get_autocommit', 'cubrid_get_charset', 'cubrid_get_class_name', 'cubrid_get_db_parameter', 'cubrid_get_query_timeout', 'cubrid_get_server_info', 'cubrid_insert_id', 'cubrid_is_instance', 'cubrid_lob2_bind', 'cubrid_lob2_close', 'cubrid_lob2_export', 'cubrid_lob2_import', 'cubrid_lob2_new', 'cubrid_lob2_read', 'cubrid_lob2_seek', 'cubrid_lob2_seek64', 'cubrid_lob2_size', 'cubrid_lob2_size64', 'cubrid_lob2_tell', 'cubrid_lob2_tell64', 'cubrid_lob2_write', 'cubrid_lob_export', 'cubrid_lob_get', 'cubrid_lob_send', 'cubrid_lob_size', 'cubrid_lock_read', 'cubrid_lock_write', 'cubrid_move_cursor', 'cubrid_next_result', 'cubrid_num_cols', 'cubrid_num_rows', 'cubrid_pconnect', 'cubrid_pconnect_with_url', 'cubrid_prepare', 'cubrid_put', 'cubrid_query', 'cubrid_rollback', 'cubrid_schema', 'cubrid_seq_add', 'cubrid_seq_drop', 'cubrid_seq_insert', 'cubrid_seq_put', 'cubrid_set_add', 'cubrid_set_autocommit', 'cubrid_set_db_parameter', 'cubrid_set_drop', 'cubrid_set_query_timeout', 'cubrid_unbuffered_query', 'curl_close', 'curl_copy_handle', 'curl_errno', 'curl_error', 'curl_escape', 'curl_exec', 'curl_getinfo', 'curl_multi_add_handle', 'curl_multi_close', 'curl_multi_errno', 'curl_multi_exec', 'curl_multi_getcontent', 'curl_multi_info_read', 'curl_multi_remove_handle', 'curl_multi_select', 'curl_multi_setopt', 'curl_pause', 'curl_reset', 'curl_setopt', 'curl_setopt_array', 'curl_share_close', 'curl_share_errno', 'curl_share_init', 'curl_share_setopt', 'curl_unescape', 'cyrus_authenticate', 'cyrus_bind', 'cyrus_close', 'cyrus_connect', 'cyrus_query', 'cyrus_unbind', 'db2_autocommit', 'db2_bind_param', 'db2_client_info', 'db2_close', 'db2_column_privileges', 'db2_columns', 'db2_commit', 'db2_conn_error', 'db2_conn_errormsg', 'db2_connect', 'db2_cursor_type', 'db2_exec', 'db2_execute', 'db2_fetch_array', 'db2_fetch_assoc', 'db2_fetch_both', 'db2_fetch_object', 'db2_fetch_row', 'db2_field_display_size', 'db2_field_name', 'db2_field_num', 'db2_field_precision', 'db2_field_scale', 'db2_field_type', 'db2_field_width', 'db2_foreign_keys', 'db2_free_result', 'db2_free_stmt', 'db2_get_option', 'db2_last_insert_id', 'db2_lob_read', 'db2_next_result', 'db2_num_fields', 'db2_num_rows', 'db2_pclose', 'db2_pconnect', 'db2_prepare', 'db2_primary_keys', 'db2_procedure_columns', 'db2_procedures', 'db2_result', 'db2_rollback', 'db2_server_info', 'db2_set_option', 'db2_special_columns', 'db2_statistics', 'db2_stmt_error', 'db2_stmt_errormsg', 'db2_table_privileges', 'db2_tables', 'dba_close', 'dba_delete', 'dba_exists', 'dba_fetch', 'dba_firstkey', 'dba_insert', 'dba_nextkey', 'dba_open', 'dba_optimize', 'dba_popen', 'dba_replace', 'dba_sync', 'dbplus_add', 'dbplus_aql', 'dbplus_close', 'dbplus_curr', 'dbplus_find', 'dbplus_first', 'dbplus_flush', 'dbplus_freelock', 'dbplus_freerlocks', 'dbplus_getlock', 'dbplus_getunique', 'dbplus_info', 'dbplus_last', 'dbplus_lockrel', 'dbplus_next', 'dbplus_open', 'dbplus_prev', 'dbplus_rchperm', 'dbplus_rcreate', 'dbplus_rcrtexact', 'dbplus_rcrtlike', 'dbplus_restorepos', 'dbplus_rkeys', 'dbplus_ropen', 'dbplus_rquery', 'dbplus_rrename', 'dbplus_rsecindex', 'dbplus_runlink', 'dbplus_rzap', 'dbplus_savepos', 'dbplus_setindex', 'dbplus_setindexbynumber', 'dbplus_sql', 'dbplus_tremove', 'dbplus_undo', 'dbplus_undoprepare', 'dbplus_unlockrel', 'dbplus_unselect', 'dbplus_update', 'dbplus_xlockrel', 'dbplus_xunlockrel', 'deflate_add', 'dio_close', 'dio_fcntl', 'dio_open', 'dio_read', 'dio_seek', 'dio_stat', 'dio_tcsetattr', 'dio_truncate', 'dio_write', 'dir', 'eio_busy', 'eio_cancel', 'eio_chmod', 'eio_chown', 'eio_close', 'eio_custom', 'eio_dup2', 'eio_fallocate', 'eio_fchmod', 'eio_fchown', 'eio_fdatasync', 'eio_fstat', 'eio_fstatvfs', 'eio_fsync', 'eio_ftruncate', 'eio_futime', 'eio_get_last_error', 'eio_grp', 'eio_grp_add', 'eio_grp_cancel', 'eio_grp_limit', 'eio_link', 'eio_lstat', 'eio_mkdir', 'eio_mknod', 'eio_nop', 'eio_open', 'eio_read', 'eio_readahead', 'eio_readdir', 'eio_readlink', 'eio_realpath', 'eio_rename', 'eio_rmdir', 'eio_seek', 'eio_sendfile', 'eio_stat', 'eio_statvfs', 'eio_symlink', 'eio_sync', 'eio_sync_file_range', 'eio_syncfs', 'eio_truncate', 'eio_unlink', 'eio_utime', 'eio_write', 'enchant_broker_describe', 'enchant_broker_dict_exists', 'enchant_broker_free', 'enchant_broker_free_dict', 'enchant_broker_get_dict_path', 'enchant_broker_get_error', 'enchant_broker_init', 'enchant_broker_list_dicts', 'enchant_broker_request_dict', 'enchant_broker_request_pwl_dict', 'enchant_broker_set_dict_path', 'enchant_broker_set_ordering', 'enchant_dict_add_to_personal', 'enchant_dict_add_to_session', 'enchant_dict_check', 'enchant_dict_describe', 'enchant_dict_get_error', 'enchant_dict_is_in_session', 'enchant_dict_quick_check', 'enchant_dict_store_replacement', 'enchant_dict_suggest', 'event_add', 'event_base_free', 'event_base_loop', 'event_base_loopbreak', 'event_base_loopexit', 'event_base_new', 'event_base_priority_init', 'event_base_reinit', 'event_base_set', 'event_buffer_base_set', 'event_buffer_disable', 'event_buffer_enable', 'event_buffer_fd_set', 'event_buffer_free', 'event_buffer_new', 'event_buffer_priority_set', 'event_buffer_read', 'event_buffer_set_callback', 'event_buffer_timeout_set', 'event_buffer_watermark_set', 'event_buffer_write', 'event_del', 'event_free', 'event_new', 'event_priority_set', 'event_set', 'event_timer_add', 'event_timer_del', 'event_timer_pending', 'event_timer_set', 'expect_expectl', 'expect_popen', 'fam_cancel_monitor', 'fam_close', 'fam_monitor_collection', 'fam_monitor_directory', 'fam_monitor_file', 'fam_next_event', 'fam_open', 'fam_pending', 'fam_resume_monitor', 'fam_suspend_monitor', 'fann_cascadetrain_on_data', 'fann_cascadetrain_on_file', 'fann_clear_scaling_params', 'fann_copy', 'fann_create_from_file', 'fann_create_shortcut_array', 'fann_create_standard', 'fann_create_standard_array', 'fann_create_train', 'fann_create_train_from_callback', 'fann_descale_input', 'fann_descale_output', 'fann_descale_train', 'fann_destroy', 'fann_destroy_train', 'fann_duplicate_train_data', 'fann_get_MSE', 'fann_get_activation_function', 'fann_get_activation_steepness', 'fann_get_bias_array', 'fann_get_bit_fail', 'fann_get_bit_fail_limit', 'fann_get_cascade_activation_functions', 'fann_get_cascade_activation_functions_count', 'fann_get_cascade_activation_steepnesses', 'fann_get_cascade_activation_steepnesses_count', 'fann_get_cascade_candidate_change_fraction', 'fann_get_cascade_candidate_limit', 'fann_get_cascade_candidate_stagnation_epochs', 'fann_get_cascade_max_cand_epochs', 'fann_get_cascade_max_out_epochs', 'fann_get_cascade_min_cand_epochs', 'fann_get_cascade_min_out_epochs', 'fann_get_cascade_num_candidate_groups', 'fann_get_cascade_num_candidates', 'fann_get_cascade_output_change_fraction', 'fann_get_cascade_output_stagnation_epochs', 'fann_get_cascade_weight_multiplier', 'fann_get_connection_array', 'fann_get_connection_rate', 'fann_get_errno', 'fann_get_errstr', 'fann_get_layer_array', 'fann_get_learning_momentum', 'fann_get_learning_rate', 'fann_get_network_type', 'fann_get_num_input', 'fann_get_num_layers', 'fann_get_num_output', 'fann_get_quickprop_decay', 'fann_get_quickprop_mu', 'fann_get_rprop_decrease_factor', 'fann_get_rprop_delta_max', 'fann_get_rprop_delta_min', 'fann_get_rprop_delta_zero', 'fann_get_rprop_increase_factor', 'fann_get_sarprop_step_error_shift', 'fann_get_sarprop_step_error_threshold_factor', 'fann_get_sarprop_temperature', 'fann_get_sarprop_weight_decay_shift', 'fann_get_total_connections', 'fann_get_total_neurons', 'fann_get_train_error_function', 'fann_get_train_stop_function', 'fann_get_training_algorithm', 'fann_init_weights', 'fann_length_train_data', 'fann_merge_train_data', 'fann_num_input_train_data', 'fann_num_output_train_data', 'fann_randomize_weights', 'fann_read_train_from_file', 'fann_reset_errno', 'fann_reset_errstr', 'fann_run', 'fann_save', 'fann_save_train', 'fann_scale_input', 'fann_scale_input_train_data', 'fann_scale_output', 'fann_scale_output_train_data', 'fann_scale_train', 'fann_scale_train_data', 'fann_set_activation_function', 'fann_set_activation_function_hidden', 'fann_set_activation_function_layer', 'fann_set_activation_function_output', 'fann_set_activation_steepness', 'fann_set_activation_steepness_hidden', 'fann_set_activation_steepness_layer', 'fann_set_activation_steepness_output', 'fann_set_bit_fail_limit', 'fann_set_callback', 'fann_set_cascade_activation_functions', 'fann_set_cascade_activation_steepnesses', 'fann_set_cascade_candidate_change_fraction', 'fann_set_cascade_candidate_limit', 'fann_set_cascade_candidate_stagnation_epochs', 'fann_set_cascade_max_cand_epochs', 'fann_set_cascade_max_out_epochs', 'fann_set_cascade_min_cand_epochs', 'fann_set_cascade_min_out_epochs', 'fann_set_cascade_num_candidate_groups', 'fann_set_cascade_output_change_fraction', 'fann_set_cascade_output_stagnation_epochs', 'fann_set_cascade_weight_multiplier', 'fann_set_error_log', 'fann_set_input_scaling_params', 'fann_set_learning_momentum', 'fann_set_learning_rate', 'fann_set_output_scaling_params', 'fann_set_quickprop_decay', 'fann_set_quickprop_mu', 'fann_set_rprop_decrease_factor', 'fann_set_rprop_delta_max', 'fann_set_rprop_delta_min', 'fann_set_rprop_delta_zero', 'fann_set_rprop_increase_factor', 'fann_set_sarprop_step_error_shift', 'fann_set_sarprop_step_error_threshold_factor', 'fann_set_sarprop_temperature', 'fann_set_sarprop_weight_decay_shift', 'fann_set_scaling_params', 'fann_set_train_error_function', 'fann_set_train_stop_function', 'fann_set_training_algorithm', 'fann_set_weight', 'fann_set_weight_array', 'fann_shuffle_train_data', 'fann_subset_train_data', 'fann_test', 'fann_test_data', 'fann_train', 'fann_train_epoch', 'fann_train_on_data', 'fann_train_on_file', 'fbsql_affected_rows', 'fbsql_autocommit', 'fbsql_blob_size', 'fbsql_change_user', 'fbsql_clob_size', 'fbsql_close', 'fbsql_commit', 'fbsql_connect', 'fbsql_create_blob', 'fbsql_create_clob', 'fbsql_create_db', 'fbsql_data_seek', 'fbsql_database', 'fbsql_database_password', 'fbsql_db_query', 'fbsql_db_status', 'fbsql_drop_db', 'fbsql_errno', 'fbsql_error', 'fbsql_fetch_array', 'fbsql_fetch_assoc', 'fbsql_fetch_field', 'fbsql_fetch_lengths', 'fbsql_fetch_object', 'fbsql_fetch_row', 'fbsql_field_flags', 'fbsql_field_len', 'fbsql_field_name', 'fbsql_field_seek', 'fbsql_field_table', 'fbsql_field_type', 'fbsql_free_result', 'fbsql_get_autostart_info', 'fbsql_hostname', 'fbsql_insert_id', 'fbsql_list_dbs', 'fbsql_list_fields', 'fbsql_list_tables', 'fbsql_next_result', 'fbsql_num_fields', 'fbsql_num_rows', 'fbsql_password', 'fbsql_pconnect', 'fbsql_query', 'fbsql_read_blob', 'fbsql_read_clob', 'fbsql_result', 'fbsql_rollback', 'fbsql_rows_fetched', 'fbsql_select_db', 'fbsql_set_characterset', 'fbsql_set_lob_mode', 'fbsql_set_password', 'fbsql_set_transaction', 'fbsql_start_db', 'fbsql_stop_db', 'fbsql_table_name', 'fbsql_username', 'fclose', 'fdf_add_doc_javascript', 'fdf_add_template', 'fdf_close', 'fdf_create', 'fdf_enum_values', 'fdf_get_ap', 'fdf_get_attachment', 'fdf_get_encoding', 'fdf_get_file', 'fdf_get_flags', 'fdf_get_opt', 'fdf_get_status', 'fdf_get_value', 'fdf_get_version', 'fdf_next_field_name', 'fdf_open', 'fdf_open_string', 'fdf_remove_item', 'fdf_save', 'fdf_save_string', 'fdf_set_ap', 'fdf_set_encoding', 'fdf_set_file', 'fdf_set_flags', 'fdf_set_javascript_action', 'fdf_set_on_import_javascript', 'fdf_set_opt', 'fdf_set_status', 'fdf_set_submit_form_action', 'fdf_set_target_frame', 'fdf_set_value', 'fdf_set_version', 'feof', 'fflush', 'ffmpeg_frame::__construct', 'ffmpeg_frame::toGDImage', 'fgetc', 'fgetcsv', 'fgets', 'fgetss', 'file', 'file_get_contents', 'file_put_contents', 'finfo::buffer', 'finfo::file', 'finfo_buffer', 'finfo_close', 'finfo_file', 'finfo_open', 'finfo_set_flags', 'flock', 'fopen', 'fpassthru', 'fprintf', 'fputcsv', 'fputs', 'fread', 'fscanf', 'fseek', 'fstat', 'ftell', 'ftp_alloc', 'ftp_append', 'ftp_cdup', 'ftp_chdir', 'ftp_chmod', 'ftp_close', 'ftp_delete', 'ftp_exec', 'ftp_fget', 'ftp_fput', 'ftp_get', 'ftp_get_option', 'ftp_login', 'ftp_mdtm', 'ftp_mkdir', 'ftp_mlsd', 'ftp_nb_continue', 'ftp_nb_fget', 'ftp_nb_fput', 'ftp_nb_get', 'ftp_nb_put', 'ftp_nlist', 'ftp_pasv', 'ftp_put', 'ftp_pwd', 'ftp_quit', 'ftp_raw', 'ftp_rawlist', 'ftp_rename', 'ftp_rmdir', 'ftp_set_option', 'ftp_site', 'ftp_size', 'ftp_systype', 'ftruncate', 'fwrite', 'get_resource_type', 'gmp_div', 'gnupg::init', 'gnupg_adddecryptkey', 'gnupg_addencryptkey', 'gnupg_addsignkey', 'gnupg_cleardecryptkeys', 'gnupg_clearencryptkeys', 'gnupg_clearsignkeys', 'gnupg_decrypt', 'gnupg_decryptverify', 'gnupg_encrypt', 'gnupg_encryptsign', 'gnupg_export', 'gnupg_geterror', 'gnupg_getprotocol', 'gnupg_import', 'gnupg_init', 'gnupg_keyinfo', 'gnupg_setarmor', 'gnupg_seterrormode', 'gnupg_setsignmode', 'gnupg_sign', 'gnupg_verify', 'gupnp_context_get_host_ip', 'gupnp_context_get_port', 'gupnp_context_get_subscription_timeout', 'gupnp_context_host_path', 'gupnp_context_new', 'gupnp_context_set_subscription_timeout', 'gupnp_context_timeout_add', 'gupnp_context_unhost_path', 'gupnp_control_point_browse_start', 'gupnp_control_point_browse_stop', 'gupnp_control_point_callback_set', 'gupnp_control_point_new', 'gupnp_device_action_callback_set', 'gupnp_device_info_get', 'gupnp_device_info_get_service', 'gupnp_root_device_get_available', 'gupnp_root_device_get_relative_location', 'gupnp_root_device_new', 'gupnp_root_device_set_available', 'gupnp_root_device_start', 'gupnp_root_device_stop', 'gupnp_service_action_get', 'gupnp_service_action_return', 'gupnp_service_action_return_error', 'gupnp_service_action_set', 'gupnp_service_freeze_notify', 'gupnp_service_info_get', 'gupnp_service_info_get_introspection', 'gupnp_service_introspection_get_state_variable', 'gupnp_service_notify', 'gupnp_service_proxy_action_get', 'gupnp_service_proxy_action_set', 'gupnp_service_proxy_add_notify', 'gupnp_service_proxy_callback_set', 'gupnp_service_proxy_get_subscribed', 'gupnp_service_proxy_remove_notify', 'gupnp_service_proxy_send_action', 'gupnp_service_proxy_set_subscribed', 'gupnp_service_thaw_notify', 'gzclose', 'gzeof', 'gzgetc', 'gzgets', 'gzgetss', 'gzpassthru', 'gzputs', 'gzread', 'gzrewind', 'gzseek', 'gztell', 'gzwrite', 'hash_update_stream', 'http\\Env\\Response::send', 'http_get_request_body_stream', 'ibase_add_user', 'ibase_affected_rows', 'ibase_backup', 'ibase_blob_add', 'ibase_blob_cancel', 'ibase_blob_close', 'ibase_blob_create', 'ibase_blob_get', 'ibase_blob_open', 'ibase_close', 'ibase_commit', 'ibase_commit_ret', 'ibase_connect', 'ibase_db_info', 'ibase_delete_user', 'ibase_drop_db', 'ibase_execute', 'ibase_fetch_assoc', 'ibase_fetch_object', 'ibase_fetch_row', 'ibase_field_info', 'ibase_free_event_handler', 'ibase_free_query', 'ibase_free_result', 'ibase_gen_id', 'ibase_maintain_db', 'ibase_modify_user', 'ibase_name_result', 'ibase_num_fields', 'ibase_num_params', 'ibase_param_info', 'ibase_pconnect', 'ibase_prepare', 'ibase_query', 'ibase_restore', 'ibase_rollback', 'ibase_rollback_ret', 'ibase_server_info', 'ibase_service_attach', 'ibase_service_detach', 'ibase_set_event_handler', 'ibase_trans', 'ifx_affected_rows', 'ifx_close', 'ifx_connect', 'ifx_do', 'ifx_error', 'ifx_fetch_row', 'ifx_fieldproperties', 'ifx_fieldtypes', 'ifx_free_result', 'ifx_getsqlca', 'ifx_htmltbl_result', 'ifx_num_fields', 'ifx_num_rows', 'ifx_pconnect', 'ifx_prepare', 'ifx_query', 'image2wbmp', 'imageaffine', 'imagealphablending', 'imageantialias', 'imagearc', 'imagebmp', 'imagechar', 'imagecharup', 'imagecolorallocate', 'imagecolorallocatealpha', 'imagecolorat', 'imagecolorclosest', 'imagecolorclosestalpha', 'imagecolorclosesthwb', 'imagecolordeallocate', 'imagecolorexact', 'imagecolorexactalpha', 'imagecolormatch', 'imagecolorresolve', 'imagecolorresolvealpha', 'imagecolorset', 'imagecolorsforindex', 'imagecolorstotal', 'imagecolortransparent', 'imageconvolution', 'imagecopy', 'imagecopymerge', 'imagecopymergegray', 'imagecopyresampled', 'imagecopyresized', 'imagecrop', 'imagecropauto', 'imagedashedline', 'imagedestroy', 'imageellipse', 'imagefill', 'imagefilledarc', 'imagefilledellipse', 'imagefilledpolygon', 'imagefilledrectangle', 'imagefilltoborder', 'imagefilter', 'imageflip', 'imagefttext', 'imagegammacorrect', 'imagegd', 'imagegd2', 'imagegetclip', 'imagegif', 'imagegrabscreen', 'imagegrabwindow', 'imageinterlace', 'imageistruecolor', 'imagejpeg', 'imagelayereffect', 'imageline', 'imageopenpolygon', 'imagepalettecopy', 'imagepalettetotruecolor', 'imagepng', 'imagepolygon', 'imagepsencodefont', 'imagepsextendfont', 'imagepsfreefont', 'imagepsloadfont', 'imagepsslantfont', 'imagepstext', 'imagerectangle', 'imageresolution', 'imagerotate', 'imagesavealpha', 'imagescale', 'imagesetbrush', 'imagesetclip', 'imagesetinterpolation', 'imagesetpixel', 'imagesetstyle', 'imagesetthickness', 'imagesettile', 'imagestring', 'imagestringup', 'imagesx', 'imagesy', 'imagetruecolortopalette', 'imagettftext', 'imagewbmp', 'imagewebp', 'imagexbm', 'imap_append', 'imap_body', 'imap_bodystruct', 'imap_check', 'imap_clearflag_full', 'imap_close', 'imap_create', 'imap_createmailbox', 'imap_delete', 'imap_deletemailbox', 'imap_expunge', 'imap_fetch_overview', 'imap_fetchbody', 'imap_fetchheader', 'imap_fetchmime', 'imap_fetchstructure', 'imap_fetchtext', 'imap_gc', 'imap_get_quota', 'imap_get_quotaroot', 'imap_getacl', 'imap_getmailboxes', 'imap_getsubscribed', 'imap_header', 'imap_headerinfo', 'imap_headers', 'imap_list', 'imap_listmailbox', 'imap_listscan', 'imap_listsubscribed', 'imap_lsub', 'imap_mail_copy', 'imap_mail_move', 'imap_mailboxmsginfo', 'imap_msgno', 'imap_num_msg', 'imap_num_recent', 'imap_ping', 'imap_rename', 'imap_renamemailbox', 'imap_reopen', 'imap_savebody', 'imap_scan', 'imap_scanmailbox', 'imap_search', 'imap_set_quota', 'imap_setacl', 'imap_setflag_full', 'imap_sort', 'imap_status', 'imap_subscribe', 'imap_thread', 'imap_uid', 'imap_undelete', 'imap_unsubscribe', 'inflate_add', 'inflate_get_read_len', 'inflate_get_status', 'ingres_autocommit', 'ingres_autocommit_state', 'ingres_charset', 'ingres_close', 'ingres_commit', 'ingres_connect', 'ingres_cursor', 'ingres_errno', 'ingres_error', 'ingres_errsqlstate', 'ingres_escape_string', 'ingres_execute', 'ingres_fetch_array', 'ingres_fetch_assoc', 'ingres_fetch_object', 'ingres_fetch_proc_return', 'ingres_fetch_row', 'ingres_field_length', 'ingres_field_name', 'ingres_field_nullable', 'ingres_field_precision', 'ingres_field_scale', 'ingres_field_type', 'ingres_free_result', 'ingres_next_error', 'ingres_num_fields', 'ingres_num_rows', 'ingres_pconnect', 'ingres_prepare', 'ingres_query', 'ingres_result_seek', 'ingres_rollback', 'ingres_set_environment', 'ingres_unbuffered_query', 'inotify_add_watch', 'inotify_init', 'inotify_queue_len', 'inotify_read', 'inotify_rm_watch', 'kadm5_chpass_principal', 'kadm5_create_principal', 'kadm5_delete_principal', 'kadm5_destroy', 'kadm5_flush', 'kadm5_get_policies', 'kadm5_get_principal', 'kadm5_get_principals', 'kadm5_init_with_password', 'kadm5_modify_principal', 'ldap_add', 'ldap_bind', 'ldap_close', 'ldap_compare', 'ldap_control_paged_result', 'ldap_control_paged_result_response', 'ldap_count_entries', 'ldap_delete', 'ldap_errno', 'ldap_error', 'ldap_exop', 'ldap_exop_passwd', 'ldap_exop_refresh', 'ldap_exop_whoami', 'ldap_first_attribute', 'ldap_first_entry', 'ldap_first_reference', 'ldap_free_result', 'ldap_get_attributes', 'ldap_get_dn', 'ldap_get_entries', 'ldap_get_option', 'ldap_get_values', 'ldap_get_values_len', 'ldap_mod_add', 'ldap_mod_del', 'ldap_mod_replace', 'ldap_modify', 'ldap_modify_batch', 'ldap_next_attribute', 'ldap_next_entry', 'ldap_next_reference', 'ldap_parse_exop', 'ldap_parse_reference', 'ldap_parse_result', 'ldap_rename', 'ldap_sasl_bind', 'ldap_set_option', 'ldap_set_rebind_proc', 'ldap_sort', 'ldap_start_tls', 'ldap_unbind', 'libxml_set_streams_context', 'm_checkstatus', 'm_completeauthorizations', 'm_connect', 'm_connectionerror', 'm_deletetrans', 'm_destroyconn', 'm_getcell', 'm_getcellbynum', 'm_getcommadelimited', 'm_getheader', 'm_initconn', 'm_iscommadelimited', 'm_maxconntimeout', 'm_monitor', 'm_numcolumns', 'm_numrows', 'm_parsecommadelimited', 'm_responsekeys', 'm_responseparam', 'm_returnstatus', 'm_setblocking', 'm_setdropfile', 'm_setip', 'm_setssl', 'm_setssl_cafile', 'm_setssl_files', 'm_settimeout', 'm_transactionssent', 'm_transinqueue', 'm_transkeyval', 'm_transnew', 'm_transsend', 'm_validateidentifier', 'm_verifyconnection', 'm_verifysslcert', 'mailparse_determine_best_xfer_encoding', 'mailparse_msg_create', 'mailparse_msg_extract_part', 'mailparse_msg_extract_part_file', 'mailparse_msg_extract_whole_part_file', 'mailparse_msg_free', 'mailparse_msg_get_part', 'mailparse_msg_get_part_data', 'mailparse_msg_get_structure', 'mailparse_msg_parse', 'mailparse_msg_parse_file', 'mailparse_stream_encode', 'mailparse_uudecode_all', 'maxdb::use_result', 'maxdb_affected_rows', 'maxdb_connect', 'maxdb_disable_rpl_parse', 'maxdb_dump_debug_info', 'maxdb_embedded_connect', 'maxdb_enable_reads_from_master', 'maxdb_enable_rpl_parse', 'maxdb_errno', 'maxdb_error', 'maxdb_fetch_lengths', 'maxdb_field_tell', 'maxdb_get_host_info', 'maxdb_get_proto_info', 'maxdb_get_server_info', 'maxdb_get_server_version', 'maxdb_info', 'maxdb_init', 'maxdb_insert_id', 'maxdb_master_query', 'maxdb_more_results', 'maxdb_next_result', 'maxdb_num_fields', 'maxdb_num_rows', 'maxdb_rpl_parse_enabled', 'maxdb_rpl_probe', 'maxdb_select_db', 'maxdb_sqlstate', 'maxdb_stmt::result_metadata', 'maxdb_stmt_affected_rows', 'maxdb_stmt_errno', 'maxdb_stmt_error', 'maxdb_stmt_num_rows', 'maxdb_stmt_param_count', 'maxdb_stmt_result_metadata', 'maxdb_stmt_sqlstate', 'maxdb_thread_id', 'maxdb_use_result', 'maxdb_warning_count', 'mcrypt_enc_get_algorithms_name', 'mcrypt_enc_get_block_size', 'mcrypt_enc_get_iv_size', 'mcrypt_enc_get_key_size', 'mcrypt_enc_get_modes_name', 'mcrypt_enc_get_supported_key_sizes', 'mcrypt_enc_is_block_algorithm', 'mcrypt_enc_is_block_algorithm_mode', 'mcrypt_enc_is_block_mode', 'mcrypt_enc_self_test', 'mcrypt_generic', 'mcrypt_generic_deinit', 'mcrypt_generic_end', 'mcrypt_generic_init', 'mcrypt_module_close', 'mcrypt_module_open', 'mdecrypt_generic', 'mkdir', 'mqseries_back', 'mqseries_begin', 'mqseries_close', 'mqseries_cmit', 'mqseries_conn', 'mqseries_connx', 'mqseries_disc', 'mqseries_get', 'mqseries_inq', 'mqseries_open', 'mqseries_put', 'mqseries_put1', 'mqseries_set', 'msg_get_queue', 'msg_receive', 'msg_remove_queue', 'msg_send', 'msg_set_queue', 'msg_stat_queue', 'msql_affected_rows', 'msql_close', 'msql_connect', 'msql_create_db', 'msql_data_seek', 'msql_db_query', 'msql_drop_db', 'msql_fetch_array', 'msql_fetch_field', 'msql_fetch_object', 'msql_fetch_row', 'msql_field_flags', 'msql_field_len', 'msql_field_name', 'msql_field_seek', 'msql_field_table', 'msql_field_type', 'msql_free_result', 'msql_list_dbs', 'msql_list_fields', 'msql_list_tables', 'msql_num_fields', 'msql_num_rows', 'msql_pconnect', 'msql_query', 'msql_result', 'msql_select_db', 'mssql_bind', 'mssql_close', 'mssql_connect', 'mssql_data_seek', 'mssql_execute', 'mssql_fetch_array', 'mssql_fetch_assoc', 'mssql_fetch_batch', 'mssql_fetch_field', 'mssql_fetch_object', 'mssql_fetch_row', 'mssql_field_length', 'mssql_field_name', 'mssql_field_seek', 'mssql_field_type', 'mssql_free_result', 'mssql_free_statement', 'mssql_init', 'mssql_next_result', 'mssql_num_fields', 'mssql_num_rows', 'mssql_pconnect', 'mssql_query', 'mssql_result', 'mssql_rows_affected', 'mssql_select_db', 'mysql_affected_rows', 'mysql_client_encoding', 'mysql_close', 'mysql_connect', 'mysql_create_db', 'mysql_data_seek', 'mysql_db_name', 'mysql_db_query', 'mysql_drop_db', 'mysql_errno', 'mysql_error', 'mysql_fetch_array', 'mysql_fetch_assoc', 'mysql_fetch_field', 'mysql_fetch_lengths', 'mysql_fetch_object', 'mysql_fetch_row', 'mysql_field_flags', 'mysql_field_len', 'mysql_field_name', 'mysql_field_seek', 'mysql_field_table', 'mysql_field_type', 'mysql_free_result', 'mysql_get_host_info', 'mysql_get_proto_info', 'mysql_get_server_info', 'mysql_info', 'mysql_insert_id', 'mysql_list_dbs', 'mysql_list_fields', 'mysql_list_processes', 'mysql_list_tables', 'mysql_num_fields', 'mysql_num_rows', 'mysql_pconnect', 'mysql_ping', 'mysql_query', 'mysql_real_escape_string', 'mysql_result', 'mysql_select_db', 'mysql_set_charset', 'mysql_stat', 'mysql_tablename', 'mysql_thread_id', 'mysql_unbuffered_query', 'mysqlnd_uh_convert_to_mysqlnd', 'ncurses_bottom_panel', 'ncurses_del_panel', 'ncurses_delwin', 'ncurses_getmaxyx', 'ncurses_getyx', 'ncurses_hide_panel', 'ncurses_keypad', 'ncurses_meta', 'ncurses_move_panel', 'ncurses_mvwaddstr', 'ncurses_new_panel', 'ncurses_newpad', 'ncurses_newwin', 'ncurses_panel_above', 'ncurses_panel_below', 'ncurses_panel_window', 'ncurses_pnoutrefresh', 'ncurses_prefresh', 'ncurses_replace_panel', 'ncurses_show_panel', 'ncurses_top_panel', 'ncurses_waddch', 'ncurses_waddstr', 'ncurses_wattroff', 'ncurses_wattron', 'ncurses_wattrset', 'ncurses_wborder', 'ncurses_wclear', 'ncurses_wcolor_set', 'ncurses_werase', 'ncurses_wgetch', 'ncurses_whline', 'ncurses_wmouse_trafo', 'ncurses_wmove', 'ncurses_wnoutrefresh', 'ncurses_wrefresh', 'ncurses_wstandend', 'ncurses_wstandout', 'ncurses_wvline', 'newt_button', 'newt_button_bar', 'newt_checkbox', 'newt_checkbox_get_value', 'newt_checkbox_set_flags', 'newt_checkbox_set_value', 'newt_checkbox_tree', 'newt_checkbox_tree_add_item', 'newt_checkbox_tree_find_item', 'newt_checkbox_tree_get_current', 'newt_checkbox_tree_get_entry_value', 'newt_checkbox_tree_get_multi_selection', 'newt_checkbox_tree_get_selection', 'newt_checkbox_tree_multi', 'newt_checkbox_tree_set_current', 'newt_checkbox_tree_set_entry', 'newt_checkbox_tree_set_entry_value', 'newt_checkbox_tree_set_width', 'newt_compact_button', 'newt_component_add_callback', 'newt_component_takes_focus', 'newt_create_grid', 'newt_draw_form', 'newt_entry', 'newt_entry_get_value', 'newt_entry_set', 'newt_entry_set_filter', 'newt_entry_set_flags', 'newt_form', 'newt_form_add_component', 'newt_form_add_components', 'newt_form_add_hot_key', 'newt_form_destroy', 'newt_form_get_current', 'newt_form_run', 'newt_form_set_background', 'newt_form_set_height', 'newt_form_set_size', 'newt_form_set_timer', 'newt_form_set_width', 'newt_form_watch_fd', 'newt_grid_add_components_to_form', 'newt_grid_basic_window', 'newt_grid_free', 'newt_grid_get_size', 'newt_grid_h_close_stacked', 'newt_grid_h_stacked', 'newt_grid_place', 'newt_grid_set_field', 'newt_grid_simple_window', 'newt_grid_v_close_stacked', 'newt_grid_v_stacked', 'newt_grid_wrapped_window', 'newt_grid_wrapped_window_at', 'newt_label', 'newt_label_set_text', 'newt_listbox', 'newt_listbox_append_entry', 'newt_listbox_clear', 'newt_listbox_clear_selection', 'newt_listbox_delete_entry', 'newt_listbox_get_current', 'newt_listbox_get_selection', 'newt_listbox_insert_entry', 'newt_listbox_item_count', 'newt_listbox_select_item', 'newt_listbox_set_current', 'newt_listbox_set_current_by_key', 'newt_listbox_set_data', 'newt_listbox_set_entry', 'newt_listbox_set_width', 'newt_listitem', 'newt_listitem_get_data', 'newt_listitem_set', 'newt_radio_get_current', 'newt_radiobutton', 'newt_run_form', 'newt_scale', 'newt_scale_set', 'newt_scrollbar_set', 'newt_textbox', 'newt_textbox_get_num_lines', 'newt_textbox_reflowed', 'newt_textbox_set_height', 'newt_textbox_set_text', 'newt_vertical_scrollbar', 'oci_bind_array_by_name', 'oci_bind_by_name', 'oci_cancel', 'oci_close', 'oci_commit', 'oci_connect', 'oci_define_by_name', 'oci_error', 'oci_execute', 'oci_fetch', 'oci_fetch_all', 'oci_fetch_array', 'oci_fetch_assoc', 'oci_fetch_object', 'oci_fetch_row', 'oci_field_is_null', 'oci_field_name', 'oci_field_precision', 'oci_field_scale', 'oci_field_size', 'oci_field_type', 'oci_field_type_raw', 'oci_free_cursor', 'oci_free_statement', 'oci_get_implicit_resultset', 'oci_new_collection', 'oci_new_connect', 'oci_new_cursor', 'oci_new_descriptor', 'oci_num_fields', 'oci_num_rows', 'oci_parse', 'oci_pconnect', 'oci_register_taf_callback', 'oci_result', 'oci_rollback', 'oci_server_version', 'oci_set_action', 'oci_set_client_identifier', 'oci_set_client_info', 'oci_set_module_name', 'oci_set_prefetch', 'oci_statement_type', 'oci_unregister_taf_callback', 'odbc_autocommit', 'odbc_close', 'odbc_columnprivileges', 'odbc_columns', 'odbc_commit', 'odbc_connect', 'odbc_cursor', 'odbc_data_source', 'odbc_do', 'odbc_error', 'odbc_errormsg', 'odbc_exec', 'odbc_execute', 'odbc_fetch_array', 'odbc_fetch_into', 'odbc_fetch_row', 'odbc_field_len', 'odbc_field_name', 'odbc_field_num', 'odbc_field_precision', 'odbc_field_scale', 'odbc_field_type', 'odbc_foreignkeys', 'odbc_free_result', 'odbc_gettypeinfo', 'odbc_next_result', 'odbc_num_fields', 'odbc_num_rows', 'odbc_pconnect', 'odbc_prepare', 'odbc_primarykeys', 'odbc_procedurecolumns', 'odbc_procedures', 'odbc_result', 'odbc_result_all', 'odbc_rollback', 'odbc_setoption', 'odbc_specialcolumns', 'odbc_statistics', 'odbc_tableprivileges', 'odbc_tables', 'openal_buffer_create', 'openal_buffer_data', 'openal_buffer_destroy', 'openal_buffer_get', 'openal_buffer_loadwav', 'openal_context_create', 'openal_context_current', 'openal_context_destroy', 'openal_context_process', 'openal_context_suspend', 'openal_device_close', 'openal_device_open', 'openal_source_create', 'openal_source_destroy', 'openal_source_get', 'openal_source_pause', 'openal_source_play', 'openal_source_rewind', 'openal_source_set', 'openal_source_stop', 'openal_stream', 'opendir', 'openssl_csr_new', 'openssl_dh_compute_key', 'openssl_free_key', 'openssl_pkey_export', 'openssl_pkey_free', 'openssl_pkey_get_details', 'openssl_spki_new', 'openssl_x509_free', 'pclose', 'pfsockopen', 'pg_affected_rows', 'pg_cancel_query', 'pg_client_encoding', 'pg_close', 'pg_connect_poll', 'pg_connection_busy', 'pg_connection_reset', 'pg_connection_status', 'pg_consume_input', 'pg_convert', 'pg_copy_from', 'pg_copy_to', 'pg_dbname', 'pg_delete', 'pg_end_copy', 'pg_escape_bytea', 'pg_escape_identifier', 'pg_escape_literal', 'pg_escape_string', 'pg_execute', 'pg_fetch_all', 'pg_fetch_all_columns', 'pg_fetch_array', 'pg_fetch_assoc', 'pg_fetch_row', 'pg_field_name', 'pg_field_num', 'pg_field_size', 'pg_field_table', 'pg_field_type', 'pg_field_type_oid', 'pg_flush', 'pg_free_result', 'pg_get_notify', 'pg_get_pid', 'pg_get_result', 'pg_host', 'pg_insert', 'pg_last_error', 'pg_last_notice', 'pg_last_oid', 'pg_lo_close', 'pg_lo_create', 'pg_lo_export', 'pg_lo_import', 'pg_lo_open', 'pg_lo_read', 'pg_lo_read_all', 'pg_lo_seek', 'pg_lo_tell', 'pg_lo_truncate', 'pg_lo_unlink', 'pg_lo_write', 'pg_meta_data', 'pg_num_fields', 'pg_num_rows', 'pg_options', 'pg_parameter_status', 'pg_ping', 'pg_port', 'pg_prepare', 'pg_put_line', 'pg_query', 'pg_query_params', 'pg_result_error', 'pg_result_error_field', 'pg_result_seek', 'pg_result_status', 'pg_select', 'pg_send_execute', 'pg_send_prepare', 'pg_send_query', 'pg_send_query_params', 'pg_set_client_encoding', 'pg_set_error_verbosity', 'pg_socket', 'pg_trace', 'pg_transaction_status', 'pg_tty', 'pg_untrace', 'pg_update', 'pg_version', 'php_user_filter::filter', 'proc_close', 'proc_get_status', 'proc_terminate', 'ps_add_bookmark', 'ps_add_launchlink', 'ps_add_locallink', 'ps_add_note', 'ps_add_pdflink', 'ps_add_weblink', 'ps_arc', 'ps_arcn', 'ps_begin_page', 'ps_begin_pattern', 'ps_begin_template', 'ps_circle', 'ps_clip', 'ps_close', 'ps_close_image', 'ps_closepath', 'ps_closepath_stroke', 'ps_continue_text', 'ps_curveto', 'ps_delete', 'ps_end_page', 'ps_end_pattern', 'ps_end_template', 'ps_fill', 'ps_fill_stroke', 'ps_findfont', 'ps_get_buffer', 'ps_get_parameter', 'ps_get_value', 'ps_hyphenate', 'ps_include_file', 'ps_lineto', 'ps_makespotcolor', 'ps_moveto', 'ps_new', 'ps_open_file', 'ps_open_image', 'ps_open_image_file', 'ps_open_memory_image', 'ps_place_image', 'ps_rect', 'ps_restore', 'ps_rotate', 'ps_save', 'ps_scale', 'ps_set_border_color', 'ps_set_border_dash', 'ps_set_border_style', 'ps_set_info', 'ps_set_parameter', 'ps_set_text_pos', 'ps_set_value', 'ps_setcolor', 'ps_setdash', 'ps_setflat', 'ps_setfont', 'ps_setgray', 'ps_setlinecap', 'ps_setlinejoin', 'ps_setlinewidth', 'ps_setmiterlimit', 'ps_setoverprintmode', 'ps_setpolydash', 'ps_shading', 'ps_shading_pattern', 'ps_shfill', 'ps_show', 'ps_show2', 'ps_show_boxed', 'ps_show_xy', 'ps_show_xy2', 'ps_string_geometry', 'ps_stringwidth', 'ps_stroke', 'ps_symbol', 'ps_symbol_name', 'ps_symbol_width', 'ps_translate', 'px_close', 'px_create_fp', 'px_date2string', 'px_delete', 'px_delete_record', 'px_get_field', 'px_get_info', 'px_get_parameter', 'px_get_record', 'px_get_schema', 'px_get_value', 'px_insert_record', 'px_new', 'px_numfields', 'px_numrecords', 'px_open_fp', 'px_put_record', 'px_retrieve_record', 'px_set_blob_file', 'px_set_parameter', 'px_set_tablename', 'px_set_targetencoding', 'px_set_value', 'px_timestamp2string', 'px_update_record', 'radius_acct_open', 'radius_add_server', 'radius_auth_open', 'radius_close', 'radius_config', 'radius_create_request', 'radius_demangle', 'radius_demangle_mppe_key', 'radius_get_attr', 'radius_put_addr', 'radius_put_attr', 'radius_put_int', 'radius_put_string', 'radius_put_vendor_addr', 'radius_put_vendor_attr', 'radius_put_vendor_int', 'radius_put_vendor_string', 'radius_request_authenticator', 'radius_salt_encrypt_attr', 'radius_send_request', 'radius_server_secret', 'radius_strerror', 'readdir', 'readfile', 'recode_file', 'rename', 'rewind', 'rewinddir', 'rmdir', 'rpm_close', 'rpm_get_tag', 'rpm_open', 'sapi_windows_vt100_support', 'scandir', 'sem_acquire', 'sem_get', 'sem_release', 'sem_remove', 'set_file_buffer', 'shm_attach', 'shm_detach', 'shm_get_var', 'shm_has_var', 'shm_put_var', 'shm_remove', 'shm_remove_var', 'shmop_close', 'shmop_delete', 'shmop_open', 'shmop_read', 'shmop_size', 'shmop_write', 'socket_accept', 'socket_addrinfo_bind', 'socket_addrinfo_connect', 'socket_addrinfo_explain', 'socket_bind', 'socket_clear_error', 'socket_close', 'socket_connect', 'socket_export_stream', 'socket_get_option', 'socket_get_status', 'socket_getopt', 'socket_getpeername', 'socket_getsockname', 'socket_import_stream', 'socket_last_error', 'socket_listen', 'socket_read', 'socket_recv', 'socket_recvfrom', 'socket_recvmsg', 'socket_send', 'socket_sendmsg', 'socket_sendto', 'socket_set_block', 'socket_set_blocking', 'socket_set_nonblock', 'socket_set_option', 'socket_set_timeout', 'socket_shutdown', 'socket_write', 'sqlite_close', 'sqlite_fetch_string', 'sqlite_has_more', 'sqlite_open', 'sqlite_popen', 'sqlsrv_begin_transaction', 'sqlsrv_cancel', 'sqlsrv_client_info', 'sqlsrv_close', 'sqlsrv_commit', 'sqlsrv_connect', 'sqlsrv_execute', 'sqlsrv_fetch', 'sqlsrv_fetch_array', 'sqlsrv_fetch_object', 'sqlsrv_field_metadata', 'sqlsrv_free_stmt', 'sqlsrv_get_field', 'sqlsrv_has_rows', 'sqlsrv_next_result', 'sqlsrv_num_fields', 'sqlsrv_num_rows', 'sqlsrv_prepare', 'sqlsrv_query', 'sqlsrv_rollback', 'sqlsrv_rows_affected', 'sqlsrv_send_stream_data', 'sqlsrv_server_info', 'ssh2_auth_agent', 'ssh2_auth_hostbased_file', 'ssh2_auth_none', 'ssh2_auth_password', 'ssh2_auth_pubkey_file', 'ssh2_disconnect', 'ssh2_exec', 'ssh2_fetch_stream', 'ssh2_fingerprint', 'ssh2_methods_negotiated', 'ssh2_publickey_add', 'ssh2_publickey_init', 'ssh2_publickey_list', 'ssh2_publickey_remove', 'ssh2_scp_recv', 'ssh2_scp_send', 'ssh2_sftp', 'ssh2_sftp_chmod', 'ssh2_sftp_lstat', 'ssh2_sftp_mkdir', 'ssh2_sftp_readlink', 'ssh2_sftp_realpath', 'ssh2_sftp_rename', 'ssh2_sftp_rmdir', 'ssh2_sftp_stat', 'ssh2_sftp_symlink', 'ssh2_sftp_unlink', 'ssh2_shell', 'ssh2_tunnel', 'stomp_connect', 'streamWrapper::stream_cast', 'stream_bucket_append', 'stream_bucket_make_writeable', 'stream_bucket_new', 'stream_bucket_prepend', 'stream_context_create', 'stream_context_get_default', 'stream_context_get_options', 'stream_context_get_params', 'stream_context_set_default', 'stream_context_set_params', 'stream_copy_to_stream', 'stream_encoding', 'stream_filter_append', 'stream_filter_prepend', 'stream_filter_remove', 'stream_get_contents', 'stream_get_line', 'stream_get_meta_data', 'stream_isatty', 'stream_set_blocking', 'stream_set_chunk_size', 'stream_set_read_buffer', 'stream_set_timeout', 'stream_set_write_buffer', 'stream_socket_accept', 'stream_socket_client', 'stream_socket_enable_crypto', 'stream_socket_get_name', 'stream_socket_recvfrom', 'stream_socket_sendto', 'stream_socket_server', 'stream_socket_shutdown', 'stream_supports_lock', 'svn_fs_abort_txn', 'svn_fs_apply_text', 'svn_fs_begin_txn2', 'svn_fs_change_node_prop', 'svn_fs_check_path', 'svn_fs_contents_changed', 'svn_fs_copy', 'svn_fs_delete', 'svn_fs_dir_entries', 'svn_fs_file_contents', 'svn_fs_file_length', 'svn_fs_is_dir', 'svn_fs_is_file', 'svn_fs_make_dir', 'svn_fs_make_file', 'svn_fs_node_created_rev', 'svn_fs_node_prop', 'svn_fs_props_changed', 'svn_fs_revision_prop', 'svn_fs_revision_root', 'svn_fs_txn_root', 'svn_fs_youngest_rev', 'svn_repos_create', 'svn_repos_fs', 'svn_repos_fs_begin_txn_for_commit', 'svn_repos_fs_commit_txn', 'svn_repos_open', 'sybase_affected_rows', 'sybase_close', 'sybase_connect', 'sybase_data_seek', 'sybase_fetch_array', 'sybase_fetch_assoc', 'sybase_fetch_field', 'sybase_fetch_object', 'sybase_fetch_row', 'sybase_field_seek', 'sybase_free_result', 'sybase_num_fields', 'sybase_num_rows', 'sybase_pconnect', 'sybase_query', 'sybase_result', 'sybase_select_db', 'sybase_set_message_handler', 'sybase_unbuffered_query', 'tmpfile', 'udm_add_search_limit', 'udm_alloc_agent', 'udm_alloc_agent_array', 'udm_cat_list', 'udm_cat_path', 'udm_check_charset', 'udm_clear_search_limits', 'udm_crc32', 'udm_errno', 'udm_error', 'udm_find', 'udm_free_agent', 'udm_free_res', 'udm_get_doc_count', 'udm_get_res_field', 'udm_get_res_param', 'udm_hash32', 'udm_load_ispell_data', 'udm_set_agent_param', 'unlink', 'vfprintf', 'w32api_init_dtype', 'wddx_add_vars', 'wddx_packet_end', 'wddx_packet_start', 'xml_get_current_byte_index', 'xml_get_current_column_number', 'xml_get_current_line_number', 'xml_get_error_code', 'xml_parse', 'xml_parse_into_struct', 'xml_parser_create', 'xml_parser_create_ns', 'xml_parser_free', 'xml_parser_get_option', 'xml_parser_set_option', 'xml_set_character_data_handler', 'xml_set_default_handler', 'xml_set_element_handler', 'xml_set_end_namespace_decl_handler', 'xml_set_external_entity_ref_handler', 'xml_set_notation_decl_handler', 'xml_set_object', 'xml_set_processing_instruction_handler', 'xml_set_start_namespace_decl_handler', 'xml_set_unparsed_entity_decl_handler', 'xmlrpc_server_add_introspection_data', 'xmlrpc_server_call_method', 'xmlrpc_server_create', 'xmlrpc_server_destroy', 'xmlrpc_server_register_introspection_callback', 'xmlrpc_server_register_method', 'xmlwriter_end_attribute', 'xmlwriter_end_cdata', 'xmlwriter_end_comment', 'xmlwriter_end_document', 'xmlwriter_end_dtd', 'xmlwriter_end_dtd_attlist', 'xmlwriter_end_dtd_element', 'xmlwriter_end_dtd_entity', 'xmlwriter_end_element', 'xmlwriter_end_pi', 'xmlwriter_flush', 'xmlwriter_full_end_element', 'xmlwriter_open_memory', 'xmlwriter_open_uri', 'xmlwriter_output_memory', 'xmlwriter_set_indent', 'xmlwriter_set_indent_string', 'xmlwriter_start_attribute', 'xmlwriter_start_attribute_ns', 'xmlwriter_start_cdata', 'xmlwriter_start_comment', 'xmlwriter_start_document', 'xmlwriter_start_dtd', 'xmlwriter_start_dtd_attlist', 'xmlwriter_start_dtd_element', 'xmlwriter_start_dtd_entity', 'xmlwriter_start_element', 'xmlwriter_start_element_ns', 'xmlwriter_start_pi', 'xmlwriter_text', 'xmlwriter_write_attribute', 'xmlwriter_write_attribute_ns', 'xmlwriter_write_cdata', 'xmlwriter_write_comment', 'xmlwriter_write_dtd', 'xmlwriter_write_dtd_attlist', 'xmlwriter_write_dtd_element', 'xmlwriter_write_dtd_entity', 'xmlwriter_write_element', 'xmlwriter_write_element_ns', 'xmlwriter_write_pi', 'xmlwriter_write_raw', 'xslt_create', 'yaz_addinfo', 'yaz_ccl_conf', 'yaz_ccl_parse', 'yaz_close', 'yaz_database', 'yaz_element', 'yaz_errno', 'yaz_error', 'yaz_es', 'yaz_es_result', 'yaz_get_option', 'yaz_hits', 'yaz_itemorder', 'yaz_present', 'yaz_range', 'yaz_record', 'yaz_scan', 'yaz_scan_result', 'yaz_schema', 'yaz_search', 'yaz_sort', 'yaz_syntax', 'zip_close', 'zip_entry_close', 'zip_entry_compressedsize', 'zip_entry_compressionmethod', 'zip_entry_filesize', 'zip_entry_name', 'zip_entry_open', 'zip_entry_read', 'zip_open', 'zip_read']; } } -Exporter +sebastian/type -Copyright (c) 2002-2020, Sebastian Bergmann . +Copyright (c) 2019-2022, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without @@ -87816,8779 +91972,8175 @@ POSSIBILITY OF SUCH DAMAGE. declare (strict_types=1); /* - * This file is part of phpunit/php-file-iterator. + * This file is part of sebastian/type. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\FileIterator; +namespace PHPUnit\SebastianBergmann\Type; -use const DIRECTORY_SEPARATOR; -use function array_unique; -use function count; -use function dirname; -use function explode; -use function is_file; -use function is_string; -use function realpath; -use function sort; -class Facade +final class Parameter { /** - * @param array|string $paths - * @param array|string $suffixes - * @param array|string $prefixes + * @psalm-var non-empty-string */ - public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = [], bool $commonPath = \false) : array + private $name; + /** + * @var Type + */ + private $type; + /** + * @psalm-param non-empty-string $name + */ + public function __construct(string $name, Type $type) { - if (is_string($paths)) { - $paths = [$paths]; - } - $iterator = (new Factory())->getFileIterator($paths, $suffixes, $prefixes, $exclude); - $files = []; - foreach ($iterator as $file) { - $file = $file->getRealPath(); - if ($file) { - $files[] = $file; - } - } - foreach ($paths as $path) { - if (is_file($path)) { - $files[] = realpath($path); - } - } - $files = array_unique($files); - sort($files); - if ($commonPath) { - return ['commonPath' => $this->getCommonPath($files), 'files' => $files]; - } - return $files; + $this->name = $name; + $this->type = $type; } - protected function getCommonPath(array $files) : string + public function name() : string { - $count = count($files); - if ($count === 0) { - return ''; - } - if ($count === 1) { - return dirname($files[0]) . \DIRECTORY_SEPARATOR; - } - $_files = []; - foreach ($files as $file) { - $_files[] = $_fileParts = explode(\DIRECTORY_SEPARATOR, $file); - if (empty($_fileParts[0])) { - $_fileParts[0] = \DIRECTORY_SEPARATOR; - } - } - $common = ''; - $done = \false; - $j = 0; - $count--; - while (!$done) { - for ($i = 0; $i < $count; $i++) { - if ($_files[$i][$j] != $_files[$i + 1][$j]) { - $done = \true; - break; - } - } - if (!$done) { - $common .= $_files[0][$j]; - if ($j > 0) { - $common .= \DIRECTORY_SEPARATOR; - } - } - $j++; - } - return \DIRECTORY_SEPARATOR . $common; + return $this->name; + } + public function type() : Type + { + return $this->type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\FileIterator; +namespace PHPUnit\SebastianBergmann\Type; -use const GLOB_ONLYDIR; -use function array_filter; -use function array_map; -use function array_merge; -use function glob; -use function is_dir; -use function is_string; -use function realpath; -use AppendIterator; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; -class Factory +use function assert; +use ReflectionFunctionAbstract; +use ReflectionIntersectionType; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionType; +use ReflectionUnionType; +final class ReflectionMapper { /** - * @param array|string $paths - * @param array|string $suffixes - * @param array|string $prefixes + * @psalm-return list */ - public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = []) : AppendIterator + public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod) : array { - if (is_string($paths)) { - $paths = [$paths]; - } - $paths = $this->getPathsAfterResolvingWildcards($paths); - $exclude = $this->getPathsAfterResolvingWildcards($exclude); - if (is_string($prefixes)) { - if ($prefixes !== '') { - $prefixes = [$prefixes]; - } else { - $prefixes = []; + $parameters = []; + foreach ($functionOrMethod->getParameters() as $parameter) { + $name = $parameter->getName(); + assert($name !== ''); + if (!$parameter->hasType()) { + $parameters[] = new Parameter($name, new UnknownType()); + continue; } - } - if (is_string($suffixes)) { - if ($suffixes !== '') { - $suffixes = [$suffixes]; - } else { - $suffixes = []; + $type = $parameter->getType(); + if ($type instanceof ReflectionNamedType) { + $parameters[] = new Parameter($name, $this->mapNamedType($type, $functionOrMethod)); + continue; } - } - $iterator = new AppendIterator(); - foreach ($paths as $path) { - if (is_dir($path)) { - $iterator->append(new Iterator($path, new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::SKIP_DOTS)), $suffixes, $prefixes, $exclude)); + if ($type instanceof ReflectionUnionType) { + $parameters[] = new Parameter($name, $this->mapUnionType($type, $functionOrMethod)); + continue; + } + if ($type instanceof ReflectionIntersectionType) { + $parameters[] = new Parameter($name, $this->mapIntersectionType($type, $functionOrMethod)); } } - return $iterator; + return $parameters; } - protected function getPathsAfterResolvingWildcards(array $paths) : array + public function fromReturnType(ReflectionFunctionAbstract $functionOrMethod) : Type { - $_paths = []; - foreach ($paths as $path) { - if ($locals = glob($path, \GLOB_ONLYDIR)) { - $_paths = array_merge($_paths, array_map('\\realpath', $locals)); - } else { - $_paths[] = realpath($path); + if (!$this->hasReturnType($functionOrMethod)) { + return new UnknownType(); + } + $returnType = $this->returnType($functionOrMethod); + assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType || $returnType instanceof ReflectionIntersectionType); + if ($returnType instanceof ReflectionNamedType) { + return $this->mapNamedType($returnType, $functionOrMethod); + } + if ($returnType instanceof ReflectionUnionType) { + return $this->mapUnionType($returnType, $functionOrMethod); + } + if ($returnType instanceof ReflectionIntersectionType) { + return $this->mapIntersectionType($returnType, $functionOrMethod); + } + } + private function mapNamedType(ReflectionNamedType $type, ReflectionFunctionAbstract $functionOrMethod) : Type + { + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'self') { + return ObjectType::fromName($functionOrMethod->getDeclaringClass()->getName(), $type->allowsNull()); + } + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'static') { + return new StaticType(TypeName::fromReflection($functionOrMethod->getDeclaringClass()), $type->allowsNull()); + } + if ($type->getName() === 'mixed') { + return new MixedType(); + } + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'parent') { + return ObjectType::fromName($functionOrMethod->getDeclaringClass()->getParentClass()->getName(), $type->allowsNull()); + } + return Type::fromName($type->getName(), $type->allowsNull()); + } + private function mapUnionType(ReflectionUnionType $type, ReflectionFunctionAbstract $functionOrMethod) : Type + { + $types = []; + foreach ($type->getTypes() as $_type) { + assert($_type instanceof ReflectionNamedType || $_type instanceof ReflectionIntersectionType); + if ($_type instanceof ReflectionNamedType) { + $types[] = $this->mapNamedType($_type, $functionOrMethod); + continue; } + $types[] = $this->mapIntersectionType($_type, $functionOrMethod); + } + return new UnionType(...$types); + } + private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunctionAbstract $functionOrMethod) : Type + { + $types = []; + foreach ($type->getTypes() as $_type) { + assert($_type instanceof ReflectionNamedType); + $types[] = $this->mapNamedType($_type, $functionOrMethod); + } + return new IntersectionType(...$types); + } + private function hasReturnType(ReflectionFunctionAbstract $functionOrMethod) : bool + { + if ($functionOrMethod->hasReturnType()) { + return \true; + } + if (!\method_exists($functionOrMethod, 'hasTentativeReturnType')) { + return \false; + } + return $functionOrMethod->hasTentativeReturnType(); + } + private function returnType(ReflectionFunctionAbstract $functionOrMethod) : ?ReflectionType + { + if ($functionOrMethod->hasReturnType()) { + return $functionOrMethod->getReturnType(); + } + if (!\method_exists($functionOrMethod, 'getTentativeReturnType')) { + return null; } - return array_filter($_paths); + return $functionOrMethod->getTentativeReturnType(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SebastianBergmann\FileIterator; +namespace PHPUnit\SebastianBergmann\Type; -use function array_filter; -use function array_map; -use function preg_match; -use function realpath; -use function str_replace; -use function strlen; -use function strpos; +use function array_pop; +use function explode; +use function implode; use function substr; -use FilterIterator; -class Iterator extends FilterIterator +use ReflectionClass; +final class TypeName { - public const PREFIX = 0; - public const SUFFIX = 1; - /** - * @var string - */ - private $basePath; /** - * @var array - */ - private $suffixes = []; - /** - * @var array + * @var ?string */ - private $prefixes = []; + private $namespaceName; /** - * @var array + * @var string */ - private $exclude = []; - public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = [], array $exclude = []) + private $simpleName; + public static function fromQualifiedName(string $fullClassName) : self { - $this->basePath = realpath($basePath); - $this->prefixes = $prefixes; - $this->suffixes = $suffixes; - $this->exclude = array_filter(array_map('realpath', $exclude)); - parent::__construct($iterator); + if ($fullClassName[0] === '\\') { + $fullClassName = substr($fullClassName, 1); + } + $classNameParts = explode('\\', $fullClassName); + $simpleName = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + return new self($namespaceName, $simpleName); } - public function accept() : bool + public static function fromReflection(ReflectionClass $type) : self { - $current = $this->getInnerIterator()->current(); - $filename = $current->getFilename(); - $realPath = $current->getRealPath(); - if ($realPath === \false) { - return \false; - } - return $this->acceptPath($realPath) && $this->acceptPrefix($filename) && $this->acceptSuffix($filename); + return new self($type->getNamespaceName(), $type->getShortName()); } - private function acceptPath(string $path) : bool + public function __construct(?string $namespaceName, string $simpleName) { - // Filter files in hidden directories by checking path that is relative to the base path. - if (preg_match('=/\\.[^/]*/=', str_replace($this->basePath, '', $path))) { - return \false; - } - foreach ($this->exclude as $exclude) { - if (strpos($path, $exclude) === 0) { - return \false; - } + if ($namespaceName === '') { + $namespaceName = null; } - return \true; + $this->namespaceName = $namespaceName; + $this->simpleName = $simpleName; } - private function acceptPrefix(string $filename) : bool + public function namespaceName() : ?string { - return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); + return $this->namespaceName; } - private function acceptSuffix(string $filename) : bool + public function simpleName() : string { - return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); + return $this->simpleName; } - private function acceptSubString(string $filename, array $subStrings, int $type) : bool + public function qualifiedName() : string { - if (empty($subStrings)) { - return \true; - } - $matched = \false; - foreach ($subStrings as $string) { - if ($type === self::PREFIX && strpos($filename, $string) === 0 || $type === self::SUFFIX && substr($filename, -1 * strlen($string)) === $string) { - $matched = \true; - break; - } - } - return $matched; + return $this->namespaceName === null ? $this->simpleName : $this->namespaceName . '\\' . $this->simpleName; + } + public function isNamespaced() : bool + { + return $this->namespaceName !== null; } } -php-file-iterator - -Copyright (c) 2009-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -/** - * Basic prophecies revealer. - * - * @author Konstantin Kudryashov - */ -class Revealer implements \Prophecy\Prophecy\RevealerInterface +use Throwable; +interface Exception extends Throwable { - /** - * Unwraps value(s). - * - * @param mixed $value - * - * @return mixed - */ - public function reveal($value) - { - if (\is_array($value)) { - return \array_map(array($this, __FUNCTION__), $value); - } - if (!\is_object($value)) { - return $value; - } - if ($value instanceof \Prophecy\Prophecy\ProphecyInterface) { - $value = $value->reveal(); - } - return $value; - } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -use Prophecy\Argument; -use Prophecy\Prophet; -use Prophecy\Promise; -use Prophecy\Prediction; -use Prophecy\Exception\Doubler\MethodNotFoundException; -use Prophecy\Exception\InvalidArgumentException; -use Prophecy\Exception\Prophecy\MethodProphecyException; -use ReflectionNamedType; -use ReflectionType; -use ReflectionUnionType; -/** - * Method prophecy. +final class RuntimeException extends \RuntimeException implements Exception +{ +} + + * (c) Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -class MethodProphecy +namespace PHPUnit\SebastianBergmann\Type; + +use function assert; +use function class_exists; +use function count; +use function explode; +use function function_exists; +use function is_array; +use function is_object; +use function is_string; +use Closure; +use ReflectionClass; +use ReflectionException; +use ReflectionObject; +final class CallableType extends Type { - private $objectProphecy; - private $methodName; - private $argumentsWildcard; - private $promise; - private $prediction; - private $checkedPredictions = array(); - private $bound = \false; - private $voidReturnType = \false; /** - * Initializes method prophecy. - * - * @param ObjectProphecy $objectProphecy - * @param string $methodName - * @param null|Argument\ArgumentsWildcard|array $arguments - * - * @throws \Prophecy\Exception\Doubler\MethodNotFoundException If method not found + * @var bool */ - public function __construct(\Prophecy\Prophecy\ObjectProphecy $objectProphecy, $methodName, $arguments = null) + private $allowsNull; + public function __construct(bool $nullable) { - $double = $objectProphecy->reveal(); - if (!\method_exists($double, $methodName)) { - throw new MethodNotFoundException(\sprintf('Method `%s::%s()` is not defined.', \get_class($double), $methodName), \get_class($double), $methodName, $arguments); + $this->allowsNull = $nullable; + } + /** + * @throws RuntimeException + */ + public function isAssignable(Type $other) : bool + { + if ($this->allowsNull && $other instanceof NullType) { + return \true; } - $this->objectProphecy = $objectProphecy; - $this->methodName = $methodName; - $reflectedMethod = new \ReflectionMethod($double, $methodName); - if ($reflectedMethod->isFinal()) { - throw new MethodProphecyException(\sprintf("Can not add prophecy for a method `%s::%s()`\n" . "as it is a final method.", \get_class($double), $methodName), $this); + if ($other instanceof self) { + return \true; } - if (null !== $arguments) { - $this->withArguments($arguments); + if ($other instanceof ObjectType) { + if ($this->isClosure($other)) { + return \true; + } + if ($this->hasInvokeMethod($other)) { + return \true; + } } - $hasTentativeReturnType = \method_exists($reflectedMethod, 'hasTentativeReturnType') && $reflectedMethod->hasTentativeReturnType(); - if (\true === $reflectedMethod->hasReturnType() || $hasTentativeReturnType) { - if ($hasTentativeReturnType) { - $reflectionType = $reflectedMethod->getTentativeReturnType(); - } else { - $reflectionType = $reflectedMethod->getReturnType(); + if ($other instanceof SimpleType) { + if ($this->isFunction($other)) { + return \true; } - if ($reflectionType instanceof ReflectionNamedType) { - $types = [$reflectionType]; - } elseif ($reflectionType instanceof ReflectionUnionType) { - $types = $reflectionType->getTypes(); + if ($this->isClassCallback($other)) { + return \true; } - $types = \array_map(function (ReflectionType $type) { - return $type->getName(); - }, $types); - \usort($types, static function (string $type1, string $type2) { - // null is lowest priority - if ($type2 == 'null') { - return -1; - } elseif ($type1 == 'null') { - return 1; - } - // objects are higher priority than scalars - $isObject = static function ($type) { - return \class_exists($type) || \interface_exists($type); - }; - if ($isObject($type1) && !$isObject($type2)) { - return -1; - } elseif (!$isObject($type1) && $isObject($type2)) { - return 1; - } - // don't sort both-scalars or both-objects - return 0; - }); - $defaultType = $types[0]; - if ('void' === $defaultType) { - $this->voidReturnType = \true; + if ($this->isObjectCallback($other)) { + return \true; } - $this->will(function () use($defaultType) { - switch ($defaultType) { - case 'void': - return; - case 'string': - return ''; - case 'float': - return 0.0; - case 'int': - return 0; - case 'bool': - return \false; - case 'array': - return array(); - case 'callable': - case 'Closure': - return function () { - }; - case 'Traversable': - case 'Generator': - return (function () { - yield; - })(); - default: - $prophet = new Prophet(); - return $prophet->prophesize($defaultType)->reveal(); - } - }); } + return \false; + } + public function name() : string + { + return 'callable'; + } + public function allowsNull() : bool + { + return $this->allowsNull; + } + /** + * @psalm-assert-if-true CallableType $this + */ + public function isCallable() : bool + { + return \true; + } + private function isClosure(ObjectType $type) : bool + { + return !$type->className()->isNamespaced() && $type->className()->simpleName() === Closure::class; + } + /** + * @throws RuntimeException + */ + private function hasInvokeMethod(ObjectType $type) : bool + { + $className = $type->className()->qualifiedName(); + assert(class_exists($className)); + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + // @codeCoverageIgnoreEnd + } + if ($class->hasMethod('__invoke')) { + return \true; + } + return \false; + } + private function isFunction(SimpleType $type) : bool + { + if (!is_string($type->value())) { + return \false; + } + return function_exists($type->value()); + } + private function isObjectCallback(SimpleType $type) : bool + { + if (!is_array($type->value())) { + return \false; + } + if (count($type->value()) !== 2) { + return \false; + } + if (!is_object($type->value()[0]) || !is_string($type->value()[1])) { + return \false; + } + [$object, $methodName] = $type->value(); + return (new ReflectionObject($object))->hasMethod($methodName); + } + private function isClassCallback(SimpleType $type) : bool + { + if (!is_string($type->value()) && !is_array($type->value())) { + return \false; + } + if (is_string($type->value())) { + if (\strpos($type->value(), '::') === \false) { + return \false; + } + [$className, $methodName] = explode('::', $type->value()); + } + if (is_array($type->value())) { + if (count($type->value()) !== 2) { + return \false; + } + if (!is_string($type->value()[0]) || !is_string($type->value()[1])) { + return \false; + } + [$className, $methodName] = $type->value(); + } + assert(isset($className) && is_string($className) && class_exists($className)); + assert(isset($methodName) && is_string($methodName)); + try { + $class = new ReflectionClass($className); + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + return $method->isPublic() && $method->isStatic(); + } + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + // @codeCoverageIgnoreEnd + } + return \false; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +final class FalseType extends Type +{ + public function isAssignable(Type $other) : bool + { + if ($other instanceof self) { + return \true; + } + return $other instanceof SimpleType && $other->name() === 'bool' && $other->value() === \false; } - /** - * Sets argument wildcard. - * - * @param array|Argument\ArgumentsWildcard $arguments - * - * @return $this - * - * @throws \Prophecy\Exception\InvalidArgumentException - */ - public function withArguments($arguments) + public function name() : string { - if (\is_array($arguments)) { - $arguments = new Argument\ArgumentsWildcard($arguments); - } - if (!$arguments instanceof Argument\ArgumentsWildcard) { - throw new InvalidArgumentException(\sprintf("Either an array or an instance of ArgumentsWildcard expected as\n" . 'a `MethodProphecy::withArguments()` argument, but got %s.', \gettype($arguments))); - } - $this->argumentsWildcard = $arguments; - return $this; + return 'false'; } - /** - * Sets custom promise to the prophecy. - * - * @param callable|Promise\PromiseInterface $promise - * - * @return $this - * - * @throws \Prophecy\Exception\InvalidArgumentException - */ - public function will($promise) + public function allowsNull() : bool { - if (\is_callable($promise)) { - $promise = new Promise\CallbackPromise($promise); - } - if (!$promise instanceof Promise\PromiseInterface) { - throw new InvalidArgumentException(\sprintf('Expected callable or instance of PromiseInterface, but got %s.', \gettype($promise))); - } - $this->bindToObjectProphecy(); - $this->promise = $promise; - return $this; + return \false; } /** - * Sets return promise to the prophecy. - * - * @see \Prophecy\Promise\ReturnPromise - * - * @return $this + * @psalm-assert-if-true FalseType $this */ - public function willReturn() + public function isFalse() : bool { - if ($this->voidReturnType) { - throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type, and so cannot return anything", $this); - } - return $this->will(new Promise\ReturnPromise(\func_get_args())); + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +final class GenericObjectType extends Type +{ /** - * @param array $items - * @param mixed $return - * - * @return $this - * - * @throws \Prophecy\Exception\InvalidArgumentException + * @var bool */ - public function willYield($items, $return = null) + private $allowsNull; + public function __construct(bool $nullable) { - if ($this->voidReturnType) { - throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type, and so cannot yield anything", $this); + $this->allowsNull = $nullable; + } + public function isAssignable(Type $other) : bool + { + if ($this->allowsNull && $other instanceof NullType) { + return \true; } - if (!\is_array($items)) { - throw new InvalidArgumentException(\sprintf('Expected array, but got %s.', \gettype($items))); + if (!$other instanceof ObjectType) { + return \false; } - $generator = function () use($items, $return) { - yield from $items; - return $return; - }; - return $this->will($generator); + return \true; } - /** - * Sets return argument promise to the prophecy. - * - * @param int $index The zero-indexed number of the argument to return - * - * @see \Prophecy\Promise\ReturnArgumentPromise - * - * @return $this - */ - public function willReturnArgument($index = 0) + public function name() : string { - if ($this->voidReturnType) { - throw new MethodProphecyException("The method \"{$this->methodName}\" has a void return type", $this); - } - return $this->will(new Promise\ReturnArgumentPromise($index)); + return 'object'; } - /** - * Sets throw promise to the prophecy. - * - * @see \Prophecy\Promise\ThrowPromise - * - * @param string|\Exception $exception Exception class or instance - * - * @return $this - */ - public function willThrow($exception) + public function allowsNull() : bool { - return $this->will(new Promise\ThrowPromise($exception)); + return $this->allowsNull; } /** - * Sets custom prediction to the prophecy. - * - * @param callable|Prediction\PredictionInterface $prediction - * - * @return $this - * - * @throws \Prophecy\Exception\InvalidArgumentException + * @psalm-assert-if-true GenericObjectType $this */ - public function should($prediction) + public function isGenericObject() : bool { - if (\is_callable($prediction)) { - $prediction = new Prediction\CallbackPrediction($prediction); - } - if (!$prediction instanceof Prediction\PredictionInterface) { - throw new InvalidArgumentException(\sprintf('Expected callable or instance of PredictionInterface, but got %s.', \gettype($prediction))); - } - $this->bindToObjectProphecy(); - $this->prediction = $prediction; - return $this; + return \true; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +use function array_unique; +use function assert; +use function count; +use function implode; +use function sort; +final class IntersectionType extends Type +{ /** - * Sets call prediction to the prophecy. - * - * @see \Prophecy\Prediction\CallPrediction - * - * @return $this + * @psalm-var non-empty-list */ - public function shouldBeCalled() - { - return $this->should(new Prediction\CallPrediction()); - } + private $types; /** - * Sets no calls prediction to the prophecy. - * - * @see \Prophecy\Prediction\NoCallsPrediction - * - * @return $this + * @throws RuntimeException */ - public function shouldNotBeCalled() + public function __construct(Type ...$types) { - return $this->should(new Prediction\NoCallsPrediction()); + $this->ensureMinimumOfTwoTypes(...$types); + $this->ensureOnlyValidTypes(...$types); + $this->ensureNoDuplicateTypes(...$types); + $this->types = $types; } - /** - * Sets call times prediction to the prophecy. - * - * @see \Prophecy\Prediction\CallTimesPrediction - * - * @param $count - * - * @return $this - */ - public function shouldBeCalledTimes($count) + public function isAssignable(Type $other) : bool { - return $this->should(new Prediction\CallTimesPrediction($count)); + return $other->isObject(); } - /** - * Sets call times prediction to the prophecy. - * - * @see \Prophecy\Prediction\CallTimesPrediction - * - * @return $this - */ - public function shouldBeCalledOnce() + public function asString() : string { - return $this->shouldBeCalledTimes(1); + return $this->name(); } - /** - * Checks provided prediction immediately. - * - * @param callable|Prediction\PredictionInterface $prediction - * - * @return $this - * - * @throws \Prophecy\Exception\InvalidArgumentException - */ - public function shouldHave($prediction) + public function name() : string { - if (\is_callable($prediction)) { - $prediction = new Prediction\CallbackPrediction($prediction); - } - if (!$prediction instanceof Prediction\PredictionInterface) { - throw new InvalidArgumentException(\sprintf('Expected callable or instance of PredictionInterface, but got %s.', \gettype($prediction))); - } - if (null === $this->promise && !$this->voidReturnType) { - $this->willReturn(); - } - $calls = $this->getObjectProphecy()->findProphecyMethodCalls($this->getMethodName(), $this->getArgumentsWildcard()); - try { - $prediction->check($calls, $this->getObjectProphecy(), $this); - $this->checkedPredictions[] = $prediction; - } catch (\Exception $e) { - $this->checkedPredictions[] = $prediction; - throw $e; + $types = []; + foreach ($this->types as $type) { + $types[] = $type->name(); } - return $this; + sort($types); + return implode('&', $types); } - /** - * Checks call prediction. - * - * @see \Prophecy\Prediction\CallPrediction - * - * @return $this - */ - public function shouldHaveBeenCalled() + public function allowsNull() : bool { - return $this->shouldHave(new Prediction\CallPrediction()); + return \false; } /** - * Checks no calls prediction. - * - * @see \Prophecy\Prediction\NoCallsPrediction - * - * @return $this + * @psalm-assert-if-true IntersectionType $this */ - public function shouldNotHaveBeenCalled() + public function isIntersection() : bool { - return $this->shouldHave(new Prediction\NoCallsPrediction()); + return \true; } /** - * Checks no calls prediction. - * - * @see \Prophecy\Prediction\NoCallsPrediction - * @deprecated - * - * @return $this + * @psalm-return non-empty-list */ - public function shouldNotBeenCalled() + public function types() : array { - return $this->shouldNotHaveBeenCalled(); + return $this->types; } /** - * Checks call times prediction. - * - * @see \Prophecy\Prediction\CallTimesPrediction - * - * @param int $count - * - * @return $this + * @throws RuntimeException */ - public function shouldHaveBeenCalledTimes($count) + private function ensureMinimumOfTwoTypes(Type ...$types) : void { - return $this->shouldHave(new Prediction\CallTimesPrediction($count)); + if (count($types) < 2) { + throw new RuntimeException('An intersection type must be composed of at least two types'); + } } /** - * Checks call times prediction. - * - * @see \Prophecy\Prediction\CallTimesPrediction - * - * @return $this + * @throws RuntimeException */ - public function shouldHaveBeenCalledOnce() + private function ensureOnlyValidTypes(Type ...$types) : void { - return $this->shouldHaveBeenCalledTimes(1); + foreach ($types as $type) { + if (!$type->isObject()) { + throw new RuntimeException('An intersection type can only be composed of interfaces and classes'); + } + } } /** - * Checks currently registered [with should(...)] prediction. + * @throws RuntimeException */ - public function checkPrediction() + private function ensureNoDuplicateTypes(Type ...$types) : void { - if (null === $this->prediction) { - return; + $names = []; + foreach ($types as $type) { + assert($type instanceof ObjectType); + $names[] = $type->className()->qualifiedName(); + } + if (count(array_unique($names)) < count($names)) { + throw new RuntimeException('An intersection type must not contain duplicate types'); } - $this->shouldHave($this->prediction); } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +use function assert; +use function class_exists; +use function is_iterable; +use ReflectionClass; +use ReflectionException; +final class IterableType extends Type +{ /** - * Returns currently registered promise. - * - * @return null|Promise\PromiseInterface + * @var bool */ - public function getPromise() + private $allowsNull; + public function __construct(bool $nullable) { - return $this->promise; + $this->allowsNull = $nullable; } /** - * Returns currently registered prediction. - * - * @return null|Prediction\PredictionInterface + * @throws RuntimeException */ - public function getPrediction() + public function isAssignable(Type $other) : bool { - return $this->prediction; + if ($this->allowsNull && $other instanceof NullType) { + return \true; + } + if ($other instanceof self) { + return \true; + } + if ($other instanceof SimpleType) { + return is_iterable($other->value()); + } + if ($other instanceof ObjectType) { + $className = $other->className()->qualifiedName(); + assert(class_exists($className)); + try { + return (new ReflectionClass($className))->isIterable(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException($e->getMessage(), (int) $e->getCode(), $e); + // @codeCoverageIgnoreEnd + } + } + return \false; } - /** - * Returns predictions that were checked on this object. - * - * @return Prediction\PredictionInterface[] - */ - public function getCheckedPredictions() + public function name() : string { - return $this->checkedPredictions; + return 'iterable'; } - /** - * Returns object prophecy this method prophecy is tied to. - * - * @return ObjectProphecy - */ - public function getObjectProphecy() + public function allowsNull() : bool { - return $this->objectProphecy; + return $this->allowsNull; } /** - * Returns method name. - * - * @return string + * @psalm-assert-if-true IterableType $this */ - public function getMethodName() + public function isIterable() : bool { - return $this->methodName; + return \true; } - /** - * Returns arguments wildcard. - * - * @return Argument\ArgumentsWildcard - */ - public function getArgumentsWildcard() +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +final class MixedType extends Type +{ + public function isAssignable(Type $other) : bool { - return $this->argumentsWildcard; + return !$other instanceof VoidType; } - /** - * @return bool - */ - public function hasReturnVoid() + public function asString() : string { - return $this->voidReturnType; + return 'mixed'; } - private function bindToObjectProphecy() + public function name() : string { - if ($this->bound) { - return; - } - $this->getObjectProphecy()->addMethodProphecy($this); - $this->bound = \true; + return 'mixed'; + } + public function allowsNull() : bool + { + return \true; + } + /** + * @psalm-assert-if-true MixedType $this + */ + public function isMixed() : bool + { + return \true; } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -/** - * Prophecies revealer interface. - * - * @author Konstantin Kudryashov - */ -interface RevealerInterface +final class NeverType extends Type { + public function isAssignable(Type $other) : bool + { + return $other instanceof self; + } + public function name() : string + { + return 'never'; + } + public function allowsNull() : bool + { + return \false; + } /** - * Unwraps value(s). - * - * @param mixed $value - * - * @return mixed + * @psalm-assert-if-true NeverType $this */ - public function reveal($value); + public function isNever() : bool + { + return \true; + } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -/** - * Controllable doubles interface. - * - * @author Konstantin Kudryashov - */ -interface ProphecySubjectInterface +final class NullType extends Type { + public function isAssignable(Type $other) : bool + { + return !$other instanceof VoidType; + } + public function name() : string + { + return 'null'; + } + public function asString() : string + { + return 'null'; + } + public function allowsNull() : bool + { + return \true; + } /** - * Sets subject prophecy. - * - * @param ProphecyInterface $prophecy - */ - public function setProphecy(\Prophecy\Prophecy\ProphecyInterface $prophecy); - /** - * Returns subject prophecy. - * - * @return ProphecyInterface + * @psalm-assert-if-true NullType $this */ - public function getProphecy(); + public function isNull() : bool + { + return \true; + } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -/** - * Core Prophecy interface. - * - * @author Konstantin Kudryashov - */ -interface ProphecyInterface +use function is_subclass_of; +use function strcasecmp; +final class ObjectType extends Type { /** - * Reveals prophecy object (double) . - * - * @return object + * @var TypeName */ - public function reveal(); + private $className; + /** + * @var bool + */ + private $allowsNull; + public function __construct(TypeName $className, bool $allowsNull) + { + $this->className = $className; + $this->allowsNull = $allowsNull; + } + public function isAssignable(Type $other) : bool + { + if ($this->allowsNull && $other instanceof NullType) { + return \true; + } + if ($other instanceof self) { + if (0 === strcasecmp($this->className->qualifiedName(), $other->className->qualifiedName())) { + return \true; + } + if (is_subclass_of($other->className->qualifiedName(), $this->className->qualifiedName(), \true)) { + return \true; + } + } + return \false; + } + public function name() : string + { + return $this->className->qualifiedName(); + } + public function allowsNull() : bool + { + return $this->allowsNull; + } + public function className() : TypeName + { + return $this->className; + } + /** + * @psalm-assert-if-true ObjectType $this + */ + public function isObject() : bool + { + return \true; + } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use Prophecy\Comparator\Factory as ComparatorFactory; -use Prophecy\Call\Call; -use Prophecy\Doubler\LazyDouble; -use Prophecy\Argument\ArgumentsWildcard; -use Prophecy\Call\CallCenter; -use Prophecy\Exception\Prophecy\ObjectProphecyException; -use Prophecy\Exception\Prophecy\MethodProphecyException; -use Prophecy\Exception\Prediction\AggregateException; -use Prophecy\Exception\Prediction\PredictionException; -/** - * Object prophecy. - * - * @author Konstantin Kudryashov - */ -class ObjectProphecy implements \Prophecy\Prophecy\ProphecyInterface +use function strtolower; +final class SimpleType extends Type { - private $lazyDouble; - private $callCenter; - private $revealer; - private $comparatorFactory; /** - * @var MethodProphecy[][] + * @var string */ - private $methodProphecies = array(); + private $name; /** - * Initializes object prophecy. - * - * @param LazyDouble $lazyDouble - * @param CallCenter $callCenter - * @param RevealerInterface $revealer - * @param ComparatorFactory $comparatorFactory + * @var bool */ - public function __construct(LazyDouble $lazyDouble, CallCenter $callCenter = null, \Prophecy\Prophecy\RevealerInterface $revealer = null, ComparatorFactory $comparatorFactory = null) - { - $this->lazyDouble = $lazyDouble; - $this->callCenter = $callCenter ?: new CallCenter(); - $this->revealer = $revealer ?: new \Prophecy\Prophecy\Revealer(); - $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); - } + private $allowsNull; /** - * Forces double to extend specific class. - * - * @param string $class - * - * @return $this + * @var mixed */ - public function willExtend($class) + private $value; + public function __construct(string $name, bool $nullable, $value = null) { - $this->lazyDouble->setParentClass($class); - return $this; + $this->name = $this->normalize($name); + $this->allowsNull = $nullable; + $this->value = $value; } - /** - * Forces double to implement specific interface. - * - * @param string $interface - * - * @return $this - */ - public function willImplement($interface) + public function isAssignable(Type $other) : bool { - $this->lazyDouble->addInterface($interface); - return $this; + if ($this->allowsNull && $other instanceof NullType) { + return \true; + } + if ($this->name === 'bool' && $other->name() === 'true') { + return \true; + } + if ($this->name === 'bool' && $other->name() === 'false') { + return \true; + } + if ($other instanceof self) { + return $this->name === $other->name; + } + return \false; } - /** - * Sets constructor arguments. - * - * @param array $arguments - * - * @return $this - */ - public function willBeConstructedWith(array $arguments = null) + public function name() : string { - $this->lazyDouble->setArguments($arguments); - return $this; + return $this->name; } - /** - * Reveals double. - * - * @return object - * - * @throws \Prophecy\Exception\Prophecy\ObjectProphecyException If double doesn't implement needed interface - */ - public function reveal() + public function allowsNull() : bool { - $double = $this->lazyDouble->getInstance(); - if (null === $double || !$double instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { - throw new ObjectProphecyException("Generated double must implement ProphecySubjectInterface, but it does not.\n" . 'It seems you have wrongly configured doubler without required ClassPatch.', $this); - } - $double->setProphecy($this); - return $double; + return $this->allowsNull; } - /** - * Adds method prophecy to object prophecy. - * - * @param MethodProphecy $methodProphecy - * - * @throws \Prophecy\Exception\Prophecy\MethodProphecyException If method prophecy doesn't - * have arguments wildcard - */ - public function addMethodProphecy(\Prophecy\Prophecy\MethodProphecy $methodProphecy) + public function value() { - $argumentsWildcard = $methodProphecy->getArgumentsWildcard(); - if (null === $argumentsWildcard) { - throw new MethodProphecyException(\sprintf("Can not add prophecy for a method `%s::%s()`\n" . "as you did not specify arguments wildcard for it.", \get_class($this->reveal()), $methodProphecy->getMethodName()), $methodProphecy); - } - $methodName = \strtolower($methodProphecy->getMethodName()); - if (!isset($this->methodProphecies[$methodName])) { - $this->methodProphecies[$methodName] = array(); - } - $this->methodProphecies[$methodName][] = $methodProphecy; + return $this->value; } /** - * Returns either all or related to single method prophecies. - * - * @param null|string $methodName - * - * @return MethodProphecy[] + * @psalm-assert-if-true SimpleType $this */ - public function getMethodProphecies($methodName = null) + public function isSimple() : bool { - if (null === $methodName) { - return $this->methodProphecies; - } - $methodName = \strtolower($methodName); - if (!isset($this->methodProphecies[$methodName])) { - return array(); + return \true; + } + private function normalize(string $name) : string + { + $name = strtolower($name); + switch ($name) { + case 'boolean': + return 'bool'; + case 'real': + case 'double': + return 'float'; + case 'integer': + return 'int'; + case '[]': + return 'array'; + default: + return $name; } - return $this->methodProphecies[$methodName]; } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +final class StaticType extends Type +{ /** - * Makes specific method call. - * - * @param string $methodName - * @param array $arguments - * - * @return mixed + * @var TypeName */ - public function makeProphecyMethodCall($methodName, array $arguments) - { - $arguments = $this->revealer->reveal($arguments); - $return = $this->callCenter->makeCall($this, $methodName, $arguments); - return $this->revealer->reveal($return); - } + private $className; /** - * Finds calls by method name & arguments wildcard. - * - * @param string $methodName - * @param ArgumentsWildcard $wildcard - * - * @return Call[] + * @var bool */ - public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard) + private $allowsNull; + public function __construct(TypeName $className, bool $allowsNull) { - return $this->callCenter->findCalls($methodName, $wildcard); + $this->className = $className; + $this->allowsNull = $allowsNull; } - /** - * Checks that registered method predictions do not fail. - * - * @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail - * @throws \Prophecy\Exception\Call\UnexpectedCallException - */ - public function checkProphecyMethodsPredictions() + public function isAssignable(Type $other) : bool { - $exception = new AggregateException(\sprintf("%s:\n", \get_class($this->reveal()))); - $exception->setObjectProphecy($this); - $this->callCenter->checkUnexpectedCalls(); - foreach ($this->methodProphecies as $prophecies) { - foreach ($prophecies as $prophecy) { - try { - $prophecy->checkPrediction(); - } catch (PredictionException $e) { - $exception->append($e); - } - } + if ($this->allowsNull && $other instanceof NullType) { + return \true; } - if (\count($exception->getExceptions())) { - throw $exception; + if (!$other instanceof ObjectType) { + return \false; } + if (0 === \strcasecmp($this->className->qualifiedName(), $other->className()->qualifiedName())) { + return \true; + } + if (\is_subclass_of($other->className()->qualifiedName(), $this->className->qualifiedName(), \true)) { + return \true; + } + return \false; + } + public function name() : string + { + return 'static'; + } + public function allowsNull() : bool + { + return $this->allowsNull; } /** - * Creates new method prophecy using specified method name and arguments. - * - * @param string $methodName - * @param array $arguments - * - * @return MethodProphecy + * @psalm-assert-if-true StaticType $this */ - public function __call($methodName, array $arguments) + public function isStatic() : bool { - $arguments = new ArgumentsWildcard($this->revealer->reveal($arguments)); - foreach ($this->getMethodProphecies($methodName) as $prophecy) { - $argumentsWildcard = $prophecy->getArgumentsWildcard(); - $comparator = $this->comparatorFactory->getComparatorFor($argumentsWildcard, $arguments); - try { - $comparator->assertEquals($argumentsWildcard, $arguments); - return $prophecy; - } catch (ComparisonFailure $failure) { - } + return \true; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann\Type; + +final class TrueType extends Type +{ + public function isAssignable(Type $other) : bool + { + if ($other instanceof self) { + return \true; } - return new \Prophecy\Prophecy\MethodProphecy($this, $methodName, $arguments); + return $other instanceof SimpleType && $other->name() === 'bool' && $other->value() === \true; } - /** - * Tries to get property value from double. - * - * @param string $name - * - * @return mixed - */ - public function __get($name) + public function name() : string { - return $this->reveal()->{$name}; + return 'true'; + } + public function allowsNull() : bool + { + return \false; } /** - * Tries to set property value to double. - * - * @param string $name - * @param mixed $value + * @psalm-assert-if-true TrueType $this */ - public function __set($name, $value) + public function isTrue() : bool { - $this->reveal()->{$name} = $this->revealer->reveal($value); + return \true; } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy; +namespace PHPUnit\SebastianBergmann\Type; -use Prophecy\Argument\Token; -/** - * Argument tokens shortcuts. - * - * @author Konstantin Kudryashov - */ -class Argument +use const PHP_VERSION; +use function get_class; +use function gettype; +use function strtolower; +use function version_compare; +abstract class Type { - /** - * Checks that argument is exact value or object. - * - * @param mixed $value - * - * @return Token\ExactValueToken - */ - public static function exact($value) + public static function fromValue($value, bool $allowsNull) : self { - return new Token\ExactValueToken($value); + if ($allowsNull === \false) { + if ($value === \true) { + return new TrueType(); + } + if ($value === \false) { + return new FalseType(); + } + } + $typeName = gettype($value); + if ($typeName === 'object') { + return new ObjectType(TypeName::fromQualifiedName(get_class($value)), $allowsNull); + } + $type = self::fromName($typeName, $allowsNull); + if ($type instanceof SimpleType) { + $type = new SimpleType($typeName, $allowsNull, $value); + } + return $type; } - /** - * Checks that argument is of specific type or instance of specific class. - * - * @param string $type Type name (`integer`, `string`) or full class name - * - * @return Token\TypeToken - */ - public static function type($type) + public static function fromName(string $typeName, bool $allowsNull) : self { - return new Token\TypeToken($type); + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && strtolower($typeName) === 'never') { + return new NeverType(); + } + switch (strtolower($typeName)) { + case 'callable': + return new CallableType($allowsNull); + case 'true': + return new TrueType(); + case 'false': + return new FalseType(); + case 'iterable': + return new IterableType($allowsNull); + case 'null': + return new NullType(); + case 'object': + return new GenericObjectType($allowsNull); + case 'unknown type': + return new UnknownType(); + case 'void': + return new VoidType(); + case 'array': + case 'bool': + case 'boolean': + case 'double': + case 'float': + case 'int': + case 'integer': + case 'real': + case 'resource': + case 'resource (closed)': + case 'string': + return new SimpleType($typeName, $allowsNull); + default: + return new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull); + } } - /** - * Checks that argument object has specific state. - * - * @param string $methodName - * @param mixed $value - * - * @return Token\ObjectStateToken - */ - public static function which($methodName, $value) + public function asString() : string { - return new Token\ObjectStateToken($methodName, $value); + return ($this->allowsNull() ? '?' : '') . $this->name(); } /** - * Checks that argument matches provided callback. - * - * @param callable $callback - * - * @return Token\CallbackToken + * @psalm-assert-if-true CallableType $this */ - public static function that($callback) + public function isCallable() : bool { - return new Token\CallbackToken($callback); + return \false; } /** - * Matches any single value. - * - * @return Token\AnyValueToken + * @psalm-assert-if-true TrueType $this */ - public static function any() + public function isTrue() : bool { - return new Token\AnyValueToken(); + return \false; } /** - * Matches all values to the rest of the signature. - * - * @return Token\AnyValuesToken + * @psalm-assert-if-true FalseType $this */ - public static function cetera() + public function isFalse() : bool { - return new Token\AnyValuesToken(); + return \false; } /** - * Checks that argument matches all tokens - * - * @param mixed ... a list of tokens - * - * @return Token\LogicalAndToken + * @psalm-assert-if-true GenericObjectType $this */ - public static function allOf() + public function isGenericObject() : bool { - return new Token\LogicalAndToken(\func_get_args()); + return \false; } /** - * Checks that argument array or countable object has exact number of elements. - * - * @param integer $value array elements count - * - * @return Token\ArrayCountToken + * @psalm-assert-if-true IntersectionType $this */ - public static function size($value) + public function isIntersection() : bool { - return new Token\ArrayCountToken($value); + return \false; } /** - * Checks that argument array contains (key, value) pair - * - * @param mixed $key exact value or token - * @param mixed $value exact value or token - * - * @return Token\ArrayEntryToken + * @psalm-assert-if-true IterableType $this */ - public static function withEntry($key, $value) + public function isIterable() : bool { - return new Token\ArrayEntryToken($key, $value); + return \false; } /** - * Checks that arguments array entries all match value - * - * @param mixed $value - * - * @return Token\ArrayEveryEntryToken + * @psalm-assert-if-true MixedType $this */ - public static function withEveryEntry($value) + public function isMixed() : bool { - return new Token\ArrayEveryEntryToken($value); + return \false; } /** - * Checks that argument array contains value - * - * @param mixed $value - * - * @return Token\ArrayEntryToken + * @psalm-assert-if-true NeverType $this */ - public static function containing($value) + public function isNever() : bool { - return new Token\ArrayEntryToken(self::any(), $value); + return \false; } /** - * Checks that argument array has key - * - * @param mixed $key exact value or token - * - * @return Token\ArrayEntryToken + * @psalm-assert-if-true NullType $this */ - public static function withKey($key) + public function isNull() : bool { - return new Token\ArrayEntryToken($key, self::any()); + return \false; } /** - * Checks that argument does not match the value|token. - * - * @param mixed $value either exact value or argument token - * - * @return Token\LogicalNotToken + * @psalm-assert-if-true ObjectType $this */ - public static function not($value) + public function isObject() : bool { - return new Token\LogicalNotToken($value); + return \false; } /** - * @param string $value - * - * @return Token\StringContainsToken + * @psalm-assert-if-true SimpleType $this */ - public static function containingString($value) + public function isSimple() : bool { - return new Token\StringContainsToken($value); + return \false; } /** - * Checks that argument is identical value. - * - * @param mixed $value - * - * @return Token\IdenticalValueToken + * @psalm-assert-if-true StaticType $this */ - public static function is($value) + public function isStatic() : bool { - return new Token\IdenticalValueToken($value); + return \false; } /** - * Check that argument is same value when rounding to the - * given precision. - * - * @param float $value - * @param float $precision - * - * @return Token\ApproximateValueToken + * @psalm-assert-if-true UnionType $this */ - public static function approximate($value, $precision = 0) + public function isUnion() : bool { - return new Token\ApproximateValueToken($value, $precision); + return \false; } /** - * Checks that argument is in array. - * - * @param array $value - * - * @return Token\InArrayToken + * @psalm-assert-if-true UnknownType $this */ - public static function in($value) + public function isUnknown() : bool { - return new Token\InArrayToken($value); + return \false; } /** - * Checks that argument is not in array. - * - * @param array $value - * - * @return Token\NotInArrayToken + * @psalm-assert-if-true VoidType $this */ - public static function notIn($value) + public function isVoid() : bool { - return new Token\NotInArrayToken($value); + return \false; } + public abstract function isAssignable(self $other) : bool; + public abstract function name() : string; + public abstract function allowsNull() : bool; } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Util; +namespace PHPUnit\SebastianBergmann\Type; -use Prophecy\Call\Call; -/** - * String utility. - * - * @author Konstantin Kudryashov - */ -class StringUtil +use function count; +use function implode; +use function sort; +final class UnionType extends Type { - private $verbose; /** - * @param bool $verbose + * @psalm-var non-empty-list */ - public function __construct($verbose = \true) - { - $this->verbose = $verbose; - } + private $types; /** - * Stringifies any provided value. - * - * @param mixed $value - * @param boolean $exportObject - * - * @return string + * @throws RuntimeException */ - public function stringify($value, $exportObject = \true) + public function __construct(Type ...$types) { - if (\is_array($value)) { - if (\range(0, \count($value) - 1) === \array_keys($value)) { - return '[' . \implode(', ', \array_map(array($this, __FUNCTION__), $value)) . ']'; + $this->ensureMinimumOfTwoTypes(...$types); + $this->ensureOnlyValidTypes(...$types); + $this->types = $types; + } + public function isAssignable(Type $other) : bool + { + foreach ($this->types as $type) { + if ($type->isAssignable($other)) { + return \true; } - $stringify = array($this, __FUNCTION__); - return '[' . \implode(', ', \array_map(function ($item, $key) use($stringify) { - return (\is_integer($key) ? $key : '"' . $key . '"') . ' => ' . \call_user_func($stringify, $item); - }, $value, \array_keys($value))) . ']'; } - if (\is_resource($value)) { - return \get_resource_type($value) . ':' . $value; - } - if (\is_object($value)) { - return $exportObject ? \Prophecy\Util\ExportUtil::export($value) : \sprintf('%s:%s', \get_class($value), \spl_object_hash($value)); + return \false; + } + public function asString() : string + { + return $this->name(); + } + public function name() : string + { + $types = []; + foreach ($this->types as $type) { + if ($type->isIntersection()) { + $types[] = '(' . $type->name() . ')'; + continue; + } + $types[] = $type->name(); } - if (\true === $value || \false === $value) { - return $value ? 'true' : 'false'; + sort($types); + return implode('|', $types); + } + public function allowsNull() : bool + { + foreach ($this->types as $type) { + if ($type instanceof NullType) { + return \true; + } } - if (\is_string($value)) { - $str = \sprintf('"%s"', \str_replace("\n", '\\n', $value)); - if (!$this->verbose && 50 <= \strlen($str)) { - return \substr($str, 0, 50) . '"...'; + return \false; + } + /** + * @psalm-assert-if-true UnionType $this + */ + public function isUnion() : bool + { + return \true; + } + public function containsIntersectionTypes() : bool + { + foreach ($this->types as $type) { + if ($type->isIntersection()) { + return \true; } - return $str; } - if (null === $value) { - return 'null'; + return \false; + } + /** + * @psalm-return non-empty-list + */ + public function types() : array + { + return $this->types; + } + /** + * @throws RuntimeException + */ + private function ensureMinimumOfTwoTypes(Type ...$types) : void + { + if (count($types) < 2) { + throw new RuntimeException('A union type must be composed of at least two types'); } - return (string) $value; } /** - * Stringifies provided array of calls. - * - * @param Call[] $calls Array of Call instances - * - * @return string + * @throws RuntimeException */ - public function stringifyCalls(array $calls) + private function ensureOnlyValidTypes(Type ...$types) : void { - $self = $this; - return \implode(\PHP_EOL, \array_map(function (Call $call) use($self) { - return \sprintf(' - %s(%s) @ %s', $call->getMethodName(), \implode(', ', \array_map(array($self, 'stringify'), $call->getArguments())), \str_replace(\GETCWD() . \DIRECTORY_SEPARATOR, '', $call->getCallPlace())); - }, $calls)); + foreach ($types as $type) { + if ($type instanceof UnknownType) { + throw new RuntimeException('A union type must not be composed of an unknown type'); + } + if ($type instanceof VoidType) { + throw new RuntimeException('A union type must not be composed of a void type'); + } + } } } - * Marcello Duarte + * This file is part of sebastian/type. + * + * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -/** - * This class is a modification from sebastianbergmann/exporter - * @see https://github.com/sebastianbergmann/exporter +namespace PHPUnit\SebastianBergmann\Type; + +final class UnknownType extends Type +{ + public function isAssignable(Type $other) : bool + { + return \true; + } + public function name() : string + { + return 'unknown type'; + } + public function asString() : string + { + return ''; + } + public function allowsNull() : bool + { + return \true; + } + /** + * @psalm-assert-if-true UnknownType $this + */ + public function isUnknown() : bool + { + return \true; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ -class ExportUtil +namespace PHPUnit\SebastianBergmann\Type; + +final class VoidType extends Type { + public function isAssignable(Type $other) : bool + { + return $other instanceof self; + } + public function name() : string + { + return 'void'; + } + public function allowsNull() : bool + { + return \false; + } /** - * Exports a value as a string - * - * The output of this method is similar to the output of print_r(), but - * improved in various aspects: - * - * - NULL is rendered as "null" (instead of "") - * - TRUE is rendered as "true" (instead of "1") - * - FALSE is rendered as "false" (instead of "") - * - Strings are always quoted with single quotes - * - Carriage returns and newlines are normalized to \n - * - Recursion and repeated rendering is treated properly - * - * @param mixed $value - * @param int $indentation The indentation level of the 2nd+ line - * @return string + * @psalm-assert-if-true VoidType $this */ - public static function export($value, $indentation = 0) + public function isVoid() : bool { - return self::recursiveExport($value, $indentation); + return \true; } +} +Version + +Copyright (c) 2013-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\SebastianBergmann; + +final class Version +{ /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param mixed $value - * @return array + * @var string */ - public static function toArray($value) + private $path; + /** + * @var string + */ + private $release; + /** + * @var string + */ + private $version; + public function __construct(string $release, string $path) { - if (!\is_object($value)) { - return (array) $value; - } - $array = array(); - foreach ((array) $value as $key => $val) { - // properties are transformed to keys in the following way: - // private $property => "\0Classname\0property" - // protected $property => "\0*\0property" - // public $property => "property" - if (\preg_match('/^\\0.+\\0(.+)$/', $key, $matches)) { - $key = $matches[1]; - } - // See https://github.com/php/php-src/commit/5721132 - if ($key === "\0gcdata") { - continue; - } - $array[$key] = $val; - } - // Some internal classes like SplObjectStorage don't work with the - // above (fast) mechanism nor with reflection in Zend. - // Format the output similarly to print_r() in this case - if ($value instanceof \SplObjectStorage) { - // However, the fast method does work in HHVM, and exposes the - // internal implementation. Hide it again. - if (\property_exists('\\SplObjectStorage', '__storage')) { - unset($array['__storage']); - } elseif (\property_exists('\\SplObjectStorage', 'storage')) { - unset($array['storage']); - } - if (\property_exists('\\SplObjectStorage', '__key')) { - unset($array['__key']); + $this->release = $release; + $this->path = $path; + } + public function getVersion() : string + { + if ($this->version === null) { + if (\substr_count($this->release, '.') + 1 === 3) { + $this->version = $this->release; + } else { + $this->version = $this->release . '-dev'; } - foreach ($value as $key => $val) { - $array[\spl_object_hash($val)] = array('obj' => $val, 'inf' => $value->getInfo()); + $git = $this->getGitInformation($this->path); + if ($git) { + if (\substr_count($this->release, '.') + 1 === 3) { + $this->version = $git; + } else { + $git = \explode('-', $git); + $this->version = $this->release . '-' . \end($git); + } } } - return $array; + return $this->version; } /** - * Recursive implementation of export - * - * @param mixed $value The value to export - * @param int $indentation The indentation level of the 2nd+ line - * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects - * @return string - * @see SebastianBergmann\Exporter\Exporter::export + * @return bool|string */ - protected static function recursiveExport(&$value, $indentation, $processed = null) + private function getGitInformation(string $path) { - if ($value === null) { - return 'null'; - } - if ($value === \true) { - return 'true'; - } - if ($value === \false) { - return 'false'; - } - if (\is_float($value) && \floatval(\intval($value)) === $value) { - return "{$value}.0"; - } - if (\is_resource($value)) { - return \sprintf('resource(%d) of type (%s)', $value, \get_resource_type($value)); - } - if (\is_string($value)) { - // Match for most non printable chars somewhat taking multibyte chars into account - if (\preg_match('/[^\\x09-\\x0d\\x20-\\xff]/', $value)) { - return 'Binary String: 0x' . \bin2hex($value); - } - return "'" . \str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . "'"; + if (!\is_dir($path . \DIRECTORY_SEPARATOR . '.git')) { + return \false; } - $whitespace = \str_repeat(' ', 4 * $indentation); - if (!$processed) { - $processed = new Context(); + $process = \proc_open('git describe --tags', [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $path); + if (!\is_resource($process)) { + return \false; } - if (\is_array($value)) { - if (($key = $processed->contains($value)) !== \false) { - return 'Array &' . $key; - } - $array = $value; - $key = $processed->add($value); - $values = ''; - if (\count($array) > 0) { - foreach ($array as $k => $v) { - $values .= \sprintf('%s %s => %s' . "\n", $whitespace, self::recursiveExport($k, $indentation), self::recursiveExport($value[$k], $indentation + 1, $processed)); - } - $values = "\n" . $values . $whitespace; - } - return \sprintf('Array &%s (%s)', $key, $values); + $result = \trim(\stream_get_contents($pipes[1])); + \fclose($pipes[1]); + \fclose($pipes[2]); + $returnCode = \proc_close($process); + if ($returnCode !== 0) { + return \false; } - if (\is_object($value)) { - $class = \get_class($value); - if ($hash = $processed->contains($value)) { - return \sprintf('%s:%s Object', $class, $hash); - } - $hash = $processed->add($value); - $values = ''; - $array = self::toArray($value); - if (\count($array) > 0) { - foreach ($array as $k => $v) { - $values .= \sprintf('%s %s => %s' . "\n", $whitespace, self::recursiveExport($k, $indentation), self::recursiveExport($v, $indentation + 1, $processed)); - } - $values = "\n" . $values . $whitespace; - } - return \sprintf('%s:%s Object (%s)', $class, $hash, $values); + return $result; + } +} + and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Arne Blankerts nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +ensureValidUri($value); + $this->value = $value; + } + public function asString() : string + { + return $this->value; + } + private function ensureValidUri($value) : void + { + if (\strpos($value, ':') === \false) { + throw new NamespaceUriException(\sprintf("Namespace URI '%s' must contain at least one colon", $value)); } - return \var_export($value, \true); } } - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prophecy; +declare (strict_types=1); +namespace PHPUnit\TheSeer\Tokenizer; -use Prophecy\Exception\Exception; -interface ProphecyException extends Exception +class NamespaceUriException extends Exception { } - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prophecy; +declare (strict_types=1); +namespace PHPUnit\TheSeer\Tokenizer; -use Prophecy\Prophecy\MethodProphecy; -class MethodProphecyException extends \Prophecy\Exception\Prophecy\ObjectProphecyException +class Token { - private $methodProphecy; - public function __construct($message, MethodProphecy $methodProphecy) - { - parent::__construct($message, $methodProphecy->getObjectProphecy()); - $this->methodProphecy = $methodProphecy; - } + /** @var int */ + private $line; + /** @var string */ + private $name; + /** @var string */ + private $value; /** - * @return MethodProphecy + * Token constructor. */ - public function getMethodProphecy() + public function __construct(int $line, string $name, string $value) { - return $this->methodProphecy; + $this->line = $line; + $this->name = $name; + $this->value = $value; + } + public function getLine() : int + { + return $this->line; + } + public function getName() : string + { + return $this->name; + } + public function getValue() : string + { + return $this->value; } } - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prophecy; +declare (strict_types=1); +namespace PHPUnit\TheSeer\Tokenizer; -use Prophecy\Prophecy\ObjectProphecy; -class ObjectProphecyException extends \RuntimeException implements \Prophecy\Exception\Prophecy\ProphecyException +class TokenCollection implements \ArrayAccess, \Iterator, \Countable { - private $objectProphecy; - public function __construct($message, ObjectProphecy $objectProphecy) + /** @var Token[] */ + private $tokens = []; + /** @var int */ + private $pos; + public function addToken(Token $token) : void { - parent::__construct($message); - $this->objectProphecy = $objectProphecy; + $this->tokens[] = $token; + } + public function current() : Token + { + return \current($this->tokens); + } + public function key() : int + { + return \key($this->tokens); + } + public function next() : void + { + \next($this->tokens); + $this->pos++; + } + public function valid() : bool + { + return $this->count() > $this->pos; + } + public function rewind() : void + { + \reset($this->tokens); + $this->pos = 0; + } + public function count() : int + { + return \count($this->tokens); + } + public function offsetExists($offset) : bool + { + return isset($this->tokens[$offset]); } /** - * @return ObjectProphecy + * @throws TokenCollectionException */ - public function getObjectProphecy() + public function offsetGet($offset) : Token { - return $this->objectProphecy; + if (!$this->offsetExists($offset)) { + throw new TokenCollectionException(\sprintf('No Token at offest %s', $offset)); + } + return $this->tokens[$offset]; + } + /** + * @param Token $value + * + * @throws TokenCollectionException + */ + public function offsetSet($offset, $value) : void + { + if (!\is_int($offset)) { + $type = \gettype($offset); + throw new TokenCollectionException(\sprintf('Offset must be of type integer, %s given', $type === 'object' ? \get_class($value) : $type)); + } + if (!$value instanceof Token) { + $type = \gettype($value); + throw new TokenCollectionException(\sprintf('Value must be of type %s, %s given', Token::class, $type === 'object' ? \get_class($value) : $type)); + } + $this->tokens[$offset] = $value; + } + public function offsetUnset($offset) : void + { + unset($this->tokens[$offset]); } } - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prediction; +declare (strict_types=1); +namespace PHPUnit\TheSeer\Tokenizer; -use Prophecy\Prophecy\MethodProphecy; -class UnexpectedCallsCountException extends \Prophecy\Exception\Prediction\UnexpectedCallsException +class TokenCollectionException extends Exception { - private $expectedCount; - public function __construct($message, MethodProphecy $methodProphecy, $count, array $calls) +} + 'T_OPEN_BRACKET', ')' => 'T_CLOSE_BRACKET', '[' => 'T_OPEN_SQUARE', ']' => 'T_CLOSE_SQUARE', '{' => 'T_OPEN_CURLY', '}' => 'T_CLOSE_CURLY', ';' => 'T_SEMICOLON', '.' => 'T_DOT', ',' => 'T_COMMA', '=' => 'T_EQUAL', '<' => 'T_LT', '>' => 'T_GT', '+' => 'T_PLUS', '-' => 'T_MINUS', '*' => 'T_MULT', '/' => 'T_DIV', '?' => 'T_QUESTION_MARK', '!' => 'T_EXCLAMATION_MARK', ':' => 'T_COLON', '"' => 'T_DOUBLE_QUOTES', '@' => 'T_AT', '&' => 'T_AMPERSAND', '%' => 'T_PERCENT', '|' => 'T_PIPE', '$' => 'T_DOLLAR', '^' => 'T_CARET', '~' => 'T_TILDE', '`' => 'T_BACKTICK']; + public function parse(string $source) : TokenCollection { - parent::__construct($message, $methodProphecy, $calls); - $this->expectedCount = \intval($count); + $result = new TokenCollection(); + if ($source === '') { + return $result; + } + $tokens = \token_get_all($source); + $lastToken = new Token($tokens[0][2], 'Placeholder', ''); + foreach ($tokens as $pos => $tok) { + if (\is_string($tok)) { + $token = new Token($lastToken->getLine(), $this->map[$tok], $tok); + $result->addToken($token); + $lastToken = $token; + continue; + } + $line = $tok[2]; + $values = \preg_split('/\\R+/Uu', $tok[1]); + foreach ($values as $v) { + $token = new Token($line, \token_name($tok[0]), $v); + $lastToken = $token; + $line++; + if ($v === '') { + continue; + } + $result->addToken($token); + } + } + return $this->fillBlanks($result, $lastToken->getLine()); } - public function getExpectedCount() + private function fillBlanks(TokenCollection $tokens, int $maxLine) : TokenCollection { - return $this->expectedCount; + $prev = new Token(0, 'Placeholder', ''); + $final = new TokenCollection(); + foreach ($tokens as $token) { + if ($prev === null) { + $final->addToken($token); + $prev = $token; + continue; + } + $gap = $token->getLine() - $prev->getLine(); + while ($gap > 1) { + $linebreak = new Token($prev->getLine() + 1, 'T_WHITESPACE', ''); + $final->addToken($linebreak); + $prev = $linebreak; + $gap--; + } + $final->addToken($token); + $prev = $token; + } + $gap = $maxLine - $prev->getLine(); + while ($gap > 0) { + $linebreak = new Token($prev->getLine() + 1, 'T_WHITESPACE', ''); + $final->addToken($linebreak); + $prev = $linebreak; + $gap--; + } + return $final; } } - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prediction; +declare (strict_types=1); +namespace PHPUnit\TheSeer\Tokenizer; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Exception\Prophecy\MethodProphecyException; -class UnexpectedCallsException extends MethodProphecyException implements \Prophecy\Exception\Prediction\PredictionException +use DOMDocument; +class XMLSerializer { - private $calls = array(); - public function __construct($message, MethodProphecy $methodProphecy, array $calls) + /** @var \XMLWriter */ + private $writer; + /** @var Token */ + private $previousToken; + /** @var NamespaceUri */ + private $xmlns; + /** + * XMLSerializer constructor. + * + * @param NamespaceUri $xmlns + */ + public function __construct(NamespaceUri $xmlns = null) { - parent::__construct($message, $methodProphecy); - $this->calls = $calls; + if ($xmlns === null) { + $xmlns = new NamespaceUri('https://github.com/theseer/tokenizer'); + } + $this->xmlns = $xmlns; } - public function getCalls() + public function toDom(TokenCollection $tokens) : DOMDocument { - return $this->calls; + $dom = new DOMDocument(); + $dom->preserveWhiteSpace = \false; + $dom->loadXML($this->toXML($tokens)); + return $dom; + } + public function toXML(TokenCollection $tokens) : string + { + $this->writer = new \XMLWriter(); + $this->writer->openMemory(); + $this->writer->setIndent(\true); + $this->writer->startDocument(); + $this->writer->startElement('source'); + $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); + if (\count($tokens) > 0) { + $this->writer->startElement('line'); + $this->writer->writeAttribute('no', '1'); + $this->previousToken = $tokens[0]; + foreach ($tokens as $token) { + $this->addToken($token); + } + } + $this->writer->endElement(); + $this->writer->endElement(); + $this->writer->endDocument(); + return $this->writer->outputMemory(); + } + private function addToken(Token $token) : void + { + if ($this->previousToken->getLine() < $token->getLine()) { + $this->writer->endElement(); + $this->writer->startElement('line'); + $this->writer->writeAttribute('no', (string) $token->getLine()); + $this->previousToken = $token; + } + if ($token->getValue() !== '') { + $this->writer->startElement('token'); + $this->writer->writeAttribute('name', $token->getName()); + $this->writer->writeRaw(\htmlspecialchars($token->getValue(), \ENT_NOQUOTES | \ENT_DISALLOWED | \ENT_XML1)); + $this->writer->endElement(); + } } } - * Marcello Duarte + * This file is part of the webmozart/assert package. + * + * (c) Bernhard Schussek * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Prophecy\Exception\Prediction; +namespace PHPUnit\Webmozart\Assert; -use RuntimeException; +use ArrayAccess; +use BadMethodCallException; +use Closure; +use Countable; +use DateTime; +use DateTimeImmutable; +use Exception; +use ResourceBundle; +use SimpleXMLElement; +use Throwable; +use Traversable; /** - * Basic failed prediction exception. - * Use it for custom prediction failures. + * Efficient assertions to validate the input/output of your methods. * - * @author Konstantin Kudryashov - */ -class FailedPredictionException extends RuntimeException implements \Prophecy\Exception\Prediction\PredictionException -{ -} - - * Marcello Duarte + * @since 1.0 * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author Bernhard Schussek */ -namespace Prophecy\Exception\Prediction; - -use Prophecy\Prophecy\ObjectProphecy; -class AggregateException extends \RuntimeException implements \Prophecy\Exception\Prediction\PredictionException +class Assert { - private $exceptions = array(); - private $objectProphecy; - public function append(\Prophecy\Exception\Prediction\PredictionException $exception) + use Mixin; + /** + * @psalm-pure + * @psalm-assert string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function string($value, $message = '') + { + if (!\is_string($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a string. Got: %s', static::typeToString($value))); + } + } + /** + * @psalm-pure + * @psalm-assert non-empty-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function stringNotEmpty($value, $message = '') + { + static::string($value, $message); + static::notEq($value, '', $message); + } + /** + * @psalm-pure + * @psalm-assert int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integer($value, $message = '') + { + if (!\is_int($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an integer. Got: %s', static::typeToString($value))); + } + } + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integerish($value, $message = '') + { + if (!\is_numeric($value) || $value != (int) $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an integerish value. Got: %s', static::typeToString($value))); + } + } + /** + * @psalm-pure + * @psalm-assert positive-int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function positiveInteger($value, $message = '') + { + if (!(\is_int($value) && $value > 0)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a positive integer. Got: %s', static::valueToString($value))); + } + } + /** + * @psalm-pure + * @psalm-assert float $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function float($value, $message = '') + { + if (!\is_float($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a float. Got: %s', static::typeToString($value))); + } + } + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function numeric($value, $message = '') { - $message = $exception->getMessage(); - $message = \strtr($message, array("\n" => "\n ")) . "\n"; - $message = empty($this->exceptions) ? $message : "\n" . $message; - $this->message = \rtrim($this->message . $message); - $this->exceptions[] = $exception; + if (!\is_numeric($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a numeric. Got: %s', static::typeToString($value))); + } } /** - * @return PredictionException[] + * @psalm-pure + * @psalm-assert positive-int|0 $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getExceptions() - { - return $this->exceptions; - } - public function setObjectProphecy(ObjectProphecy $objectProphecy) + public static function natural($value, $message = '') { - $this->objectProphecy = $objectProphecy; + if (!\is_int($value) || $value < 0) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a non-negative integer. Got: %s', static::valueToString($value))); + } } /** - * @return ObjectProphecy + * @psalm-pure + * @psalm-assert bool $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getObjectProphecy() + public static function boolean($value, $message = '') { - return $this->objectProphecy; + if (!\is_bool($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a boolean. Got: %s', static::typeToString($value))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prediction; - -use Prophecy\Exception\Prophecy\MethodProphecyException; -class NoCallsException extends MethodProphecyException implements \Prophecy\Exception\Prediction\PredictionException -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Prediction; - -use Prophecy\Exception\Exception; -interface PredictionException extends Exception -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Call; - -use Prophecy\Exception\Prophecy\ObjectProphecyException; -use Prophecy\Prophecy\ObjectProphecy; -class UnexpectedCallException extends ObjectProphecyException -{ - private $methodName; - private $arguments; - public function __construct($message, ObjectProphecy $objectProphecy, $methodName, array $arguments) + /** + * @psalm-pure + * @psalm-assert scalar $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function scalar($value, $message = '') { - parent::__construct($message, $objectProphecy); - $this->methodName = $methodName; - $this->arguments = $arguments; + if (!\is_scalar($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a scalar. Got: %s', static::typeToString($value))); + } } - public function getMethodName() + /** + * @psalm-pure + * @psalm-assert object $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function object($value, $message = '') { - return $this->methodName; + if (!\is_object($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an object. Got: %s', static::typeToString($value))); + } } - public function getArguments() + /** + * @psalm-pure + * @psalm-assert resource $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function resource($value, $type = null, $message = '') { - return $this->arguments; + if (!\is_resource($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a resource. Got: %s', static::typeToString($value))); + } + if ($type && $type !== \get_resource_type($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a resource of type %2$s. Got: %s', static::typeToString($value), $type)); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception; - -/** - * Core Prophecy exception interface. - * All Prophecy exceptions implement it. - * - * @author Konstantin Kudryashov - */ -interface Exception extends \Throwable -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -class MethodNotFoundException extends \Prophecy\Exception\Doubler\DoubleException -{ /** - * @var string|object + * @psalm-pure + * @psalm-assert callable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - private $classname; + public static function isCallable($value, $message = '') + { + if (!\is_callable($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a callable. Got: %s', static::typeToString($value))); + } + } /** - * @var string + * @psalm-pure + * @psalm-assert array $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - private $methodName; + public static function isArray($value, $message = '') + { + if (!\is_array($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array. Got: %s', static::typeToString($value))); + } + } /** - * @var array + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - private $arguments; + public static function isTraversable($value, $message = '') + { + @\trigger_error(\sprintf('The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', __METHOD__), \E_USER_DEPRECATED); + if (!\is_array($value) && !$value instanceof Traversable) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a traversable. Got: %s', static::typeToString($value))); + } + } /** + * @psalm-pure + * @psalm-assert array|ArrayAccess $value + * + * @param mixed $value * @param string $message - * @param string|object $classname - * @param string $methodName - * @param null|Argument\ArgumentsWildcard|array $arguments + * + * @throws InvalidArgumentException */ - public function __construct($message, $classname, $methodName, $arguments = null) + public static function isArrayAccessible($value, $message = '') { - parent::__construct($message); - $this->classname = $classname; - $this->methodName = $methodName; - $this->arguments = $arguments; + if (!\is_array($value) && !$value instanceof ArrayAccess) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array accessible. Got: %s', static::typeToString($value))); + } } - public function getClassname() + /** + * @psalm-pure + * @psalm-assert countable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCountable($value, $message = '') { - return $this->classname; + if (!\is_array($value) && !$value instanceof Countable && !$value instanceof ResourceBundle && !$value instanceof SimpleXMLElement) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a countable. Got: %s', static::typeToString($value))); + } } - public function getMethodName() + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isIterable($value, $message = '') { - return $this->methodName; + if (!\is_array($value) && !$value instanceof Traversable) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an iterable. Got: %s', static::typeToString($value))); + } } - public function getArguments() + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOf($value, $class, $message = '') { - return $this->arguments; + if (!$value instanceof $class) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of %2$s. Got: %s', static::typeToString($value), $class)); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -class ClassNotFoundException extends \Prophecy\Exception\Doubler\DoubleException -{ - private $classname; /** - * @param string $message - * @param string $classname + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert !ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct($message, $classname) + public static function notInstanceOf($value, $class, $message = '') { - parent::__construct($message); - $this->classname = $classname; + if ($value instanceof $class) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance other than %2$s. Got: %s', static::typeToString($value), $class)); + } } - public function getClassname() + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOfAny($value, array $classes, $message = '') { - return $this->classname; + foreach ($classes as $class) { + if ($value instanceof $class) { + return; + } + } + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of any of %2$s. Got: %s', static::typeToString($value), \implode(', ', \array_map(array(static::class, 'valueToString'), $classes)))); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -use ReflectionClass; -class ClassMirrorException extends \RuntimeException implements \Prophecy\Exception\Doubler\DoublerException -{ - private $class; - public function __construct($message, ReflectionClass $class) + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAOf($value, $class, $message = '') { - parent::__construct($message); - $this->class = $class; + static::string($class, 'Expected class as a string. Got: %s'); + if (!\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s', static::valueToString($value), $class)); + } } - public function getReflectedClass() + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * @psalm-assert !UnexpectedType $value + * @psalm-assert !class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNotA($value, $class, $message = '') { - return $this->class; + static::string($class, 'Expected class as a string. Got: %s'); + if (\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s', static::valueToString($value), $class)); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -use Prophecy\Exception\Exception; -interface DoublerException extends Exception -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -class InterfaceNotFoundException extends \Prophecy\Exception\Doubler\ClassNotFoundException -{ - public function getInterfaceName() + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param object|string $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAnyOf($value, array $classes, $message = '') { - return $this->getClassname(); + foreach ($classes as $class) { + static::string($class, 'Expected class as a string. Got: %s'); + if (\is_a($value, $class, \is_string($value))) { + return; + } + } + static::reportInvalidArgument(\sprintf($message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s', static::valueToString($value), \implode(', ', $classes))); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -class ReturnByReferenceException extends \Prophecy\Exception\Doubler\DoubleException -{ - private $classname; - private $methodName; /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value * @param string $message - * @param string $classname - * @param string $methodName + * + * @throws InvalidArgumentException */ - public function __construct($message, $classname, $methodName) - { - parent::__construct($message); - $this->classname = $classname; - $this->methodName = $methodName; - } - public function getClassname() + public static function isEmpty($value, $message = '') { - return $this->classname; + if (!empty($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an empty value. Got: %s', static::valueToString($value))); + } } - public function getMethodName() + /** + * @psalm-pure + * @psalm-assert !empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEmpty($value, $message = '') { - return $this->methodName; + if (empty($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a non-empty value. Got: %s', static::valueToString($value))); + } } -} -methodName = $methodName; - $this->className = $className; + if (null !== $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected null. Got: %s', static::valueToString($value))); + } } /** - * @return string + * @psalm-pure + * @psalm-assert !null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getMethodName() + public static function notNull($value, $message = '') { - return $this->methodName; + if (null === $value) { + static::reportInvalidArgument($message ?: 'Expected a value other than null.'); + } } /** - * @return string + * @psalm-pure + * @psalm-assert true $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getClassName() - { - return $this->className; - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -use RuntimeException; -class DoubleException extends RuntimeException implements \Prophecy\Exception\Doubler\DoublerException -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception\Doubler; - -use Prophecy\Doubler\Generator\Node\ClassNode; -class ClassCreatorException extends \RuntimeException implements \Prophecy\Exception\Doubler\DoublerException -{ - private $node; - public function __construct($message, ClassNode $node) - { - parent::__construct($message); - $this->node = $node; - } - public function getClassNode() + public static function true($value, $message = '') { - return $this->node; + if (\true !== $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be true. Got: %s', static::valueToString($value))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Exception; - -class InvalidArgumentException extends \InvalidArgumentException implements \Prophecy\Exception\Exception -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Promise; - -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -/** - * Promise interface. - * Promises are logical blocks, tied to `will...` keyword. - * - * @author Konstantin Kudryashov - */ -interface PromiseInterface -{ /** - * Evaluates promise. + * @psalm-pure + * @psalm-assert false $value * - * @param array $args - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param mixed $value + * @param string $message * - * @return mixed - */ - public function execute(array $args, ObjectProphecy $object, MethodProphecy $method); -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Promise; - -use PHPUnit\Doctrine\Instantiator\Instantiator; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Exception\InvalidArgumentException; -use ReflectionClass; -/** - * Throw promise. - * - * @author Konstantin Kudryashov - */ -class ThrowPromise implements \Prophecy\Promise\PromiseInterface -{ - private $exception; - /** - * @var \Doctrine\Instantiator\Instantiator + * @throws InvalidArgumentException */ - private $instantiator; + public static function false($value, $message = '') + { + if (\false !== $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be false. Got: %s', static::valueToString($value))); + } + } /** - * Initializes promise. + * @psalm-pure + * @psalm-assert !false $value * - * @param string|\Exception|\Throwable $exception Exception class name or instance + * @param mixed $value + * @param string $message * - * @throws \Prophecy\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ - public function __construct($exception) + public static function notFalse($value, $message = '') { - if (\is_string($exception)) { - if (!\class_exists($exception) && !\interface_exists($exception) || !$this->isAValidThrowable($exception)) { - throw new InvalidArgumentException(\sprintf('Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', $exception)); - } - } elseif (!$exception instanceof \Exception && !$exception instanceof \Throwable) { - throw new InvalidArgumentException(\sprintf('Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', \is_object($exception) ? \get_class($exception) : \gettype($exception))); + if (\false === $value) { + static::reportInvalidArgument($message ?: 'Expected a value other than false.'); } - $this->exception = $exception; } /** - * Throws predefined exception. + * @param mixed $value + * @param string $message * - * @param array $args - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @throws InvalidArgumentException + */ + public static function ip($value, $message = '') + { + if (\false === \filter_var($value, \FILTER_VALIDATE_IP)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be an IP. Got: %s', static::valueToString($value))); + } + } + /** + * @param mixed $value + * @param string $message * - * @throws object + * @throws InvalidArgumentException */ - public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + public static function ipv4($value, $message = '') { - if (\is_string($this->exception)) { - $classname = $this->exception; - $reflection = new ReflectionClass($classname); - $constructor = $reflection->getConstructor(); - if ($constructor->isPublic() && 0 == $constructor->getNumberOfRequiredParameters()) { - throw $reflection->newInstance(); - } - if (!$this->instantiator) { - $this->instantiator = new Instantiator(); - } - throw $this->instantiator->instantiate($classname); + if (\false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be an IPv4. Got: %s', static::valueToString($value))); } - throw $this->exception; } /** - * @param string $exception + * @param mixed $value + * @param string $message * - * @return bool + * @throws InvalidArgumentException */ - private function isAValidThrowable($exception) + public static function ipv6($value, $message = '') { - return \is_a($exception, 'Exception', \true) || \is_a($exception, 'Throwable', \true); + if (\false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be an IPv6. Got: %s', static::valueToString($value))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Promise; - -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -/** - * Return promise. - * - * @author Konstantin Kudryashov - */ -class ReturnPromise implements \Prophecy\Promise\PromiseInterface -{ - private $returnValues = array(); /** - * Initializes promise. + * @param mixed $value + * @param string $message * - * @param array $returnValues Array of values + * @throws InvalidArgumentException */ - public function __construct(array $returnValues) + public static function email($value, $message = '') { - $this->returnValues = $returnValues; + if (\false === \filter_var($value, \FILTER_VALIDATE_EMAIL)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to be a valid e-mail address. Got: %s', static::valueToString($value))); + } } /** - * Returns saved values one by one until last one, then continuously returns last value. + * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. * - * @param array $args - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param array $values + * @param string $message * - * @return mixed + * @throws InvalidArgumentException */ - public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + public static function uniqueValues(array $values, $message = '') { - $value = \array_shift($this->returnValues); - if (!\count($this->returnValues)) { - $this->returnValues[] = $value; + $allValues = \count($values); + $uniqueValues = \count(\array_unique($values)); + if ($allValues !== $uniqueValues) { + $difference = $allValues - $uniqueValues; + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array of unique values, but %s of them %s duplicated', $difference, 1 === $difference ? 'is' : 'are')); } - return $value; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Promise; - -use Prophecy\Exception\InvalidArgumentException; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -/** - * Return argument promise. - * - * @author Konstantin Kudryashov - */ -class ReturnArgumentPromise implements \Prophecy\Promise\PromiseInterface -{ /** - * @var int + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException */ - private $index; + public static function eq($value, $expect, $message = '') + { + if ($expect != $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect))); + } + } /** - * Initializes callback promise. - * - * @param int $index The zero-indexed number of the argument to return + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @throws \Prophecy\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ - public function __construct($index = 0) + public static function notEq($value, $expect, $message = '') { - if (!\is_int($index) || $index < 0) { - throw new InvalidArgumentException(\sprintf('Zero-based index expected as argument to ReturnArgumentPromise, but got %s.', $index)); + if ($expect == $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a different value than %s.', static::valueToString($expect))); } - $this->index = $index; } /** - * Returns nth argument if has one, null otherwise. + * @psalm-pure * - * @param array $args - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return null|mixed + * @throws InvalidArgumentException */ - public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + public static function same($value, $expect, $message = '') { - return \count($args) > $this->index ? $args[$this->index] : null; + if ($expect !== $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value identical to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Promise; - -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Exception\InvalidArgumentException; -use Closure; -use ReflectionFunction; -/** - * Callback promise. - * - * @author Konstantin Kudryashov - */ -class CallbackPromise implements \Prophecy\Promise\PromiseInterface -{ - private $callback; /** - * Initializes callback promise. + * @psalm-pure * - * @param callable $callback Custom callback + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @throws \Prophecy\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ - public function __construct($callback) + public static function notSame($value, $expect, $message = '') { - if (!\is_callable($callback)) { - throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackPromise, but got %s.', \gettype($callback))); + if ($expect === $value) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value not identical to %s.', static::valueToString($expect))); } - $this->callback = $callback; } /** - * Evaluates promise callback. + * @psalm-pure * - * @param array $args - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @return mixed + * @throws InvalidArgumentException */ - public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + public static function greaterThan($value, $limit, $message = '') { - $callback = $this->callback; - if ($callback instanceof Closure && \method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { - $callback = Closure::bind($callback, $object); + if ($value <= $limit) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value greater than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); } - return \call_user_func($callback, $args, $object, $method); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Prediction; - -use Prophecy\Call\Call; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Exception\InvalidArgumentException; -use Closure; -use ReflectionFunction; -/** - * Callback prediction. - * - * @author Konstantin Kudryashov - */ -class CallbackPrediction implements \Prophecy\Prediction\PredictionInterface -{ - private $callback; /** - * Initializes callback prediction. + * @psalm-pure * - * @param callable $callback Custom callback + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @throws \Prophecy\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ - public function __construct($callback) + public static function greaterThanEq($value, $limit, $message = '') { - if (!\is_callable($callback)) { - throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackPrediction, but got %s.', \gettype($callback))); + if ($value < $limit) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value greater than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); } - $this->callback = $callback; } /** - * Executes preset callback. + * @psalm-pure * - * @param Call[] $calls - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException */ - public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + public static function lessThan($value, $limit, $message = '') { - $callback = $this->callback; - if ($callback instanceof Closure && \method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { - $callback = Closure::bind($callback, $object); + if ($value >= $limit) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value less than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); } - \call_user_func($callback, $calls, $object, $method); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Prediction; - -use Prophecy\Call\Call; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Argument\ArgumentsWildcard; -use Prophecy\Argument\Token\AnyValuesToken; -use Prophecy\Util\StringUtil; -use Prophecy\Exception\Prediction\NoCallsException; -/** - * Call prediction. - * - * @author Konstantin Kudryashov - */ -class CallPrediction implements \Prophecy\Prediction\PredictionInterface -{ - private $util; /** - * Initializes prediction. + * @psalm-pure * - * @param StringUtil $util + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct(StringUtil $util = null) + public static function lessThanEq($value, $limit, $message = '') { - $this->util = $util ?: new StringUtil(); + if ($value > $limit) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value less than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit))); + } } /** - * Tests that there was at least one call. + * Inclusive range, so Assert::(3, 3, 5) passes. * - * @param Call[] $calls - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @psalm-pure * - * @throws \Prophecy\Exception\Prediction\NoCallsException + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException */ - public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + public static function range($value, $min, $max, $message = '') { - if (\count($calls)) { - return; - } - $methodCalls = $object->findProphecyMethodCalls($method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken()))); - if (\count($methodCalls)) { - throw new NoCallsException(\sprintf("No calls have been made that match:\n" . " %s->%s(%s)\n" . "but expected at least one.\n" . "Recorded `%s(...)` calls:\n%s", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls)), $method); + if ($value < $min || $value > $max) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value between %2$s and %3$s. Got: %s', static::valueToString($value), static::valueToString($min), static::valueToString($max))); } - throw new NoCallsException(\sprintf("No calls have been made that match:\n" . " %s->%s(%s)\n" . "but expected at least one.", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard()), $method); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Prediction; - -use Prophecy\Call\Call; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Argument\ArgumentsWildcard; -use Prophecy\Argument\Token\AnyValuesToken; -use Prophecy\Util\StringUtil; -use Prophecy\Exception\Prediction\UnexpectedCallsCountException; -/** - * Prediction interface. - * Predictions are logical test blocks, tied to `should...` keyword. - * - * @author Konstantin Kudryashov - */ -class CallTimesPrediction implements \Prophecy\Prediction\PredictionInterface -{ - private $times; - private $util; /** - * Initializes prediction. + * A more human-readable alias of Assert::inArray(). * - * @param int $times - * @param StringUtil $util + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct($times, StringUtil $util = null) + public static function oneOf($value, array $values, $message = '') { - $this->times = \intval($times); - $this->util = $util ?: new StringUtil(); + static::inArray($value, $values, $message); } /** - * Tests that there was exact amount of calls made. + * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. * - * @param Call[] $calls - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @psalm-pure * - * @throws \Prophecy\Exception\Prediction\UnexpectedCallsCountException + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException */ - public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + public static function inArray($value, array $values, $message = '') { - if ($this->times == \count($calls)) { - return; - } - $methodCalls = $object->findProphecyMethodCalls($method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken()))); - if (\count($calls)) { - $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but %d were made:\n%s", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), \count($calls), $this->util->stringifyCalls($calls)); - } elseif (\count($methodCalls)) { - $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but none were made.\n" . "Recorded `%s(...)` calls:\n%s", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls)); - } else { - $message = \sprintf("Expected exactly %d calls that match:\n" . " %s->%s(%s)\n" . "but none were made.", $this->times, \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard()); + if (!\in_array($value, $values, \true)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected one of: %2$s. Got: %s', static::valueToString($value), \implode(', ', \array_map(array(static::class, 'valueToString'), $values)))); } - throw new UnexpectedCallsCountException($message, $method, $this->times, $calls); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Prediction; - -use Prophecy\Call\Call; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -/** - * Prediction interface. - * Predictions are logical test blocks, tied to `should...` keyword. - * - * @author Konstantin Kudryashov - */ -interface PredictionInterface -{ /** - * Tests that double fulfilled prediction. + * @psalm-pure * - * @param Call[] $calls - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param string $value + * @param string $subString + * @param string $message * - * @throws object - * @return void + * @throws InvalidArgumentException */ - public function check(array $calls, ObjectProphecy $object, MethodProphecy $method); -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Prediction; - -use Prophecy\Call\Call; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Util\StringUtil; -use Prophecy\Exception\Prediction\UnexpectedCallsException; -/** - * No calls prediction. - * - * @author Konstantin Kudryashov - */ -class NoCallsPrediction implements \Prophecy\Prediction\PredictionInterface -{ - private $util; + public static function contains($value, $subString, $message = '') + { + if (\false === \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain %2$s. Got: %s', static::valueToString($value), static::valueToString($subString))); + } + } /** - * Initializes prediction. + * @psalm-pure * - * @param null|StringUtil $util + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct(StringUtil $util = null) + public static function notContains($value, $subString, $message = '') { - $this->util = $util ?: new StringUtil(); + if (\false !== \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf($message ?: '%2$s was not expected to be contained in a value. Got: %s', static::valueToString($value), static::valueToString($subString))); + } } /** - * Tests that there were no calls made. + * @psalm-pure * - * @param Call[] $calls - * @param ObjectProphecy $object - * @param MethodProphecy $method + * @param string $value + * @param string $message * - * @throws \Prophecy\Exception\Prediction\UnexpectedCallsException + * @throws InvalidArgumentException */ - public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + public static function notWhitespaceOnly($value, $message = '') { - if (!\count($calls)) { - return; + if (\preg_match('/^\\s*$/', $value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a non-whitespace string. Got: %s', static::valueToString($value))); } - $verb = \count($calls) === 1 ? 'was' : 'were'; - throw new UnexpectedCallsException(\sprintf("No calls expected that match:\n" . " %s->%s(%s)\n" . "but %d %s made:\n%s", \get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), \count($calls), $verb, $this->util->stringifyCalls($calls)), $method, $calls); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\PhpDocumentor; - -use PHPUnit\phpDocumentor\Reflection\DocBlock; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; -/** - * @author Théo FIDRY - * - * @internal - */ -final class LegacyClassTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface -{ /** - * @param \ReflectionClass $reflectionClass + * @psalm-pure * - * @return LegacyMethodTag[] + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException */ - public function getTagList(\ReflectionClass $reflectionClass) + public static function startsWith($value, $prefix, $message = '') { - $phpdoc = new DocBlock($reflectionClass->getDocComment()); - return $phpdoc->getTagsByName('method'); + if (0 !== \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\PhpDocumentor; - -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; -/** - * @author Théo FIDRY - * - * @internal - */ -interface MethodTagRetrieverInterface -{ /** - * @param \ReflectionClass $reflectionClass + * @psalm-pure * - * @return LegacyMethodTag[]|Method[] + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException */ - public function getTagList(\ReflectionClass $reflectionClass); -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\PhpDocumentor; - -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; -use PHPUnit\phpDocumentor\Reflection\DocBlockFactory; -use PHPUnit\phpDocumentor\Reflection\Types\ContextFactory; -/** - * @author Théo FIDRY - * - * @internal - */ -final class ClassTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface -{ - private $docBlockFactory; - private $contextFactory; - public function __construct() + public static function notStartsWith($value, $prefix, $message = '') { - $this->docBlockFactory = DocBlockFactory::createInstance(); - $this->contextFactory = new ContextFactory(); + if (0 === \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value not to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix))); + } } /** - * @param \ReflectionClass $reflectionClass + * @psalm-pure * - * @return Method[] + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getTagList(\ReflectionClass $reflectionClass) + public static function startsWithLetter($value, $message = '') { - try { - $phpdoc = $this->docBlockFactory->create($reflectionClass, $this->contextFactory->createFromReflector($reflectionClass)); - $methods = array(); - foreach ($phpdoc->getTagsByName('method') as $tag) { - if ($tag instanceof Method) { - $methods[] = $tag; - } - } - return $methods; - } catch (\InvalidArgumentException $e) { - return array(); + static::string($value); + $valid = isset($value[0]); + if ($valid) { + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = \ctype_alpha($value[0]); + \setlocale(\LC_CTYPE, $locale); } - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\PhpDocumentor; - -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; -use PHPUnit\phpDocumentor\Reflection\DocBlock\Tags\Method; -/** - * @author Théo FIDRY - * - * @internal - */ -final class ClassAndInterfaceTagRetriever implements \Prophecy\PhpDocumentor\MethodTagRetrieverInterface -{ - private $classRetriever; - public function __construct(\Prophecy\PhpDocumentor\MethodTagRetrieverInterface $classRetriever = null) - { - if (null !== $classRetriever) { - $this->classRetriever = $classRetriever; - return; + if (!$valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to start with a letter. Got: %s', static::valueToString($value))); } - $this->classRetriever = \class_exists('PHPUnit\\phpDocumentor\\Reflection\\DocBlockFactory') && \class_exists('PHPUnit\\phpDocumentor\\Reflection\\Types\\ContextFactory') ? new \Prophecy\PhpDocumentor\ClassTagRetriever() : new \Prophecy\PhpDocumentor\LegacyClassTagRetriever(); } /** - * @param \ReflectionClass $reflectionClass + * @psalm-pure * - * @return LegacyMethodTag[]|Method[] + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException */ - public function getTagList(\ReflectionClass $reflectionClass) + public static function endsWith($value, $suffix, $message = '') { - return \array_merge($this->classRetriever->getTagList($reflectionClass), $this->getInterfacesTagList($reflectionClass)); + if ($suffix !== \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix))); + } } /** - * @param \ReflectionClass $reflectionClass + * @psalm-pure * - * @return LegacyMethodTag[]|Method[] + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException */ - private function getInterfacesTagList(\ReflectionClass $reflectionClass) + public static function notEndsWith($value, $suffix, $message = '') { - $interfaces = $reflectionClass->getInterfaces(); - $tagList = array(); - foreach ($interfaces as $interface) { - $tagList = \array_merge($tagList, $this->classRetriever->getTagList($interface)); + if ($suffix === \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value not to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix))); } - return $tagList; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Call; - -use Prophecy\Exception\Prophecy\MethodProphecyException; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Argument\ArgumentsWildcard; -use Prophecy\Util\StringUtil; -use Prophecy\Exception\Call\UnexpectedCallException; -use SplObjectStorage; -/** - * Calls receiver & manager. - * - * @author Konstantin Kudryashov - */ -class CallCenter -{ - private $util; - /** - * @var Call[] - */ - private $recordedCalls = array(); /** - * @var SplObjectStorage + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException */ - private $unexpectedCalls; + public static function regex($value, $pattern, $message = '') + { + if (!\preg_match($pattern, $value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The value %s does not match the expected pattern.', static::valueToString($value))); + } + } /** - * Initializes call center. + * @psalm-pure * - * @param StringUtil $util + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct(StringUtil $util = null) + public static function notRegex($value, $pattern, $message = '') { - $this->util = $util ?: new StringUtil(); - $this->unexpectedCalls = new SplObjectStorage(); + if (\preg_match($pattern, $value, $matches, \PREG_OFFSET_CAPTURE)) { + static::reportInvalidArgument(\sprintf($message ?: 'The value %s matches the pattern %s (at offset %d).', static::valueToString($value), static::valueToString($pattern), $matches[0][1])); + } } /** - * Makes and records specific method call for object prophecy. - * - * @param ObjectProphecy $prophecy - * @param string $methodName - * @param array $arguments + * @psalm-pure * - * @return mixed Returns null if no promise for prophecy found or promise return value. + * @param mixed $value + * @param string $message * - * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found + * @throws InvalidArgumentException */ - public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments) + public static function unicodeLetters($value, $message = '') { - // For efficiency exclude 'args' from the generated backtrace - // Limit backtrace to last 3 calls as we don't use the rest - $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); - $file = $line = null; - if (isset($backtrace[2]) && isset($backtrace[2]['file'])) { - $file = $backtrace[2]['file']; - $line = $backtrace[2]['line']; - } - // If no method prophecies defined, then it's a dummy, so we'll just return null - if ('__destruct' === \strtolower($methodName) || 0 == \count($prophecy->getMethodProphecies())) { - $this->recordedCalls[] = new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line); - return null; - } - // There are method prophecies, so it's a fake/stub. Searching prophecy for this call - $matches = $this->findMethodProphecies($prophecy, $methodName, $arguments); - // If fake/stub doesn't have method prophecy for this call - throw exception - if (!\count($matches)) { - $this->unexpectedCalls->attach(new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line), $prophecy); - $this->recordedCalls[] = new \Prophecy\Call\Call($methodName, $arguments, null, null, $file, $line); - return null; - } - // Sort matches by their score value - @\usort($matches, function ($match1, $match2) { - return $match2[0] - $match1[0]; - }); - $score = $matches[0][0]; - // If Highest rated method prophecy has a promise - execute it or return null instead - $methodProphecy = $matches[0][1]; - $returnValue = null; - $exception = null; - if ($promise = $methodProphecy->getPromise()) { - try { - $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy); - } catch (\Exception $e) { - $exception = $e; - } - } - if ($methodProphecy->hasReturnVoid() && $returnValue !== null) { - throw new MethodProphecyException("The method \"{$methodName}\" has a void return type, but the promise returned a value", $methodProphecy); - } - $this->recordedCalls[] = $call = new \Prophecy\Call\Call($methodName, $arguments, $returnValue, $exception, $file, $line); - $call->addScore($methodProphecy->getArgumentsWildcard(), $score); - if (null !== $exception) { - throw $exception; + static::string($value); + if (!\preg_match('/^\\p{L}+$/u', $value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain only Unicode letters. Got: %s', static::valueToString($value))); } - return $returnValue; } /** - * Searches for calls by method name & arguments wildcard. + * @psalm-pure * - * @param string $methodName - * @param ArgumentsWildcard $wildcard + * @param mixed $value + * @param string $message * - * @return Call[] + * @throws InvalidArgumentException */ - public function findCalls($methodName, ArgumentsWildcard $wildcard) + public static function alpha($value, $message = '') { - $methodName = \strtolower($methodName); - return \array_values(\array_filter($this->recordedCalls, function (\Prophecy\Call\Call $call) use($methodName, $wildcard) { - return $methodName === \strtolower($call->getMethodName()) && 0 < $call->getScore($wildcard); - })); + static::string($value); + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = !\ctype_alpha($value); + \setlocale(\LC_CTYPE, $locale); + if ($valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain only letters. Got: %s', static::valueToString($value))); + } } /** - * @throws UnexpectedCallException + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function checkUnexpectedCalls() + public static function digits($value, $message = '') { - /** @var Call $call */ - foreach ($this->unexpectedCalls as $call) { - $prophecy = $this->unexpectedCalls[$call]; - // If fake/stub doesn't have method prophecy for this call - throw exception - if (!\count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) { - throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments()); - } + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = !\ctype_digit($value); + \setlocale(\LC_CTYPE, $locale); + if ($valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain digits only. Got: %s', static::valueToString($value))); } } - private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName, array $arguments) + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alnum($value, $message = '') { - $classname = \get_class($prophecy->reveal()); - $indentationLength = 8; - // looks good - $argstring = \implode(",\n", $this->indentArguments(\array_map(array($this->util, 'stringify'), $arguments), $indentationLength)); - $expected = array(); - foreach (\array_merge(...\array_values($prophecy->getMethodProphecies())) as $methodProphecy) { - $expected[] = \sprintf(" - %s(\n" . "%s\n" . " )", $methodProphecy->getMethodName(), \implode(",\n", $this->indentArguments(\array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()), $indentationLength))); + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = !\ctype_alnum($value); + \setlocale(\LC_CTYPE, $locale); + if ($valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain letters and digits only. Got: %s', static::valueToString($value))); } - return new UnexpectedCallException(\sprintf("Unexpected method call on %s:\n" . " - %s(\n" . "%s\n" . " )\n" . "expected calls were:\n" . "%s", $classname, $methodName, $argstring, \implode("\n", $expected)), $prophecy, $methodName, $arguments); } - private function indentArguments(array $arguments, $indentationLength) + /** + * @psalm-pure + * @psalm-assert lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lower($value, $message = '') { - return \preg_replace_callback('/^/m', function () use($indentationLength) { - return \str_repeat(' ', $indentationLength); - }, $arguments); + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = !\ctype_lower($value); + \setlocale(\LC_CTYPE, $locale); + if ($valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain lowercase characters only. Got: %s', static::valueToString($value))); + } } /** - * @param ObjectProphecy $prophecy - * @param string $methodName - * @param array $arguments + * @psalm-pure + * @psalm-assert !lowercase-string $value * - * @return array + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException */ - private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments) + public static function upper($value, $message = '') { - $matches = array(); - foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) { - if (0 < ($score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments))) { - $matches[] = array($score, $methodProphecy); - } + $locale = \setlocale(\LC_CTYPE, 0); + \setlocale(\LC_CTYPE, 'C'); + $valid = !\ctype_upper($value); + \setlocale(\LC_CTYPE, $locale); + if ($valid) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain uppercase characters only. Got: %s', static::valueToString($value))); } - return $matches; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Call; - -use Exception; -use Prophecy\Argument\ArgumentsWildcard; -/** - * Call object. - * - * @author Konstantin Kudryashov - */ -class Call -{ - private $methodName; - private $arguments; - private $returnValue; - private $exception; - private $file; - private $line; - private $scores; /** - * Initializes call. + * @psalm-pure * - * @param string $methodName - * @param array $arguments - * @param mixed $returnValue - * @param Exception $exception - * @param null|string $file - * @param null|int $line + * @param string $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct($methodName, array $arguments, $returnValue, Exception $exception = null, $file, $line) + public static function length($value, $length, $message = '') { - $this->methodName = $methodName; - $this->arguments = $arguments; - $this->returnValue = $returnValue; - $this->exception = $exception; - $this->scores = new \SplObjectStorage(); - if ($file) { - $this->file = $file; - $this->line = \intval($line); + if ($length !== static::strlen($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain %2$s characters. Got: %s', static::valueToString($value), $length)); } } /** - * Returns called method name. + * Inclusive min. * - * @return string + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException */ - public function getMethodName() + public static function minLength($value, $min, $message = '') { - return $this->methodName; + if (static::strlen($value) < $min) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain at least %2$s characters. Got: %s', static::valueToString($value), $min)); + } } /** - * Returns called method arguments. + * Inclusive max. * - * @return array + * @psalm-pure + * + * @param string $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException */ - public function getArguments() + public static function maxLength($value, $max, $message = '') { - return $this->arguments; + if (static::strlen($value) > $max) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain at most %2$s characters. Got: %s', static::valueToString($value), $max)); + } } /** - * Returns called method return value. + * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. * - * @return null|mixed + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException */ - public function getReturnValue() + public static function lengthBetween($value, $min, $max, $message = '') { - return $this->returnValue; + $length = static::strlen($value); + if ($length < $min || $length > $max) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', static::valueToString($value), $min, $max)); + } } /** - * Returns exception that call thrown. + * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. * - * @return null|Exception + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getException() + public static function fileExists($value, $message = '') { - return $this->exception; + static::string($value); + if (!\file_exists($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The file %s does not exist.', static::valueToString($value))); + } } /** - * Returns callee filename. + * @param mixed $value + * @param string $message * - * @return string + * @throws InvalidArgumentException */ - public function getFile() + public static function file($value, $message = '') { - return $this->file; + static::fileExists($value, $message); + if (!\is_file($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The path %s is not a file.', static::valueToString($value))); + } } /** - * Returns callee line number. + * @param mixed $value + * @param string $message * - * @return int + * @throws InvalidArgumentException */ - public function getLine() + public static function directory($value, $message = '') { - return $this->line; + static::fileExists($value, $message); + if (!\is_dir($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The path %s is no directory.', static::valueToString($value))); + } } /** - * Returns short notation for callee place. + * @param string $value + * @param string $message * - * @return string + * @throws InvalidArgumentException */ - public function getCallPlace() + public static function readable($value, $message = '') { - if (null === $this->file) { - return 'unknown'; + if (!\is_readable($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The path %s is not readable.', static::valueToString($value))); } - return \sprintf('%s:%d', $this->file, $this->line); } /** - * Adds the wildcard match score for the provided wildcard. - * - * @param ArgumentsWildcard $wildcard - * @param false|int $score + * @param string $value + * @param string $message * - * @return $this + * @throws InvalidArgumentException */ - public function addScore(ArgumentsWildcard $wildcard, $score) + public static function writable($value, $message = '') { - $this->scores[$wildcard] = $score; - return $this; + if (!\is_writable($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'The path %s is not writable.', static::valueToString($value))); + } } /** - * Returns wildcard match score for the provided wildcard. The score is - * calculated if not already done. + * @psalm-assert class-string $value * - * @param ArgumentsWildcard $wildcard + * @param mixed $value + * @param string $message * - * @return false|int False OR integer score (higher - better) + * @throws InvalidArgumentException */ - public function getScore(ArgumentsWildcard $wildcard) + public static function classExists($value, $message = '') { - if (isset($this->scores[$wildcard])) { - return $this->scores[$wildcard]; + if (!\class_exists($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an existing class name. Got: %s', static::valueToString($value))); } - return $this->scores[$wildcard] = $wildcard->scoreArguments($this->getArguments()); - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Comparator; - -use Prophecy\Prophecy\ProphecyInterface; -use PHPUnit\SebastianBergmann\Comparator\ObjectComparator; -class ProphecyComparator extends ObjectComparator -{ - public function accepts($expected, $actual) - { - return \is_object($expected) && \is_object($actual) && $actual instanceof ProphecyInterface; } - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = array()) + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert class-string|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function subclassOf($value, $class, $message = '') { - parent::assertEquals($expected, $actual->reveal(), $delta, $canonicalize, $ignoreCase, $processed); + if (!\is_subclass_of($value, $class)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected a sub-class of %2$s. Got: %s', static::valueToString($value), static::valueToString($class))); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Comparator; - -use PHPUnit\SebastianBergmann\Comparator\Factory as BaseFactory; -/** - * Prophecy comparator factory. - * - * @author Konstantin Kudryashov - */ -final class Factory extends BaseFactory -{ /** - * @var Factory + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - private static $instance; - public function __construct() + public static function interfaceExists($value, $message = '') { - parent::__construct(); - $this->register(new \Prophecy\Comparator\ClosureComparator()); - $this->register(new \Prophecy\Comparator\ProphecyComparator()); + if (!\interface_exists($value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an existing interface name. got %s', static::valueToString($value))); + } } /** - * @return Factory + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert class-string $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException */ - public static function getInstance() + public static function implementsInterface($value, $interface, $message = '') { - if (self::$instance === null) { - self::$instance = new \Prophecy\Comparator\Factory(); + if (!\in_array($interface, \class_implements($value))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an implementation of %2$s. Got: %s', static::valueToString($value), static::valueToString($interface))); } - return self::$instance; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Comparator; - -use PHPUnit\SebastianBergmann\Comparator\Comparator; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -/** - * Closure comparator. - * - * @author Konstantin Kudryashov - */ -final class ClosureComparator extends Comparator -{ - public function accepts($expected, $actual) + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyExists($classOrObject, $property, $message = '') { - return \is_object($expected) && $expected instanceof \Closure && \is_object($actual) && $actual instanceof \Closure; + if (!\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the property %s to exist.', static::valueToString($property))); + } } - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = \false, $ignoreCase = \false, array &$processed = array()) + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyNotExists($classOrObject, $property, $message = '') { - if ($expected !== $actual) { - throw new ComparisonFailure( - $expected, - $actual, - // we don't need a diff - '', - '', - \false, - 'all closures are different if not identical' - ); + if (\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the property %s to not exist.', static::valueToString($property))); } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy; - -use Prophecy\Doubler\CachedDoubler; -use Prophecy\Doubler\Doubler; -use Prophecy\Doubler\LazyDouble; -use Prophecy\Doubler\ClassPatch; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophecy\RevealerInterface; -use Prophecy\Prophecy\Revealer; -use Prophecy\Call\CallCenter; -use Prophecy\Util\StringUtil; -use Prophecy\Exception\Prediction\PredictionException; -use Prophecy\Exception\Prediction\AggregateException; -/** - * Prophet creates prophecies. - * - * @author Konstantin Kudryashov - */ -class Prophet -{ - private $doubler; - private $revealer; - private $util; /** - * @var ObjectProphecy[] + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException */ - private $prophecies = array(); + public static function methodExists($classOrObject, $method, $message = '') + { + if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the method %s to exist.', static::valueToString($method))); + } + } /** - * Initializes Prophet. + * @psalm-pure + * @psalm-param class-string|object $classOrObject * - * @param null|Doubler $doubler - * @param null|RevealerInterface $revealer - * @param null|StringUtil $util + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct(Doubler $doubler = null, RevealerInterface $revealer = null, StringUtil $util = null) + public static function methodNotExists($classOrObject, $method, $message = '') { - if (null === $doubler) { - $doubler = new CachedDoubler(); - $doubler->registerClassPatch(new ClassPatch\SplFileInfoPatch()); - $doubler->registerClassPatch(new ClassPatch\TraversablePatch()); - $doubler->registerClassPatch(new ClassPatch\ThrowablePatch()); - $doubler->registerClassPatch(new ClassPatch\DisableConstructorPatch()); - $doubler->registerClassPatch(new ClassPatch\ProphecySubjectPatch()); - $doubler->registerClassPatch(new ClassPatch\ReflectionClassNewInstancePatch()); - $doubler->registerClassPatch(new ClassPatch\HhvmExceptionPatch()); - $doubler->registerClassPatch(new ClassPatch\MagicCallPatch()); - $doubler->registerClassPatch(new ClassPatch\KeywordPatch()); + if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the method %s to not exist.', static::valueToString($method))); } - $this->doubler = $doubler; - $this->revealer = $revealer ?: new Revealer(); - $this->util = $util ?: new StringUtil(); } /** - * Creates new object prophecy. + * @psalm-pure * - * @param null|string $classOrInterface Class or interface name + * @param array $array + * @param string|int $key + * @param string $message * - * @return ObjectProphecy + * @throws InvalidArgumentException */ - public function prophesize($classOrInterface = null) + public static function keyExists($array, $key, $message = '') { - $this->prophecies[] = $prophecy = new ObjectProphecy(new LazyDouble($this->doubler), new CallCenter($this->util), $this->revealer); - if ($classOrInterface && \class_exists($classOrInterface)) { - return $prophecy->willExtend($classOrInterface); - } - if ($classOrInterface && \interface_exists($classOrInterface)) { - return $prophecy->willImplement($classOrInterface); + if (!(isset($array[$key]) || \array_key_exists($key, $array))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the key %s to exist.', static::valueToString($key))); } - return $prophecy; } /** - * Returns all created object prophecies. + * @psalm-pure * - * @return ObjectProphecy[] + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException */ - public function getProphecies() + public static function keyNotExists($array, $key, $message = '') { - return $this->prophecies; + if (isset($array[$key]) || \array_key_exists($key, $array)) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected the key %s to not exist.', static::valueToString($key))); + } } /** - * Returns Doubler instance assigned to this Prophet. + * Checks if a value is a valid array key (int or string). * - * @return Doubler + * @psalm-pure + * @psalm-assert array-key $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException */ - public function getDoubler() + public static function validArrayKey($value, $message = '') { - return $this->doubler; + if (!(\is_int($value) || \is_string($value))) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected string or integer. Got: %s', static::typeToString($value))); + } } /** - * Checks all predictions defined by prophecies of this Prophet. + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. * - * @throws Exception\Prediction\AggregateException If any prediction fails + * @param Countable|array $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException */ - public function checkPredictions() + public static function count($array, $number, $message = '') { - $exception = new AggregateException("Some predictions failed:\n"); - foreach ($this->prophecies as $prophecy) { - try { - $prophecy->checkProphecyMethodsPredictions(); - } catch (PredictionException $e) { - $exception->append($e); - } - } - if (\count($exception->getExceptions())) { - throw $exception; - } + static::eq(\count($array), $number, \sprintf($message ?: 'Expected an array to contain %d elements. Got: %d.', $number, \count($array))); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler; - -use ReflectionClass; -/** - * Name generator. - * Generates classname for double. - * - * @author Konstantin Kudryashov - */ -class NameGenerator -{ - private static $counter = 1; /** - * Generates name. + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. * - * @param ReflectionClass $class - * @param ReflectionClass[] $interfaces + * @param Countable|array $array + * @param int|float $min + * @param string $message * - * @return string + * @throws InvalidArgumentException */ - public function name(ReflectionClass $class = null, array $interfaces) + public static function minCount($array, $min, $message = '') { - $parts = array(); - if (null !== $class) { - $parts[] = $class->getName(); - } else { - foreach ($interfaces as $interface) { - $parts[] = $interface->getShortName(); - } - } - if (!\count($parts)) { - $parts[] = 'stdClass'; + if (\count($array) < $min) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain at least %2$d elements. Got: %d', \count($array), $min)); } - return \sprintf('Double\\%s\\P%d', \implode('\\', $parts), self::$counter++); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler; - -use Prophecy\Exception\Doubler\DoubleException; -use Prophecy\Exception\Doubler\ClassNotFoundException; -use Prophecy\Exception\Doubler\InterfaceNotFoundException; -use ReflectionClass; -/** - * Lazy double. - * Gives simple interface to describe double before creating it. - * - * @author Konstantin Kudryashov - */ -class LazyDouble -{ - private $doubler; - private $class; - private $interfaces = array(); - private $arguments = null; - private $double; /** - * Initializes lazy double. + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. * - * @param Doubler $doubler + * @param Countable|array $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException */ - public function __construct(\Prophecy\Doubler\Doubler $doubler) + public static function maxCount($array, $max, $message = '') { - $this->doubler = $doubler; + if (\count($array) > $max) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain at most %2$d elements. Got: %d', \count($array), $max)); + } } /** - * Tells doubler to use specific class as parent one for double. + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. * - * @param string|ReflectionClass $class + * @param Countable|array $array + * @param int|float $min + * @param int|float $max + * @param string $message * - * @throws \Prophecy\Exception\Doubler\ClassNotFoundException - * @throws \Prophecy\Exception\Doubler\DoubleException + * @throws InvalidArgumentException */ - public function setParentClass($class) + public static function countBetween($array, $min, $max, $message = '') { - if (null !== $this->double) { - throw new DoubleException('Can not extend class with already instantiated double.'); - } - if (!$class instanceof ReflectionClass) { - if (!\class_exists($class)) { - throw new ClassNotFoundException(\sprintf('Class %s not found.', $class), $class); - } - $class = new ReflectionClass($class); + $count = \count($array); + if ($count < $min || $count > $max) { + static::reportInvalidArgument(\sprintf($message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', $count, $min, $max)); } - $this->class = $class; } /** - * Tells doubler to implement specific interface with double. + * @psalm-pure + * @psalm-assert list $array * - * @param string|ReflectionClass $interface + * @param mixed $array + * @param string $message * - * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException - * @throws \Prophecy\Exception\Doubler\DoubleException + * @throws InvalidArgumentException */ - public function addInterface($interface) + public static function isList($array, $message = '') { - if (null !== $this->double) { - throw new DoubleException('Can not implement interface with already instantiated double.'); + if (!\is_array($array)) { + static::reportInvalidArgument($message ?: 'Expected list - non-associative array.'); } - if (!$interface instanceof ReflectionClass) { - if (!\interface_exists($interface)) { - throw new InterfaceNotFoundException(\sprintf('Interface %s not found.', $interface), $interface); + if ($array === \array_values($array)) { + return; + } + $nextKey = -1; + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + static::reportInvalidArgument($message ?: 'Expected list - non-associative array.'); } - $interface = new ReflectionClass($interface); } - $this->interfaces[] = $interface; } /** - * Sets constructor arguments. + * @psalm-pure + * @psalm-assert non-empty-list $array * - * @param array $arguments + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException */ - public function setArguments(array $arguments = null) + public static function isNonEmptyList($array, $message = '') { - $this->arguments = $arguments; + static::isList($array, $message); + static::notEmpty($array, $message); } /** - * Creates double instance or returns already created one. + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array * - * @return DoubleInterface + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException */ - public function getInstance() + public static function isMap($array, $message = '') { - if (null === $this->double) { - if (null !== $this->arguments) { - return $this->double = $this->doubler->double($this->class, $this->interfaces, $this->arguments); - } - $this->double = $this->doubler->double($this->class, $this->interfaces); + if (!\is_array($array) || \array_keys($array) !== \array_filter(\array_keys($array), '\\is_string')) { + static::reportInvalidArgument($message ?: 'Expected map - associative array with string keys.'); } - return $this->double; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler; - -use ReflectionClass; -/** - * Cached class doubler. - * Prevents mirroring/creation of the same structure twice. - * - * @author Konstantin Kudryashov - */ -class CachedDoubler extends \Prophecy\Doubler\Doubler -{ - private static $classes = array(); /** - * {@inheritdoc} + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array + * @psalm-assert !empty $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException */ - protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + public static function isNonEmptyMap($array, $message = '') { - $classId = $this->generateClassId($class, $interfaces); - if (isset(self::$classes[$classId])) { - return self::$classes[$classId]; - } - return self::$classes[$classId] = parent::createDoubleClass($class, $interfaces); + static::isMap($array, $message); + static::notEmpty($array, $message); } /** - * @param ReflectionClass $class - * @param ReflectionClass[] $interfaces + * @psalm-pure * - * @return string + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException */ - private function generateClassId(ReflectionClass $class = null, array $interfaces) - { - $parts = array(); - if (null !== $class) { - $parts[] = $class->getName(); - } - foreach ($interfaces as $interface) { - $parts[] = $interface->getName(); - } - foreach ($this->getClassPatches() as $patch) { - $parts[] = \get_class($patch); - } - \sort($parts); - return \md5(\implode('', $parts)); - } - public function resetCache() - { - self::$classes = array(); - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler; - -/** - * Core double interface. - * All doubled classes will implement this one. - * - * @author Konstantin Kudryashov - */ -interface DoubleInterface -{ -} -= 80000; - default: - return \false; + $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); + // The nil UUID is special form of UUID that is specified to have all + // 128 bits set to zero. + if ('00000000-0000-0000-0000-000000000000' === $value) { + return; } - } - public function isBuiltInReturnTypeHint($type) - { - if ($type === 'void') { - return \true; + if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { + static::reportInvalidArgument(\sprintf($message ?: 'Value %s is not a valid UUID.', static::valueToString($value))); } - return $this->isBuiltInParamTypeHint($type); - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator; - -use Prophecy\Doubler\Generator\Node\ReturnTypeNode; -use Prophecy\Doubler\Generator\Node\TypeNodeAbstract; -/** - * Class code creator. - * Generates PHP code for specific class node tree. - * - * @author Konstantin Kudryashov - */ -class ClassCodeGenerator -{ - public function __construct(\Prophecy\Doubler\Generator\TypeHintReference $typeHintReference = null) - { } /** - * Generates PHP code for class node. + * @psalm-param class-string $class * - * @param string $classname - * @param Node\ClassNode $class + * @param Closure $expression + * @param string $class + * @param string $message * - * @return string + * @throws InvalidArgumentException */ - public function generate($classname, \Prophecy\Doubler\Generator\Node\ClassNode $class) - { - $parts = \explode('\\', $classname); - $classname = \array_pop($parts); - $namespace = \implode('\\', $parts); - $code = \sprintf("class %s extends \\%s implements %s {\n", $classname, $class->getParentClass(), \implode(', ', \array_map(function ($interface) { - return '\\' . $interface; - }, $class->getInterfaces()))); - foreach ($class->getProperties() as $name => $visibility) { - $code .= \sprintf("%s \$%s;\n", $visibility, $name); - } - $code .= "\n"; - foreach ($class->getMethods() as $method) { - $code .= $this->generateMethod($method) . "\n"; - } - $code .= "\n}"; - return \sprintf("namespace %s {\n%s\n}", $namespace, $code); - } - private function generateMethod(\Prophecy\Doubler\Generator\Node\MethodNode $method) - { - $php = \sprintf("%s %s function %s%s(%s)%s {\n", $method->getVisibility(), $method->isStatic() ? 'static' : '', $method->returnsReference() ? '&' : '', $method->getName(), \implode(', ', $this->generateArguments($method->getArguments())), ($ret = $this->generateTypes($method->getReturnTypeNode())) ? ': ' . $ret : ''); - $php .= $method->getCode() . "\n"; - return $php . '}'; - } - private function generateTypes(TypeNodeAbstract $typeNode) : string - { - if (!$typeNode->getTypes()) { - return ''; - } - // When we require PHP 8 we can stop generating ?foo nullables and remove this first block - if ($typeNode->canUseNullShorthand()) { - return \sprintf('?%s', $typeNode->getNonNullTypes()[0]); - } else { - return \join('|', $typeNode->getTypes()); - } - } - private function generateArguments(array $arguments) + public static function throws(Closure $expression, $class = 'Exception', $message = '') { - return \array_map(function (\Prophecy\Doubler\Generator\Node\ArgumentNode $argument) { - $php = $this->generateTypes($argument->getTypeNode()); - $php .= ' ' . ($argument->isPassedByReference() ? '&' : ''); - $php .= $argument->isVariadic() ? '...' : ''; - $php .= '$' . $argument->getName(); - if ($argument->isOptional() && !$argument->isVariadic()) { - $php .= ' = ' . \var_export($argument->getDefault(), \true); + static::string($class); + $actual = 'none'; + try { + $expression(); + } catch (Exception $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; } - return $php; - }, $arguments); + } catch (Throwable $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } + static::reportInvalidArgument($message ?: \sprintf('Expected to throw "%s", got "%s"', $class, $actual)); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator; - -use Prophecy\Exception\Doubler\ClassCreatorException; -/** - * Class creator. - * Creates specific class in current environment. - * - * @author Konstantin Kudryashov - */ -class ClassCreator -{ - private $generator; /** - * Initializes creator. - * - * @param ClassCodeGenerator $generator + * @throws BadMethodCallException */ - public function __construct(\Prophecy\Doubler\Generator\ClassCodeGenerator $generator = null) + public static function __callStatic($name, $arguments) { - $this->generator = $generator ?: new \Prophecy\Doubler\Generator\ClassCodeGenerator(); + if ('nullOr' === \substr($name, 0, 6)) { + if (null !== $arguments[0]) { + $method = \lcfirst(\substr($name, 6)); + \call_user_func_array(array(static::class, $method), $arguments); + } + return; + } + if ('all' === \substr($name, 0, 3)) { + static::isIterable($arguments[0]); + $method = \lcfirst(\substr($name, 3)); + $args = $arguments; + foreach ($arguments[0] as $entry) { + $args[0] = $entry; + \call_user_func_array(array(static::class, $method), $args); + } + return; + } + throw new BadMethodCallException('No such method: ' . $name); } /** - * Creates class. - * - * @param string $classname - * @param Node\ClassNode $class - * - * @return mixed + * @param mixed $value * - * @throws \Prophecy\Exception\Doubler\ClassCreatorException + * @return string */ - public function create($classname, \Prophecy\Doubler\Generator\Node\ClassNode $class) + protected static function valueToString($value) { - $code = $this->generator->generate($classname, $class); - $return = eval($code); - if (!\class_exists($classname, \false)) { - if (\count($class->getInterfaces())) { - throw new ClassCreatorException(\sprintf('Could not double `%s` and implement interfaces: [%s].', $class->getParentClass(), \implode(', ', $class->getInterfaces())), $class); + if (null === $value) { + return 'null'; + } + if (\true === $value) { + return 'true'; + } + if (\false === $value) { + return 'false'; + } + if (\is_array($value)) { + return 'array'; + } + if (\is_object($value)) { + if (\method_exists($value, '__toString')) { + return \get_class($value) . ': ' . self::valueToString($value->__toString()); } - throw new ClassCreatorException(\sprintf('Could not double `%s`.', $class->getParentClass()), $class); + if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { + return \get_class($value) . ': ' . self::valueToString($value->format('c')); + } + return \get_class($value); } - return $return; + if (\is_resource($value)) { + return 'resource'; + } + if (\is_string($value)) { + return '"' . $value . '"'; + } + return (string) $value; } -} -types['void']) && \count($this->types) !== 1) { - throw new DoubleException('void cannot be part of a union'); + if (!\function_exists('mb_detect_encoding')) { + return \strlen($value); } - if (isset($this->types['never']) && \count($this->types) !== 1) { - throw new DoubleException('never cannot be part of a union'); + if (\false === ($encoding = \mb_detect_encoding($value))) { + return \strlen($value); } - parent::guardIsValidType(); + return \mb_strlen($value, $encoding); } /** - * @deprecated use hasReturnStatement + * @param string $message + * + * @throws InvalidArgumentException + * + * @psalm-pure this method is not supposed to perform side-effects + * @psalm-return never */ - public function isVoid() + protected static function reportInvalidArgument($message) { - return $this->types == ['void' => 'void']; + throw new InvalidArgumentException($message); } - public function hasReturnStatement() : bool + private function __construct() { - return $this->types !== ['void' => 'void'] && $this->types !== ['never' => 'never']; } } + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Webmozart\Assert; -use Prophecy\Exception\Doubler\DoubleException; -class ArgumentTypeNode extends \Prophecy\Doubler\Generator\Node\TypeNodeAbstract +class InvalidArgumentException extends \InvalidArgumentException { } -getRealType($type); - $this->types[$type] = $type; - } - $this->guardIsValidType(); - } - public function canUseNullShorthand() : bool - { - return isset($this->types['null']) && \count($this->types) <= 2; - } - public function getTypes() : array - { - return \array_values($this->types); - } - public function getNonNullTypes() : array - { - $nonNullTypes = $this->types; - unset($nonNullTypes['null']); - return \array_values($nonNullTypes); - } - protected function prefixWithNsSeparator(string $type) : string - { - return '\\' . \ltrim($type, '\\'); - } - protected function getRealType(string $type) : string - { - switch ($type) { - // type aliases - case 'double': - case 'real': - return 'float'; - case 'boolean': - return 'bool'; - case 'integer': - return 'int'; - // built in types - case 'self': - case 'array': - case 'callable': - case 'bool': - case 'false': - case 'float': - case 'int': - case 'string': - case 'iterable': - case 'object': - case 'null': - return $type; - case 'mixed': - return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; - default: - return $this->prefixWithNsSeparator($type); - } - } - protected function guardIsValidType() - { - if ($this->types == ['null' => 'null']) { - throw new DoubleException('Type cannot be standalone null'); - } - if ($this->types == ['false' => 'false']) { - throw new DoubleException('Type cannot be standalone false'); - } - if ($this->types == ['false' => 'false', 'null' => 'null']) { - throw new DoubleException('Type cannot be nullable false'); - } - if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && \count($this->types) !== 1) { - throw new DoubleException('mixed cannot be part of a union'); - } - } -} +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. - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator\Node; +namespace PHPUnit\Webmozart\Assert; -use Prophecy\Exception\Doubler\MethodNotExtendableException; -use Prophecy\Exception\InvalidArgumentException; +use ArrayAccess; +use Closure; +use Countable; +use Throwable; /** - * Class node. - * - * @author Konstantin Kudryashov + * This trait provides nurllOr*, all* and allNullOr* variants of assertion base methods. + * Do not use this trait directly: it will change, and is not designed for reuse. */ -class ClassNode +trait Mixin { - private $parentClass = 'stdClass'; - private $interfaces = array(); - private $properties = array(); - private $unextendableMethods = array(); - /** - * @var MethodNode[] - */ - private $methods = array(); - public function getParentClass() - { - return $this->parentClass; - } /** - * @param string $class + * @psalm-pure + * @psalm-assert string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setParentClass($class) + public static function nullOrString($value, $message = '') { - $this->parentClass = $class ?: 'stdClass'; + null === $value || static::string($value, $message); } /** - * @return string[] + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getInterfaces() + public static function allString($value, $message = '') { - return $this->interfaces; + static::isIterable($value); + foreach ($value as $entry) { + static::string($entry, $message); + } } /** - * @param string $interface + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function addInterface($interface) + public static function allNullOrString($value, $message = '') { - if ($this->hasInterface($interface)) { - return; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::string($entry, $message); } - \array_unshift($this->interfaces, $interface); } /** - * @param string $interface + * @psalm-pure + * @psalm-assert non-empty-string|null $value * - * @return bool + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function hasInterface($interface) - { - return \in_array($interface, $this->interfaces); - } - public function getProperties() - { - return $this->properties; - } - public function addProperty($name, $visibility = 'public') + public static function nullOrStringNotEmpty($value, $message = '') { - $visibility = \strtolower($visibility); - if (!\in_array($visibility, array('public', 'private', 'protected'))) { - throw new InvalidArgumentException(\sprintf('`%s` property visibility is not supported.', $visibility)); - } - $this->properties[$name] = $visibility; + null === $value || static::stringNotEmpty($value, $message); } /** - * @return MethodNode[] + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getMethods() - { - return $this->methods; - } - public function addMethod(\Prophecy\Doubler\Generator\Node\MethodNode $method, $force = \false) + public static function allStringNotEmpty($value, $message = '') { - if (!$this->isExtendable($method->getName())) { - $message = \sprintf('Method `%s` is not extendable, so can not be added.', $method->getName()); - throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); - } - if ($force || !isset($this->methods[$method->getName()])) { - $this->methods[$method->getName()] = $method; + static::isIterable($value); + foreach ($value as $entry) { + static::stringNotEmpty($entry, $message); } } - public function removeMethod($name) - { - unset($this->methods[$name]); - } /** - * @param string $name + * @psalm-pure + * @psalm-assert iterable $value * - * @return MethodNode|null + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getMethod($name) + public static function allNullOrStringNotEmpty($value, $message = '') { - return $this->hasMethod($name) ? $this->methods[$name] : null; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::stringNotEmpty($entry, $message); + } } /** - * @param string $name + * @psalm-pure + * @psalm-assert int|null $value * - * @return bool + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function hasMethod($name) + public static function nullOrInteger($value, $message = '') { - return isset($this->methods[$name]); + null === $value || static::integer($value, $message); } /** - * @return string[] + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getUnextendableMethods() + public static function allInteger($value, $message = '') { - return $this->unextendableMethods; + static::isIterable($value); + foreach ($value as $entry) { + static::integer($entry, $message); + } } /** - * @param string $unextendableMethod + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function addUnextendableMethod($unextendableMethod) + public static function allNullOrInteger($value, $message = '') { - if (!$this->isExtendable($unextendableMethod)) { - return; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::integer($entry, $message); } - $this->unextendableMethods[] = $unextendableMethod; } /** - * @param string $method - * @return bool + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isExtendable($method) + public static function nullOrIntegerish($value, $message = '') { - return !\in_array($method, $this->unextendableMethods); + null === $value || static::integerish($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator\Node; - -use Prophecy\Doubler\Generator\TypeHintReference; -use Prophecy\Exception\InvalidArgumentException; -/** - * Method node. - * - * @author Konstantin Kudryashov - */ -class MethodNode -{ - private $name; - private $code; - private $visibility = 'public'; - private $static = \false; - private $returnsReference = \false; - /** @var ReturnTypeNode */ - private $returnTypeNode; - /** - * @var ArgumentNode[] - */ - private $arguments = array(); /** - * @param string $name - * @param string $code + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($name, $code = null, TypeHintReference $typeHintReference = null) - { - $this->name = $name; - $this->code = $code; - $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode(); - } - public function getVisibility() + public static function allIntegerish($value, $message = '') { - return $this->visibility; + static::isIterable($value); + foreach ($value as $entry) { + static::integerish($entry, $message); + } } /** - * @param string $visibility + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setVisibility($visibility) + public static function allNullOrIntegerish($value, $message = '') { - $visibility = \strtolower($visibility); - if (!\in_array($visibility, array('public', 'private', 'protected'))) { - throw new InvalidArgumentException(\sprintf('`%s` method visibility is not supported.', $visibility)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::integerish($entry, $message); } - $this->visibility = $visibility; - } - public function isStatic() - { - return $this->static; - } - public function setStatic($static = \true) - { - $this->static = (bool) $static; - } - public function returnsReference() - { - return $this->returnsReference; } - public function setReturnsReference() - { - $this->returnsReference = \true; - } - public function getName() + /** + * @psalm-pure + * @psalm-assert positive-int|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPositiveInteger($value, $message = '') { - return $this->name; + null === $value || static::positiveInteger($value, $message); } - public function addArgument(\Prophecy\Doubler\Generator\Node\ArgumentNode $argument) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPositiveInteger($value, $message = '') { - $this->arguments[] = $argument; + static::isIterable($value); + foreach ($value as $entry) { + static::positiveInteger($entry, $message); + } } /** - * @return ArgumentNode[] + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getArguments() + public static function allNullOrPositiveInteger($value, $message = '') { - return $this->arguments; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::positiveInteger($entry, $message); + } } /** - * @deprecated use getReturnTypeNode instead - * @return bool + * @psalm-pure + * @psalm-assert float|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function hasReturnType() + public static function nullOrFloat($value, $message = '') { - return (bool) $this->returnTypeNode->getNonNullTypes(); + null === $value || static::float($value, $message); } - public function setReturnTypeNode(\Prophecy\Doubler\Generator\Node\ReturnTypeNode $returnTypeNode) : void + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFloat($value, $message = '') { - $this->returnTypeNode = $returnTypeNode; + static::isIterable($value); + foreach ($value as $entry) { + static::float($entry, $message); + } } /** - * @deprecated use setReturnTypeNode instead - * @param string $type + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setReturnType($type = null) + public static function allNullOrFloat($value, $message = '') { - $this->returnTypeNode = $type === '' || $type === null ? new \Prophecy\Doubler\Generator\Node\ReturnTypeNode() : new \Prophecy\Doubler\Generator\Node\ReturnTypeNode($type); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::float($entry, $message); + } } /** - * @deprecated use setReturnTypeNode instead - * @param bool $bool + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setNullableReturnType($bool = \true) + public static function nullOrNumeric($value, $message = '') { - if ($bool) { - $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode('null', ...$this->returnTypeNode->getTypes()); - } else { - $this->returnTypeNode = new \Prophecy\Doubler\Generator\Node\ReturnTypeNode(...$this->returnTypeNode->getNonNullTypes()); - } + null === $value || static::numeric($value, $message); } /** - * @deprecated use getReturnTypeNode instead - * @return string|null + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getReturnType() + public static function allNumeric($value, $message = '') { - if ($types = $this->returnTypeNode->getNonNullTypes()) { - return $types[0]; + static::isIterable($value); + foreach ($value as $entry) { + static::numeric($entry, $message); } - return null; } - public function getReturnTypeNode() : \Prophecy\Doubler\Generator\Node\ReturnTypeNode + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNumeric($value, $message = '') { - return $this->returnTypeNode; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::numeric($entry, $message); + } } /** - * @deprecated use getReturnTypeNode instead - * @return bool + * @psalm-pure + * @psalm-assert positive-int|0|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function hasNullableReturnType() + public static function nullOrNatural($value, $message = '') { - return $this->returnTypeNode->canUseNullShorthand(); + null === $value || static::natural($value, $message); } /** - * @param string $code + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setCode($code) + public static function allNatural($value, $message = '') { - $this->code = $code; + static::isIterable($value); + foreach ($value as $entry) { + static::natural($entry, $message); + } } - public function getCode() + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNatural($value, $message = '') { - if ($this->returnsReference) { - return "throw new \\Prophecy\\Exception\\Doubler\\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');"; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::natural($entry, $message); } - return (string) $this->code; } - public function useParentCode() + /** + * @psalm-pure + * @psalm-assert bool|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrBoolean($value, $message = '') { - $this->code = \sprintf('return parent::%s(%s);', $this->getName(), \implode(', ', \array_map(array($this, 'generateArgument'), $this->arguments))); + null === $value || static::boolean($value, $message); } - private function generateArgument(\Prophecy\Doubler\Generator\Node\ArgumentNode $arg) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allBoolean($value, $message = '') { - $argument = '$' . $arg->getName(); - if ($arg->isVariadic()) { - $argument = '...' . $argument; + static::isIterable($value); + foreach ($value as $entry) { + static::boolean($entry, $message); } - return $argument; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator\Node; - -/** - * Argument node. - * - * @author Konstantin Kudryashov - */ -class ArgumentNode -{ - private $name; - private $default; - private $optional = \false; - private $byReference = \false; - private $isVariadic = \false; - /** @var ArgumentTypeNode */ - private $typeNode; /** - * @param string $name + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($name) - { - $this->name = $name; - $this->typeNode = new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode(); - } - public function getName() + public static function allNullOrBoolean($value, $message = '') { - return $this->name; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::boolean($entry, $message); + } } - public function setTypeNode(\Prophecy\Doubler\Generator\Node\ArgumentTypeNode $typeNode) + /** + * @psalm-pure + * @psalm-assert scalar|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrScalar($value, $message = '') { - $this->typeNode = $typeNode; + null === $value || static::scalar($value, $message); } - public function getTypeNode() : \Prophecy\Doubler\Generator\Node\ArgumentTypeNode + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allScalar($value, $message = '') { - return $this->typeNode; + static::isIterable($value); + foreach ($value as $entry) { + static::scalar($entry, $message); + } } - public function hasDefault() + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrScalar($value, $message = '') { - return $this->isOptional() && !$this->isVariadic(); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::scalar($entry, $message); + } } - public function getDefault() + /** + * @psalm-pure + * @psalm-assert object|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrObject($value, $message = '') { - return $this->default; + null === $value || static::object($value, $message); } - public function setDefault($default = null) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allObject($value, $message = '') { - $this->optional = \true; - $this->default = $default; + static::isIterable($value); + foreach ($value as $entry) { + static::object($entry, $message); + } } - public function isOptional() + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrObject($value, $message = '') { - return $this->optional; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::object($entry, $message); + } } - public function setAsPassedByReference($byReference = \true) + /** + * @psalm-pure + * @psalm-assert resource|null $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrResource($value, $type = null, $message = '') { - $this->byReference = $byReference; + null === $value || static::resource($value, $type, $message); } - public function isPassedByReference() + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allResource($value, $type = null, $message = '') { - return $this->byReference; + static::isIterable($value); + foreach ($value as $entry) { + static::resource($entry, $type, $message); + } } - public function setAsVariadic($isVariadic = \true) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrResource($value, $type = null, $message = '') { - $this->isVariadic = $isVariadic; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::resource($entry, $type, $message); + } } - public function isVariadic() + /** + * @psalm-pure + * @psalm-assert callable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCallable($value, $message = '') { - return $this->isVariadic; + null === $value || static::isCallable($value, $message); } /** - * @deprecated use getArgumentTypeNode instead - * @return string|null + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getTypeHint() + public static function allIsCallable($value, $message = '') { - $type = $this->typeNode->getNonNullTypes() ? $this->typeNode->getNonNullTypes()[0] : null; - return $type ? \ltrim($type, '\\') : null; + static::isIterable($value); + foreach ($value as $entry) { + static::isCallable($entry, $message); + } } /** - * @deprecated use setArgumentTypeNode instead - * @param string|null $typeHint + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setTypeHint($typeHint = null) + public static function allNullOrIsCallable($value, $message = '') { - $this->typeNode = $typeHint === null ? new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode() : new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode($typeHint); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isCallable($entry, $message); + } } /** - * @deprecated use getArgumentTypeNode instead - * @return bool + * @psalm-pure + * @psalm-assert array|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isNullable() + public static function nullOrIsArray($value, $message = '') { - return $this->typeNode->canUseNullShorthand(); + null === $value || static::isArray($value, $message); } /** - * @deprecated use getArgumentTypeNode instead - * @param bool $isNullable + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setAsNullable($isNullable = \true) + public static function allIsArray($value, $message = '') { - $nonNullTypes = $this->typeNode->getNonNullTypes(); - $this->typeNode = $isNullable ? new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode('null', ...$nonNullTypes) : new \Prophecy\Doubler\Generator\Node\ArgumentTypeNode(...$nonNullTypes); + static::isIterable($value); + foreach ($value as $entry) { + static::isArray($entry, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator; - -use Prophecy\Doubler\Generator\Node\ArgumentTypeNode; -use Prophecy\Doubler\Generator\Node\ReturnTypeNode; -use Prophecy\Exception\InvalidArgumentException; -use Prophecy\Exception\Doubler\ClassMirrorException; -use ReflectionClass; -use PHPUnit\ReflectionIntersectionType; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionParameter; -use ReflectionType; -use ReflectionUnionType; -/** - * Class mirror. - * Core doubler class. Mirrors specific class and/or interfaces into class node tree. - * - * @author Konstantin Kudryashov - */ -class ClassMirror -{ - private static $reflectableMethods = array('__construct', '__destruct', '__sleep', '__wakeup', '__toString', '__call', '__invoke'); /** - * Reflects provided arguments into class node. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ReflectionClass|null $class - * @param ReflectionClass[] $interfaces + * @param mixed $value + * @param string $message * - * @return Node\ClassNode + * @throws InvalidArgumentException * + * @return void */ - public function reflect(?ReflectionClass $class, array $interfaces) + public static function allNullOrIsArray($value, $message = '') { - $node = new \Prophecy\Doubler\Generator\Node\ClassNode(); - if (null !== $class) { - if (\true === $class->isInterface()) { - throw new InvalidArgumentException(\sprintf("Could not reflect %s as a class, because it\n" . "is interface - use the second argument instead.", $class->getName())); - } - $this->reflectClassToNode($class, $node); - } - foreach ($interfaces as $interface) { - if (!$interface instanceof ReflectionClass) { - throw new InvalidArgumentException(\sprintf("[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n" . "a second argument to `ClassMirror::reflect(...)`, but got %s.", \is_object($interface) ? \get_class($interface) . ' class' : \gettype($interface))); - } - if (\false === $interface->isInterface()) { - throw new InvalidArgumentException(\sprintf("Could not reflect %s as an interface, because it\n" . "is class - use the first argument instead.", $interface->getName())); - } - $this->reflectInterfaceToNode($interface, $node); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isArray($entry, $message); } - $node->addInterface('Prophecy\\Doubler\\Generator\\ReflectionInterface'); - return $node; } - private function reflectClassToNode(ReflectionClass $class, \Prophecy\Doubler\Generator\Node\ClassNode $node) + /** + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsTraversable($value, $message = '') + { + null === $value || static::isTraversable($value, $message); + } + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsTraversable($value, $message = '') { - if (\true === $class->isFinal()) { - throw new ClassMirrorException(\sprintf('Could not reflect class %s as it is marked final.', $class->getName()), $class); - } - $node->setParentClass($class->getName()); - foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { - if (\false === $method->isProtected()) { - continue; - } - $this->reflectMethodToNode($method, $node); - } - foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - if (0 === \strpos($method->getName(), '_') && !\in_array($method->getName(), self::$reflectableMethods)) { - continue; - } - if (\true === $method->isFinal()) { - $node->addUnextendableMethod($method->getName()); - continue; - } - $this->reflectMethodToNode($method, $node); + static::isIterable($value); + foreach ($value as $entry) { + static::isTraversable($entry, $message); } } - private function reflectInterfaceToNode(ReflectionClass $interface, \Prophecy\Doubler\Generator\Node\ClassNode $node) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsTraversable($value, $message = '') { - $node->addInterface($interface->getName()); - foreach ($interface->getMethods() as $method) { - $this->reflectMethodToNode($method, $node); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isTraversable($entry, $message); } } - private function reflectMethodToNode(ReflectionMethod $method, \Prophecy\Doubler\Generator\Node\ClassNode $classNode) + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsArrayAccessible($value, $message = '') { - $node = new \Prophecy\Doubler\Generator\Node\MethodNode($method->getName()); - if (\true === $method->isProtected()) { - $node->setVisibility('protected'); - } - if (\true === $method->isStatic()) { - $node->setStatic(); - } - if (\true === $method->returnsReference()) { - $node->setReturnsReference(); - } - if ($method->hasReturnType()) { - $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()->allowsNull()); - $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); - } elseif (\method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) { - $returnTypes = $this->getTypeHints($method->getTentativeReturnType(), $method->getDeclaringClass(), $method->getTentativeReturnType()->allowsNull()); - $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); - } - if (\is_array($params = $method->getParameters()) && \count($params)) { - foreach ($params as $param) { - $this->reflectArgumentToNode($param, $node); - } - } - $classNode->addMethod($node); + null === $value || static::isArrayAccessible($value, $message); } - private function reflectArgumentToNode(ReflectionParameter $parameter, \Prophecy\Doubler\Generator\Node\MethodNode $methodNode) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsArrayAccessible($value, $message = '') { - $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); - $node = new \Prophecy\Doubler\Generator\Node\ArgumentNode($name); - $typeHints = $this->getTypeHints($parameter->getType(), $parameter->getDeclaringClass(), $parameter->allowsNull()); - $node->setTypeNode(new ArgumentTypeNode(...$typeHints)); - if ($parameter->isVariadic()) { - $node->setAsVariadic(); - } - if ($this->hasDefaultValue($parameter)) { - $node->setDefault($this->getDefaultValue($parameter)); - } - if ($parameter->isPassedByReference()) { - $node->setAsPassedByReference(); + static::isIterable($value); + foreach ($value as $entry) { + static::isArrayAccessible($entry, $message); } - $methodNode->addArgument($node); } - private function hasDefaultValue(ReflectionParameter $parameter) + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsArrayAccessible($value, $message = '') { - if ($parameter->isVariadic()) { - return \false; - } - if ($parameter->isDefaultValueAvailable()) { - return \true; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isArrayAccessible($entry, $message); } - return $parameter->isOptional() || $parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100; } - private function getDefaultValue(ReflectionParameter $parameter) + /** + * @psalm-pure + * @psalm-assert countable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCountable($value, $message = '') { - if (!$parameter->isDefaultValueAvailable()) { - return null; - } - return $parameter->getDefaultValue(); + null === $value || static::isCountable($value, $message); } - private function getTypeHints(?ReflectionType $type, ?ReflectionClass $class, bool $allowsNull) : array + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsCountable($value, $message = '') { - $types = []; - if ($type instanceof ReflectionNamedType) { - $types = [$type->getName()]; - } elseif ($type instanceof ReflectionUnionType) { - $types = $type->getTypes(); - } elseif ($type instanceof ReflectionIntersectionType) { - throw new ClassMirrorException('Doubling intersection types is not supported', $class); - } elseif (\is_object($type)) { - throw new ClassMirrorException('Unknown reflection type ' . \get_class($type), $class); - } - $types = \array_map(function (string $type) use($class) { - if ($type === 'self') { - return $class->getName(); - } - if ($type === 'parent') { - return $class->getParentClass()->getName(); - } - return $type; - }, $types); - if ($types && $types != ['mixed'] && $allowsNull) { - $types[] = 'null'; + static::isIterable($value); + foreach ($value as $entry) { + static::isCountable($entry, $message); } - return $types; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\Generator; - -/** - * Reflection interface. - * All reflected classes implement this interface. - * - * @author Konstantin Kudryashov - */ -interface ReflectionInterface -{ -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler; - -use PHPUnit\Doctrine\Instantiator\Instantiator; -use Prophecy\Doubler\ClassPatch\ClassPatchInterface; -use Prophecy\Doubler\Generator\ClassMirror; -use Prophecy\Doubler\Generator\ClassCreator; -use Prophecy\Exception\InvalidArgumentException; -use ReflectionClass; -/** - * Cached class doubler. - * Prevents mirroring/creation of the same structure twice. - * - * @author Konstantin Kudryashov - */ -class Doubler -{ - private $mirror; - private $creator; - private $namer; /** - * @var ClassPatchInterface[] + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $patches = array(); + public static function allNullOrIsCountable($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isCountable($entry, $message); + } + } /** - * @var \Doctrine\Instantiator\Instantiator + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $instantiator; + public static function nullOrIsIterable($value, $message = '') + { + null === $value || static::isIterable($value, $message); + } /** - * Initializes doubler. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassMirror $mirror - * @param ClassCreator $creator - * @param NameGenerator $namer + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null, \Prophecy\Doubler\NameGenerator $namer = null) + public static function allIsIterable($value, $message = '') { - $this->mirror = $mirror ?: new ClassMirror(); - $this->creator = $creator ?: new ClassCreator(); - $this->namer = $namer ?: new \Prophecy\Doubler\NameGenerator(); + static::isIterable($value); + foreach ($value as $entry) { + static::isIterable($entry, $message); + } } /** - * Returns list of registered class patches. + * @psalm-pure + * @psalm-assert iterable $value * - * @return ClassPatchInterface[] + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getClassPatches() + public static function allNullOrIsIterable($value, $message = '') { - return $this->patches; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isIterable($entry, $message); + } } /** - * Registers new class patch. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|null $value * - * @param ClassPatchInterface $patch + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function registerClassPatch(ClassPatchInterface $patch) + public static function nullOrIsInstanceOf($value, $class, $message = '') { - $this->patches[] = $patch; - @\usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { - return $patch2->getPriority() - $patch1->getPriority(); - }); + null === $value || static::isInstanceOf($value, $class, $message); } /** - * Creates double from specific class or/and list of interfaces. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value * - * @param ReflectionClass $class - * @param ReflectionClass[] $interfaces Array of ReflectionClass instances - * @param array $args Constructor arguments + * @param mixed $value + * @param string|object $class + * @param string $message * - * @return DoubleInterface + * @throws InvalidArgumentException * - * @throws \Prophecy\Exception\InvalidArgumentException + * @return void */ - public function double(ReflectionClass $class = null, array $interfaces, array $args = null) + public static function allIsInstanceOf($value, $class, $message = '') { - foreach ($interfaces as $interface) { - if (!$interface instanceof ReflectionClass) { - throw new InvalidArgumentException(\sprintf("[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n" . "a second argument to `Doubler::double(...)`, but got %s.", \is_object($interface) ? \get_class($interface) . ' class' : \gettype($interface))); - } - } - $classname = $this->createDoubleClass($class, $interfaces); - $reflection = new ReflectionClass($classname); - if (null !== $args) { - return $reflection->newInstanceArgs($args); - } - if (null === ($constructor = $reflection->getConstructor()) || $constructor->isPublic() && !$constructor->isFinal()) { - return $reflection->newInstance(); - } - if (!$this->instantiator) { - $this->instantiator = new Instantiator(); + static::isIterable($value); + foreach ($value as $entry) { + static::isInstanceOf($entry, $class, $message); } - return $this->instantiator->instantiate($classname); } /** - * Creates double class and returns its FQN. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value * - * @param ReflectionClass $class - * @param ReflectionClass[] $interfaces + * @param mixed $value + * @param string|object $class + * @param string $message * - * @return string + * @throws InvalidArgumentException + * + * @return void */ - protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + public static function allNullOrIsInstanceOf($value, $class, $message = '') { - $name = $this->namer->name($class, $interfaces); - $node = $this->mirror->reflect($class, $interfaces); - foreach ($this->patches as $patch) { - if ($patch->supports($node)) { - $patch->apply($node); - } + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isInstanceOf($entry, $class, $message); } - $this->creator->create($name, $node); - return $name; } -} - $class * - * @param ClassNode $node - * @return bool + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function nullOrNotInstanceOf($value, $class, $message = '') { - return $this->implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node); + null === $value || static::notInstanceOf($value, $class, $message); } /** - * @param ClassNode $node - * @return bool + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function implementsAThrowableInterface(ClassNode $node) + public static function allNotInstanceOf($value, $class, $message = '') { - foreach ($node->getInterfaces() as $type) { - if (\is_a($type, 'Throwable', \true)) { - return \true; - } + static::isIterable($value); + foreach ($value as $entry) { + static::notInstanceOf($entry, $class, $message); } - return \false; } /** - * @param ClassNode $node - * @return bool + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function doesNotExtendAThrowableClass(ClassNode $node) + public static function allNullOrNotInstanceOf($value, $class, $message = '') { - return !\is_a($node->getParentClass(), 'Throwable', \true); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notInstanceOf($entry, $class, $message); + } } /** - * Applies patch to the specific class node. + * @psalm-pure + * @psalm-param array $classes * - * @param ClassNode $node + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException * * @return void */ - public function apply(ClassNode $node) + public static function nullOrIsInstanceOfAny($value, $classes, $message = '') { - $this->checkItCanBeDoubled($node); - $this->setParentClassToException($node); + null === $value || static::isInstanceOfAny($value, $classes, $message); } - private function checkItCanBeDoubled(ClassNode $node) + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsInstanceOfAny($value, $classes, $message = '') { - $className = $node->getParentClass(); - if ($className !== 'stdClass') { - throw new ClassCreatorException(\sprintf('Cannot double concrete class %s as well as implement Traversable', $className), $node); + static::isIterable($value); + foreach ($value as $entry) { + static::isInstanceOfAny($entry, $classes, $message); } } - private function setParentClassToException(ClassNode $node) - { - $node->setParentClass('Exception'); - $node->removeMethod('getMessage'); - $node->removeMethod('getCode'); - $node->removeMethod('getFile'); - $node->removeMethod('getLine'); - $node->removeMethod('getTrace'); - $node->removeMethod('getPrevious'); - $node->removeMethod('getNext'); - $node->removeMethod('getTraceAsString'); - } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure + * @psalm-param array $classes * - * @return int Priority number (higher - earlier) + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function allNullOrIsInstanceOfAny($value, $classes, $message = '') { - return 100; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isInstanceOfAny($entry, $classes, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -use Prophecy\Doubler\Generator\Node\MethodNode; -/** - * Disable constructor. - * Makes all constructor arguments optional. - * - * @author Konstantin Kudryashov - */ -class DisableConstructorPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Checks if class has `__construct` method. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|class-string|null $value + * + * @param object|string|null $value + * @param string $class + * @param string $message * - * @param ClassNode $node + * @throws InvalidArgumentException * - * @return bool + * @return void */ - public function supports(ClassNode $node) + public static function nullOrIsAOf($value, $class, $message = '') { - return \true; + null === $value || static::isAOf($value, $class, $message); } /** - * Makes all class constructor arguments optional. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable> $value * - * @param ClassNode $node + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allIsAOf($value, $class, $message = '') { - if (!$node->isExtendable('__construct')) { - return; - } - if (!$node->hasMethod('__construct')) { - $node->addMethod(new MethodNode('__construct', '')); - return; - } - $constructor = $node->getMethod('__construct'); - foreach ($constructor->getArguments() as $argument) { - $argument->setDefault(null); + static::isIterable($value); + foreach ($value as $entry) { + static::isAOf($entry, $class, $message); } - $constructor->setCode(<< $class + * @psalm-assert iterable|null> $value * - * @return int Priority number (higher - earlier) + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() - { - return 100; - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ArgumentNode; -use Prophecy\Doubler\Generator\Node\ClassNode; -use Prophecy\Doubler\Generator\Node\MethodNode; -use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever; -use Prophecy\PhpDocumentor\MethodTagRetrieverInterface; -/** - * Discover Magical API using "@method" PHPDoc format. - * - * @author Thomas Tourlourat - * @author Kévin Dunglas - * @author Théo FIDRY - */ -class MagicCallPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ - const MAGIC_METHODS_WITH_ARGUMENTS = ['__call', '__callStatic', '__get', '__isset', '__set', '__set_state', '__unserialize', '__unset']; - private $tagRetriever; - public function __construct(MethodTagRetrieverInterface $tagRetriever = null) + public static function allNullOrIsAOf($value, $class, $message = '') { - $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isAOf($entry, $class, $message); + } } /** - * Support any class + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class * - * @param ClassNode $node + * @param object|string|null $value + * @param string $class + * @param string $message * - * @return boolean + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function nullOrIsNotA($value, $class, $message = '') { - return \true; + null === $value || static::isNotA($value, $class, $message); } /** - * Discover Magical API + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class * - * @param ClassNode $node + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allIsNotA($value, $class, $message = '') { - $types = \array_filter($node->getInterfaces(), function ($interface) { - return 0 !== \strpos($interface, 'Prophecy\\'); - }); - $types[] = $node->getParentClass(); - foreach ($types as $type) { - $reflectionClass = new \ReflectionClass($type); - while ($reflectionClass) { - $tagList = $this->tagRetriever->getTagList($reflectionClass); - foreach ($tagList as $tag) { - $methodName = $tag->getMethodName(); - if (empty($methodName)) { - continue; - } - if (!$reflectionClass->hasMethod($methodName)) { - $methodNode = new MethodNode($methodName); - // only magic methods can have a contract that needs to be enforced - if (\in_array($methodName, self::MAGIC_METHODS_WITH_ARGUMENTS)) { - foreach ($tag->getArguments() as $argument) { - $argumentNode = new ArgumentNode($argument['name']); - $methodNode->addArgument($argumentNode); - } - } - $methodNode->setStatic($tag->isStatic()); - $node->addMethod($methodNode); - } - } - $reflectionClass = $reflectionClass->getParentClass(); - } + static::isIterable($value); + foreach ($value as $entry) { + static::isNotA($entry, $class, $message); } } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * @psalm-assert iterable|null> $value * - * @return integer Priority number (higher - earlier) + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function allNullOrIsNotA($value, $class, $message = '') { - return 50; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isNotA($entry, $class, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -/** - * Class patch interface. - * Class patches extend doubles functionality or help - * Prophecy to avoid some internal PHP bugs. - * - * @author Konstantin Kudryashov - */ -interface ClassPatchInterface -{ /** - * Checks if patch supports specific class node. + * @psalm-pure + * @psalm-param array $classes * - * @param ClassNode $node + * @param object|string|null $value + * @param string[] $classes + * @param string $message * - * @return bool - */ - public function supports(ClassNode $node); - /** - * Applies patch to the specific class node. + * @throws InvalidArgumentException * - * @param ClassNode $node * @return void */ - public function apply(ClassNode $node); + public static function nullOrIsAnyOf($value, $classes, $message = '') + { + null === $value || static::isAnyOf($value, $classes, $message); + } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure + * @psalm-param array $classes * - * @return int Priority number (higher - earlier) - */ - public function getPriority(); -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ArgumentTypeNode; -use Prophecy\Doubler\Generator\Node\ClassNode; -use Prophecy\Doubler\Generator\Node\MethodNode; -use Prophecy\Doubler\Generator\Node\ArgumentNode; -use Prophecy\Doubler\Generator\Node\ReturnTypeNode; -/** - * Add Prophecy functionality to the double. - * This is a core class patch for Prophecy. - * - * @author Konstantin Kudryashov - */ -class ProphecySubjectPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ - /** - * Always returns true. + * @param iterable $value + * @param string[] $classes + * @param string $message * - * @param ClassNode $node + * @throws InvalidArgumentException * - * @return bool + * @return void */ - public function supports(ClassNode $node) + public static function allIsAnyOf($value, $classes, $message = '') { - return \true; + static::isIterable($value); + foreach ($value as $entry) { + static::isAnyOf($entry, $classes, $message); + } } /** - * Apply Prophecy functionality to class node. + * @psalm-pure + * @psalm-param array $classes * - * @param ClassNode $node + * @param iterable $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allNullOrIsAnyOf($value, $classes, $message = '') { - $node->addInterface('Prophecy\\Prophecy\\ProphecySubjectInterface'); - $node->addProperty('objectProphecyClosure', 'private'); - foreach ($node->getMethods() as $name => $method) { - if ('__construct' === \strtolower($name)) { - continue; - } - if (!$method->getReturnTypeNode()->hasReturnStatement()) { - $method->setCode('$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'); - } else { - $method->setCode('return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'); - } - } - $prophecySetter = new MethodNode('setProphecy'); - $prophecyArgument = new ArgumentNode('prophecy'); - $prophecyArgument->setTypeNode(new ArgumentTypeNode('Prophecy\\Prophecy\\ProphecyInterface')); - $prophecySetter->addArgument($prophecyArgument); - $prophecySetter->setCode(<<objectProphecyClosure) { - \$this->objectProphecyClosure = static function () use (\$prophecy) { - return \$prophecy; - }; -} -PHP -); - $prophecyGetter = new MethodNode('getProphecy'); - $prophecyGetter->setCode('return \\call_user_func($this->objectProphecyClosure);'); - if ($node->hasMethod('__call')) { - $__call = $node->getMethod('__call'); - } else { - $__call = new MethodNode('__call'); - $__call->addArgument(new ArgumentNode('name')); - $__call->addArgument(new ArgumentNode('arguments')); - $node->addMethod($__call, \true); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isAnyOf($entry, $classes, $message); } - $__call->setCode(<<addMethod($prophecySetter, \true); - $node->addMethod($prophecyGetter, \true); } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure + * @psalm-assert empty $value * - * @return int Priority number (higher - earlier) + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function nullOrIsEmpty($value, $message = '') { - return 0; + null === $value || static::isEmpty($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -use Prophecy\Doubler\Generator\Node\MethodNode; -/** - * SplFileInfo patch. - * Makes SplFileInfo and derivative classes usable with Prophecy. - * - * @author Konstantin Kudryashov - */ -class SplFileInfoPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Supports everything that extends SplFileInfo. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message * - * @return bool + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function allIsEmpty($value, $message = '') { - if (null === $node->getParentClass()) { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + static::isEmpty($entry, $message); } - return 'SplFileInfo' === $node->getParentClass() || \is_subclass_of($node->getParentClass(), 'SplFileInfo'); } /** - * Updated constructor code to call parent one with dummy file argument. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allNullOrIsEmpty($value, $message = '') { - if ($node->hasMethod('__construct')) { - $constructor = $node->getMethod('__construct'); - } else { - $constructor = new MethodNode('__construct'); - $node->addMethod($constructor); - } - if ($this->nodeIsDirectoryIterator($node)) { - $constructor->setCode('return parent::__construct("' . __DIR__ . '");'); - return; - } - if ($this->nodeIsSplFileObject($node)) { - $filePath = \str_replace('\\', '\\\\', __FILE__); - $constructor->setCode('return parent::__construct("' . $filePath . '");'); - return; - } - if ($this->nodeIsSymfonySplFileInfo($node)) { - $filePath = \str_replace('\\', '\\\\', __FILE__); - $constructor->setCode('return parent::__construct("' . $filePath . '", "", "");'); - return; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::isEmpty($entry, $message); } - $constructor->useParentCode(); } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure * - * @return int Priority number (higher - earlier) - */ - public function getPriority() - { - return 50; - } - /** - * @param ClassNode $node - * @return boolean - */ - private function nodeIsDirectoryIterator(ClassNode $node) - { - $parent = $node->getParentClass(); - return 'DirectoryIterator' === $parent || \is_subclass_of($parent, 'DirectoryIterator'); - } - /** - * @param ClassNode $node - * @return boolean + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function nodeIsSplFileObject(ClassNode $node) + public static function nullOrNotEmpty($value, $message = '') { - $parent = $node->getParentClass(); - return 'SplFileObject' === $parent || \is_subclass_of($parent, 'SplFileObject'); + null === $value || static::notEmpty($value, $message); } /** - * @param ClassNode $node - * @return boolean + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function nodeIsSymfonySplFileInfo(ClassNode $node) + public static function allNotEmpty($value, $message = '') { - $parent = $node->getParentClass(); - return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; + static::isIterable($value); + foreach ($value as $entry) { + static::notEmpty($entry, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -/** - * Remove method functionality from the double which will clash with php keywords. - * - * @author Milan Magudia - */ -class KeywordPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Support any class + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message * - * @return boolean + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function allNullOrNotEmpty($value, $message = '') { - return \true; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notEmpty($entry, $message); + } } /** - * Remove methods that clash with php keywords + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allNull($value, $message = '') { - $methodNames = \array_keys($node->getMethods()); - $methodsToRemove = \array_intersect($methodNames, $this->getKeywords()); - foreach ($methodsToRemove as $methodName) { - $node->removeMethod($methodName); + static::isIterable($value); + foreach ($value as $entry) { + static::null($entry, $message); } } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure * - * @return int Priority number (higher - earlier) + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function allNotNull($value, $message = '') { - return 49; + static::isIterable($value); + foreach ($value as $entry) { + static::notNull($entry, $message); + } } /** - * Returns array of php keywords. + * @psalm-pure + * @psalm-assert true|null $value * - * @return array + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function getKeywords() + public static function nullOrTrue($value, $message = '') { - return ['__halt_compiler']; + null === $value || static::true($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -/** - * Exception patch for HHVM to remove the stubs from special methods - * - * @author Christophe Coevoet - */ -class HhvmExceptionPatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Supports exceptions on HHVM. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message * - * @return bool + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function allTrue($value, $message = '') { - if (!\defined('HHVM_VERSION')) { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + static::true($entry, $message); } - return 'Exception' === $node->getParentClass() || \is_subclass_of($node->getParentClass(), 'Exception'); } /** - * Removes special exception static methods from the doubled methods. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException * * @return void */ - public function apply(ClassNode $node) + public static function allNullOrTrue($value, $message = '') { - if ($node->hasMethod('setTraceOptions')) { - $node->getMethod('setTraceOptions')->useParentCode(); - } - if ($node->hasMethod('getTraceOptions')) { - $node->getMethod('getTraceOptions')->useParentCode(); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::true($entry, $message); } } /** - * {@inheritdoc} + * @psalm-pure + * @psalm-assert false|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function nullOrFalse($value, $message = '') { - return -50; + null === $value || static::false($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -use Prophecy\Doubler\Generator\Node\MethodNode; -use Prophecy\Doubler\Generator\Node\ReturnTypeNode; -/** - * Traversable interface patch. - * Forces classes that implement interfaces, that extend Traversable to also implement Iterator. - * - * @author Konstantin Kudryashov - */ -class TraversablePatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Supports nodetree, that implement Traversable, but not Iterator or IteratorAggregate. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message * - * @return bool + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function allFalse($value, $message = '') { - if (\in_array('Iterator', $node->getInterfaces())) { - return \false; - } - if (\in_array('IteratorAggregate', $node->getInterfaces())) { - return \false; - } - foreach ($node->getInterfaces() as $interface) { - if ('Traversable' !== $interface && !\is_subclass_of($interface, 'Traversable')) { - continue; - } - if ('Iterator' === $interface || \is_subclass_of($interface, 'Iterator')) { - continue; - } - if ('IteratorAggregate' === $interface || \is_subclass_of($interface, 'IteratorAggregate')) { - continue; - } - return \true; + static::isIterable($value); + foreach ($value as $entry) { + static::false($entry, $message); } - return \false; } /** - * Forces class to implement Iterator interface. + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allNullOrFalse($value, $message = '') { - $node->addInterface('Iterator'); - $currentMethod = new MethodNode('current'); - \PHP_VERSION_ID >= 80100 && $currentMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); - $node->addMethod($currentMethod); - $keyMethod = new MethodNode('key'); - \PHP_VERSION_ID >= 80100 && $keyMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); - $node->addMethod($keyMethod); - $nextMethod = new MethodNode('next'); - \PHP_VERSION_ID >= 80100 && $nextMethod->setReturnTypeNode(new ReturnTypeNode('void')); - $node->addMethod($nextMethod); - $rewindMethod = new MethodNode('rewind'); - \PHP_VERSION_ID >= 80100 && $rewindMethod->setReturnTypeNode(new ReturnTypeNode('void')); - $node->addMethod($rewindMethod); - $validMethod = new MethodNode('valid'); - \PHP_VERSION_ID >= 80100 && $validMethod->setReturnTypeNode(new ReturnTypeNode('bool')); - $node->addMethod($validMethod); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::false($entry, $message); + } } /** - * Returns patch priority, which determines when patch will be applied. + * @psalm-pure * - * @return int Priority number (higher - earlier) + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function nullOrNotFalse($value, $message = '') { - return 100; + null === $value || static::notFalse($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; -/** - * ReflectionClass::newInstance patch. - * Makes first argument of newInstance optional, since it works but signature is misleading - * - * @author Florian Klein - */ -class ReflectionClassNewInstancePatch implements \Prophecy\Doubler\ClassPatch\ClassPatchInterface -{ /** - * Supports ReflectionClass + * @psalm-pure * - * @param ClassNode $node + * @param mixed $value + * @param string $message * - * @return bool + * @throws InvalidArgumentException + * + * @return void */ - public function supports(ClassNode $node) + public static function allNotFalse($value, $message = '') { - return 'ReflectionClass' === $node->getParentClass(); + static::isIterable($value); + foreach ($value as $entry) { + static::notFalse($entry, $message); + } } /** - * Updates newInstance's first argument to make it optional + * @psalm-pure + * @psalm-assert iterable $value * - * @param ClassNode $node + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function apply(ClassNode $node) + public static function allNullOrNotFalse($value, $message = '') { - foreach ($node->getMethod('newInstance')->getArguments() as $argument) { - $argument->setDefault(null); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notFalse($entry, $message); } } /** - * Returns patch priority, which determines when patch will be applied. + * @param mixed $value + * @param string $message * - * @return int Priority number (higher = earlier) + * @throws InvalidArgumentException + * + * @return void */ - public function getPriority() + public static function nullOrIp($value, $message = '') { - return 50; + null === $value || static::ip($value, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Logical NOT token. - * - * @author Boris Mikhaylov - */ -class LogicalNotToken implements \Prophecy\Argument\Token\TokenInterface -{ - /** @var \Prophecy\Argument\Token\TokenInterface */ - private $token; /** - * @param mixed $value exact value or token + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value) + public static function allIp($value, $message = '') { - $this->token = $value instanceof \Prophecy\Argument\Token\TokenInterface ? $value : new \Prophecy\Argument\Token\ExactValueToken($value); + static::isIterable($value); + foreach ($value as $entry) { + static::ip($entry, $message); + } } /** - * Scores 4 when preset token does not match the argument. + * @param mixed $value + * @param string $message * - * @param $argument + * @throws InvalidArgumentException * - * @return bool|int + * @return void */ - public function scoreArgument($argument) + public static function allNullOrIp($value, $message = '') { - return \false === $this->token->scoreArgument($argument) ? 4 : \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::ip($entry, $message); + } } /** - * Returns true if preset token is last. + * @param mixed $value + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function nullOrIpv4($value, $message = '') { - return $this->token->isLast(); + null === $value || static::ipv4($value, $message); } /** - * Returns originating token. + * @param mixed $value + * @param string $message * - * @return TokenInterface + * @throws InvalidArgumentException + * + * @return void */ - public function getOriginatingToken() + public static function allIpv4($value, $message = '') { - return $this->token; + static::isIterable($value); + foreach ($value as $entry) { + static::ipv4($entry, $message); + } } /** - * Returns string representation for token. + * @param mixed $value + * @param string $message * - * @return string + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrIpv4($value, $message = '') { - return \sprintf('not(%s)', $this->token); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::ipv4($entry, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Array every entry token. - * - * @author Adrien Brault - */ -class ArrayEveryEntryToken implements \Prophecy\Argument\Token\TokenInterface -{ /** - * @var TokenInterface + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $value; + public static function nullOrIpv6($value, $message = '') + { + null === $value || static::ipv6($value, $message); + } /** - * @param mixed $value exact value or token + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value) + public static function allIpv6($value, $message = '') { - if (!$value instanceof \Prophecy\Argument\Token\TokenInterface) { - $value = new \Prophecy\Argument\Token\ExactValueToken($value); + static::isIterable($value); + foreach ($value as $entry) { + static::ipv6($entry, $message); } - $this->value = $value; } /** - * {@inheritdoc} + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allNullOrIpv6($value, $message = '') { - if (!$argument instanceof \Traversable && !\is_array($argument)) { - return \false; - } - $scores = array(); - foreach ($argument as $key => $argumentEntry) { - $scores[] = $this->value->scoreArgument($argumentEntry); - } - if (empty($scores) || \in_array(\false, $scores, \true)) { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::ipv6($entry, $message); } - return \array_sum($scores) / \count($scores); } /** - * {@inheritdoc} + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function nullOrEmail($value, $message = '') { - return \false; + null === $value || static::email($value, $message); } /** - * {@inheritdoc} + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allEmail($value, $message = '') { - return \sprintf('[%s, ..., %s]', $this->value, $this->value); + static::isIterable($value); + foreach ($value as $entry) { + static::email($entry, $message); + } } /** - * @return TokenInterface + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getValue() + public static function allNullOrEmail($value, $message = '') { - return $this->value; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::email($entry, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Logical AND token. - * - * @author Boris Mikhaylov - */ -class LogicalAndToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $tokens = array(); /** - * @param array $arguments exact values or tokens + * @param array|null $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(array $arguments) + public static function nullOrUniqueValues($values, $message = '') { - foreach ($arguments as $argument) { - if (!$argument instanceof \Prophecy\Argument\Token\TokenInterface) { - $argument = new \Prophecy\Argument\Token\ExactValueToken($argument); - } - $this->tokens[] = $argument; - } + null === $values || static::uniqueValues($values, $message); } /** - * Scores maximum score from scores returned by tokens for this argument if all of them score. + * @param iterable $values + * @param string $message * - * @param $argument + * @throws InvalidArgumentException * - * @return bool|int + * @return void */ - public function scoreArgument($argument) + public static function allUniqueValues($values, $message = '') { - if (0 === \count($this->tokens)) { - return \false; - } - $maxScore = 0; - foreach ($this->tokens as $token) { - $score = $token->scoreArgument($argument); - if (\false === $score) { - return \false; - } - $maxScore = \max($score, $maxScore); + static::isIterable($values); + foreach ($values as $entry) { + static::uniqueValues($entry, $message); } - return $maxScore; } /** - * Returns false. + * @param iterable $values + * @param string $message * - * @return boolean + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrUniqueValues($values, $message = '') { - return \false; + static::isIterable($values); + foreach ($values as $entry) { + null === $entry || static::uniqueValues($entry, $message); + } } /** - * Returns string representation for token. + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return string + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrEq($value, $expect, $message = '') { - return \sprintf('bool(%s)', \implode(' AND ', $this->tokens)); + null === $value || static::eq($value, $expect, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use Prophecy\Exception\InvalidArgumentException; -/** - * Array entry token. - * - * @author Boris Mikhaylov - */ -class ArrayEntryToken implements \Prophecy\Argument\Token\TokenInterface -{ - /** @var \Prophecy\Argument\Token\TokenInterface */ - private $key; - /** @var \Prophecy\Argument\Token\TokenInterface */ - private $value; /** - * @param mixed $key exact value or token - * @param mixed $value exact value or token + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($key, $value) + public static function allEq($value, $expect, $message = '') { - $this->key = $this->wrapIntoExactValueToken($key); - $this->value = $this->wrapIntoExactValueToken($value); + static::isIterable($value); + foreach ($value as $entry) { + static::eq($entry, $expect, $message); + } } /** - * Scores half of combined scores from key and value tokens for same entry. Capped at 8. - * If argument implements \ArrayAccess without \Traversable, then key token is restricted to ExactValueToken. + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @param array|\ArrayAccess|\Traversable $argument + * @throws InvalidArgumentException * - * @throws \Prophecy\Exception\InvalidArgumentException - * @return bool|int + * @return void */ - public function scoreArgument($argument) + public static function allNullOrEq($value, $expect, $message = '') { - if ($argument instanceof \Traversable) { - $argument = \iterator_to_array($argument); - } - if ($argument instanceof \ArrayAccess) { - $argument = $this->convertArrayAccessToEntry($argument); - } - if (!\is_array($argument) || empty($argument)) { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::eq($entry, $expect, $message); } - $keyScores = \array_map(array($this->key, 'scoreArgument'), \array_keys($argument)); - $valueScores = \array_map(array($this->value, 'scoreArgument'), $argument); - $scoreEntry = function ($value, $key) { - return $value && $key ? \min(8, ($key + $value) / 2) : \false; - }; - return \max(\array_map($scoreEntry, $valueScores, $keyScores)); } /** - * Returns false. + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return boolean + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function nullOrNotEq($value, $expect, $message = '') { - return \false; + null === $value || static::notEq($value, $expect, $message); } /** - * Returns string representation for token. + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException * - * @return string + * @return void */ - public function __toString() + public static function allNotEq($value, $expect, $message = '') { - return \sprintf('[..., %s => %s, ...]', $this->key, $this->value); + static::isIterable($value); + foreach ($value as $entry) { + static::notEq($entry, $expect, $message); + } } /** - * Returns key + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return TokenInterface + * @throws InvalidArgumentException + * + * @return void */ - public function getKey() + public static function allNullOrNotEq($value, $expect, $message = '') { - return $this->key; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notEq($entry, $expect, $message); + } } /** - * Returns value + * @psalm-pure * - * @return TokenInterface + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getValue() + public static function nullOrSame($value, $expect, $message = '') { - return $this->value; + null === $value || static::same($value, $expect, $message); } /** - * Wraps non token $value into ExactValueToken + * @psalm-pure * - * @param $value - * @return TokenInterface + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function wrapIntoExactValueToken($value) + public static function allSame($value, $expect, $message = '') { - return $value instanceof \Prophecy\Argument\Token\TokenInterface ? $value : new \Prophecy\Argument\Token\ExactValueToken($value); + static::isIterable($value); + foreach ($value as $entry) { + static::same($entry, $expect, $message); + } } /** - * Converts instance of \ArrayAccess to key => value array entry + * @psalm-pure * - * @param \ArrayAccess $object + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return array|null - * @throws \Prophecy\Exception\InvalidArgumentException + * @throws InvalidArgumentException + * + * @return void */ - private function convertArrayAccessToEntry(\ArrayAccess $object) + public static function allNullOrSame($value, $expect, $message = '') { - if (!$this->key instanceof \Prophecy\Argument\Token\ExactValueToken) { - throw new InvalidArgumentException(\sprintf('You can only use exact value tokens to match key of ArrayAccess object' . \PHP_EOL . 'But you used `%s`.', $this->key)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::same($entry, $expect, $message); } - $key = $this->key->getValue(); - return $object->offsetExists($key) ? array($key => $object[$key]) : array(); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Check if values is not in array - * - * @author Vinícius Alonso - */ -class NotInArrayToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $token = array(); - private $strict; /** - * @param array $arguments tokens - * @param bool $strict + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(array $arguments, $strict = \true) + public static function nullOrNotSame($value, $expect, $message = '') { - $this->token = $arguments; - $this->strict = $strict; + null === $value || static::notSame($value, $expect, $message); } /** - * Return scores 8 score if argument is in array. + * @psalm-pure * - * @param $argument + * @param mixed $value + * @param mixed $expect + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allNotSame($value, $expect, $message = '') { - if (\count($this->token) === 0) { - return \false; - } - if (!\in_array($argument, $this->token, $this->strict)) { - return 8; + static::isIterable($value); + foreach ($value as $entry) { + static::notSame($entry, $expect, $message); } - return \false; } /** - * Returns false. + * @psalm-pure * - * @return boolean + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrNotSame($value, $expect, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notSame($entry, $expect, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrGreaterThan($value, $limit, $message = '') { - $arrayAsString = \implode(', ', $this->token); - return "[{$arrayAsString}]"; + null === $value || static::greaterThan($value, $limit, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use Prophecy\Util\StringUtil; -/** - * Identical value token. - * - * @author Florian Voutzinos - */ -class IdenticalValueToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $value; - private $string; - private $util; /** - * Initializes token. + * @psalm-pure * - * @param mixed $value - * @param StringUtil $util + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value, StringUtil $util = null) + public static function allGreaterThan($value, $limit, $message = '') { - $this->value = $value; - $this->util = $util ?: new StringUtil(); + static::isIterable($value); + foreach ($value as $entry) { + static::greaterThan($entry, $limit, $message); + } } /** - * Scores 11 if argument matches preset value. + * @psalm-pure * - * @param $argument + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allNullOrGreaterThan($value, $limit, $message = '') { - return $argument === $this->value ? 11 : \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::greaterThan($entry, $limit, $message); + } } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function nullOrGreaterThanEq($value, $limit, $message = '') { - return \false; + null === $value || static::greaterThanEq($value, $limit, $message); } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allGreaterThanEq($value, $limit, $message = '') { - if (null === $this->string) { - $this->string = \sprintf('identical(%s)', $this->util->stringify($this->value)); + static::isIterable($value); + foreach ($value as $entry) { + static::greaterThanEq($entry, $limit, $message); } - return $this->string; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use Prophecy\Exception\InvalidArgumentException; -/** - * Value type token. - * - * @author Konstantin Kudryashov - */ -class TypeToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $type; /** - * @param string $type + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($type) + public static function allNullOrGreaterThanEq($value, $limit, $message = '') { - $checker = "is_{$type}"; - if (!\function_exists($checker) && !\interface_exists($type) && !\class_exists($type)) { - throw new InvalidArgumentException(\sprintf('Type or class name expected as an argument to TypeToken, but got %s.', $type)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::greaterThanEq($entry, $limit, $message); } - $this->type = $type; } /** - * Scores 5 if argument has the same type this token was constructed with. + * @psalm-pure * - * @param $argument + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function nullOrLessThan($value, $limit, $message = '') { - $checker = "is_{$this->type}"; - if (\function_exists($checker)) { - return \call_user_func($checker, $argument) ? 5 : \false; - } - return $argument instanceof $this->type ? 5 : \false; + null === $value || static::lessThan($value, $limit, $message); } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allLessThan($value, $limit, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + static::lessThan($entry, $limit, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrLessThan($value, $limit, $message = '') { - return \sprintf('type(%s)', $this->type); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::lessThan($entry, $limit, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use Prophecy\Comparator\Factory as ComparatorFactory; -use Prophecy\Util\StringUtil; -/** - * Object state-checker token. - * - * @author Konstantin Kudryashov - */ -class ObjectStateToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $name; - private $value; - private $util; - private $comparatorFactory; /** - * Initializes token. + * @psalm-pure * - * @param string $methodName - * @param mixed $value Expected return value - * @param null|StringUtil $util - * @param ComparatorFactory $comparatorFactory + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($methodName, $value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null) + public static function nullOrLessThanEq($value, $limit, $message = '') { - $this->name = $methodName; - $this->value = $value; - $this->util = $util ?: new StringUtil(); - $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + null === $value || static::lessThanEq($value, $limit, $message); } /** - * Scores 8 if argument is an object, which method returns expected value. + * @psalm-pure * - * @param mixed $argument + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allLessThanEq($value, $limit, $message = '') { - if (\is_object($argument) && \method_exists($argument, $this->name)) { - $actual = \call_user_func(array($argument, $this->name)); - $comparator = $this->comparatorFactory->getComparatorFor($this->value, $actual); - try { - $comparator->assertEquals($this->value, $actual); - return 8; - } catch (ComparisonFailure $failure) { - return \false; - } - } - if (\is_object($argument) && \property_exists($argument, $this->name)) { - return $argument->{$this->name} === $this->value ? 8 : \false; + static::isIterable($value); + foreach ($value as $entry) { + static::lessThanEq($entry, $limit, $message); } - return \false; } /** - * Returns false. + * @psalm-pure * - * @return bool - */ - public function isLast() - { - return \false; - } - /** - * Returns string representation for token. + * @param mixed $value + * @param mixed $limit + * @param string $message * - * @return string + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrLessThanEq($value, $limit, $message = '') { - return \sprintf('state(%s(), %s)', $this->name, $this->util->stringify($this->value)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::lessThanEq($entry, $limit, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Check if values is in array - * - * @author Vinícius Alonso - */ -class InArrayToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $token = array(); - private $strict; /** - * @param array $arguments tokens - * @param bool $strict + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(array $arguments, $strict = \true) + public static function nullOrRange($value, $min, $max, $message = '') { - $this->token = $arguments; - $this->strict = $strict; + null === $value || static::range($value, $min, $max, $message); } /** - * Return scores 8 score if argument is in array. + * @psalm-pure * - * @param $argument + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allRange($value, $min, $max, $message = '') { - if (\count($this->token) === 0) { - return \false; - } - if (\in_array($argument, $this->token, $this->strict)) { - return 8; + static::isIterable($value); + foreach ($value as $entry) { + static::range($entry, $min, $max, $message); } - return \false; } /** - * Returns false. + * @psalm-pure * - * @return boolean + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrRange($value, $min, $max, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::range($entry, $min, $max, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrOneOf($value, $values, $message = '') { - $arrayAsString = \implode(', ', $this->token); - return "[{$arrayAsString}]"; + null === $value || static::oneOf($value, $values, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Any single value token. - * - * @author Konstantin Kudryashov - */ -class AnyValueToken implements \Prophecy\Argument\Token\TokenInterface -{ /** - * Always scores 3 for any argument. + * @psalm-pure * - * @param $argument + * @param mixed $value + * @param array $values + * @param string $message * - * @return int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allOneOf($value, $values, $message = '') { - return 3; + static::isIterable($value); + foreach ($value as $entry) { + static::oneOf($entry, $values, $message); + } } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrOneOf($value, $values, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::oneOf($entry, $values, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() - { - return '*'; - } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Approximate value token - * - * @author Daniel Leech - */ -class ApproximateValueToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $value; - private $precision; - public function __construct($value, $precision = 0) + public static function nullOrInArray($value, $values, $message = '') { - $this->value = $value; - $this->precision = $precision; + null === $value || static::inArray($value, $values, $message); } /** - * {@inheritdoc} + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allInArray($value, $values, $message = '') { - return \round((float) $argument, $this->precision) === \round($this->value, $this->precision) ? 10 : \false; + static::isIterable($value); + foreach ($value as $entry) { + static::inArray($entry, $values, $message); + } } /** - * {@inheritdoc} + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrInArray($value, $values, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::inArray($entry, $values, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrContains($value, $subString, $message = '') { - return \sprintf('≅%s', \round($this->value, $this->precision)); + null === $value || static::contains($value, $subString, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Any values token. - * - * @author Konstantin Kudryashov - */ -class AnyValuesToken implements \Prophecy\Argument\Token\TokenInterface -{ /** - * Always scores 2 for any argument. + * @psalm-pure * - * @param $argument + * @param iterable $value + * @param string $subString + * @param string $message * - * @return int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allContains($value, $subString, $message = '') { - return 2; + static::isIterable($value); + foreach ($value as $entry) { + static::contains($entry, $subString, $message); + } } /** - * Returns true to stop wildcard from processing other tokens. + * @psalm-pure * - * @return bool + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrContains($value, $subString, $message = '') { - return \true; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::contains($entry, $subString, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrNotContains($value, $subString, $message = '') { - return '* [, ...]'; + null === $value || static::notContains($value, $subString, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use Prophecy\Comparator\Factory as ComparatorFactory; -use Prophecy\Util\StringUtil; -/** - * Exact value token. - * - * @author Konstantin Kudryashov - */ -class ExactValueToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $value; - private $string; - private $util; - private $comparatorFactory; /** - * Initializes token. + * @psalm-pure * - * @param mixed $value - * @param StringUtil $util - * @param ComparatorFactory $comparatorFactory + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null) + public static function allNotContains($value, $subString, $message = '') { - $this->value = $value; - $this->util = $util ?: new StringUtil(); - $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + static::isIterable($value); + foreach ($value as $entry) { + static::notContains($entry, $subString, $message); + } } /** - * Scores 10 if argument matches preset value. + * @psalm-pure * - * @param $argument + * @param iterable $value + * @param string $subString + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allNullOrNotContains($value, $subString, $message = '') { - if (\is_object($argument) && \is_object($this->value)) { - $comparator = $this->comparatorFactory->getComparatorFor($argument, $this->value); - try { - $comparator->assertEquals($argument, $this->value); - return 10; - } catch (ComparisonFailure $failure) { - return \false; - } - } - // If either one is an object it should be castable to a string - if (\is_object($argument) xor \is_object($this->value)) { - if (\is_object($argument) && !\method_exists($argument, '__toString')) { - return \false; - } - if (\is_object($this->value) && !\method_exists($this->value, '__toString')) { - return \false; - } - } elseif (\is_numeric($argument) && \is_numeric($this->value)) { - // noop - } elseif (\gettype($argument) !== \gettype($this->value)) { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notContains($entry, $subString, $message); } - return $argument == $this->value ? 10 : \false; } /** - * Returns preset value against which token checks arguments. + * @psalm-pure * - * @return mixed + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getValue() + public static function nullOrNotWhitespaceOnly($value, $message = '') { - return $this->value; + null === $value || static::notWhitespaceOnly($value, $message); } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNotWhitespaceOnly($value, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + static::notWhitespaceOnly($entry, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrNotWhitespaceOnly($value, $message = '') { - if (null === $this->string) { - $this->string = \sprintf('exact(%s)', $this->util->stringify($this->value)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notWhitespaceOnly($entry, $message); } - return $this->string; } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Array elements count token. - * - * @author Boris Mikhaylov - */ -class ArrayCountToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $count; /** - * @param integer $value + * @psalm-pure + * + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value) + public static function nullOrStartsWith($value, $prefix, $message = '') { - $this->count = $value; + null === $value || static::startsWith($value, $prefix, $message); } /** - * Scores 6 when argument has preset number of elements. + * @psalm-pure * - * @param $argument + * @param iterable $value + * @param string $prefix + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function allStartsWith($value, $prefix, $message = '') { - return $this->isCountable($argument) && $this->hasProperCount($argument) ? 6 : \false; + static::isIterable($value); + foreach ($value as $entry) { + static::startsWith($entry, $prefix, $message); + } } /** - * Returns false. + * @psalm-pure * - * @return boolean + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrStartsWith($value, $prefix, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::startsWith($entry, $prefix, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrNotStartsWith($value, $prefix, $message = '') { - return \sprintf('count(%s)', $this->count); + null === $value || static::notStartsWith($value, $prefix, $message); } /** - * Returns true if object is either array or instance of \Countable + * @psalm-pure * - * @param $argument - * @return bool + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function isCountable($argument) + public static function allNotStartsWith($value, $prefix, $message = '') { - return \is_array($argument) || $argument instanceof \Countable; + static::isIterable($value); + foreach ($value as $entry) { + static::notStartsWith($entry, $prefix, $message); + } } /** - * Returns true if $argument has expected number of elements + * @psalm-pure * - * @param array|\Countable $argument + * @param iterable $value + * @param string $prefix + * @param string $message * - * @return bool + * @throws InvalidArgumentException + * + * @return void */ - private function hasProperCount($argument) + public static function allNullOrNotStartsWith($value, $prefix, $message = '') { - return $this->count === \count($argument); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notStartsWith($entry, $prefix, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * String contains token. - * - * @author Peter Mitchell - */ -class StringContainsToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $value; /** - * Initializes token. + * @psalm-pure * - * @param string $value + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct($value) - { - $this->value = $value; - } - public function scoreArgument($argument) + public static function nullOrStartsWithLetter($value, $message = '') { - return \is_string($argument) && \strpos($argument, $this->value) !== \false ? 6 : \false; + null === $value || static::startsWithLetter($value, $message); } /** - * Returns preset value against which token checks arguments. + * @psalm-pure * - * @return mixed + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getValue() + public static function allStartsWithLetter($value, $message = '') { - return $this->value; + static::isIterable($value); + foreach ($value as $entry) { + static::startsWithLetter($entry, $message); + } } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNullOrStartsWithLetter($value, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::startsWithLetter($entry, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param string|null $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function nullOrEndsWith($value, $suffix, $message = '') { - return \sprintf('contains("%s")', $this->value); + null === $value || static::endsWith($value, $suffix, $message); } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -/** - * Argument token interface. - * - * @author Konstantin Kudryashov - */ -interface TokenInterface -{ /** - * Calculates token match score for provided argument. + * @psalm-pure * - * @param $argument + * @param iterable $value + * @param string $suffix + * @param string $message * - * @return bool|int - */ - public function scoreArgument($argument); - /** - * Returns true if this token prevents check of other tokens (is last one). + * @throws InvalidArgumentException * - * @return bool|int + * @return void */ - public function isLast(); + public static function allEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::endsWith($entry, $suffix, $message); + } + } /** - * Returns string representation for token. + * @psalm-pure * - * @return string - */ - public function __toString(); -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument\Token; - -use Prophecy\Exception\InvalidArgumentException; -/** - * Callback-verified token. - * - * @author Konstantin Kudryashov - */ -class CallbackToken implements \Prophecy\Argument\Token\TokenInterface -{ - private $callback; - /** - * Initializes token. + * @param iterable $value + * @param string $suffix + * @param string $message * - * @param callable $callback + * @throws InvalidArgumentException * - * @throws \Prophecy\Exception\InvalidArgumentException + * @return void */ - public function __construct($callback) - { - if (!\is_callable($callback)) { - throw new InvalidArgumentException(\sprintf('Callable expected as an argument to CallbackToken, but got %s.', \gettype($callback))); + public static function allNullOrEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::endsWith($entry, $suffix, $message); } - $this->callback = $callback; } /** - * Scores 7 if callback returns true, false otherwise. + * @psalm-pure * - * @param $argument + * @param string|null $value + * @param string $suffix + * @param string $message * - * @return bool|int + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArgument($argument) + public static function nullOrNotEndsWith($value, $suffix, $message = '') { - return \call_user_func($this->callback, $argument) ? 7 : \false; + null === $value || static::notEndsWith($value, $suffix, $message); } /** - * Returns false. + * @psalm-pure * - * @return bool + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function isLast() + public static function allNotEndsWith($value, $suffix, $message = '') { - return \false; + static::isIterable($value); + foreach ($value as $entry) { + static::notEndsWith($entry, $suffix, $message); + } } /** - * Returns string representation for token. + * @psalm-pure * - * @return string + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrNotEndsWith($value, $suffix, $message = '') { - return 'callback()'; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notEndsWith($entry, $suffix, $message); + } } -} - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Prophecy\Argument; - -/** - * Arguments wildcarding. - * - * @author Konstantin Kudryashov - */ -class ArgumentsWildcard -{ - /** - * @var Token\TokenInterface[] - */ - private $tokens = array(); - private $string; /** - * Initializes wildcard. + * @psalm-pure * - * @param array $arguments Array of argument tokens or values + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(array $arguments) + public static function nullOrRegex($value, $pattern, $message = '') { - foreach ($arguments as $argument) { - if (!$argument instanceof \Prophecy\Argument\Token\TokenInterface) { - $argument = new \Prophecy\Argument\Token\ExactValueToken($argument); - } - $this->tokens[] = $argument; - } + null === $value || static::regex($value, $pattern, $message); } /** - * Calculates wildcard match score for provided arguments. + * @psalm-pure * - * @param array $arguments + * @param iterable $value + * @param string $pattern + * @param string $message * - * @return false|int False OR integer score (higher - better) + * @throws InvalidArgumentException + * + * @return void */ - public function scoreArguments(array $arguments) + public static function allRegex($value, $pattern, $message = '') { - if (0 == \count($arguments) && 0 == \count($this->tokens)) { - return 1; + static::isIterable($value); + foreach ($value as $entry) { + static::regex($entry, $pattern, $message); } - $arguments = \array_values($arguments); - $totalScore = 0; - foreach ($this->tokens as $i => $token) { - $argument = isset($arguments[$i]) ? $arguments[$i] : null; - if (1 >= ($score = $token->scoreArgument($argument))) { - return \false; - } - $totalScore += $score; - if (\true === $token->isLast()) { - return $totalScore; - } - } - if (\count($arguments) > \count($this->tokens)) { - return \false; - } - return $totalScore; } /** - * Returns string representation for wildcard. + * @psalm-pure * - * @return string + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __toString() + public static function allNullOrRegex($value, $pattern, $message = '') { - if (null === $this->string) { - $this->string = \implode(', ', \array_map(function ($token) { - return (string) $token; - }, $this->tokens)); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::regex($entry, $pattern, $message); } - return $this->string; } /** - * @return array + * @psalm-pure + * + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getTokens() + public static function nullOrNotRegex($value, $pattern, $message = '') { - return $this->tokens; + null === $value || static::notRegex($value, $pattern, $message); } -} -Copyright (c) 2013 Konstantin Kudryashov -Copyright (c) 2013 Marcello Duarte - -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. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff\Output; - -use function fclose; -use function fopen; -use function fwrite; -use function stream_get_contents; -use function substr; -use PHPUnit\SebastianBergmann\Diff\Differ; -/** - * Builds a diff string representation in a loose unified diff format - * listing only changes lines. Does not include line numbers. - */ -final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface -{ /** - * @var string + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $header; - public function __construct(string $header = "--- Original\n+++ New\n") - { - $this->header = $header; - } - public function getDiff(array $diff) : string + public static function allNotRegex($value, $pattern, $message = '') { - $buffer = fopen('php://memory', 'r+b'); - if ('' !== $this->header) { - fwrite($buffer, $this->header); - if ("\n" !== substr($this->header, -1, 1)) { - fwrite($buffer, "\n"); - } - } - foreach ($diff as $diffEntry) { - if ($diffEntry[1] === Differ::ADDED) { - fwrite($buffer, '+' . $diffEntry[0]); - } elseif ($diffEntry[1] === Differ::REMOVED) { - fwrite($buffer, '-' . $diffEntry[0]); - } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { - fwrite($buffer, ' ' . $diffEntry[0]); - continue; - // Warnings should not be tested for line break, it will always be there - } else { - /* Not changed (old) 0 */ - continue; - // we didn't write the non changs line, so do not add a line break either - } - $lc = substr($diffEntry[0], -1); - if ($lc !== "\n" && $lc !== "\r") { - fwrite($buffer, "\n"); - // \No newline at end of file - } + static::isIterable($value); + foreach ($value as $entry) { + static::notRegex($entry, $pattern, $message); } - $diff = stream_get_contents($buffer, -1, 0); - fclose($buffer); - return $diff; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff\Output; - -/** - * Defines how an output builder should take a generated - * diff array and return a string representation of that diff. - */ -interface DiffOutputBuilderInterface -{ - public function getDiff(array $diff) : string; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff\Output; - -use function array_splice; -use function count; -use function fclose; -use function fopen; -use function fwrite; -use function max; -use function min; -use function stream_get_contents; -use function strlen; -use function substr; -use PHPUnit\SebastianBergmann\Diff\Differ; -/** - * Builds a diff string representation in unified diff format in chunks. - */ -final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder -{ - /** - * @var bool - */ - private $collapseRanges = \true; /** - * @var int >= 0 - */ - private $commonLineThreshold = 6; - /** - * @var int >= 0 - */ - private $contextLines = 3; - /** - * @var string - */ - private $header; - /** - * @var bool + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $addLineNumbers; - public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = \false) - { - $this->header = $header; - $this->addLineNumbers = $addLineNumbers; - } - public function getDiff(array $diff) : string - { - $buffer = fopen('php://memory', 'r+b'); - if ('' !== $this->header) { - fwrite($buffer, $this->header); - if ("\n" !== substr($this->header, -1, 1)) { - fwrite($buffer, "\n"); - } - } - if (0 !== count($diff)) { - $this->writeDiffHunks($buffer, $diff); - } - $diff = stream_get_contents($buffer, -1, 0); - fclose($buffer); - // If the diff is non-empty and last char is not a linebreak: add it. - // This might happen when both the `from` and `to` do not have a trailing linebreak - $last = substr($diff, -1); - return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff; - } - private function writeDiffHunks($output, array $diff) : void + public static function allNullOrNotRegex($value, $pattern, $message = '') { - // detect "No newline at end of file" and insert into `$diff` if needed - $upperLimit = count($diff); - if (0 === $diff[$upperLimit - 1][1]) { - $lc = substr($diff[$upperLimit - 1][0], -1); - if ("\n" !== $lc) { - array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); - } - } else { - // search back for the last `+` and `-` line, - // check if has trailing linebreak, else add under it warning under it - $toFind = [1 => \true, 2 => \true]; - for ($i = $upperLimit - 1; $i >= 0; --$i) { - if (isset($toFind[$diff[$i][1]])) { - unset($toFind[$diff[$i][1]]); - $lc = substr($diff[$i][0], -1); - if ("\n" !== $lc) { - array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); - } - if (!count($toFind)) { - break; - } - } - } - } - // write hunks to output buffer - $cutOff = max($this->commonLineThreshold, $this->contextLines); - $hunkCapture = \false; - $sameCount = $toRange = $fromRange = 0; - $toStart = $fromStart = 1; - $i = 0; - /** @var int $i */ - foreach ($diff as $i => $entry) { - if (0 === $entry[1]) { - // same - if (\false === $hunkCapture) { - ++$fromStart; - ++$toStart; - continue; - } - ++$sameCount; - ++$toRange; - ++$fromRange; - if ($sameCount === $cutOff) { - $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; - // note: $contextEndOffset = $this->contextLines; - // - // because we never go beyond the end of the diff. - // with the cutoff/contextlines here the follow is never true; - // - // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { - // $contextEndOffset = count($diff) - 1; - // } - // - // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop - $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output); - $fromStart += $fromRange; - $toStart += $toRange; - $hunkCapture = \false; - $sameCount = $toRange = $fromRange = 0; - } - continue; - } - $sameCount = 0; - if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { - continue; - } - if (\false === $hunkCapture) { - $hunkCapture = $i; - } - if (Differ::ADDED === $entry[1]) { - ++$toRange; - } - if (Differ::REMOVED === $entry[1]) { - ++$fromRange; - } + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::notRegex($entry, $pattern, $message); } - if (\false === $hunkCapture) { - return; - } - // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, - // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold - $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; - // prevent trying to write out more common lines than there are in the diff _and_ - // do not write more than configured through the context lines - $contextEndOffset = min($sameCount, $this->contextLines); - $fromRange -= $sameCount; - $toRange -= $sameCount; - $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output); } - private function writeHunk(array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output) : void + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUnicodeLetters($value, $message = '') { - if ($this->addLineNumbers) { - fwrite($output, '@@ -' . $fromStart); - if (!$this->collapseRanges || 1 !== $fromRange) { - fwrite($output, ',' . $fromRange); - } - fwrite($output, ' +' . $toStart); - if (!$this->collapseRanges || 1 !== $toRange) { - fwrite($output, ',' . $toRange); - } - fwrite($output, " @@\n"); - } else { - fwrite($output, "@@ @@\n"); - } - for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { - if ($diff[$i][1] === Differ::ADDED) { - fwrite($output, '+' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::REMOVED) { - fwrite($output, '-' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::OLD) { - fwrite($output, ' ' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { - fwrite($output, "\n"); - // $diff[$i][0] - } else { - /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */ - fwrite($output, ' ' . $diff[$i][0]); - } - } + null === $value || static::unicodeLetters($value, $message); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff\Output; - -use function array_merge; -use function array_splice; -use function count; -use function fclose; -use function fopen; -use function fwrite; -use function is_bool; -use function is_int; -use function is_string; -use function max; -use function min; -use function sprintf; -use function stream_get_contents; -use function substr; -use PHPUnit\SebastianBergmann\Diff\ConfigurationException; -use PHPUnit\SebastianBergmann\Diff\Differ; -/** - * Strict Unified diff output builder. - * - * Generates (strict) Unified diff's (unidiffs) with hunks. - */ -final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface -{ - private static $default = [ - 'collapseRanges' => \true, - // ranges of length one are rendered with the trailing `,1` - 'commonLineThreshold' => 6, - // number of same lines before ending a new hunk and creating a new one (if needed) - 'contextLines' => 3, - // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 - 'fromFile' => null, - 'fromFileDate' => null, - 'toFile' => null, - 'toFileDate' => null, - ]; /** - * @var bool + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $changed; + public static function allUnicodeLetters($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::unicodeLetters($entry, $message); + } + } /** - * @var bool + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $collapseRanges; + public static function allNullOrUnicodeLetters($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::unicodeLetters($entry, $message); + } + } /** - * @var int >= 0 + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $commonLineThreshold; + public static function nullOrAlpha($value, $message = '') + { + null === $value || static::alpha($value, $message); + } /** - * @var string + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $header; + public static function allAlpha($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::alpha($entry, $message); + } + } /** - * @var int >= 0 + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $contextLines; - public function __construct(array $options = []) + public static function allNullOrAlpha($value, $message = '') { - $options = array_merge(self::$default, $options); - if (!is_bool($options['collapseRanges'])) { - throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); - } - if (!is_int($options['contextLines']) || $options['contextLines'] < 0) { - throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::alpha($entry, $message); } - if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { - throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); - } - $this->assertString($options, 'fromFile'); - $this->assertString($options, 'toFile'); - $this->assertStringOrNull($options, 'fromFileDate'); - $this->assertStringOrNull($options, 'toFileDate'); - $this->header = sprintf("--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], $options['toFile'], null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate']); - $this->collapseRanges = $options['collapseRanges']; - $this->commonLineThreshold = $options['commonLineThreshold']; - $this->contextLines = $options['contextLines']; } - public function getDiff(array $diff) : string + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrDigits($value, $message = '') { - if (0 === count($diff)) { - return ''; - } - $this->changed = \false; - $buffer = fopen('php://memory', 'r+b'); - fwrite($buffer, $this->header); - $this->writeDiffHunks($buffer, $diff); - if (!$this->changed) { - fclose($buffer); - return ''; - } - $diff = stream_get_contents($buffer, -1, 0); - fclose($buffer); - // If the last char is not a linebreak: add it. - // This might happen when both the `from` and `to` do not have a trailing linebreak - $last = substr($diff, -1); - return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff; + null === $value || static::digits($value, $message); } - private function writeDiffHunks($output, array $diff) : void + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allDigits($value, $message = '') { - // detect "No newline at end of file" and insert into `$diff` if needed - $upperLimit = count($diff); - if (0 === $diff[$upperLimit - 1][1]) { - $lc = substr($diff[$upperLimit - 1][0], -1); - if ("\n" !== $lc) { - array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); - } - } else { - // search back for the last `+` and `-` line, - // check if has trailing linebreak, else add under it warning under it - $toFind = [1 => \true, 2 => \true]; - for ($i = $upperLimit - 1; $i >= 0; --$i) { - if (isset($toFind[$diff[$i][1]])) { - unset($toFind[$diff[$i][1]]); - $lc = substr($diff[$i][0], -1); - if ("\n" !== $lc) { - array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); - } - if (!count($toFind)) { - break; - } - } - } - } - // write hunks to output buffer - $cutOff = max($this->commonLineThreshold, $this->contextLines); - $hunkCapture = \false; - $sameCount = $toRange = $fromRange = 0; - $toStart = $fromStart = 1; - $i = 0; - /** @var int $i */ - foreach ($diff as $i => $entry) { - if (0 === $entry[1]) { - // same - if (\false === $hunkCapture) { - ++$fromStart; - ++$toStart; - continue; - } - ++$sameCount; - ++$toRange; - ++$fromRange; - if ($sameCount === $cutOff) { - $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; - // note: $contextEndOffset = $this->contextLines; - // - // because we never go beyond the end of the diff. - // with the cutoff/contextlines here the follow is never true; - // - // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { - // $contextEndOffset = count($diff) - 1; - // } - // - // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop - $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output); - $fromStart += $fromRange; - $toStart += $toRange; - $hunkCapture = \false; - $sameCount = $toRange = $fromRange = 0; - } - continue; - } - $sameCount = 0; - if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { - continue; - } - $this->changed = \true; - if (\false === $hunkCapture) { - $hunkCapture = $i; - } - if (Differ::ADDED === $entry[1]) { - // added - ++$toRange; - } - if (Differ::REMOVED === $entry[1]) { - // removed - ++$fromRange; - } + static::isIterable($value); + foreach ($value as $entry) { + static::digits($entry, $message); } - if (\false === $hunkCapture) { - return; + } + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrDigits($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::digits($entry, $message); } - // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, - // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold - $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines; - // prevent trying to write out more common lines than there are in the diff _and_ - // do not write more than configured through the context lines - $contextEndOffset = min($sameCount, $this->contextLines); - $fromRange -= $sameCount; - $toRange -= $sameCount; - $this->writeHunk($diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output); } - private function writeHunk(array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output) : void + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrAlnum($value, $message = '') { - fwrite($output, '@@ -' . $fromStart); - if (!$this->collapseRanges || 1 !== $fromRange) { - fwrite($output, ',' . $fromRange); + null === $value || static::alnum($value, $message); + } + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allAlnum($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::alnum($entry, $message); } - fwrite($output, ' +' . $toStart); - if (!$this->collapseRanges || 1 !== $toRange) { - fwrite($output, ',' . $toRange); + } + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrAlnum($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::alnum($entry, $message); } - fwrite($output, " @@\n"); - for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { - if ($diff[$i][1] === Differ::ADDED) { - $this->changed = \true; - fwrite($output, '+' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::REMOVED) { - $this->changed = \true; - fwrite($output, '-' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::OLD) { - fwrite($output, ' ' . $diff[$i][0]); - } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { - $this->changed = \true; - fwrite($output, $diff[$i][0]); - } - //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package - // skip - //} else { - // unknown/invalid - //} + } + /** + * @psalm-pure + * @psalm-assert lowercase-string|null $value + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLower($value, $message = '') + { + null === $value || static::lower($value, $message); + } + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLower($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::lower($entry, $message); } } - private function assertString(array $options, string $option) : void + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLower($value, $message = '') { - if (!is_string($options[$option])) { - throw new ConfigurationException($option, 'a string', $options[$option]); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::lower($entry, $message); } } - private function assertStringOrNull(array $options, string $option) : void + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUpper($value, $message = '') { - if (null !== $options[$option] && !is_string($options[$option])) { - throw new ConfigurationException($option, 'a string or ', $options[$option]); + null === $value || static::upper($value, $message); + } + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUpper($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::upper($entry, $message); } } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff\Output; - -use function count; -abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface -{ /** - * Takes input of the diff array and returns the common parts. - * Iterates through diff line by line. + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - protected function getCommonChunks(array $diff, int $lineThreshold = 5) : array + public static function allNullOrUpper($value, $message = '') { - $diffSize = count($diff); - $capturing = \false; - $chunkStart = 0; - $chunkSize = 0; - $commonChunks = []; - for ($i = 0; $i < $diffSize; ++$i) { - if ($diff[$i][1] === 0) { - if ($capturing === \false) { - $capturing = \true; - $chunkStart = $i; - $chunkSize = 0; - } else { - ++$chunkSize; - } - } elseif ($capturing !== \false) { - if ($chunkSize >= $lineThreshold) { - $commonChunks[$chunkStart] = $chunkStart + $chunkSize; - } - $capturing = \false; - } + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::upper($entry, $message); } - if ($capturing !== \false && $chunkSize >= $lineThreshold) { - $commonChunks[$chunkStart] = $chunkStart + $chunkSize; + } + /** + * @psalm-pure + * + * @param string|null $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLength($value, $length, $message = '') + { + null === $value || static::length($value, $length, $message); + } + /** + * @psalm-pure + * + * @param iterable $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLength($value, $length, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::length($entry, $length, $message); } - return $commonChunks; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use function get_class; -use function gettype; -use function is_object; -use function sprintf; -use Exception; -final class ConfigurationException extends InvalidArgumentException -{ - public function __construct(string $option, string $expected, $value, int $code = 0, Exception $previous = null) + /** + * @psalm-pure + * + * @param iterable $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLength($value, $length, $message = '') { - parent::__construct(sprintf('Option "%s" must be %s, got "%s".', $option, $expected, is_object($value) ? get_class($value) : (null === $value ? '' : gettype($value) . '#' . $value)), $code, $previous); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::length($entry, $length, $message); + } + } + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinLength($value, $min, $message = '') + { + null === $value || static::minLength($value, $min, $message); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use Throwable; -interface Exception extends Throwable -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -class InvalidArgumentException extends \InvalidArgumentException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use function array_reverse; -use function count; -use function max; -use SplFixedArray; -final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator -{ /** - * {@inheritdoc} + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function calculate(array $from, array $to) : array + public static function allMinLength($value, $min, $message = '') { - $common = []; - $fromLength = count($from); - $toLength = count($to); - $width = $fromLength + 1; - $matrix = new SplFixedArray($width * ($toLength + 1)); - for ($i = 0; $i <= $fromLength; ++$i) { - $matrix[$i] = 0; - } - for ($j = 0; $j <= $toLength; ++$j) { - $matrix[$j * $width] = 0; - } - for ($i = 1; $i <= $fromLength; ++$i) { - for ($j = 1; $j <= $toLength; ++$j) { - $o = $j * $width + $i; - $matrix[$o] = max($matrix[$o - 1], $matrix[$o - $width], $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0); - } - } - $i = $fromLength; - $j = $toLength; - while ($i > 0 && $j > 0) { - if ($from[$i - 1] === $to[$j - 1]) { - $common[] = $from[$i - 1]; - --$i; - --$j; - } else { - $o = $j * $width + $i; - if ($matrix[$o - $width] > $matrix[$o - 1]) { - --$j; - } else { - --$i; - } - } + static::isIterable($value); + foreach ($value as $entry) { + static::minLength($entry, $min, $message); } - return array_reverse($common); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use function array_fill; -use function array_merge; -use function array_reverse; -use function array_slice; -use function count; -use function in_array; -use function max; -final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator -{ /** - * {@inheritdoc} + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function calculate(array $from, array $to) : array + public static function allNullOrMinLength($value, $min, $message = '') { - $cFrom = count($from); - $cTo = count($to); - if ($cFrom === 0) { - return []; - } - if ($cFrom === 1) { - if (in_array($from[0], $to, \true)) { - return [$from[0]]; - } - return []; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::minLength($entry, $min, $message); } - $i = (int) ($cFrom / 2); - $fromStart = array_slice($from, 0, $i); - $fromEnd = array_slice($from, $i); - $llB = $this->length($fromStart, $to); - $llE = $this->length(array_reverse($fromEnd), array_reverse($to)); - $jMax = 0; - $max = 0; - for ($j = 0; $j <= $cTo; $j++) { - $m = $llB[$j] + $llE[$cTo - $j]; - if ($m >= $max) { - $max = $m; - $jMax = $j; - } + } + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxLength($value, $max, $message = '') + { + null === $value || static::maxLength($value, $max, $message); + } + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMaxLength($value, $max, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::maxLength($entry, $max, $message); } - $toStart = array_slice($to, 0, $jMax); - $toEnd = array_slice($to, $jMax); - return array_merge($this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd)); } - private function length(array $from, array $to) : array + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMaxLength($value, $max, $message = '') { - $current = array_fill(0, count($to) + 1, 0); - $cFrom = count($from); - $cTo = count($to); - for ($i = 0; $i < $cFrom; $i++) { - $prev = $current; - for ($j = 0; $j < $cTo; $j++) { - if ($from[$i] === $to[$j]) { - $current[$j + 1] = $prev[$j] + 1; - } else { - $current[$j + 1] = max($current[$j], $prev[$j + 1]); - } - } + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::maxLength($entry, $max, $message); } - return $current; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -final class Chunk -{ /** - * @var int + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $start; + public static function nullOrLengthBetween($value, $min, $max, $message = '') + { + null === $value || static::lengthBetween($value, $min, $max, $message); + } /** - * @var int + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $startRange; + public static function allLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::lengthBetween($entry, $min, $max, $message); + } + } /** - * @var int + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $end; + public static function allNullOrLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::lengthBetween($entry, $min, $max, $message); + } + } /** - * @var int + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $endRange; + public static function nullOrFileExists($value, $message = '') + { + null === $value || static::fileExists($value, $message); + } /** - * @var Line[] + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $lines; - public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) + public static function allFileExists($value, $message = '') { - $this->start = $start; - $this->startRange = $startRange; - $this->end = $end; - $this->endRange = $endRange; - $this->lines = $lines; + static::isIterable($value); + foreach ($value as $entry) { + static::fileExists($entry, $message); + } } - public function getStart() : int + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFileExists($value, $message = '') { - return $this->start; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::fileExists($entry, $message); + } } - public function getStartRange() : int + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFile($value, $message = '') { - return $this->startRange; + null === $value || static::file($value, $message); } - public function getEnd() : int + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFile($value, $message = '') { - return $this->end; + static::isIterable($value); + foreach ($value as $entry) { + static::file($entry, $message); + } } - public function getEndRange() : int + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFile($value, $message = '') { - return $this->endRange; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::file($entry, $message); + } } /** - * @return Line[] + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getLines() : array + public static function nullOrDirectory($value, $message = '') { - return $this->lines; + null === $value || static::directory($value, $message); } /** - * @param Line[] $lines + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setLines(array $lines) : void + public static function allDirectory($value, $message = '') { - foreach ($lines as $line) { - if (!$line instanceof Line) { - throw new InvalidArgumentException(); - } + static::isIterable($value); + foreach ($value as $entry) { + static::directory($entry, $message); } - $this->lines = $lines; } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -final class Diff -{ /** - * @var string + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $from; + public static function allNullOrDirectory($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::directory($entry, $message); + } + } /** - * @var string + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $to; + public static function nullOrReadable($value, $message = '') + { + null === $value || static::readable($value, $message); + } /** - * @var Chunk[] + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $chunks; + public static function allReadable($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::readable($entry, $message); + } + } /** - * @param Chunk[] $chunks + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(string $from, string $to, array $chunks = []) + public static function allNullOrReadable($value, $message = '') { - $this->from = $from; - $this->to = $to; - $this->chunks = $chunks; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::readable($entry, $message); + } } - public function getFrom() : string + /** + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrWritable($value, $message = '') { - return $this->from; + null === $value || static::writable($value, $message); } - public function getTo() : string + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allWritable($value, $message = '') { - return $this->to; + static::isIterable($value); + foreach ($value as $entry) { + static::writable($entry, $message); + } } /** - * @return Chunk[] + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function getChunks() : array + public static function allNullOrWritable($value, $message = '') { - return $this->chunks; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::writable($entry, $message); + } } /** - * @param Chunk[] $chunks + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function setChunks(array $chunks) : void + public static function nullOrClassExists($value, $message = '') { - $this->chunks = $chunks; + null === $value || static::classExists($value, $message); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -interface LongestCommonSubsequenceCalculator -{ /** - * Calculates the longest common subsequence of two arrays. + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function calculate(array $from, array $to) : array; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use const PHP_INT_SIZE; -use const PREG_SPLIT_DELIM_CAPTURE; -use const PREG_SPLIT_NO_EMPTY; -use function array_shift; -use function array_unshift; -use function array_values; -use function count; -use function current; -use function end; -use function get_class; -use function gettype; -use function is_array; -use function is_object; -use function is_string; -use function key; -use function min; -use function preg_split; -use function prev; -use function reset; -use function sprintf; -use function substr; -use PHPUnit\SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; -use PHPUnit\SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; -final class Differ -{ - public const OLD = 0; - public const ADDED = 1; - public const REMOVED = 2; - public const DIFF_LINE_END_WARNING = 3; - public const NO_LINE_END_EOF_WARNING = 4; + public static function allClassExists($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + static::classExists($entry, $message); + } + } /** - * @var DiffOutputBuilderInterface + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrClassExists($value, $message = '') + { + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::classExists($entry, $message); + } + } + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert class-string|ExpectedType|null $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $outputBuilder; + public static function nullOrSubclassOf($value, $class, $message = '') + { + null === $value || static::subclassOf($value, $class, $message); + } /** - * @param DiffOutputBuilderInterface $outputBuilder + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|ExpectedType> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message * * @throws InvalidArgumentException + * + * @return void */ - public function __construct($outputBuilder = null) + public static function allSubclassOf($value, $class, $message = '') { - if ($outputBuilder instanceof DiffOutputBuilderInterface) { - $this->outputBuilder = $outputBuilder; - } elseif (null === $outputBuilder) { - $this->outputBuilder = new UnifiedDiffOutputBuilder(); - } elseif (is_string($outputBuilder)) { - // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support - // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 - // @deprecated - $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); - } else { - throw new InvalidArgumentException(sprintf('Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', is_object($outputBuilder) ? 'instance of "' . get_class($outputBuilder) . '"' : gettype($outputBuilder) . ' "' . $outputBuilder . '"')); + static::isIterable($value); + foreach ($value as $entry) { + static::subclassOf($entry, $class, $message); } } /** - * Returns the diff between two arrays or strings as string. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|ExpectedType|null> $value * - * @param array|string $from - * @param array|string $to + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) : string + public static function allNullOrSubclassOf($value, $class, $message = '') { - $diff = $this->diffToArray($this->normalizeDiffInput($from), $this->normalizeDiffInput($to), $lcs); - return $this->outputBuilder->getDiff($diff); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::subclassOf($entry, $class, $message); + } } /** - * Returns the diff between two arrays or strings as array. + * @psalm-assert class-string|null $value * - * Each array element contains two elements: - * - [0] => mixed $token - * - [1] => 2|1|0 + * @param mixed $value + * @param string $message * - * - 2: REMOVED: $token was removed from $from - * - 1: ADDED: $token was added to $from - * - 0: OLD: $token is not changed in $to + * @throws InvalidArgumentException * - * @param array|string $from - * @param array|string $to - * @param LongestCommonSubsequenceCalculator $lcs + * @return void */ - public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) : array + public static function nullOrInterfaceExists($value, $message = '') { - if (is_string($from)) { - $from = $this->splitStringByLines($from); - } elseif (!is_array($from)) { - throw new InvalidArgumentException('"from" must be an array or string.'); - } - if (is_string($to)) { - $to = $this->splitStringByLines($to); - } elseif (!is_array($to)) { - throw new InvalidArgumentException('"to" must be an array or string.'); - } - [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); - if ($lcs === null) { - $lcs = $this->selectLcsImplementation($from, $to); - } - $common = $lcs->calculate(array_values($from), array_values($to)); - $diff = []; - foreach ($start as $token) { - $diff[] = [$token, self::OLD]; - } - reset($from); - reset($to); - foreach ($common as $token) { - while (($fromToken = reset($from)) !== $token) { - $diff[] = [array_shift($from), self::REMOVED]; - } - while (($toToken = reset($to)) !== $token) { - $diff[] = [array_shift($to), self::ADDED]; - } - $diff[] = [$token, self::OLD]; - array_shift($from); - array_shift($to); - } - while (($token = array_shift($from)) !== null) { - $diff[] = [$token, self::REMOVED]; - } - while (($token = array_shift($to)) !== null) { - $diff[] = [$token, self::ADDED]; - } - foreach ($end as $token) { - $diff[] = [$token, self::OLD]; - } - if ($this->detectUnmatchedLineEndings($diff)) { - array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); - } - return $diff; + null === $value || static::interfaceExists($value, $message); } /** - * Casts variable to string if it is not a string or array. + * @psalm-assert iterable $value * - * @return array|string + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function normalizeDiffInput($input) + public static function allInterfaceExists($value, $message = '') { - if (!is_array($input) && !is_string($input)) { - return (string) $input; + static::isIterable($value); + foreach ($value as $entry) { + static::interfaceExists($entry, $message); } - return $input; } /** - * Checks if input is string, if so it will split it line-by-line. + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function splitStringByLines(string $input) : array - { - return preg_split('/(.*\\R)/', $input, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); - } - private function selectLcsImplementation(array $from, array $to) : LongestCommonSubsequenceCalculator + public static function allNullOrInterfaceExists($value, $message = '') { - // We do not want to use the time-efficient implementation if its memory - // footprint will probably exceed this value. Note that the footprint - // calculation is only an estimation for the matrix and the LCS method - // will typically allocate a bit more memory than this. - $memoryLimit = 100 * 1024 * 1024; - if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { - return new MemoryEfficientLongestCommonSubsequenceCalculator(); + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::interfaceExists($entry, $message); } - return new TimeEfficientLongestCommonSubsequenceCalculator(); } /** - * Calculates the estimated memory footprint for the DP-based method. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert class-string|null $value * - * @return float|int + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function calculateEstimatedFootprint(array $from, array $to) + public static function nullOrImplementsInterface($value, $interface, $message = '') { - $itemSize = \PHP_INT_SIZE === 4 ? 76 : 144; - return $itemSize * min(count($from), count($to)) ** 2; + null === $value || static::implementsInterface($value, $interface, $message); } /** - * Returns true if line ends don't match in a diff. + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert iterable> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function detectUnmatchedLineEndings(array $diff) : bool + public static function allImplementsInterface($value, $interface, $message = '') { - $newLineBreaks = ['' => \true]; - $oldLineBreaks = ['' => \true]; - foreach ($diff as $entry) { - if (self::OLD === $entry[1]) { - $ln = $this->getLinebreak($entry[0]); - $oldLineBreaks[$ln] = \true; - $newLineBreaks[$ln] = \true; - } elseif (self::ADDED === $entry[1]) { - $newLineBreaks[$this->getLinebreak($entry[0])] = \true; - } elseif (self::REMOVED === $entry[1]) { - $oldLineBreaks[$this->getLinebreak($entry[0])] = \true; - } - } - // if either input or output is a single line without breaks than no warning should be raised - if (['' => \true] === $newLineBreaks || ['' => \true] === $oldLineBreaks) { - return \false; - } - // two way compare - foreach ($newLineBreaks as $break => $set) { - if (!isset($oldLineBreaks[$break])) { - return \true; - } - } - foreach ($oldLineBreaks as $break => $set) { - if (!isset($newLineBreaks[$break])) { - return \true; - } + static::isIterable($value); + foreach ($value as $entry) { + static::implementsInterface($entry, $interface, $message); } - return \false; } - private function getLinebreak($line) : string + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert iterable|null> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrImplementsInterface($value, $interface, $message = '') { - if (!is_string($line)) { - return ''; - } - $lc = substr($line, -1); - if ("\r" === $lc) { - return "\r"; - } - if ("\n" !== $lc) { - return ''; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::implementsInterface($entry, $interface, $message); } - if ("\r\n" === substr($line, -2)) { - return "\r\n"; - } - return "\n"; } - private static function getArrayDiffParted(array &$from, array &$to) : array + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPropertyExists($classOrObject, $property, $message = '') { - $start = []; - $end = []; - reset($to); - foreach ($from as $k => $v) { - $toK = key($to); - if ($toK === $k && $v === $to[$k]) { - $start[$k] = $v; - unset($from[$k], $to[$k]); - } else { - break; - } - } - end($from); - end($to); - do { - $fromK = key($from); - $toK = key($to); - if (null === $fromK || null === $toK || current($from) !== current($to)) { - break; - } - prev($from); - prev($to); - $end = [$fromK => $from[$fromK]] + $end; - unset($from[$fromK], $to[$toK]); - } while (\true); - return [$from, $to, $start, $end]; + null === $classOrObject || static::propertyExists($classOrObject, $property, $message); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -use function array_pop; -use function count; -use function max; -use function preg_match; -use function preg_split; -/** - * Unified diff parser. - */ -final class Parser -{ /** - * @return Diff[] + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function parse(string $string) : array + public static function allPropertyExists($classOrObject, $property, $message = '') { - $lines = preg_split('(\\r\\n|\\r|\\n)', $string); - if (!empty($lines) && $lines[count($lines) - 1] === '') { - array_pop($lines); - } - $lineCount = count($lines); - $diffs = []; - $diff = null; - $collected = []; - for ($i = 0; $i < $lineCount; ++$i) { - if (preg_match('#^---\\h+"?(?P[^\\v\\t"]+)#', $lines[$i], $fromMatch) && preg_match('#^\\+\\+\\+\\h+"?(?P[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) { - if ($diff !== null) { - $this->parseFileDiff($diff, $collected); - $diffs[] = $diff; - $collected = []; - } - $diff = new Diff($fromMatch['file'], $toMatch['file']); - ++$i; - } else { - if (preg_match('/^(?:diff --git |index [\\da-f\\.]+|[+-]{3} [ab])/', $lines[$i])) { - continue; - } - $collected[] = $lines[$i]; - } - } - if ($diff !== null && count($collected)) { - $this->parseFileDiff($diff, $collected); - $diffs[] = $diff; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + static::propertyExists($entry, $property, $message); } - return $diffs; } - private function parseFileDiff(Diff $diff, array $lines) : void + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPropertyExists($classOrObject, $property, $message = '') { - $chunks = []; - $chunk = null; - $diffLines = []; - foreach ($lines as $line) { - if (preg_match('/^@@\\s+-(?P\\d+)(?:,\\s*(?P\\d+))?\\s+\\+(?P\\d+)(?:,\\s*(?P\\d+))?\\s+@@/', $line, $match)) { - $chunk = new Chunk((int) $match['start'], isset($match['startrange']) ? max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? max(1, (int) $match['endrange']) : 1); - $chunks[] = $chunk; - $diffLines = []; - continue; - } - if (preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { - $type = Line::UNCHANGED; - if ($match['type'] === '+') { - $type = Line::ADDED; - } elseif ($match['type'] === '-') { - $type = Line::REMOVED; - } - $diffLines[] = new Line($type, $match['line']); - if (null !== $chunk) { - $chunk->setLines($diffLines); - } - } + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + null === $entry || static::propertyExists($entry, $property, $message); } - $diff->setChunks($chunks); } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\Diff; - -final class Line -{ - public const ADDED = 1; - public const REMOVED = 2; - public const UNCHANGED = 3; - /** - * @var int - */ - private $type; /** - * @var string + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $content; - public function __construct(int $type = self::UNCHANGED, string $content = '') - { - $this->type = $type; - $this->content = $content; - } - public function getContent() : string + public static function nullOrPropertyNotExists($classOrObject, $property, $message = '') { - return $this->content; + null === $classOrObject || static::propertyNotExists($classOrObject, $property, $message); } - public function getType() : int + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPropertyNotExists($classOrObject, $property, $message = '') { - return $this->type; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + static::propertyNotExists($entry, $property, $message); + } } -} -sebastian/diff - -Copyright (c) 2002-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(int $line, string $name, string $value) + public static function allNullOrPropertyNotExists($classOrObject, $property, $message = '') { - $this->line = $line; - $this->name = $name; - $this->value = $value; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + null === $entry || static::propertyNotExists($entry, $property, $message); + } } - public function getLine() : int + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMethodExists($classOrObject, $method, $message = '') { - return $this->line; + null === $classOrObject || static::methodExists($classOrObject, $method, $message); } - public function getName() : string + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodExists($classOrObject, $method, $message = '') { - return $this->name; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + static::methodExists($entry, $method, $message); + } } - public function getValue() : string + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMethodExists($classOrObject, $method, $message = '') { - return $this->value; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + null === $entry || static::methodExists($entry, $method, $message); + } } -} - 'T_OPEN_BRACKET', ')' => 'T_CLOSE_BRACKET', '[' => 'T_OPEN_SQUARE', ']' => 'T_CLOSE_SQUARE', '{' => 'T_OPEN_CURLY', '}' => 'T_CLOSE_CURLY', ';' => 'T_SEMICOLON', '.' => 'T_DOT', ',' => 'T_COMMA', '=' => 'T_EQUAL', '<' => 'T_LT', '>' => 'T_GT', '+' => 'T_PLUS', '-' => 'T_MINUS', '*' => 'T_MULT', '/' => 'T_DIV', '?' => 'T_QUESTION_MARK', '!' => 'T_EXCLAMATION_MARK', ':' => 'T_COLON', '"' => 'T_DOUBLE_QUOTES', '@' => 'T_AT', '&' => 'T_AMPERSAND', '%' => 'T_PERCENT', '|' => 'T_PIPE', '$' => 'T_DOLLAR', '^' => 'T_CARET', '~' => 'T_TILDE', '`' => 'T_BACKTICK']; - public function parse(string $source) : TokenCollection + public static function nullOrMethodNotExists($classOrObject, $method, $message = '') { - $result = new TokenCollection(); - if ($source === '') { - return $result; - } - $tokens = \token_get_all($source); - $lastToken = new Token($tokens[0][2], 'Placeholder', ''); - foreach ($tokens as $pos => $tok) { - if (\is_string($tok)) { - $token = new Token($lastToken->getLine(), $this->map[$tok], $tok); - $result->addToken($token); - $lastToken = $token; - continue; - } - $line = $tok[2]; - $values = \preg_split('/\\R+/Uu', $tok[1]); - foreach ($values as $v) { - $token = new Token($line, \token_name($tok[0]), $v); - $lastToken = $token; - $line++; - if ($v === '') { - continue; - } - $result->addToken($token); - } - } - return $this->fillBlanks($result, $lastToken->getLine()); + null === $classOrObject || static::methodNotExists($classOrObject, $method, $message); } - private function fillBlanks(TokenCollection $tokens, int $maxLine) : TokenCollection + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodNotExists($classOrObject, $method, $message = '') { - $prev = new Token(0, 'Placeholder', ''); - $final = new TokenCollection(); - foreach ($tokens as $token) { - if ($prev === null) { - $final->addToken($token); - $prev = $token; - continue; - } - $gap = $token->getLine() - $prev->getLine(); - while ($gap > 1) { - $linebreak = new Token($prev->getLine() + 1, 'T_WHITESPACE', ''); - $final->addToken($linebreak); - $prev = $linebreak; - $gap--; - } - $final->addToken($token); - $prev = $token; + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + static::methodNotExists($entry, $method, $message); } - $gap = $maxLine - $prev->getLine(); - while ($gap > 0) { - $linebreak = new Token($prev->getLine() + 1, 'T_WHITESPACE', ''); - $final->addToken($linebreak); - $prev = $linebreak; - $gap--; - } - return $final; } -} - $classOrObject * - * @param NamespaceUri $xmlns + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct(NamespaceUri $xmlns = null) + public static function allNullOrMethodNotExists($classOrObject, $method, $message = '') { - if ($xmlns === null) { - $xmlns = new NamespaceUri('https://github.com/theseer/tokenizer'); + static::isIterable($classOrObject); + foreach ($classOrObject as $entry) { + null === $entry || static::methodNotExists($entry, $method, $message); } - $this->xmlns = $xmlns; } - public function toDom(TokenCollection $tokens) : DOMDocument + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyExists($array, $key, $message = '') { - $dom = new DOMDocument(); - $dom->preserveWhiteSpace = \false; - $dom->loadXML($this->toXML($tokens)); - return $dom; + null === $array || static::keyExists($array, $key, $message); } - public function toXML(TokenCollection $tokens) : string + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyExists($array, $key, $message = '') { - $this->writer = new \XMLWriter(); - $this->writer->openMemory(); - $this->writer->setIndent(\true); - $this->writer->startDocument(); - $this->writer->startElement('source'); - $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); - if (\count($tokens) > 0) { - $this->writer->startElement('line'); - $this->writer->writeAttribute('no', '1'); - $this->previousToken = $tokens[0]; - foreach ($tokens as $token) { - $this->addToken($token); - } + static::isIterable($array); + foreach ($array as $entry) { + static::keyExists($entry, $key, $message); } - $this->writer->endElement(); - $this->writer->endElement(); - $this->writer->endDocument(); - return $this->writer->outputMemory(); } - private function addToken(Token $token) : void + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyExists($array, $key, $message = '') { - if ($this->previousToken->getLine() < $token->getLine()) { - $this->writer->endElement(); - $this->writer->startElement('line'); - $this->writer->writeAttribute('no', (string) $token->getLine()); - $this->previousToken = $token; + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::keyExists($entry, $key, $message); } - if ($token->getValue() !== '') { - $this->writer->startElement('token'); - $this->writer->writeAttribute('name', $token->getName()); - $this->writer->writeRaw(\htmlspecialchars($token->getValue(), \ENT_NOQUOTES | \ENT_DISALLOWED | \ENT_XML1)); - $this->writer->endElement(); - } - } -} -ensureValidUri($value); - $this->value = $value; } - public function asString() : string + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyNotExists($array, $key, $message = '') { - return $this->value; + null === $array || static::keyNotExists($array, $key, $message); } - private function ensureValidUri($value) : void + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyNotExists($array, $key, $message = '') { - if (\strpos($value, ':') === \false) { - throw new NamespaceUriException(\sprintf("Namespace URI '%s' must contain at least one colon", $value)); + static::isIterable($array); + foreach ($array as $entry) { + static::keyNotExists($entry, $key, $message); } } -} -Tokenizer - -Copyright (c) 2017 Arne Blankerts and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Arne Blankerts nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyNotExists($array, $key, $message = '') { - $this->tokens[] = $token; + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::keyNotExists($entry, $key, $message); + } } - public function current() : Token + /** + * @psalm-pure + * @psalm-assert array-key|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrValidArrayKey($value, $message = '') { - return \current($this->tokens); + null === $value || static::validArrayKey($value, $message); } - public function key() : int + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allValidArrayKey($value, $message = '') { - return \key($this->tokens); + static::isIterable($value); + foreach ($value as $entry) { + static::validArrayKey($entry, $message); + } } - public function next() : void + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrValidArrayKey($value, $message = '') { - \next($this->tokens); - $this->pos++; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::validArrayKey($entry, $message); + } } - public function valid() : bool + /** + * @param Countable|array|null $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrCount($array, $number, $message = '') { - return $this->count() > $this->pos; + null === $array || static::count($array, $number, $message); } - public function rewind() : void + /** + * @param iterable $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allCount($array, $number, $message = '') { - \reset($this->tokens); - $this->pos = 0; + static::isIterable($array); + foreach ($array as $entry) { + static::count($entry, $number, $message); + } } - public function count() : int + /** + * @param iterable $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCount($array, $number, $message = '') { - return \count($this->tokens); + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::count($entry, $number, $message); + } } - public function offsetExists($offset) : bool + /** + * @param Countable|array|null $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinCount($array, $min, $message = '') { - return isset($this->tokens[$offset]); + null === $array || static::minCount($array, $min, $message); } /** - * @throws TokenCollectionException + * @param iterable $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function offsetGet($offset) : Token + public static function allMinCount($array, $min, $message = '') { - if (!$this->offsetExists($offset)) { - throw new TokenCollectionException(\sprintf('No Token at offest %s', $offset)); + static::isIterable($array); + foreach ($array as $entry) { + static::minCount($entry, $min, $message); } - return $this->tokens[$offset]; } /** - * @param Token $value + * @param iterable $array + * @param int|float $min + * @param string $message * - * @throws TokenCollectionException + * @throws InvalidArgumentException + * + * @return void */ - public function offsetSet($offset, $value) : void + public static function allNullOrMinCount($array, $min, $message = '') { - if (!\is_int($offset)) { - $type = \gettype($offset); - throw new TokenCollectionException(\sprintf('Offset must be of type integer, %s given', $type === 'object' ? \get_class($value) : $type)); + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::minCount($entry, $min, $message); } - if (!$value instanceof Token) { - $type = \gettype($value); - throw new TokenCollectionException(\sprintf('Value must be of type %s, %s given', Token::class, $type === 'object' ? \get_class($value) : $type)); - } - $this->tokens[$offset] = $value; } - public function offsetUnset($offset) : void + /** + * @param Countable|array|null $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxCount($array, $max, $message = '') { - unset($this->tokens[$offset]); + null === $array || static::maxCount($array, $max, $message); } -} -Object Enumerator - -Copyright (c) 2016-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\CodeUnitReverseLookup; - -use function array_merge; -use function assert; -use function get_declared_classes; -use function get_declared_traits; -use function get_defined_functions; -use function is_array; -use function range; -use ReflectionClass; -use ReflectionFunction; -use ReflectionFunctionAbstract; -use ReflectionMethod; -/** - * @since Class available since Release 1.0.0 - */ -class Wizard -{ /** - * @var array + * @param iterable $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $lookupTable = []; + public static function allMaxCount($array, $max, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + static::maxCount($entry, $max, $message); + } + } /** - * @var array + * @param iterable $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $processedClasses = []; + public static function allNullOrMaxCount($array, $max, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::maxCount($entry, $max, $message); + } + } /** - * @var array + * @param Countable|array|null $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $processedFunctions = []; + public static function nullOrCountBetween($array, $min, $max, $message = '') + { + null === $array || static::countBetween($array, $min, $max, $message); + } /** - * @param string $filename - * @param int $lineNumber + * @param iterable $array + * @param int|float $min + * @param int|float $max + * @param string $message * - * @return string + * @throws InvalidArgumentException + * + * @return void */ - public function lookup($filename, $lineNumber) + public static function allCountBetween($array, $min, $max, $message = '') { - if (!isset($this->lookupTable[$filename][$lineNumber])) { - $this->updateLookupTable(); + static::isIterable($array); + foreach ($array as $entry) { + static::countBetween($entry, $min, $max, $message); } - if (isset($this->lookupTable[$filename][$lineNumber])) { - return $this->lookupTable[$filename][$lineNumber]; + } + /** + * @param iterable $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCountBetween($array, $min, $max, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::countBetween($entry, $min, $max, $message); } - return $filename . ':' . $lineNumber; } - private function updateLookupTable() : void + /** + * @psalm-pure + * @psalm-assert list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsList($array, $message = '') { - $this->processClassesAndTraits(); - $this->processFunctions(); + null === $array || static::isList($array, $message); } - private function processClassesAndTraits() : void + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsList($array, $message = '') { - $classes = get_declared_classes(); - $traits = get_declared_traits(); - assert(is_array($classes)); - assert(is_array($traits)); - foreach (array_merge($classes, $traits) as $classOrTrait) { - if (isset($this->processedClasses[$classOrTrait])) { - continue; - } - $reflector = new ReflectionClass($classOrTrait); - foreach ($reflector->getMethods() as $method) { - $this->processFunctionOrMethod($method); - } - $this->processedClasses[$classOrTrait] = \true; + static::isIterable($array); + foreach ($array as $entry) { + static::isList($entry, $message); } } - private function processFunctions() : void + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsList($array, $message = '') { - foreach (get_defined_functions()['user'] as $function) { - if (isset($this->processedFunctions[$function])) { - continue; - } - $this->processFunctionOrMethod(new ReflectionFunction($function)); - $this->processedFunctions[$function] = \true; + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::isList($entry, $message); } } - private function processFunctionOrMethod(ReflectionFunctionAbstract $functionOrMethod) : void + /** + * @psalm-pure + * @psalm-assert non-empty-list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyList($array, $message = '') { - if ($functionOrMethod->isInternal()) { - return; - } - $name = $functionOrMethod->getName(); - if ($functionOrMethod instanceof ReflectionMethod) { - $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name; - } - if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) { - $this->lookupTable[$functionOrMethod->getFileName()] = []; - } - foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) { - $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name; - } + null === $array || static::isNonEmptyList($array, $message); } -} -code-unit-reverse-lookup - -Copyright (c) 2016-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\RecursionContext; - -use const PHP_INT_MAX; -use const PHP_INT_MIN; -use function array_pop; -use function array_slice; -use function count; -use function is_array; -use function is_object; -use function random_int; -use function spl_object_hash; -use SplObjectStorage; -/** - * A context containing previously processed arrays and objects - * when recursively processing a value. - */ -final class Context -{ /** - * @var array[] + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $arrays; + public static function allIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + static::isNonEmptyList($entry, $message); + } + } /** - * @var SplObjectStorage + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private $objects; + public static function allNullOrIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::isNonEmptyList($entry, $message); + } + } /** - * Initialises the context. + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array|null $array + * @psalm-assert array|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __construct() + public static function nullOrIsMap($array, $message = '') { - $this->arrays = []; - $this->objects = new SplObjectStorage(); + null === $array || static::isMap($array, $message); } /** - * @codeCoverageIgnore + * @psalm-pure + * @psalm-template T + * @psalm-param iterable> $array + * @psalm-assert iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function __destruct() + public static function allIsMap($array, $message = '') { - foreach ($this->arrays as &$array) { - if (is_array($array)) { - array_pop($array); - array_pop($array); - } + static::isIterable($array); + foreach ($array as $entry) { + static::isMap($entry, $message); } } /** - * Adds a value to the context. + * @psalm-pure + * @psalm-template T + * @psalm-param iterable|null> $array + * @psalm-assert iterable|null> $array * - * @param array|object $value the value to add + * @param mixed $array + * @param string $message * - * @throws InvalidArgumentException Thrown if $value is not an array or object + * @throws InvalidArgumentException * - * @return bool|int|string the ID of the stored value, either as a string or integer + * @return void + */ + public static function allNullOrIsMap($array, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::isMap($entry, $message); + } + } + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array|null $array * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyMap($array, $message = '') + { + null === $array || static::isNonEmptyMap($array, $message); + } + /** + * @psalm-pure * @psalm-template T - * @psalm-param T $value - * @param-out T $value + * @psalm-param iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function add(&$value) + public static function allIsNonEmptyMap($array, $message = '') { - if (is_array($value)) { - return $this->addArray($value); + static::isIterable($array); + foreach ($array as $entry) { + static::isNonEmptyMap($entry, $message); } - if (is_object($value)) { - return $this->addObject($value); + } + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable|null> $array + * @psalm-assert iterable|null> $array + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNonEmptyMap($array, $message = '') + { + static::isIterable($array); + foreach ($array as $entry) { + null === $entry || static::isNonEmptyMap($entry, $message); } - throw new InvalidArgumentException('Only arrays and objects are supported'); } /** - * Checks if the given value exists within the context. + * @psalm-pure * - * @param array|object $value the value to check + * @param string|null $value + * @param string $message * - * @throws InvalidArgumentException Thrown if $value is not an array or object + * @throws InvalidArgumentException * - * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored + * @return void + */ + public static function nullOrUuid($value, $message = '') + { + null === $value || static::uuid($value, $message); + } + /** + * @psalm-pure * - * @psalm-template T - * @psalm-param T $value - * @param-out T $value + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - public function contains(&$value) + public static function allUuid($value, $message = '') { - if (is_array($value)) { - return $this->containsArray($value); - } - if (is_object($value)) { - return $this->containsObject($value); + static::isIterable($value); + foreach ($value as $entry) { + static::uuid($entry, $message); } - throw new InvalidArgumentException('Only arrays and objects are supported'); } /** - * @return bool|int + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function addArray(array &$array) + public static function allNullOrUuid($value, $message = '') { - $key = $this->containsArray($array); - if ($key !== \false) { - return $key; - } - $key = count($this->arrays); - $this->arrays[] =& $array; - if (!isset($array[\PHP_INT_MAX]) && !isset($array[\PHP_INT_MAX - 1])) { - $array[] = $key; - $array[] = $this->objects; - } else { - /* cover the improbable case too */ - do { - $key = random_int(\PHP_INT_MIN, \PHP_INT_MAX); - } while (isset($array[$key])); - $array[$key] = $key; - do { - $key = random_int(\PHP_INT_MIN, \PHP_INT_MAX); - } while (isset($array[$key])); - $array[$key] = $this->objects; + static::isIterable($value); + foreach ($value as $entry) { + null === $entry || static::uuid($entry, $message); } - return $key; } /** - * @param object $object + * @psalm-param class-string $class + * + * @param Closure|null $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function addObject($object) : string + public static function nullOrThrows($expression, $class = 'Exception', $message = '') { - if (!$this->objects->contains($object)) { - $this->objects->attach($object); - } - return spl_object_hash($object); + null === $expression || static::throws($expression, $class, $message); } /** - * @return false|int + * @psalm-param class-string $class + * + * @param iterable $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function containsArray(array &$array) + public static function allThrows($expression, $class = 'Exception', $message = '') { - $end = array_slice($array, -2); - return isset($end[1]) && $end[1] === $this->objects ? $end[0] : \false; + static::isIterable($expression); + foreach ($expression as $entry) { + static::throws($entry, $class, $message); + } } /** - * @param object $value + * @psalm-param class-string $class * - * @return false|string + * @param iterable $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void */ - private function containsObject($value) + public static function allNullOrThrows($expression, $class = 'Exception', $message = '') { - if ($this->objects->contains($value)) { - return spl_object_hash($value); + static::isIterable($expression); + foreach ($expression as $entry) { + null === $entry || static::throws($entry, $class, $message); } - return \false; } } - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\RecursionContext; - -use Throwable; -interface Exception extends Throwable -{ -} -Recursion Context - -Copyright (c) 2002-2020, Sebastian Bergmann . -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + override( + \PHPUnit\Framework\TestCase::createMock(0), + map([""=>"$0"]) + ); - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. + override( + \PHPUnit\Framework\TestCase::createStub(0), + map([""=>"$0"]) + ); - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. + override( + \PHPUnit\Framework\TestCase::createConfiguredMock(0), + map([""=>"$0"]) + ); -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -"$0"]) + ); -declare (strict_types=1); -/* - * This file is part of the Recursion Context package. - * - * (c) Sebastian Bergmann - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SebastianBergmann\RecursionContext; + override( + \PHPUnit\Framework\TestCase::createTestProxy(0), + map([""=>"$0"]) + ); -final class InvalidArgumentException extends \InvalidArgumentException implements Exception -{ + override( + \PHPUnit\Framework\TestCase::getMockForAbstractClass(0), + map([""=>"$0"]) + ); } -^5xB7C=7^HzSU/NrXÙZ_P͡-frijqwcNW=CPh4GBMB \ No newline at end of file +4k{1rj3'|.D^C vh@*nd 0Ҩ +GBMB \ No newline at end of file diff --git a/tools/psalm.phar b/tools/psalm.phar index f9ccfd5..313c40b 100755 Binary files a/tools/psalm.phar and b/tools/psalm.phar differ