Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add psalm (level 2) #68

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/.travis.yml export-ignore
/composer.lock export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ jobs:
- name: Code style checks
run: ./vendor/bin/phpcs .

- name: Run psalm
if: ${{ matrix.php-versions == '7.4' }}
run: |
php vendor/bin/psalm --config=psalm.xml --no-progress

- name: Unit tests
run: ./vendor/bin/phpunit
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"ext-json": "*"
},
"require-dev": {
"vimeo/psalm": "4.*",
"phpunit/phpunit": "^8.5",
"php-coveralls/php-coveralls": "*",
"squizlabs/php_codesniffer": "3.*"
Expand Down
20 changes: 20 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<issueHandlers>
<PropertyNotSetInConstructor errorLevel="suppress" />
<UnsafeInstantiation errorLevel="suppress" />
</issueHandlers>

<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
6 changes: 3 additions & 3 deletions src/Feedback.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class Feedback
{
/**
* @param int $score
* @param MatchInterface[] $sequence
* @return array
* @param array<int, \ZxcvbnPhp\Matchers\BaseMatch> $sequence
* @return array{warning: string, suggestions: array<int, string>}
*/
public function getFeedback(int $score, array $sequence): array
{
Expand All @@ -42,7 +42,7 @@ public function getFeedback(int $score, array $sequence): array

// tie feedback to the longest match for longer sequences
$longestMatch = $sequence[0];
foreach (array_slice($sequence, 1) as $match) {
foreach (\array_slice($sequence, 1) as $match) {
if (mb_strlen($match->token) > mb_strlen($longestMatch->token)) {
$longestMatch = $match;
}
Expand Down
39 changes: 29 additions & 10 deletions src/Matcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ class Matcher
Matchers\YearMatch::class,
];

/**
* @var array<class-string<BaseMatch>, class-string<BaseMatch>>
*/
private $additionalMatchers = [];

/**
* Get matches for a password.
*
* @param string $password Password string to match
* @param array $userInputs Array of values related to the user (optional)
* @param array<int, string> $userInputs Array of values related to the user (optional)
* @code array('Alice Smith')
* @endcode
*
* @return MatchInterface[] Array of Match objects.
* @return BaseMatch[]
*
* @see zxcvbn/src/matching.coffee::omnimatch
*/
Expand All @@ -39,7 +42,7 @@ public function getMatches(string $password, array $userInputs = []): array
$matches = [];
foreach ($this->getMatchers() as $matcher) {
$matched = $matcher::match($password, $userInputs);
if (is_array($matched) && !empty($matched)) {
if (!empty($matched)) {
$matches[] = $matched;
}
}
Expand All @@ -50,6 +53,11 @@ public function getMatches(string $password, array $userInputs = []): array
return $matches;
}

/**
* @param class-string<BaseMatch> $className
*
* @return $this
*/
public function addMatcher(string $className): self
{
if (!is_a($className, MatchInterface::class, true)) {
Expand All @@ -71,8 +79,9 @@ public function addMatcher(string $className): self
* This function taken from https://github.com/vanderlee/PHP-stable-sort-functions
* Copyright © 2015-2018 Martijn van der Lee (http://martijn.vanderlee.com). MIT License applies.
*
* @param array $array
* @param callable $value_compare_func
* @template TSort
* @param array<TSort> $array
* @param callable(TSort,TSort): int $value_compare_func
* @return bool
*/
public static function usortStable(array &$array, callable $value_compare_func): bool
Expand All @@ -81,13 +90,23 @@ public static function usortStable(array &$array, callable $value_compare_func):
foreach ($array as &$item) {
$item = [$index++, $item];
}
$result = usort($array, function ($a, $b) use ($value_compare_func) {
$result = $value_compare_func($a[1], $b[1]);
return $result == 0 ? $a[0] - $b[0] : $result;
});
unset($item);
$result = usort(
$array,
/**
* @param array $a
* @param array $b
* @return int
*/
function ($a, $b) use ($value_compare_func) {
$result = $value_compare_func($a[1], $b[1]);
return $result == 0 ? $a[0] - $b[0] : $result;
}
);
foreach ($array as &$item) {
$item = $item[1];
}
unset($item);
return $result;
}

Expand All @@ -103,7 +122,7 @@ public static function compareMatches(BaseMatch $a, BaseMatch $b): int
/**
* Load available Match objects to match against a password.
*
* @return array Array of classes implementing MatchInterface
* @return array<class-string<MatchInterface>>
*/
protected function getMatchers(): array
{
Expand Down
22 changes: 11 additions & 11 deletions src/Matchers/BaseMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@
abstract class BaseMatch implements MatchInterface
{
/**
* @var
* @var string
*/
public $password;

/**
* @var
* @var int
*/
public $begin;

/**
* @var
* @var int
*/
public $end;

/**
* @var
* @var string
*/
public $token;

/**
* @var
* @var string
*/
public $pattern;

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

$count = preg_match_all($regex, $string, $matches, PREG_SET_ORDER, $byteOffset);
if (!$count) {
Expand All @@ -93,7 +93,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
foreach ($matches as $group) {
$captureBegin = 0;
$match = array_shift($group);
$matchBegin = mb_strpos($string, $match, $offset);
$matchBegin = (int)mb_strpos($string, $match, $offset);
$captures = [
[
'begin' => $matchBegin,
Expand All @@ -102,7 +102,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
],
];
foreach ($group as $capture) {
$captureBegin = mb_strpos($match, $capture, $captureBegin);
$captureBegin = (int)mb_strpos($match, $capture, $captureBegin);
$captures[] = [
'begin' => $matchBegin + $captureBegin,
'end' => $matchBegin + $captureBegin + mb_strlen($capture) - 1,
Expand Down
2 changes: 1 addition & 1 deletion src/Matchers/Bruteforce.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Bruteforce extends BaseMatch

/**
* @param string $password
* @param array $userInputs
* @param array<int, string> $userInputs
* @return Bruteforce[]
*/
public static function match(string $password, array $userInputs = []): array
Expand Down
43 changes: 33 additions & 10 deletions src/Matchers/DateMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class DateMatch extends BaseMatch

public $pattern = 'date';

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

/** @var int The day portion of the date in the token. */
/**
* The day portion of the date in the token.
*
* @var int
*/
public $day;

/** @var int The month portion of the date in the token. */
/**
* The month portion of the date in the token.
*
* @var int
*/
public $month;

/** @var int The year portion of the date in the token. */
/**
* The year portion of the date in the token.
*
* @var int
*/
public $year;

/** @var string The separator used for the date in the token. */
/**
* The separator used for the date in the token.
*
* @var string
*/
public $separator;

/**
* Match occurences of dates in a password
*
* @param string $password
* @param array $userInputs
* @param array<int, string> $userInputs
* @return DateMatch[]
*/
public static function match(string $password, array $userInputs = []): array
Expand Down Expand Up @@ -124,7 +143,7 @@ public function getFeedback(bool $isSoleMatch): array
* @param int $begin
* @param int $end
* @param string $token
* @param array $params An array with keys: [day, month, year, separator].
* @param array{day: int, month: int, year: int, separator: string} $params
*/
public function __construct(string $password, int $begin, int $end, string $token, array $params)
{
Expand Down Expand Up @@ -272,7 +291,7 @@ public static function getReferenceYear(): int

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

foreach ($possibleYearSplits as [$year, $rest]) {
if ($year >= static::MIN_YEAR && $year <= static::MAX_YEAR) {
if ($dm = static::mapIntsToDayMonth($rest)) {
$dm = static::mapIntsToDayMonth($rest);
if ($dm) {
return [
'year' => $year,
'month' => $dm['month'],
Expand All @@ -334,7 +354,8 @@ protected static function checkDate(array $ints)
}

foreach ($possibleYearSplits as [$year, $rest]) {
if ($dm = static::mapIntsToDayMonth($rest)) {
$dm = static::mapIntsToDayMonth($rest);
if ($dm) {
return [
'year' => static::twoToFourDigitYear($year),
'month' => $dm['month'],
Expand All @@ -348,7 +369,9 @@ protected static function checkDate(array $ints)

/**
* @param int[] $ints Two numbers in an array representing day and month (not necessarily in that order).
* @return array|bool Returns an associative array containing 'day' and 'month' keys, or false if any combination
*
* @return array{day: int, month: int}|false
* Returns an associative array containing 'day' and 'month' keys, or false if any combination
* of the two numbers does not match a day and month.
*/
protected static function mapIntsToDayMonth(array $ints)
Expand Down
Loading