Skip to content

Commit 505c9b2

Browse files
MC-19366: Adds sniff for valid enum values
1 parent 2a8d521 commit 505c9b2

File tree

5 files changed

+220
-15
lines changed

5 files changed

+220
-15
lines changed

Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* To obtain a valid license for using this software please contact us at license@techdivision.com
1212
*/
13+
1314
namespace Magento2\Sniffs\GraphQL;
1415

1516
use PHP_CodeSniffer\Sniffs\Sniff;
@@ -38,13 +39,38 @@ protected function isCamelCase($name)
3839
}
3940

4041
/**
41-
* Returns whether <var>$name</var> is strictly lower case, potentially separated by underscores.
42+
* Returns whether <var>$name</var> is specified in snake case (either all lower case or all upper case).
4243
*
4344
* @param string $name
45+
* @param bool $upperCase If set to <kbd>true</kbd> checks for all upper case, otherwise all lower case
4446
* @return bool
4547
*/
46-
protected function isSnakeCase($name)
48+
protected function isSnakeCase($name, $upperCase = false)
4749
{
48-
return preg_match('/^[a-z][a-z0-9_]*$/', $name);
50+
$pattern = $upperCase ? '/^[A-Z][A-Z0-9_]*$/' : '/^[a-z][a-z0-9_]*$/';
51+
return preg_match($pattern, $name);
52+
}
53+
54+
/**
55+
* Searches for the first token that has <var>$tokenCode</var> in <var>$tokens</var> from position
56+
* <var>$startPointer</var> (excluded).
57+
*
58+
* @param mixed $tokenCode
59+
* @param array $tokens
60+
* @param int $startPointer
61+
* @return bool|int If token was found, returns its pointer, <kbd>false</kbd> otherwise
62+
*/
63+
protected function seekToken($tokenCode, array $tokens, $startPointer = 0)
64+
{
65+
$numTokens = count($tokens);
66+
67+
for ($i = $startPointer + 1; $i < $numTokens; ++$i) {
68+
if ($tokens[$i]['code'] === $tokenCode) {
69+
return $i;
70+
}
71+
}
72+
73+
//if we came here we could not find the requested token
74+
return false;
4975
}
5076
}

Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright © Magento. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
67
namespace Magento2\Sniffs\GraphQL;
78

89
use PHP_CodeSniffer\Files\File;
@@ -64,7 +65,7 @@ public function process(File $phpcsFile, $stackPtr)
6465
}
6566

