Skip to content

Commit 29a74db

Browse files
authored
Merge pull request phpbb#6815 from rxu/ticket/17512
[ticket/17512] Add PHP Sniffer coding standard for union types
2 parents 5e12e8b + 1cd17ca commit 29a74db

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
/**
3+
*
4+
* This file is part of the phpBB Forum Software package.
5+
*
6+
* @copyright (c) phpBB Limited <https://www.phpbb.com>
7+
* @license GNU General Public License, version 2 (GPL-2.0)
8+
*
9+
* For full copyright and license information, please see
10+
* the docs/CREDITS.txt file.
11+
*
12+
*/
13+
14+
namespace phpbb\Sniffs\CodeLayout;
15+
16+
use PHP_CodeSniffer\Files\File;
17+
use PHP_CodeSniffer\Sniffs\Sniff;
18+
19+
/**
20+
* Checks that union type declarations follows the coding guidelines.
21+
*/
22+
class UnionTypesCheckSniff implements Sniff
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function register()
28+
{
29+
return [
30+
T_FUNCTION,
31+
T_CLASS,
32+
];
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function process(File $phpcsFile, $stackPtr)
39+
{
40+
$tokens = $phpcsFile->getTokens();
41+
if ($tokens[$stackPtr]['type'] === 'T_FUNCTION')
42+
{
43+
$method_params = $phpcsFile->getMethodParameters($stackPtr);
44+
$method_params_array = array_column($method_params, 'type_hint', 'token');
45+
foreach ($method_params_array as $stack_pointer => $type_hint)
46+
{
47+
$this->check_union_type($phpcsFile, $stack_pointer, $type_hint);
48+
}
49+
50+
$method_properties = $phpcsFile->getMethodProperties($stackPtr);
51+
$this->check_union_type($phpcsFile, $stackPtr, $method_properties['return_type']);
52+
}
53+
else if ($tokens[$stackPtr]['type'] === 'T_CLASS')
54+
{
55+
$class_token = $tokens[$stackPtr];
56+
$class_closer_pointer = $class_token['scope_closer'];
57+
$first_method_pointer = $phpcsFile->findNext(T_FUNCTION, $stackPtr);
58+
$class_members_declarations_end_pointer = $first_method_pointer ?: $class_closer_pointer;
59+
60+
$stack_pointer = $stackPtr;
61+
while(($class_member_pointer = $phpcsFile->findNext(T_VARIABLE, $stack_pointer)) !== false && ($class_member_pointer < $class_members_declarations_end_pointer))
62+
{
63+
$properties = $phpcsFile->getMemberProperties($class_member_pointer);
64+
$this->check_union_type($phpcsFile, $class_member_pointer, $properties['type']);
65+
$stack_pointer = $class_member_pointer + 1;
66+
}
67+
}
68+
}
69+
70+
public function check_union_type(File $phpcsFile, $stack_pointer, $type_hint)
71+
{
72+
if (empty($type_hint))
73+
{
74+
return;
75+
}
76+
77+
if (!strpos($type_hint, '|') && $type_hint[0] == '?') // Check nullable shortcut syntax
78+
{
79+
$type = substr($type_hint, 1);
80+
$error = 'Nullable shortcut syntax must not be used. Use union type instead: %1$s|null; found %2$s';
81+
$data = [$type, $type_hint];
82+
$phpcsFile->addError($error, $stack_pointer, 'ShortNullableSyntax', $data);
83+
}
84+
else if ((count($types_array = explode('|', $type_hint))) > 1) // Check union type layout
85+
{
86+
$types_array_null_less = $types_array;
87+
88+
// Check 'null' to be the last element
89+
$null_position = array_search('null', $types_array);
90+
if ($null_position !== false && $null_position != array_key_last($types_array))
91+
{
92+
$error = 'The "null" type hint must be the last of the union type elements; found %s';
93+
$data = [implode('|', $types_array)];
94+
$phpcsFile->addError($error, $stack_pointer, 'NullAlwaysLast', $data);
95+
}
96+
97+
// Check types excepting 'null' to follow alphabetical order
98+
if ($null_position !== false)
99+
{
100+
array_splice($types_array_null_less, $null_position, 1);
101+
}
102+
103+
if (count($types_array_null_less) > 1)
104+
{
105+
$types_array_null_less_sorted = $types_array_null_less;
106+
sort($types_array_null_less_sorted);
107+
if (!empty(array_diff_assoc($types_array_null_less, $types_array_null_less_sorted)))
108+
{
109+
$error = 'Union type elements must be sorted alphabetically excepting the "null" type hint must be the last if any; found %s';
110+
$data = [implode('|', $types_array)];
111+
$phpcsFile->addError($error, $stack_pointer, 'AlphabeticalSort', $data);
112+
}
113+
}
114+
}
115+
}
116+
}

build/code_sniffer/ruleset-php-strict.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@
4545
<!-- There MUST NOT be unused use statements. -->
4646
<rule ref="./phpbb/Sniffs/Namespaces/UnusedUseSniff.php" />
4747

48+
<!-- The null type SHALL be the last union type element, other types SHALL follow alphabetical order.
49+
No nullable type shortcut syntax should be used. -->
50+
<rule ref="./phpbb/Sniffs/CodeLayout/UnionTypesCheckSniff.php" />
51+
4852
</ruleset>

phpBB/phpbb/routing/loader_resolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function __construct($loaders = [])
3333
/**
3434
* {@inheritdoc}
3535
*/
36-
public function resolve($resource, $type = null): false|\Symfony\Component\Config\Loader\LoaderInterface
36+
public function resolve($resource, $type = null): \Symfony\Component\Config\Loader\LoaderInterface|false
3737
{
3838
/** @var \Symfony\Component\Config\Loader\LoaderInterface $loader */
3939
foreach ($this->loaders as $loader)

0 commit comments

Comments
 (0)