Skip to content

Commit 3bac935

Browse files
committed
Modernize sniffs & helpers and embrace PHPCSUtils
See #72, #73 - Split PHPCSHelpers class in multiple smaller classes, easier to test and maintain - Make use of new helpers and PHPCSUtils in all sniffs - Modernize sniffs and their tests for modern PHP feautures - Reorganize tests and improve bootstrapping - Standardize file headers
1 parent eb1a128 commit 3bac935

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+3373
-1848
lines changed

Inpsyde/Helpers/Boundaries.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the "php-coding-standards" package.
5+
*
6+
* Copyright (c) 2023 Inpsyde GmbH
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
declare(strict_types=1);
28+
29+
namespace Inpsyde\Helpers;
30+
31+
use PHP_CodeSniffer\Files\File;
32+
use PHP_CodeSniffer\Util\Tokens;
33+
use PHPCSUtils\Tokens\Collections;
34+
use PHPCSUtils\Utils\Arrays;
35+
36+
final class Boundaries
37+
{
38+
/**
39+
* @param File $file
40+
* @param int $position
41+
* @return list{int, int}
42+
*/
43+
public static function functionBoundaries(File $file, int $position): array
44+
{
45+
/** @var array<int, array<string, mixed>> $tokens */
46+
$tokens = $file->getTokens();
47+
48+
if (
49+
!in_array(
50+
$tokens[$position]['code'] ?? null,
51+
array_keys(Collections::functionDeclarationTokens()),
52+
true
53+
)
54+
) {
55+
return [-1, -1];
56+
}
57+
58+
return static::startEnd($file, $position);
59+
}
60+
61+
/**
62+
* @param File $file
63+
* @param int $position
64+
* @return list{int, int}
65+
*/
66+
public static function objectBoundaries(File $file, int $position): array
67+
{
68+
/** @var array<int, array<string, mixed>> $tokens */
69+
$tokens = $file->getTokens();
70+
71+
if (!in_array(($tokens[$position]['code'] ?? null), Tokens::$ooScopeTokens, true)) {
72+
return [-1, -1];
73+
}
74+
75+
return static::startEnd($file, $position);
76+
}
77+
78+
/**
79+
* @param File $file
80+
* @param int $position
81+
* @return list{int, int}
82+
*/
83+
public static function arrayBoundaries(File $file, int $position): array
84+
{
85+
$openClose = Arrays::getOpenClose($file, $position);
86+
if (
87+
!is_array($openClose)
88+
|| !is_int($openClose['opener'] ?? null)
89+
|| !is_int($openClose['closer'] ?? null)
90+
) {
91+
return [-1, -1];
92+
}
93+
94+
return [(int)$openClose['opener'], (int)$openClose['closer']];
95+
}
96+
97+
/**
98+
* @param File $file
99+
* @param int $position
100+
* @return list{int, int}
101+
*/
102+
private static function startEnd(File $file, int $position): array
103+
{
104+
/** @var array<string, mixed> $token */
105+
$token = $file->getTokens()[$position] ?? [];
106+
if (($token['code'] ?? '') === T_FN) {
107+
$start = $file->findNext(T_FN_ARROW, $position + 1, null, false, null, true);
108+
if (!$start) {
109+
return [-1, -1];
110+
}
111+
112+
return [$start + 1, $file->findEndOfStatement($start)];
113+
}
114+
115+
$start = (int)($token['scope_opener'] ?? 0);
116+
$end = (int)($token['scope_closer'] ?? 0);
117+
if (($start <= 0) || ($end <= 0) || ($start >= ($end - 1))) {
118+
return [-1, -1];
119+
}
120+
121+
return [$start, $end];
122+
}
123+
}

