Skip to content

Commit

Permalink
MC-19366: Adds sniff for valid enum values
Browse files Browse the repository at this point in the history
  • Loading branch information
jean-bernard-valentaten committed Sep 12, 2019
1 parent 2a8d521 commit 505c9b2
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 15 deletions.
32 changes: 29 additions & 3 deletions Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*
* To obtain a valid license for using this software please contact us at [email protected]
*/

namespace Magento2\Sniffs\GraphQL;

use PHP_CodeSniffer\Sniffs\Sniff;
Expand Down Expand Up @@ -38,13 +39,38 @@ protected function isCamelCase($name)
}

/**
* Returns whether <var>$name</var> is strictly lower case, potentially separated by underscores.
* Returns whether <var>$name</var> is specified in snake case (either all lower case or all upper case).
*
* @param string $name
* @param bool $upperCase If set to <kbd>true</kbd> checks for all upper case, otherwise all lower case
* @return bool
*/
protected function isSnakeCase($name)
protected function isSnakeCase($name, $upperCase = false)
{
return preg_match('/^[a-z][a-z0-9_]*$/', $name);
$pattern = $upperCase ? '/^[A-Z][A-Z0-9_]*$/' : '/^[a-z][a-z0-9_]*$/';
return preg_match($pattern, $name);
}

/**
* Searches for the first token that has <var>$tokenCode</var> in <var>$tokens</var> from position
* <var>$startPointer</var> (excluded).
*
* @param mixed $tokenCode
* @param array $tokens
* @param int $startPointer
* @return bool|int If token was found, returns its pointer, <kbd>false</kbd> otherwise
*/
protected function seekToken($tokenCode, array $tokens, $startPointer = 0)
{
$numTokens = count($tokens);

for ($i = $startPointer + 1; $i < $numTokens; ++$i) {
if ($tokens[$i]['code'] === $tokenCode) {
return $i;
}
}

//if we came here we could not find the requested token
return false;
}
}
16 changes: 4 additions & 12 deletions Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento2\Sniffs\GraphQL;

use PHP_CodeSniffer\Files\File;
Expand Down Expand Up @@ -64,7 +65,7 @@ public function process(File $phpcsFile, $stackPtr)
}

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

for ($i = $startPointer + 1; $i < $endPointer; ++$i) {
//skip comment tokens
//skip some tokens
if (in_array($tokens[$i]['code'], $skipTypes)) {
continue;
}
Expand Down Expand Up @@ -110,15 +111,6 @@ private function getArguments($startPointer, $endPointer, array $tokens)
*/
private function getCloseParenthesisPointer($stackPointer, array $tokens)
{
$numTokens = count($tokens);

for ($i = $stackPointer + 1; $i < $numTokens; ++$i) {
if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
return $i;
}
}

//if we came here we could not find the closing parenthesis
return false;
return $this->seekToken(T_CLOSE_PARENTHESIS, $tokens, $stackPointer);
}
}
134 changes: 134 additions & 0 deletions Magento2/Sniffs/GraphQL/ValidEnumValueSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php
/**
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento2\Sniffs\GraphQL;

use PHP_CodeSniffer\Files\File;

/**
* Detects enum values that are not specified in <kbd>SCREAMING_SNAKE_CASE</kbd>.
*/
class ValidEnumValueSniff extends AbstractGraphQLSniff
{

/**
* @inheritDoc
*/
public function register()
{
return [T_CLASS];
}

/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

//bail out if we're not inspecting an enum
if ($tokens[$stackPtr]['content'] !== 'enum') {
return;
}

$openingCurlyPointer = $this->getOpeningCurlyBracketPointer($stackPtr, $tokens);
$closingCurlyPointer = $this->getClosingCurlyBracketPointer($stackPtr, $tokens);

//if we could not find the closing curly bracket pointer, we add a warning and terminate
if ($openingCurlyPointer === false || $closingCurlyPointer === false) {
$error = 'Possible parse error: %s missing opening or closing brace';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addWarning($error, $stackPtr, 'MissingBrace', $data);
return;
}

$values = $this->getValues($openingCurlyPointer, $closingCurlyPointer, $tokens, $phpcsFile->eolChar);

foreach ($values as $value) {
$pointer = $value[0];
$name = $value[1];

if (!$this->isSnakeCase($name, true)) {
$type = 'Enum value';
$error = '%s "%s" is not in SCREAMING_SNAKE_CASE format';
$data = [
$type,
$name,
];

$phpcsFile->addError($error, $pointer, 'NotScreamingSnakeCase', $data);
$phpcsFile->recordMetric($pointer, 'SCREAMING_SNAKE_CASE enum value', 'no');
} else {
$phpcsFile->recordMetric($pointer, 'SCREAMING_SNAKE_CASE enum value', 'yes');
}
}

return $closingCurlyPointer;
}

