diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..955198c3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +/.github export-ignore +/bench export-ignore +/tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +phpbench.json export-ignore +phpunit.xml.dist export-ignore +psalm.xml export-ignore diff --git a/.travis.yml b/.travis.yml index 68e047b5..db67d35f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,75 +1,76 @@ -sudo: false -language: php - -cache: - directories: - - $HOME/.composer/cache - - $HOME/.local - - $HOME/ocular.phar - - $HOME/phpDocumentor.phar - -env: - global: - - CODE_COVERAGE="0" - - PHPDOC="0" - -matrix: - fast_finish: true - include: - - php: 7.1 - env: - - CODE_COVERAGE="1" - - php: 7.2 - env: - - CODE_COVERAGE="1" - - PHPDOC="1" - - php: 7.3 - env: - - CODE_COVERAGE="1" - - php: 7.4 - env: - - CODE_COVERAGE="1" - - php: nightly - allow_failures: - - php: nightly - -install: - - if [ "${CODE_COVERAGE}" == "0" ]; then - phpenv config-rm xdebug.ini || return 0; - fi - - if [ "${CODE_COVERAGE}" == "1" ]; then - wget -q -N -t 3 --retry-connrefused 'https://scrutinizer-ci.com/ocular.phar' || return 0; - fi - - if [ "${PHPDOC}" == "1" ]; then - wget -q -N -t 3 --retry-connrefused 'https://github.com/phpDocumentor/phpDocumentor2/releases/download/v2.9.0/phpDocumentor.phar' || return 0; - composer require --no-update --dev "evert/phpdoc-md:~0.2.0" || return 0; - fi - - composer install -n - -script: - - if [ "$CODE_COVERAGE" == "1" ]; then - php -d 'zend.assertions=1' vendor/bin/phpunit --verbose --coverage-text --coverage-clover=coverage.clover; - else - php -d 'zend.assertions=1' vendor/bin/phpunit --verbose; - fi - - # run benchmarks to make sure they are working fine - - php vendor/bin/phpbench run --no-interaction --revs=1 --retry-threshold=100 - -after_script: - - if [ "${CODE_COVERAGE}" == "1" ]; then - php ocular.phar code-coverage:upload --format=php-clover coverage.clover; - fi - - if [ "${PHPDOC}" == "1" ]; then - git clone "https://${CI_USER_TOKEN}@github.com/marc-mabe/php-enum.wiki.git" && - php phpDocumentor.phar -d src -t docs/ --template="xml" && - php vendor/bin/phpdocmd --lt '%c' --index 'Home.md' docs/structure.xml php-enum.wiki/ && - cp php-enum.wiki/Home.md php-enum.wiki/_Sidebar.md && - cd php-enum.wiki/ && - git add . && - git commit -m "auto generated PHP doc" && - git push origin master:master; - fi - -notifications: - email: false +sudo: false +language: php + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.local + - $HOME/ocular.phar + - $HOME/phpDocumentor.phar + +env: + global: + - CODE_COVERAGE="0" + - PHPDOC="0" + +matrix: + fast_finish: true + include: + - php: 7.1 + env: + - CODE_COVERAGE="1" + - php: 7.2 + env: + - CODE_COVERAGE="1" + - PHPDOC="1" + - php: 7.3 + env: + - CODE_COVERAGE="1" + - php: 7.4 + env: + - CODE_COVERAGE="1" + - php: nightly + allow_failures: + - php: nightly + +install: + - if [ "${CODE_COVERAGE}" == "0" ]; then + phpenv config-rm xdebug.ini || return 0; + fi + - if [ "${CODE_COVERAGE}" == "1" ]; then + wget -q -N -t 3 --retry-connrefused 'https://scrutinizer-ci.com/ocular.phar' || return 0; + fi + - if [ "${PHPDOC}" == "1" ]; then + wget -q -N -t 3 --retry-connrefused 'https://github.com/phpDocumentor/phpDocumentor2/releases/download/v2.9.0/phpDocumentor.phar' || return 0; + composer require --no-update --dev "evert/phpdoc-md:~0.2.0" || return 0; + fi + - composer install -n + +script: + - if [ "$CODE_COVERAGE" == "1" ]; then + php -d 'zend.assertions=1' vendor/bin/phpunit --verbose --coverage-text --coverage-clover=coverage.clover; + else + php -d 'zend.assertions=1' vendor/bin/phpunit --verbose; + fi + + # run benchmarks to make sure they are working fine + - php vendor/bin/phpbench run --no-interaction --revs=1 --retry-threshold=100 + - vendor/bin/psalm + +after_script: + - if [ "${CODE_COVERAGE}" == "1" ]; then + php ocular.phar code-coverage:upload --format=php-clover coverage.clover; + fi + - if [ "${PHPDOC}" == "1" ]; then + git clone "https://${CI_USER_TOKEN}@github.com/marc-mabe/php-enum.wiki.git" && + php phpDocumentor.phar -d src -t docs/ --template="xml" && + php vendor/bin/phpdocmd --lt '%c' --index 'Home.md' docs/structure.xml php-enum.wiki/ && + cp php-enum.wiki/Home.md php-enum.wiki/_Sidebar.md && + cd php-enum.wiki/ && + git add . && + git commit -m "auto generated PHP doc" && + git push origin master:master; + fi + +notifications: + email: false diff --git a/composer.json b/composer.json index 46c98a9d..c49d166e 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,9 @@ "ext-reflection": "*" }, "require-dev": { - "phpunit/phpunit": "^6.0", - "phpbench/phpbench": "^0.16.1" + "phpunit/phpunit": "^7.0", + "phpbench/phpbench": "^0.16.1", + "vimeo/psalm": "^3.10" }, "autoload": { "psr-4": { diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..5cfb3f97 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/Enum.php b/src/Enum.php index ddc4b58f..5a460444 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -14,6 +14,8 @@ * @copyright 2019 Marc Bennewitz * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License * @link http://github.com/marc-mabe/php-enum for the canonical source repository + * + * @psalm-immutable */ abstract class Enum { @@ -87,6 +89,8 @@ final private function __clone() /** * @throws LogicException Enums are not serializable * because instances are implemented as singletons + * + * @psalm-return never-return */ final public function __sleep() { @@ -96,6 +100,8 @@ final public function __sleep() /** * @throws LogicException Enums are not serializable * because instances are implemented as singletons + * + * @psalm-return never-return */ final public function __wakeup() { @@ -116,6 +122,8 @@ final public function getValue() * Get the name of the enumerator * * @return string + * + * @psalm-return non-empty-string */ final public function getName() { @@ -170,6 +178,8 @@ final public function is($enumerator) * @return static * @throws InvalidArgumentException On an unknown or invalid value * @throws LogicException On ambiguous constant values + * + * @psalm-pure */ final public static function get($enumerator) { @@ -187,6 +197,8 @@ final public static function get($enumerator) * @return static * @throws InvalidArgumentException On an unknown or invalid value * @throws LogicException On ambiguous constant values + * + * @psalm-pure */ final public static function byValue($value) { @@ -214,6 +226,8 @@ final public static function byValue($value) * @return static * @throws InvalidArgumentException On an invalid or unknown name * @throws LogicException On ambiguous values + * + * @psalm-pure */ final public static function byName(string $name) { @@ -241,6 +255,8 @@ final public static function byName(string $name) * @return static * @throws InvalidArgumentException On an invalid ordinal number * @throws LogicException On ambiguous values + * + * @psalm-pure */ final public static function byOrdinal(int $ordinal) { @@ -263,6 +279,9 @@ final public static function byOrdinal(int $ordinal) * Get a list of enumerator instances ordered by ordinal number * * @return static[] + * + * @psalm-return list + * @psalm-pure */ final public static function getEnumerators() { @@ -276,6 +295,9 @@ final public static function getEnumerators() * Get a list of enumerator values ordered by ordinal number * * @return mixed[] + * + * @psalm-return list + * @psalm-pure */ final public static function getValues() { @@ -286,6 +308,9 @@ final public static function getValues() * Get a list of enumerator names ordered by ordinal number * * @return string[] + * + * @psalm-return list + * @psalm-pure */ final public static function getNames() { @@ -294,11 +319,14 @@ final public static function getNames() } return self::$names[static::class]; } - + /** * Get a list of enumerator ordinal numbers * * @return int[] + * + * @psalm-return list + * @psalm-pure */ final public static function getOrdinals() { @@ -309,8 +337,11 @@ final public static function getOrdinals() /** * Get all available constants of the called class * - * @return array + * @return mixed[] * @throws LogicException On ambiguous constant values + * + * @psalm-return array + * @psalm-pure */ final public static function getConstants() { @@ -361,9 +392,11 @@ private static function noAmbiguousValues($constants) /** * Test if the given enumerator is part of this enumeration - * + * * @param static|null|bool|int|float|string|array $enumerator * @return bool + * + * @psalm-pure */ final public static function has($enumerator) { @@ -376,6 +409,8 @@ final public static function has($enumerator) * * @param null|bool|int|float|string|array $value * @return bool + * + * @psalm-pure */ final public static function hasValue($value) { @@ -387,6 +422,8 @@ final public static function hasValue($value) * * @param string $name * @return bool + * + * @psalm-pure */ final public static function hasName(string $name) { @@ -404,6 +441,8 @@ final public static function hasName(string $name) * @return static * @throws InvalidArgumentException On an invalid or unknown name * @throws LogicException On ambiguous constant values + * + * @psalm-pure */ final public static function __callStatic(string $method, array $args) { diff --git a/tests/MabeEnumStaticAnalysis/DummyEnum.php b/tests/MabeEnumStaticAnalysis/DummyEnum.php new file mode 100644 index 00000000..9e50297d --- /dev/null +++ b/tests/MabeEnumStaticAnalysis/DummyEnum.php @@ -0,0 +1,26 @@ +__toString(); + } + + /** + * @psalm-pure + * + * @psalm-return never-return + */ + public static function sleepIsPure(): void + { + DummyEnum::a() + ->__sleep(); + } + + /** + * @psalm-pure + * + * @psalm-return never-return + */ + public static function wakeUpIsPure(): void + { + DummyEnum::a() + ->__wakeup(); + } + + /** + * @psalm-pure + * + * @psalm-return non-empty-string + */ + public static function nameRetrievalIsPure(): string + { + return DummyEnum::a() + ->getName(); + } + + /** + * @return null|bool|int|float|string|array + * + * @psalm-pure + */ + public static function valueRetrievalIsPure() + { + return DummyEnum::a() + ->getValue(); + } + + /** @psalm-pure */ + public static function getIsPure(): DummyEnum + { + return DummyEnum::get(DummyEnum::A); + } + + /** @psalm-pure */ + public static function ordinalRetrievalIsPure(): int + { + return DummyEnum::a() + ->getOrdinal(); + } + + /** @psalm-pure */ + public static function comparisonIsPure(): bool + { + return DummyEnum::a()->is(DummyEnum::b()); + } + + /** @psalm-pure */ + public static function byValueIsPure(): DummyEnum + { + return DummyEnum::byValue('A_VALUE'); + } + + /** @psalm-pure */ + public static function byNameIsPure(): DummyEnum + { + return DummyEnum::byValue('A'); + } + + /** @psalm-pure */ + public static function byOrdinalIsPure(): DummyEnum + { + return DummyEnum::byOrdinal(1); + } + + /** + * @psalm-pure + * + * @psalm-return list + */ + public static function getEnumeratorsIsPure(): array + { + return DummyEnum::getEnumerators(); + } + + /** + * @psalm-pure + * + * @psalm-return list + */ + public static function getValuesIsPure(): array + { + return DummyEnum::getValues(); + } + + /** + * @psalm-pure + * + * @psalm-return list + */ + public static function getNamesIsPure(): array + { + return DummyEnum::getNames(); + } + + /** + * @psalm-pure + * + * @psalm-return list + */ + public static function getOrdinalsIsPure(): array + { + return DummyEnum::getOrdinals(); + } + + /** @psalm-pure */ + public static function hasIsPure(): bool + { + return DummyEnum::has('a'); + } + + /** @psalm-pure */ + public static function hasValueIsPure(): bool + { + return DummyEnum::hasValue('A_VALUE'); + } + + /** @psalm-pure */ + public static function hasNameIsPure(): bool + { + return DummyEnum::hasName('A'); + } + + /** @psalm-pure */ + public static function callStaticIsPure(): DummyEnum + { + return DummyEnum::__callStatic('a', []); + } +}