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', []);
+ }
+}