diff --git a/Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php b/Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php
index b03cffe8..b9e95fae 100644
--- a/Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php
+++ b/Magento2/Sniffs/GraphQL/AbstractGraphQLSniff.php
@@ -10,6 +10,7 @@
*
* To obtain a valid license for using this software please contact us at license@techdivision.com
*/
+
namespace Magento2\Sniffs\GraphQL;
use PHP_CodeSniffer\Sniffs\Sniff;
@@ -38,13 +39,38 @@ protected function isCamelCase($name)
}
/**
- * Returns whether $name is strictly lower case, potentially separated by underscores.
+ * Returns whether $name is specified in snake case (either all lower case or all upper case).
*
* @param string $name
+ * @param bool $upperCase If set to true 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 $tokenCode in $tokens from position
+ * $startPointer (excluded).
+ *
+ * @param mixed $tokenCode
+ * @param array $tokens
+ * @param int $startPointer
+ * @return bool|int If token was found, returns its pointer, false 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;
}
}
diff --git a/Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php b/Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php
index ffb1f8d4..03de433f 100644
--- a/Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php
+++ b/Magento2/Sniffs/GraphQL/ValidArgumentNameSniff.php
@@ -3,6 +3,7 @@
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento2\Sniffs\GraphQL;
use PHP_CodeSniffer\Files\File;
@@ -64,7 +65,7 @@ public function process(File $phpcsFile, $stackPtr)
}
/**
- * Finds all argument names contained in $tokens range $startPointer and
+ * Finds all argument names contained in $tokens in range $startPointer to
* $endPointer.
*
* @param int $startPointer
@@ -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;
}
@@ -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);
}
}
diff --git a/Magento2/Sniffs/GraphQL/ValidEnumValueSniff.php b/Magento2/Sniffs/GraphQL/ValidEnumValueSniff.php
new file mode 100644
index 00000000..b7c702c7
--- /dev/null
+++ b/Magento2/Sniffs/GraphQL/ValidEnumValueSniff.php
@@ -0,0 +1,134 @@
+SCREAMING_SNAKE_CASE.
+ */
+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 $tokens 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 $tokens 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 $tokens in range $startPointer to
+ * $endPointer.
+ *
+ * @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;
+ }
+}
diff --git a/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.graphqls b/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.graphqls
new file mode 100644
index 00000000..994e75f2
--- /dev/null
+++ b/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.graphqls
@@ -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
+}
\ No newline at end of file
diff --git a/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.php b/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.php
new file mode 100644
index 00000000..9d577f68
--- /dev/null
+++ b/Magento2/Tests/GraphQL/ValidEnumValueUnitTest.php
@@ -0,0 +1,34 @@
+ 1,
+ 11 => 1,
+ 12 => 1,
+ 13 => 1,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getWarningList()
+ {
+ return [];
+ }
+}