Skip to content

MC-19366: Adds GraphQL sniffs #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ba0bbce
MC-19366: Adds a tokenizer for GraphQL schema files
jean-bernard-valentaten Sep 6, 2019
6003dcd
MC-19366: Adds sniff for type names
jean-bernard-valentaten Sep 6, 2019
8cb90c8
MC-19366: Improves GraphQL tokenizer by making use of webonyx/graphql…
jean-bernard-valentaten Sep 9, 2019
586244a
MC-19366: Adds sniff for field names
jean-bernard-valentaten Sep 10, 2019
27e99de
MC-19366: Adds sniff for argument names
jean-bernard-valentaten Sep 11, 2019
47fb203
MC-19366: Adds sniff for top level fields
jean-bernard-valentaten Sep 11, 2019
6f5d136
MC-19366: Downgrade of package webonyx/graphql-php to match lowest ph…
jean-bernard-valentaten Sep 11, 2019
7a5b266
MC-19366: Fixes code style issues
jean-bernard-valentaten Sep 11, 2019
e310c5c
MC-19366: Fixes code style issue
jean-bernard-valentaten Sep 11, 2019
2a8d521
MC-19366: Deactivates field name validation
jean-bernard-valentaten Sep 12, 2019
505c9b2
MC-19366: Adds sniff for valid enum values
jean-bernard-valentaten Sep 12, 2019
19cdbec
MC-19366: Activates sniff for enum values
jean-bernard-valentaten Sep 12, 2019
01eee92
MC-19366: Fixes file doc
jean-bernard-valentaten Sep 12, 2019
d99c922
MC-19366: Fixes that tokenizer for GraphQL files could not be loaded …
jean-bernard-valentaten Sep 13, 2019
49f0b0b
MC-19366: Fixes that agruments with directives would result in errone…
jean-bernard-valentaten Sep 13, 2019
0b8eeaa
MC-19366: Fixes that field name validation was executed although it s…
jean-bernard-valentaten Sep 13, 2019
571b642
MC-19366: Fixes that field and argument names were marked as entities…
jean-bernard-valentaten Sep 13, 2019
c574003
MC-19366: Fixes code style issue
jean-bernard-valentaten Sep 13, 2019
43ef0a4
MC-19366: Removes obsolete TODOs
jean-bernard-valentaten Sep 16, 2019
c539ed4
MC-19366: Fixes order of rules and silences GraphQL field name valida…
jean-bernard-valentaten Sep 17, 2019
af9c041
MC-19366: Fixes that list type and non null arguments would not be pa…
jean-bernard-valentaten Sep 17, 2019
c4069e0
MC-19366: Fixes that enum values with directives would be parsed as e…
jean-bernard-valentaten Sep 17, 2019
874c9d7
MC-19366: Fixes code style issue
jean-bernard-valentaten Sep 17, 2019
4d99ee4
MC-19366: Fixes warning when composer install was run
jean-bernard-valentaten Sep 18, 2019
e252b19
MC-19366: Fixes invalid tag in PHPDoc
jean-bernard-valentaten Sep 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
MC-19366: Adds sniff for valid enum values
  • Loading branch information
jean-bernard-valentaten committed Sep 12, 2019
commit 505c9b25c8e974a8f430992aba3f9a8adcc6958d
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 license@techdivision.com
*/

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 [];
}
}