diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce6f607..340fdf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,11 +12,12 @@ jobs: # - windows-latest # Disabled - apparently checkouts have \r\n which breaks phpcs - macos-latest php-versions: - - '7.2' - - '7.3' - '7.4' - '8.0' - '8.1' + - '8.2' + - '8.3' + - '8.4' name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: - name: Checkout @@ -39,3 +40,6 @@ jobs: - name: Unit tests run: ./vendor/bin/phpunit + + - name: PHPStan + run: ./vendor/bin/phpstan analyze diff --git a/composer.json b/composer.json index 2c40aaa..ae9c8a9 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "require-dev": { "phpunit/phpunit": "^8.5", "php-coveralls/php-coveralls": "*", - "squizlabs/php_codesniffer": "3.*" + "squizlabs/php_codesniffer": "3.*", + "phpstan/phpstan": "^2.0" }, "suggest": { "ext-gmp": "Required for optimized binomial calculations (also requires PHP >= 7.3)" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..7df3b82 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: src/Matchers/DictionaryMatch.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..2b8bf25 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 0 + paths: + - src + - test diff --git a/src/Matchers/BaseMatch.php b/src/Matchers/BaseMatch.php index 527dbc5..97f77d3 100644 --- a/src/Matchers/BaseMatch.php +++ b/src/Matchers/BaseMatch.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Math\Binomial; use ZxcvbnPhp\Scorer; @@ -48,10 +47,8 @@ public function __construct(string $password, int $begin, int $end, string $toke * * @param bool $isSoleMatch * Whether this is the only match in the password - * @return array - * Associative array with warning (string) and suggestions (array of strings) + * @return array{'warning': string, "suggestions": string[]} */ - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] abstract public function getFeedback(bool $isSoleMatch): array; /** diff --git a/src/Matchers/Bruteforce.php b/src/Matchers/Bruteforce.php index cba7bcc..8e99092 100644 --- a/src/Matchers/Bruteforce.php +++ b/src/Matchers/Bruteforce.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Scorer; /** @@ -13,7 +12,7 @@ * * Intentionally not named with Match suffix to prevent autoloading from Matcher. */ -class Bruteforce extends BaseMatch +final class Bruteforce extends BaseMatch { public const BRUTEFORCE_CARDINALITY = 10; @@ -32,7 +31,9 @@ public static function match(string $password, array $userInputs = []): array } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { return [ diff --git a/src/Matchers/DateMatch.php b/src/Matchers/DateMatch.php index f3fdcd9..c67bffe 100644 --- a/src/Matchers/DateMatch.php +++ b/src/Matchers/DateMatch.php @@ -4,10 +4,9 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; -class DateMatch extends BaseMatch +final class DateMatch extends BaseMatch { public const NUM_YEARS = 119; // Years match against 1900 - 2019 public const NUM_MONTHS = 12; @@ -108,7 +107,9 @@ public static function match(string $password, array $userInputs = []): array return $matches; } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { return [ diff --git a/src/Matchers/DictionaryMatch.php b/src/Matchers/DictionaryMatch.php index b936681..334ac90 100644 --- a/src/Matchers/DictionaryMatch.php +++ b/src/Matchers/DictionaryMatch.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Math\Binomial; @@ -88,10 +87,8 @@ public function __construct(string $password, int $begin, int $end, string $toke } /** - * @param bool $isSoleMatch - * @return array + * @return array{'warning': string, "suggestions": string[]} */ - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] public function getFeedback(bool $isSoleMatch): array { $startUpper = '/^[A-Z][^A-Z]+$/u'; diff --git a/src/Matchers/L33tMatch.php b/src/Matchers/L33tMatch.php index 6b6288b..9163706 100644 --- a/src/Matchers/L33tMatch.php +++ b/src/Matchers/L33tMatch.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Math\Binomial; @@ -98,7 +97,9 @@ public function __construct(string $password, int $begin, int $end, string $toke } } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { $feedback = parent::getFeedback($isSoleMatch); diff --git a/src/Matchers/RepeatMatch.php b/src/Matchers/RepeatMatch.php index 3f38da2..fe44a03 100644 --- a/src/Matchers/RepeatMatch.php +++ b/src/Matchers/RepeatMatch.php @@ -4,11 +4,10 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Scorer; -class RepeatMatch extends BaseMatch +final class RepeatMatch extends BaseMatch { public const GREEDY_MATCH = '/(.+)\1+/u'; public const LAZY_MATCH = '/(.+?)\1+/u'; @@ -85,7 +84,9 @@ public static function match(string $password, array $userInputs = []): array return $matches; } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { $warning = mb_strlen($this->repeatedChar) == 1 diff --git a/src/Matchers/ReverseDictionaryMatch.php b/src/Matchers/ReverseDictionaryMatch.php index c7b86b6..a935bff 100644 --- a/src/Matchers/ReverseDictionaryMatch.php +++ b/src/Matchers/ReverseDictionaryMatch.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; class ReverseDictionaryMatch extends DictionaryMatch @@ -42,7 +41,9 @@ protected function getRawGuesses(): float return parent::getRawGuesses() * 2; } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { $feedback = parent::getFeedback($isSoleMatch); @@ -54,7 +55,7 @@ public function getFeedback(bool $isSoleMatch): array return $feedback; } - public static function mbStrRev(string $string, string $encoding = null): string + public static function mbStrRev(string $string, ?string $encoding = null): string { if ($encoding === null) { $encoding = mb_detect_encoding($string) ?: 'UTF-8'; diff --git a/src/Matchers/SequenceMatch.php b/src/Matchers/SequenceMatch.php index 1350f2a..59531cf 100644 --- a/src/Matchers/SequenceMatch.php +++ b/src/Matchers/SequenceMatch.php @@ -4,9 +4,7 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; - -class SequenceMatch extends BaseMatch +final class SequenceMatch extends BaseMatch { public const MAX_DELTA = 5; @@ -87,7 +85,9 @@ public static function findSequenceMatch(string $password, int $begin, int $end, } } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { return [ diff --git a/src/Matchers/SpatialMatch.php b/src/Matchers/SpatialMatch.php index 6de328b..32297e6 100644 --- a/src/Matchers/SpatialMatch.php +++ b/src/Matchers/SpatialMatch.php @@ -4,11 +4,10 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Math\Binomial; -class SpatialMatch extends BaseMatch +final class SpatialMatch extends BaseMatch { public const SHIFTED_CHARACTERS = '~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?'; @@ -58,7 +57,9 @@ public static function match(string $password, array $userInputs = [], array $gr return $matches; } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { $warning = $this->turns == 1 @@ -93,7 +94,7 @@ public function __construct(string $password, int $begin, int $end, string $toke /** * Match spatial patterns in a adjacency graph. * @param string $password - * @param array $graph + * @param array $graph * @param string $graphName * @return array */ diff --git a/src/Matchers/YearMatch.php b/src/Matchers/YearMatch.php index 9deeea6..ebabf6b 100644 --- a/src/Matchers/YearMatch.php +++ b/src/Matchers/YearMatch.php @@ -4,10 +4,9 @@ namespace ZxcvbnPhp\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matcher; -class YearMatch extends BaseMatch +final class YearMatch extends BaseMatch { public const NUM_YEARS = 119; @@ -32,7 +31,10 @@ public static function match(string $password, array $userInputs = []): array return $matches; } - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] + + /** + * @return array{'warning': string, "suggestions": string[]} + */ public function getFeedback(bool $isSoleMatch): array { return [ diff --git a/test/Matchers/MatchTest.php b/test/Matchers/MatchTest.php deleted file mode 100644 index f5843eb..0000000 --- a/test/Matchers/MatchTest.php +++ /dev/null @@ -1,51 +0,0 @@ -getMockBuilder(DateMatch::class) - ->disableOriginalConstructor() - ->onlyMethods(['getGuesses']) - ->getMock(); - $stub->method('getGuesses')->willReturn($guesses); - - return $stub; - } - - /** - * @return float[][] - */ - public function log10Provider(): array - { - return [ - [1.0, 0.0], - [10.0, 1.0], - [100.0, 2.0], - [500.0, log10(500)], - ]; - } - - /** - * @dataProvider log10Provider - * @param float $n - * @param float $expected - */ - public function testGuessesLog10(float $n, float $expected): void - { - $stub = $this->getMatchMock($n); - $this->assertSame($expected, $stub->getGuessesLog10(), "log10 guesses"); - } -} diff --git a/test/Matchers/MockMatch.php b/test/Matchers/MockMatch.php index cf5d10b..869b7e6 100644 --- a/test/Matchers/MockMatch.php +++ b/test/Matchers/MockMatch.php @@ -4,7 +4,6 @@ namespace ZxcvbnPhp\Test\Matchers; -use JetBrains\PhpStorm\ArrayShape; use ZxcvbnPhp\Matchers\BaseMatch; class MockMatch extends BaseMatch @@ -22,10 +21,8 @@ public function __construct(int $begin, int $end, float $guesses) * Get feedback to a user based on the match. * @param bool $isSoleMatch * Whether this is the only match in the password - * @return array - * Associative array with warning (string) and suggestions (array of strings) + * @return array{'warning': string, "suggestions": string[]} */ - #[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])] public function getFeedback(bool $isSoleMatch): array { return [ diff --git a/test/ZxcvbnTest.php b/test/ZxcvbnTest.php index 962ed49..4aab198 100644 --- a/test/ZxcvbnTest.php +++ b/test/ZxcvbnTest.php @@ -80,7 +80,7 @@ public function sanityCheckDataProvider() ['fortitude22', 2, ['dictionary', 'repeat',], '2 minutes', 1140700], ['absoluteadnap', 2, ['dictionary', 'dictionary',], '25 minutes', 15187504], ['knifeandspoon', 3, ['dictionary', 'dictionary', 'dictionary'], '1 day', 1108057600], - ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date'], '3 days', 2730628000], + ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date'], '3 days', 2993690800], ['4rfv1236yhn!', 4, ['spatial', 'sequence', 'bruteforce'], '1 month', 38980000000], ['BVidSNqe3oXVyE1996', 4, ['bruteforce', 'regex',], 'centuries', 10000000000010000], ];