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

Support class-string type and attributes on parameters #148

Open
wants to merge 2 commits into
base: main
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
11 changes: 11 additions & 0 deletions file.php
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,17 @@ public function &get_functions() {
continue;
}
}
if (version_compare(PHP_VERSION, '8.0.0') >= 0) {
// T_ATTRIBUTE introduced in PHP 8.0.
if ($argtokens[$j][0] === T_ATTRIBUTE) {
[, $attrclose] = $this->find_tag_pair_inlist($argtokens, $j, "#[", "]");
if ($attrclose) {
// Skip the attribute.
$j = $attrclose;
}
continue;
}
}
switch ($argtokens[$j][0]) {
// Skip any whitespace, or argument visibility.
case T_COMMENT:
Expand Down
72 changes: 49 additions & 23 deletions rules/phpdocs_basic.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,34 +157,17 @@ function local_moodlecheck_functionarguments(local_moodlecheck_file $file) {
// Must be at least type and parameter name.
$match = false;
} else {
$expectedtype = local_moodlecheck_normalise_function_type((string) $function->arguments[$i][0]);
$expectedtype = local_moodlecheck_normalise_function_type((string)$function->arguments[$i][0]);
$expectedparam = (string)$function->arguments[$i][1];
$documentedtype = local_moodlecheck_normalise_function_type((string) $documentedarguments[$i][0]);
$documentedtype = local_moodlecheck_normalise_function_type((string)$documentedarguments[$i][0]);
$documentedparam = $documentedarguments[$i][1];

$typematch = $expectedtype === $documentedtype;
$parammatch = $expectedparam === $documentedparam;
if ($typematch && $parammatch) {
continue;
if ($expectedparam !== $documentedparam) {
$match = false;
}

// Documented types can be a collection (| separated).
foreach (explode('|', $documentedtype) as $documentedtype) {
// Ignore null. They cannot match any type in function.
if (trim($documentedtype) === 'null') {
continue;
}

if (strlen($expectedtype) && $expectedtype !== $documentedtype) {
// It could be a type hinted array.
if ($expectedtype !== 'array' || substr($documentedtype, -2) !== '[]') {
$match = false;
}
} else if ($documentedtype === 'type') {
$match = false;
} else if ($expectedparam !== $documentedparam) {
$match = false;
}
if (!local_moodlecheck_is_documented_type_allowed($expectedtype, $documentedtype)) {
$match = false;
}
}
}
Expand All @@ -204,6 +187,49 @@ function local_moodlecheck_functionarguments(local_moodlecheck_file $file) {
return $errors;
}

/**
* Checks if a documented type is allowed for a parameter with the given real type declaration.
*
* @param string $expectedtype the real type declaration
* @param string $documentedtype the type documented in PHPdoc
* @return bool true if allowed, false if not
*/
function local_moodlecheck_is_documented_type_allowed(string $expectedtype, string $documentedtype): bool {
if ($expectedtype === $documentedtype) {
return true;
}

// Documented types can be a collection (| separated).
foreach (explode('|', $documentedtype) as $documentedtype) {
// Ignore null. They cannot match any type in function.
if (trim($documentedtype) === 'null') {
continue;
}

if (strlen($expectedtype) && $expectedtype !== $documentedtype) {
// Allow type-hinted arrays.
if ($expectedtype === 'array' && substr($documentedtype, -2) === '[]') {
continue;
}

$withoutgenerics = substr($documentedtype, 0, strpos($documentedtype, "<")) ?: $documentedtype;
// Allow class-string<T> and the like for a string parameter.
if ($expectedtype === 'string' && in_array($withoutgenerics,
["class-string", "interface-string", "trait-string", "enum-string", "callable-string",
"numeric-string", "literal-string", "lowercase-string", "non-empty-string",
"non-empty-lowercase-string"])) {
continue;
}

return false;
} else if ($documentedtype === 'type') {
return false;
}
}

return true;
}

/**
* Normalise function type to be able to compare it.
*
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/phpdoc_method_multiline.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,17 @@ public function function_multiline5(
): array {
// Do something.
}

/**
* One function, what else.
*
* @param string $arg
* @return array
*/
public function function_attribute(
#[some_attr]
string $arg
): array {
// Do something.
}
}
9 changes: 9 additions & 0 deletions tests/fixtures/phpdoc_tags_general.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ public function correct_param_types5(string $one, ...$params) {
echo "yay!";
}

/**
* Correct param types.
*
* @param class-string $myclass
*/
public function correct_param_types6(string $myclass) {
echo "yay!";
}

/**
* Incomplete return annotation (type is missing).
*
Expand Down
Loading