-
Notifications
You must be signed in to change notification settings - Fork 160
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
Changes from 10 commits
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 6003dcd
MC-19366: Adds sniff for type names
jean-bernard-valentaten 8cb90c8
MC-19366: Improves GraphQL tokenizer by making use of webonyx/graphql…
jean-bernard-valentaten 586244a
MC-19366: Adds sniff for field names
jean-bernard-valentaten 27e99de
MC-19366: Adds sniff for argument names
jean-bernard-valentaten 47fb203
MC-19366: Adds sniff for top level fields
jean-bernard-valentaten 6f5d136
MC-19366: Downgrade of package webonyx/graphql-php to match lowest ph…
jean-bernard-valentaten 7a5b266
MC-19366: Fixes code style issues
jean-bernard-valentaten e310c5c
MC-19366: Fixes code style issue
jean-bernard-valentaten 2a8d521
MC-19366: Deactivates field name validation
jean-bernard-valentaten 505c9b2
MC-19366: Adds sniff for valid enum values
jean-bernard-valentaten 19cdbec
MC-19366: Activates sniff for enum values
jean-bernard-valentaten 01eee92
MC-19366: Fixes file doc
jean-bernard-valentaten d99c922
MC-19366: Fixes that tokenizer for GraphQL files could not be loaded …
jean-bernard-valentaten 49f0b0b
MC-19366: Fixes that agruments with directives would result in errone…
jean-bernard-valentaten 0b8eeaa
MC-19366: Fixes that field name validation was executed although it s…
jean-bernard-valentaten 571b642
MC-19366: Fixes that field and argument names were marked as entities…
jean-bernard-valentaten c574003
MC-19366: Fixes code style issue
jean-bernard-valentaten 43ef0a4
MC-19366: Removes obsolete TODOs
jean-bernard-valentaten c539ed4
MC-19366: Fixes order of rules and silences GraphQL field name valida…
jean-bernard-valentaten af9c041
MC-19366: Fixes that list type and non null arguments would not be pa…
jean-bernard-valentaten c4069e0
MC-19366: Fixes that enum values with directives would be parsed as e…
jean-bernard-valentaten 874c9d7
MC-19366: Fixes code style issue
jean-bernard-valentaten 4d99ee4
MC-19366: Fixes warning when composer install was run
jean-bernard-valentaten e252b19
MC-19366: Fixes invalid tag in PHPDoc
jean-bernard-valentaten File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
/** | ||
* NOTICE OF LICENSE | ||
* | ||
* Copyright (c) 2019 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH | ||
* All rights reserved | ||
* | ||
* This product includes proprietary software developed at TechDivision GmbH, Germany | ||
* For more information see https://www.techdivision.com/ | ||
* | ||
* 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; | ||
|
||
/** | ||
* Defines an abstract base class for GraphQL sniffs. | ||
*/ | ||
abstract class AbstractGraphQLSniff implements Sniff | ||
{ | ||
/** | ||
* Defines the tokenizers that this sniff is using. | ||
* | ||
* @var array | ||
*/ | ||
public $supportedTokenizers = ['GraphQL']; | ||
|
||
/** | ||
* Returns whether <var>$name</var> starts with a lower case character and is written in camel case. | ||
* | ||
* @param string $name | ||
* @return bool | ||
*/ | ||
protected function isCamelCase($name) | ||
{ | ||
return (preg_match('/^[a-z][a-zA-Z0-9]+$/', $name) !== 0); | ||
} | ||
|
||
/** | ||
* Returns whether <var>$name</var> is strictly lower case, potentially separated by underscores. | ||
* | ||
* @param string $name | ||
* @return bool | ||
*/ | ||
protected function isSnakeCase($name) | ||
{ | ||
return preg_match('/^[a-z][a-z0-9_]*$/', $name); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento2\Sniffs\GraphQL; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
|
||
/** | ||
* Detects argument names that are not specified in <kbd>cameCase</kbd>. | ||
*/ | ||
class ValidArgumentNameSniff extends AbstractGraphQLSniff | ||
{ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register() | ||
{ | ||
return [T_OPEN_PARENTHESIS]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
$closeParenthesisPointer = $this->getCloseParenthesisPointer($stackPtr, $tokens); | ||
|
||
//if we could not find the closing parenthesis pointer, we add a warning and terminate | ||
if ($closeParenthesisPointer === false) { | ||
$error = 'Possible parse error: Missing closing parenthesis for argument list in line %d'; | ||
$data = [ | ||
$tokens[$stackPtr]['line'], | ||
]; | ||
$phpcsFile->addWarning($error, $stackPtr, 'UnclosedArgumentList', $data); | ||
return; | ||
} | ||
|
||
$arguments = $this->getArguments($stackPtr, $closeParenthesisPointer, $tokens); | ||
|
||
foreach ($arguments as $argument) { | ||
$pointer = $argument[0]; | ||
$name = $argument[1]; | ||
|
||
if (!$this->isCamelCase($name)) { | ||
$type = 'Argument'; | ||
$error = '%s name "%s" is not in CamelCase format'; | ||
$data = [ | ||
$type, | ||
$name, | ||
]; | ||
|
||
$phpcsFile->addError($error, $pointer, 'NotCamelCase', $data); | ||
$phpcsFile->recordMetric($pointer, 'CamelCase argument name', 'no'); | ||
} else { | ||
$phpcsFile->recordMetric($pointer, 'CamelCase argument name', 'yes'); | ||
} | ||
} | ||
|
||
//return stack pointer of closing parenthesis | ||
return $closeParenthesisPointer; | ||
} | ||
|
||
/** | ||
* Finds all argument names contained in <var>$tokens</var> range <var>$startPointer</var> and | ||
* <var>$endPointer</var>. | ||
* | ||
* @param int $startPointer | ||
* @param int $endPointer | ||
* @param array $tokens | ||
* @return array[] | ||
*/ | ||
private function getArguments($startPointer, $endPointer, array $tokens) | ||
{ | ||
$argumentTokenPointer = null; | ||
$argument = ''; | ||
$names = []; | ||
$skipTypes = [T_COMMENT, T_WHITESPACE]; | ||
|
||
for ($i = $startPointer + 1; $i < $endPointer; ++$i) { | ||
//skip comment tokens | ||
if (in_array($tokens[$i]['code'], $skipTypes)) { | ||
continue; | ||
} | ||
$argument .= $tokens[$i]['content']; | ||
|
||
if ($argumentTokenPointer === null) { | ||
$argumentTokenPointer = $i; | ||
} | ||
|
||
if (preg_match('/^.+:.+$/', $argument)) { | ||
list($name, $type) = explode(':', $argument); | ||
$names[] = [$argumentTokenPointer, $name]; | ||
$argument = ''; | ||
$argumentTokenPointer = null; | ||
} | ||
} | ||
|
||
return $names; | ||
} | ||
|
||
/** | ||
* Seeks the next available token of type {@link T_CLOSE_PARENTHESIS} in <var>$tokens</var> and returns its pointer. | ||
* | ||
* @param int $stackPointer | ||
* @param array $tokens | ||
* @return bool|int | ||
*/ | ||
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; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento2\Sniffs\GraphQL; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
|
||
/** | ||
* Detects field names the are not specified in <kbd>snake_case</kbd>. | ||
*/ | ||
class ValidFieldNameSniff extends AbstractGraphQLSniff | ||
{ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register() | ||
{ | ||
return [T_VARIABLE]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
$name = $tokens[$stackPtr]['content']; | ||
|
||
if (!$this->isSnakeCase($name)) { | ||
$type = 'Field'; | ||
$error = '%s name "%s" is not in snake_case format'; | ||
$data = [ | ||
$type, | ||
$name, | ||
]; | ||
$phpcsFile->addError($error, $stackPtr, 'NotSnakeCase', $data); | ||
$phpcsFile->recordMetric($stackPtr, 'SnakeCase field name', 'no'); | ||
} else { | ||
$phpcsFile->recordMetric($stackPtr, 'SnakeCase field name', 'yes'); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento2\Sniffs\GraphQL; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
|
||
/** | ||
* Detects top level field names that are not specified in <kbd>cameCase</kbd>. | ||
*/ | ||
class ValidTopLevelFieldNameSniff extends AbstractGraphQLSniff | ||
{ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register() | ||
{ | ||
return [T_FUNCTION]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
|
||
//compose function name by making use of the next strings that we find until we hit a non-string token | ||
$name = ''; | ||
for ($i=$stackPtr+1; $tokens[$i]['code'] === T_STRING; ++$i) { | ||
$name .= $tokens[$i]['content']; | ||
} | ||
|
||
if (strlen($name) > 0 && !$this->isCamelCase($name)) { | ||
$type = ucfirst($tokens[$stackPtr]['content']); | ||
$error = '%s name "%s" is not in PascalCase format'; | ||
$data = [ | ||
$type, | ||
$name, | ||
]; | ||
$phpcsFile->addError($error, $stackPtr, 'NotCamelCase', $data); | ||
$phpcsFile->recordMetric($stackPtr, 'CamelCase top level field name', 'no'); | ||
} else { | ||
$phpcsFile->recordMetric($stackPtr, 'CamelCase top level field name', 'yes'); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento2\Sniffs\GraphQL; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Util\Common; | ||
|
||
/** | ||
* Detects types (<kbd>type</kbd>, <kbd>interface</kbd> and <kbd>enum</kbd>) that are not specified in | ||
* <kbd>UpperCamelCase</kbd>. | ||
*/ | ||
class ValidTypeNameSniff extends AbstractGraphQLSniff | ||
{ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register() | ||
{ | ||
return [T_CLASS, T_INTERFACE]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
|
||
//compose entity name by making use of the next strings that we find until we hit a non-string token | ||
$name = ''; | ||
for ($i=$stackPtr+1; $tokens[$i]['code'] === T_STRING; ++$i) { | ||
$name .= $tokens[$i]['content']; | ||
} | ||
|
||
$valid = Common::isCamelCaps($name, true, true, false); | ||
|
||
if ($valid === false) { | ||
$type = ucfirst($tokens[$stackPtr]['content']); | ||
$error = '%s name "%s" is not in PascalCase format'; | ||
$data = [ | ||
$type, | ||
$name, | ||
]; | ||
$phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data); | ||
$phpcsFile->recordMetric($stackPtr, 'PascalCase class name', 'no'); | ||
} else { | ||
$phpcsFile->recordMetric($stackPtr, 'PascalCase class name', 'yes'); | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
Magento2/Tests/GraphQL/AbstractGraphQLSniffUnitTestCase.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento2\Tests\GraphQL; | ||
|
||
use PHP_CodeSniffer\Config; | ||
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; | ||
|
||
/** | ||
* Implements an abstract base for unit tests that cover GraphQL sniffs. | ||
*/ | ||
abstract class AbstractGraphQLSniffUnitTestCase extends AbstractSniffUnitTest | ||
{ | ||
protected function setUp() | ||
{ | ||
//let parent do its job | ||
parent::setUp(); | ||
|
||
//generate a config that allows ro use our GraphQL tokenizer | ||
$config = new Config(); | ||
$config->extensions = array_merge( | ||
$config->extensions, | ||
[ | ||
'graphqls' => 'GraphQL' | ||
] | ||
); | ||
|
||
//and write back to a global that is used in base class | ||
$GLOBALS['PHP_CODESNIFFER_CONFIG'] = $config; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
type Foo { | ||
bar( | ||
# Valid argument names | ||
validArgumentName: Int | ||
validArgumentNameWith1Number: Int | ||
validArgumentNameWith12345Numbers: Int | ||
validArgumentNameEndingWithNumber5: Int | ||
validArgumentNameWithUPPERCASE: Int | ||
validargumentwithlowercase: Int | ||
# Invalid argument names | ||
InvalidArgumentNameUpperCamelCase: Int | ||
INVALIDARGUMENTNAMEUPPERCASE: Int | ||
invalid_argument_name_snake_case: Int | ||
5invalidArgumentNameStatingWithNumber: Int | ||
): String | ||
} | ||
interface Foo { | ||
bar( | ||
# Valid argument names | ||
validArgumentName: Int | ||
validArgumentNameWith1Number: Int | ||
validArgumentNameWith12345Numbers: Int | ||
validArgumentNameEndingWithNumber5: Int | ||
validArgumentNameWithUPPERCASE: Int | ||
validargumentwithlowercase: Int | ||
# Invalid argument names | ||
InvalidArgumentNameUpperCamelCase: Int | ||
INVALIDARGUMENTNAMEUPPERCASE: Int | ||
invalid_argument_name_snake_case: Int | ||
5invalidArgumentNameStatingWithNumber: Int | ||
): String | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.