From 4ad34bc462d635b5d791e37ce5d3022c92b8bbb0 Mon Sep 17 00:00:00 2001 From: James C <5689414+james-cnz@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:04:52 +1300 Subject: [PATCH] Support templates --- classes/type_parser.php | 28 +++++++++++++++++-------- file.php | 45 +++++++++++++++++++++++++++++++++++------ rules/phpdocs_basic.php | 2 +- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/classes/type_parser.php b/classes/type_parser.php index b1e1407..304c779 100644 --- a/classes/type_parser.php +++ b/classes/type_parser.php @@ -141,6 +141,9 @@ class type_parser { /** @var bool when we encounter an unknown type, should we go wide or narrow */ protected $gowide = false; + /** @var array type templates */ + protected $templates = []; + /** @var object{startpos: non-negative-int, endpos: non-negative-int, text: ?non-empty-string}[] next tokens */ protected $nexts = []; @@ -149,26 +152,34 @@ class type_parser { /** * Constructor - * @param array $usealiases aliases are keys, class names are values - * @param array $artifacts inherit tree + * @param \local_moodlecheck_file $file */ - public function __construct(array $usealiases = [], array $artifacts = []) { - $this->usealiases = $usealiases; - $this->artifacts = $artifacts; + public function __construct(\local_moodlecheck_file $file) { + $this->usealiases = $file->get_use_aliases(); + $this->artifacts = $file->get_artifacts_flat(); } /** * Parse a type and possibly variable name * + * @param ?\local_moodlecheck_phpdocs $phpdocs * @param string $text the text to parse * @param 0|1|2|3 $getwhat what to get 0=type only 1=also var 2=also modifiers (& ...) 3=also default * @param bool $gowide if we can't determine the type, should we assume wide (for native type) or narrow (for PHPDoc)? * @return array{?non-empty-string, ?non-empty-string, string, bool} the simplified type, variable, remaining text, * and whether the type is explicitly nullable */ - public function parse_type_and_var(string $text, int $getwhat, bool $gowide): array { + public function parse_type_and_var(?\local_moodlecheck_phpdocs $phpdocs, + string $text, int $getwhat, bool $gowide): array { // Initialise variables. + if ($phpdocs) { + $artifact = $phpdocs->get_artifact(); + $artifacttemplates = ($artifact && $artifact->phpdocs) ? $artifact->phpdocs->get_templates() : []; + $this->templates = array_merge($artifacttemplates, $phpdocs->get_templates()); + } else { + $this->templates = []; + } $this->text = strtolower($text); $this->textwithcase = $text; $this->gowide = $gowide; @@ -944,7 +955,9 @@ protected function parse_basic_type(): string { } $type = ucfirst($type); assert($type != ''); - if ($this->next == '<') { + if ($this->templates[strtolower($type)] ?? null) { + $type = $this->templates[strtolower($type)]; + } else if ($this->next == '<') { // Collection / Traversable. $this->parse_token('<'); $firsttype = $this->parse_any_type(); @@ -978,7 +991,6 @@ protected function parse_basic_type(): string { $type = $this->gowide ? 'mixed' : 'never'; } - assert(strpos($type, '|') === false && strpos($type, '&') === false); return $type; } diff --git a/file.php b/file.php index ac7196a..e82734d 100644 --- a/file.php +++ b/file.php @@ -429,7 +429,7 @@ public function &get_classes() { */ public function get_type_parser() { if (!$this->typeparser) { - $this->typeparser = new \local_moodlecheck\type_parser($this->get_use_aliases(), $this->get_artifacts_flat()); + $this->typeparser = new \local_moodlecheck\type_parser($this); } return $this->typeparser; } @@ -517,13 +517,13 @@ public function &get_functions() { $text .= $argtokens[$j][1]; } } - list($type, $variable, $default, $nullable) = $typeparser->parse_type_and_var($text, 3, true); + list($type, $variable, $default, $nullable) = $typeparser->parse_type_and_var(null, $text, 3, true); $function->arguments[] = [$type, $variable, $nullable]; } // Get return type. - $returnpair = $this->find_tag_pair($argumentspair[1] + 1, ':', '{', ['{', ';']); + $returnpair = $this->find_tag_pair($argumentspair ? $argumentspair[1] + 1 : $tid + 1, ':', '{', ['{', ';']); if ($returnpair !== false && $returnpair[1] - $returnpair[0] > 1) { $rettokens = array_slice($tokens, $returnpair[0] + 1, $returnpair[1] - $returnpair[0] - 1); @@ -536,7 +536,7 @@ public function &get_functions() { $text .= $rettokens[$j][1]; } } - list($type, $varname, $default, $nullable) = $typeparser->parse_type_and_var($text, 0, true); + list($type, $varname, $default, $nullable) = $typeparser->parse_type_and_var(null, $text, 0, true); $function->return = $type; @@ -586,7 +586,7 @@ public function &get_variables() { $text .= $this->tokens[$typetid][1]; } } - list($type, $varname, $default, $nullable) = $typeparser->parse_type_and_var($text, 1, true); + list($type, $varname, $default, $nullable) = $typeparser->parse_type_and_var(null, $text, 1, true); $variable->type = $type; } else { $variable->type = null; @@ -1237,6 +1237,7 @@ class local_moodlecheck_phpdocs { 'static', 'staticvar', 'subpackage', + // 'template', 'throws', 'todo', 'tutorial', @@ -1283,6 +1284,7 @@ class local_moodlecheck_phpdocs { 'see', 'since', 'subpackage', + // 'template', 'throws', 'todo', 'uses', @@ -1378,6 +1380,10 @@ public function __construct($file, $token, $tid) { $this->description = trim($this->description); } + public function get_artifact() { + return $this->file->is_inside_artifact($this->originaltid); + } + /** * Returns all tags found in phpdocs * @@ -1475,6 +1481,32 @@ public function get_shortdescription() { } } + public function get_templates() { + $typeparser = $this->file->get_type_parser(); + $templates = []; + + foreach ($this->get_tags('template') as $token) { + $token = trim($token); + $nameend = 0; + while ($nameend < strlen($token) && (ctype_alnum($token[$nameend]) || $token[$nameend] == '_')) { + $nameend++; + } + if ($nameend > 0) { + $ofstart = $nameend; + while ($ofstart < strlen($token) && ctype_space($token[$ofstart])) { + $ofstart++; + } + $type = 'mixed'; + if (substr($token, $ofstart, 2) == 'of') { + list($type) = $typeparser->parse_type_and_var(null, substr($token, $ofstart+2), 0, false); + } + $templates[strtolower(substr($token, 0, $nameend))] = $type; + } + } + + return $templates; + } + /** * Returns list of parsed param tokens found in phpdocs * @@ -1489,7 +1521,8 @@ public function get_params(string $tag, int $getwhat) { $params = []; foreach ($this->get_tags($tag) as $token) { - list($type, $variable, $description) = $typeparser->parse_type_and_var($token, $getwhat, false); + list($type, $variable, $description) = + $typeparser->parse_type_and_var($this, $token, $getwhat, false); $param = []; $param[] = $type; if ($getwhat >= 1) { diff --git a/rules/phpdocs_basic.php b/rules/phpdocs_basic.php index b6a5f32..13f5a63 100644 --- a/rules/phpdocs_basic.php +++ b/rules/phpdocs_basic.php @@ -503,7 +503,7 @@ function local_moodlecheck_functionarguments(local_moodlecheck_file $file) { */ function local_moodlecheck_normalise_function_type(string $typelist): ?string { $typeparser = new \local_moodlecheck\type_parser(); - list($type, $variable, $remainder) = $typeparser->parse_type_and_var($typelist, 0, true); + list($type, $variable, $remainder) = $typeparser->parse_type_and_var(null, $typelist, 0, true); return $type; }