Skip to content

Commit 22fd97e

Browse files
committed
add psalm (level 2)
1 parent f6a82ae commit 22fd97e

26 files changed

+364
-126
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
/.travis.yml export-ignore
77
/composer.lock export-ignore
88
/phpunit.xml.dist export-ignore
9+
/psalm.xml export-ignore

.github/workflows/ci.yml

+5
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,10 @@ jobs:
3737
- name: Code style checks
3838
run: ./vendor/bin/phpcs .
3939

40+
- name: Run psalm
41+
if: ${{ matrix.php-versions == '7.4' }}
42+
run: |
43+
php vendor/bin/psalm --config=psalm.xml --no-progress
44+
4045
- name: Unit tests
4146
run: ./vendor/bin/phpunit

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"ext-json": "*"
1818
},
1919
"require-dev": {
20+
"vimeo/psalm": "4.*",
2021
"phpunit/phpunit": "^8.5",
2122
"php-coveralls/php-coveralls": "*",
2223
"squizlabs/php_codesniffer": "3.*"

psalm.xml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0"?>
2+
<psalm
3+
errorLevel="2"
4+
resolveFromConfigFile="true"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xmlns="https://getpsalm.org/schema/config"
7+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
8+
>
9+
<issueHandlers>
10+
<PropertyNotSetInConstructor errorLevel="suppress" />
11+
<UnsafeInstantiation errorLevel="suppress" />
12+
</issueHandlers>
13+
14+
<projectFiles>
15+
<directory name="src" />
16+
<ignoreFiles>
17+
<directory name="vendor" />
18+
</ignoreFiles>
19+
</projectFiles>
20+
</psalm>

src/Feedback.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class Feedback
1616
{
1717
/**
1818
* @param int $score
19-
* @param MatchInterface[] $sequence
20-
* @return array
19+
* @param array<int, \ZxcvbnPhp\Matchers\BaseMatch> $sequence
20+
* @return array{warning: string, suggestions: array<int, string>}
2121
*/
2222
public function getFeedback(int $score, array $sequence): array
2323
{
@@ -42,7 +42,7 @@ public function getFeedback(int $score, array $sequence): array
4242

4343
// tie feedback to the longest match for longer sequences
4444
$longestMatch = $sequence[0];
45-
foreach (array_slice($sequence, 1) as $match) {
45+
foreach (\array_slice($sequence, 1) as $match) {
4646
if (mb_strlen($match->token) > mb_strlen($longestMatch->token)) {
4747
$longestMatch = $match;
4848
}

src/Matcher.php

+29-10
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@ class Matcher
2020
Matchers\YearMatch::class,
2121
];
2222

23+
/**
24+
* @var array<class-string<BaseMatch>, class-string<BaseMatch>>
25+
*/
2326
private $additionalMatchers = [];
2427

2528
/**
2629
* Get matches for a password.
2730
*
2831
* @param string $password Password string to match
29-
* @param array $userInputs Array of values related to the user (optional)
32+
* @param array<int, string> $userInputs Array of values related to the user (optional)
3033
* @code array('Alice Smith')
3134
* @endcode
3235
*
33-
* @return MatchInterface[] Array of Match objects.
36+
* @return BaseMatch[]
3437
*
3538
* @see zxcvbn/src/matching.coffee::omnimatch
3639
*/
@@ -39,7 +42,7 @@ public function getMatches(string $password, array $userInputs = []): array
3942
$matches = [];
4043
foreach ($this->getMatchers() as $matcher) {
4144
$matched = $matcher::match($password, $userInputs);
42-
if (is_array($matched) && !empty($matched)) {
45+
if (!empty($matched)) {
4346
$matches[] = $matched;
4447
}
4548
}
@@ -50,6 +53,11 @@ public function getMatches(string $password, array $userInputs = []): array
5053
return $matches;
5154
}
5255

56+
/**
57+
* @param class-string<BaseMatch> $className
58+
*
59+
* @return $this
60+
*/
5361
public function addMatcher(string $className): self
5462
{
5563
if (!is_a($className, MatchInterface::class, true)) {
@@ -71,8 +79,9 @@ public function addMatcher(string $className): self
7179
* This function taken from https://github.com/vanderlee/PHP-stable-sort-functions
7280
* Copyright © 2015-2018 Martijn van der Lee (http://martijn.vanderlee.com). MIT License applies.
7381
*
74-
* @param array $array
75-
* @param callable $value_compare_func
82+
* @template TSort
83+
* @param array<TSort> $array
84+
* @param callable(TSort,TSort): int $value_compare_func
7685
* @return bool
7786
*/
7887
public static function usortStable(array &$array, callable $value_compare_func): bool
@@ -81,13 +90,23 @@ public static function usortStable(array &$array, callable $value_compare_func):
8190
foreach ($array as &$item) {
8291
$item = [$index++, $item];
8392
}
84-
$result = usort($array, function ($a, $b) use ($value_compare_func) {
85-
$result = $value_compare_func($a[1], $b[1]);
86-
return $result == 0 ? $a[0] - $b[0] : $result;
87-
});
93+
unset($item);
94+
$result = usort(
95+
$array,
96+
/**
97+
* @param array $a
98+
* @param array $b
99+
* @return int
100+
*/
101+
function ($a, $b) use ($value_compare_func) {
102+
$result = $value_compare_func($a[1], $b[1]);
103+
return $result == 0 ? $a[0] - $b[0] : $result;
104+
}
105+
);
88106
foreach ($array as &$item) {
89107
$item = $item[1];
90108
}
109+
unset($item);
91110
return $result;
92111
}
93112

@@ -103,7 +122,7 @@ public static function compareMatches(BaseMatch $a, BaseMatch $b): int
103122
/**
104123
* Load available Match objects to match against a password.
105124
*
106-
* @return array Array of classes implementing MatchInterface
125+
* @return array<class-string<MatchInterface>>
107126
*/
108127
protected function getMatchers(): array
109128
{

src/Matchers/BaseMatch.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@
1111
abstract class BaseMatch implements MatchInterface
1212
{
1313
/**
14-
* @var
14+
* @var string
1515
*/
1616
public $password;
1717

1818
/**
19-
* @var
19+
* @var int
2020
*/
2121
public $begin;
2222

2323
/**
24-
* @var
24+
* @var int
2525
*/
2626
public $end;
2727

2828
/**
29-
* @var
29+
* @var string
3030
*/
3131
public $token;
3232

3333
/**
34-
* @var
34+
* @var string
3535
*/
3636
public $pattern;
3737

@@ -46,9 +46,9 @@ public function __construct(string $password, int $begin, int $end, string $toke
4646
/**
4747
* Get feedback to a user based on the match.
4848
*
49-
* @param bool $isSoleMatch
49+
* @param bool $isSoleMatch
5050
* Whether this is the only match in the password
51-
* @return array
51+
* @return array{warning: string, suggestions: array<int, string>}
5252
* Associative array with warning (string) and suggestions (array of strings)
5353
*/
5454
#[ArrayShape(['warning' => 'string', 'suggestions' => 'string[]'])]
@@ -62,7 +62,7 @@ abstract public function getFeedback(bool $isSoleMatch): array;
6262
* @param string $regex
6363
* Regular expression with captures.
6464
* @param int $offset
65-
* @return array
65+
* @return array<int, array<array{begin: int, end: int, token: string}>>
6666
* Array of capture groups. Captures in a group have named indexes: 'begin', 'end', 'token'.
6767
* e.g. fishfish /(fish)/
6868
* array(
@@ -82,7 +82,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
8282
// preg_match_all counts bytes, not characters: to correct this, we need to calculate the byte offset and pass
8383
// that in instead.
8484
$charsBeforeOffset = mb_substr($string, 0, $offset);
85-
$byteOffset = strlen($charsBeforeOffset);
85+
$byteOffset = \strlen($charsBeforeOffset);
8686

8787
$count = preg_match_all($regex, $string, $matches, PREG_SET_ORDER, $byteOffset);
8888
if (!$count) {
@@ -93,7 +93,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
9393
foreach ($matches as $group) {
9494
$captureBegin = 0;
9595
$match = array_shift($group);
96-
$matchBegin = mb_strpos($string, $match, $offset);
96+
$matchBegin = (int)mb_strpos($string, $match, $offset);
9797
$captures = [
9898
[
9999
'begin' => $matchBegin,
@@ -102,7 +102,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
102102
],
103103
];
104104
foreach ($group as $capture) {
105-
$captureBegin = mb_strpos($match, $capture, $captureBegin);
105+
$captureBegin = (int)mb_strpos($match, $capture, $captureBegin);
106106
$captures[] = [
107107
'begin' => $matchBegin + $captureBegin,
108108
'end' => $matchBegin + $captureBegin + mb_strlen($capture) - 1,

src/Matchers/Bruteforce.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Bruteforce extends BaseMatch
2121

2222
/**
2323
* @param string $password
24-
* @param array $userInputs
24+
* @param array<int, string> $userInputs
2525
* @return Bruteforce[]
2626
*/
2727
public static function match(string $password, array $userInputs = []): array

src/Matchers/DateMatch.php

+33-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class DateMatch extends BaseMatch
2020

2121
public $pattern = 'date';
2222

23+
/**
24+
* @var array<int, array<array<int, int>>>
25+
*/
2326
private static $DATE_SPLITS = [
2427
4 => [ # For length-4 strings, eg 1191 or 9111, two ways to split:
2528
[1, 2], # 1 1 91 (2nd split starts at index 1, 3rd at index 2)
@@ -57,23 +60,39 @@ class DateMatch extends BaseMatch
5760
*/
5861
protected const DATE_WITH_SEPARATOR = '/^(\d{1,4})([\s\/\\\\_.-])(\d{1,2})\2(\d{1,4})$/u';
5962

60-
/** @var int The day portion of the date in the token. */
63+
/**
64+
* The day portion of the date in the token.
65+
*
66+
* @var int
67+
*/
6168
public $day;
6269

63-
/** @var int The month portion of the date in the token. */
70+
/**
71+
* The month portion of the date in the token.
72+
*
73+
* @var int
74+
*/
6475
public $month;
6576

66-
/** @var int The year portion of the date in the token. */
77+
/**
78+
* The year portion of the date in the token.
79+
*
80+
* @var int
81+
*/
6782
public $year;
6883

69-
/** @var string The separator used for the date in the token. */
84+
/**
85+
* The separator used for the date in the token.
86+
*
87+
* @var string
88+
*/
7089
public $separator;
7190

7291
/**
7392
* Match occurences of dates in a password
7493
*
7594
* @param string $password
76-
* @param array $userInputs
95+
* @param array<int, string> $userInputs
7796
* @return DateMatch[]
7897
*/
7998
public static function match(string $password, array $userInputs = []): array
@@ -124,7 +143,7 @@ public function getFeedback(bool $isSoleMatch): array
124143
* @param int $begin
125144
* @param int $end
126145
* @param string $token
127-
* @param array $params An array with keys: [day, month, year, separator].
146+
* @param array{day: int, month: int, year: int, separator: string} $params
128147
*/
129148
public function __construct(string $password, int $begin, int $end, string $token, array $params)
130149
{
@@ -272,7 +291,7 @@ public static function getReferenceYear(): int
272291

273292
/**
274293
* @param int[] $ints Three numbers in an array representing day, month and year (not necessarily in that order).
275-
* @return array|bool Returns an associative array containing 'day', 'month' and 'year' keys, or false if the
294+
* @return array{year: int, month: int, day: int}|false Returns an associative array containing 'day', 'month' and 'year' keys, or false if the
276295
* provided date array is invalid.
277296
*/
278297
protected static function checkDate(array $ints)
@@ -319,7 +338,8 @@ protected static function checkDate(array $ints)
319338

320339
foreach ($possibleYearSplits as [$year, $rest]) {
321340
if ($year >= static::MIN_YEAR && $year <= static::MAX_YEAR) {
322-
if ($dm = static::mapIntsToDayMonth($rest)) {
341+
$dm = static::mapIntsToDayMonth($rest);
342+
if ($dm) {
323343
return [
324344
'year' => $year,
325345
'month' => $dm['month'],
@@ -334,7 +354,8 @@ protected static function checkDate(array $ints)
334354
}
335355

336356
foreach ($possibleYearSplits as [$year, $rest]) {
337-
if ($dm = static::mapIntsToDayMonth($rest)) {
357+
$dm = static::mapIntsToDayMonth($rest);
358+
if ($dm) {
338359
return [
339360
'year' => static::twoToFourDigitYear($year),
340361
'month' => $dm['month'],
@@ -348,7 +369,9 @@ protected static function checkDate(array $ints)
348369

349370
/**
350371
* @param int[] $ints Two numbers in an array representing day and month (not necessarily in that order).
351-
* @return array|bool Returns an associative array containing 'day' and 'month' keys, or false if any combination
372+
*
373+
* @return array{day: int, month: int}|false
374+
* Returns an associative array containing 'day' and 'month' keys, or false if any combination
352375
* of the two numbers does not match a day and month.
353376
*/
354377
protected static function mapIntsToDayMonth(array $ints)

0 commit comments

Comments
 (0)