/**
* Seeks the next available token of type {@link T_CLOSE_CURLY_BRACKET} in <var>$tokens</var> and returns its
* pointer.
*
* @param int $startPointer
* @param array $tokens
* @return bool|int
*/
private function getClosingCurlyBracketPointer($startPointer, array $tokens)
{
return $this->seekToken(T_CLOSE_CURLY_BRACKET, $tokens, $startPointer);
}

/**
* Seeks the next available token of type {@link T_OPEN_CURLY_BRACKET} in <var>$tokens</var> and returns its
* pointer.
*
* @param $startPointer
* @param array $tokens
* @return bool|int
*/
private function getOpeningCurlyBracketPointer($startPointer, array $tokens)
{
return $this->seekToken(T_OPEN_CURLY_BRACKET, $tokens, $startPointer);
}

/**
* Finds all enum values contained in <var>$tokens</var> in range <var>$startPointer</var> to
* <var>$endPointer</var>.
*
* @param int $startPointer
* @param int $endPointer
* @param array $tokens
* @param string $eolChar
* @return array[]
*/
private function getValues($startPointer, $endPointer, array $tokens, $eolChar)
{
$valueTokenPointer = null;
$enumValue = '';
$values = [];
$skipTypes = [T_COMMENT, T_WHITESPACE];

for ($i = $startPointer + 1; $i < $endPointer; ++$i) {
//skip some tokens
if (in_array($tokens[$i]['code'], $skipTypes)) {
continue;
}
$enumValue .= $tokens[$i]['content'];

if ($valueTokenPointer === null) {
$valueTokenPointer = $i;
}

if (strpos($enumValue, $eolChar) !== false) {
$values[] = [$valueTokenPointer, rtrim($enumValue, $eolChar)];
$enumValue = '';
$valueTokenPointer = null;
}
}

return $values;
}
}
19 changes: 19 additions & 0 deletions Magento2/Tests/GraphQL/ValidEnumValueUnitTest.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
enum Foo {
# Valid values
VALID_SCREAMING_SNAKE_CASE_VALUE
VALID_SCREAMING_SNAKE_CASE_VALUE_WITH_1_NUMBER
VALID_SCREAMING_SNAKE_CASE_VALUE_WITH_12345_NUMBERS
VALID_SCREAMING_SNAKE_CASE_VALUE_ENDING_WITH_NUMBER_5
VALIDUPPERCASEVALUE

# Invalid values
1_INVALID_SCREAMING_SNAKE_CASE_VALUE_STARTING_WITH_NUMBER
invalidCamelCaseValue
InvalidCamelCaseCapsValue
invalidlowercasevalue
}

# Ignored although it triggers a T_CLASS token
type Bar {
some_field: String
}
34 changes: 34 additions & 0 deletions Magento2/Tests/GraphQL/ValidEnumValueUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento2\Tests\GraphQL;

/**
* Covers {@link \Magento2\Sniffs\GraphQL\ValidEnumValueSniff}
*/
class ValidEnumValueUnitTest extends AbstractGraphQLSniffUnitTestCase
{
/**
* @inheritDoc
*/
protected function getErrorList()
{
return [
10 => 1,
11 => 1,
12 => 1,
13 => 1,
];
}

/**
* @inheritDoc
*/
protected function getWarningList()
{
return [];
}
}

0 comments on commit 505c9b2

Please sign in to comment.