6667
/**
67-
* Finds all argument names contained in <var>$tokens</var> range <var>$startPointer</var> and
68+
* Finds all argument names contained in <var>$tokens</var> in range <var>$startPointer</var> to
6869
* <var>$endPointer</var>.
6970
*
7071
* @param int $startPointer
@@ -80,7 +81,7 @@ private function getArguments($startPointer, $endPointer, array $tokens)
8081
$skipTypes = [T_COMMENT, T_WHITESPACE];
8182

8283
for ($i = $startPointer + 1; $i < $endPointer; ++$i) {
83-
//skip comment tokens
84+
//skip some tokens
8485
if (in_array($tokens[$i]['code'], $skipTypes)) {
8586
continue;
8687
}
@@ -110,15 +111,6 @@ private function getArguments($startPointer, $endPointer, array $tokens)
110111
*/
111112
private function getCloseParenthesisPointer($stackPointer, array $tokens)
112113
{
113-
$numTokens = count($tokens);
114-
115-
for ($i = $stackPointer + 1; $i < $numTokens; ++$i) {
116-
if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
117-
return $i;
118-
}
119-
}
120-
121-
//if we came here we could not find the closing parenthesis
122-
return false;
114+
return $this->seekToken(T_CLOSE_PARENTHESIS, $tokens, $stackPointer);
123115
}
124116
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/**
3+
* Copyright © Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento2\Sniffs\GraphQL;
7+
8+
use PHP_CodeSniffer\Files\File;
9+
10+
/**
11+
* Detects enum values that are not specified in <kbd>SCREAMING_SNAKE_CASE</kbd>.
12+
*/
13+
class ValidEnumValueSniff extends AbstractGraphQLSniff
14+
{
15+
16+
/**
17+
* @inheritDoc
18+
*/
19+
public function register()
20+
{
21+
return [T_CLASS];
22+
}
23+
24+
/**
25+
* @inheritDoc
26+
*/
27+
public function process(File $phpcsFile, $stackPtr)
28+
{
29+
$tokens = $phpcsFile->getTokens();
30+
31+
//bail out if we're not inspecting an enum
32+
if ($tokens[$stackPtr]['content'] !== 'enum') {
33+
return;
34+
}
35+
36+
$openingCurlyPointer = $this->getOpeningCurlyBracketPointer($stackPtr, $tokens);
37+
$closingCurlyPointer = $this->getClosingCurlyBracketPointer($stackPtr, $tokens);
38+
39+
//if we could not find the closing curly bracket pointer, we add a warning and terminate
40+
if ($openingCurlyPointer === false || $closingCurlyPointer === false) {
41+
$error = 'Possible parse error: %s missing opening or closing brace';
42+
$data = [$tokens[$stackPtr]['content']];
43+
$phpcsFile->addWarning($error, $stackPtr, 'MissingBrace', $data);
44+
return;
45+
}
46+
47+
$values = $this->getValues($openingCurlyPointer, $closingCurlyPointer, $tokens, $phpcsFile->eolChar);
48+
49+
foreach ($values as $value) {
50+
$pointer = $value[0];
51+
$name = $value[1];
52+
53+
if (!$this->isSnakeCase($name, true)) {
54+
$type = 'Enum value';
55+
$error = '%s "%s" is not in SCREAMING_SNAKE_CASE format';
56+
$data = [
57+
$type,
58+
$name,
59+
];
60+
61+
$phpcsFile->addError($error, $pointer, 'NotScreamingSnakeCase', $data);
62+
$phpcsFile->recordMetric($pointer, 'SCREAMING_SNAKE_CASE enum value', 'no');
63+
} else {
64+
$phpcsFile->recordMetric($pointer, 'SCREAMING_SNAKE_CASE enum value', 'yes');
65+
}
66+
}
67+
68+
return $closingCurlyPointer;
69+
}
70+
71+
/**
72+
* Seeks the next available token of type {@link T_CLOSE_CURLY_BRACKET} in <var>$tokens</var> and returns its
73+
* pointer.
74+
*
75+
* @param int $startPointer
76+
* @param array $tokens
77+
* @return bool|int
78+
*/
79+
private function getClosingCurlyBracketPointer($startPointer, array $tokens)
80+
{
81+
return $this->seekToken(T_CLOSE_CURLY_BRACKET, $tokens, $startPointer);
82+
}
83+
84+
/**
85+
* Seeks the next available token of type {@link T_OPEN_CURLY_BRACKET} in <var>$tokens</var> and returns its
86+
* pointer.
87+
*
88+
* @param $startPointer
89+
* @param array $tokens
90+
* @return bool|int
91+
*/
92+
private function getOpeningCurlyBracketPointer($startPointer, array $tokens)
93+
{
94+
return $this->seekToken(T_OPEN_CURLY_BRACKET, $tokens, $startPointer);
95+
}
96+
97+
/**
98+
* Finds all enum values contained in <var>$tokens</var> in range <var>$startPointer</var> to
99+
* <var>$endPointer</var>.
100+
*
101+
* @param int $startPointer
102+
* @param int $endPointer
103+
* @param array $tokens
104+
* @param string $eolChar
105+
* @return array[]
106+
*/
107+
private function getValues($startPointer, $endPointer, array $tokens, $eolChar)
108+
{
109+
$valueTokenPointer = null;
110+
$enumValue = '';
111+
$values = [];
112+
$skipTypes = [T_COMMENT, T_WHITESPACE];
113+
114+
for ($i = $startPointer + 1; $i < $endPointer; ++$i) {
115+
//skip some tokens
116+
if (in_array($tokens[$i]['code'], $skipTypes)) {
117+
continue;
118+
}
119+
$enumValue .= $tokens[$i]['content'];
120+
121+
if ($valueTokenPointer === null) {
122+
$valueTokenPointer = $i;
123+
}
124+
125+
if (strpos($enumValue, $eolChar) !== false) {
126+
$values[] = [$valueTokenPointer, rtrim($enumValue, $eolChar)];
127+
$enumValue = '';
128+
$valueTokenPointer = null;
129+
}
130+
}
131+
132+
return $values;
133+
}
134+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
enum Foo {
2+
# Valid values
3+
VALID_SCREAMING_SNAKE_CASE_VALUE
4+
VALID_SCREAMING_SNAKE_CASE_VALUE_WITH_1_NUMBER
5+
VALID_SCREAMING_SNAKE_CASE_VALUE_WITH_12345_NUMBERS
6+
VALID_SCREAMING_SNAKE_CASE_VALUE_ENDING_WITH_NUMBER_5
7+
VALIDUPPERCASEVALUE
8+
9+
# Invalid values
10+
1_INVALID_SCREAMING_SNAKE_CASE_VALUE_STARTING_WITH_NUMBER
11+
invalidCamelCaseValue
12+
InvalidCamelCaseCapsValue
13+
invalidlowercasevalue
14+
}
15+
16+
# Ignored although it triggers a T_CLASS token
17+
type Bar {
18+
some_field: String
19+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/**
3+
* Copyright © Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento2\Tests\GraphQL;
8+
9+
/**
10+
* Covers {@link \Magento2\Sniffs\GraphQL\ValidEnumValueSniff}
11+
*/
12+
class ValidEnumValueUnitTest extends AbstractGraphQLSniffUnitTestCase
13+
{
14+
/**
15+
* @inheritDoc
16+
*/
17+
protected function getErrorList()
18+
{
19+
return [
20+
10 => 1,
21+
11 => 1,
22+
12 => 1,
23+
13 => 1,
24+
];
25+
}
26+
27+
/**
28+
* @inheritDoc
29+
*/
30+
protected function getWarningList()
31+
{
32+
return [];
33+
}
34+
}

0 commit comments

Comments
 (0)