Inpsyde/Helpers/FunctionDocBlock.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the "php-coding-standards" package.
5+
*
6+
* Copyright (c) 2023 Inpsyde GmbH
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
declare(strict_types=1);
28+
29+
namespace Inpsyde\Helpers;
30+
31+
use PHP_CodeSniffer\Files\File;
32+
33+
final class FunctionDocBlock
34+
{
35+
/**
36+
* @param File $file
37+
* @param int $position
38+
* @param bool $normalizeContent
39+
* @return array<string, list<string>>
40+
*
41+
* phpcs:disable Inpsyde.CodeQuality.FunctionLength
42+
* phpcs:disable Generic.Metrics.CyclomaticComplexity
43+
*/
44+
public static function allTags(
45+
File $file,
46+
int $position,
47+
bool $normalizeContent = true
48+
): array {
49+
// phpcs:enable Inpsyde.CodeQuality.FunctionLength
50+
// phpcs:enable Generic.Metrics.CyclomaticComplexity
51+
52+
/** @var array<int, array<string, mixed>> $tokens */
53+
$tokens = $file->getTokens();
54+
55+
if (
56+
!array_key_exists($position, $tokens)
57+
|| !in_array($tokens[$position]['code'], [T_FUNCTION, T_CLOSURE, T_FN], true)
58+
) {
59+
return [];
60+
}
61+
62+
$closeType = T_DOC_COMMENT_CLOSE_TAG;
63+
$closeTag = $file->findPrevious($closeType, $position - 1, null, false, null, true);
64+
65+
if (!$closeTag || empty($tokens[$closeTag]['comment_opener'])) {
66+
return [];
67+
}
68+
69+
$functionLine = (int)($tokens[$position]['line'] ?? -1);
70+
$closeLine = (int)($tokens[$closeTag]['line'] ?? -1);
71+
if ($closeLine !== ($functionLine - 1)) {
72+
return [];
73+
}
74+
75+
/** @var array<int, array{string, string}> $tags */
76+
$tags = [];
77+
$start = (int)$tokens[$closeTag]['comment_opener'] + 1;
78+
$key = -1;
79+
$inTag = false;
80+
81+
for ($i = $start; $i < $closeTag; $i++) {
82+
$code = $tokens[$i]['code'];
83+
if ($code === T_DOC_COMMENT_STAR) {
84+
continue;
85+
}
86+
87+
$content = (string)$tokens[$i]['content'];
88+
if (($tokens[$i]['code'] === T_DOC_COMMENT_TAG)) {
89+
$inTag = true;
90+
$key++;
91+
$tags[$key] = [$content, ''];
92+
continue;
93+
}
94+
95+
if ($inTag) {
96+
$tags[$key][1] .= $content;
97+
}
98+
}
99+
100+
$normalizedTags = [];
101+
static $rand;
102+
$rand or $rand = bin2hex(random_bytes(3));
103+
foreach ($tags as [$tagName, $tagContent]) {
104+
empty($normalizedTags[$tagName]) and $normalizedTags[$tagName] = [];
105+
if (!$normalizeContent) {
106+
$normalizedTags[$tagName][] = $tagContent;
107+
continue;
108+
}
109+
110+
$lines = array_filter(array_map('trim', explode("\n", $tagContent)));
111+
$normalized = preg_replace('~\s+~', ' ', implode("%LB%{$rand}%LB%", $lines)) ?? '';
112+
$normalizedTags[$tagName][] = trim(str_replace("%LB%{$rand}%LB%", "\n", $normalized));
113+
}
114+
115+
return $normalizedTags;
116+
}
117+
118+
/**
119+
* @param string $tag
120+
* @param File $file
121+
* @param int $position
122+
* @return list<string>
123+
*/
124+
public static function tag(string $tag, File $file, int $position): array
125+
{
126+
$tagName = '@' . ltrim($tag, '@');
127+
$tags = static::allTags($file, $position);
128+
if (empty($tags[$tagName])) {
129+
return [];
130+
}
131+
132+
return $tags[$tagName];
133+
}
134+
135+
/**
136+
* @param File $file
137+
* @param int $functionPosition
138+
* @return array<string, list<string>>
139+
*/
140+
public static function allParamTypes(File $file, int $functionPosition): array
141+
{
142+
$params = static::tag('@param', $file, $functionPosition);
143+
if (!$params) {
144+
return [];
145+
}
146+
147+
$types = [];
148+
foreach ($params as $param) {
149+
preg_match('~^([^$]+)\s*(\$\S+)~', trim($param), $matches);
150+
if (($matches[1] ?? null) && ($matches[2] ?? null)) {
151+
$types[$matches[2]] = static::normalizeTypesString($matches[1]);
152+
}
153+
}
154+
155+
return $types;
156+
}
157+
158+
/**
159+
* @param File $file
160+
* @param int $functionPosition
161+
* @return list<string>
162+
*/
163+
public static function normalizeTypesString(string $typesString): array
164+
{
165+
$typesString = preg_replace('~\s+~', '', $typesString);
166+
$splitTypes = explode('|', $typesString ?? '');
167+
$normalized = [];
168+
$hasNull = false;
169+
foreach ($splitTypes as $splitType) {
170+
if (strpos($splitType, '&') !== false) {
171+
$splitType = rtrim(ltrim($splitType, '('), ')');
172+
} elseif (strpos($splitType, '?') === 0) {
173+
$splitType = substr($splitType, 1);
174+
$hasNull = $splitType !== false;
175+
}
176+
if ($splitType === false) {
177+
continue;
178+
}
179+
if (strtolower($splitType) === 'null') {
180+
$hasNull = true;
181+
continue;
182+
}
183+
$normalized[] = $splitType;
184+
}
185+
$ordered = array_values(array_unique($normalized));
186+
sort($ordered, SORT_STRING);
187+
$hasNull and $ordered[] = 'null';
188+
189+
return $ordered;
190+
}
191+
}

0 commit comments

Comments
 (0)