Skip to content

Commit 8cb90c8

Browse files
MC-19366: Improves GraphQL tokenizer by making use of webonyx/graphql-php library
1 parent 6003dcd commit 8cb90c8

File tree

5 files changed

+273
-19
lines changed

5 files changed

+273
-19
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/**
3+
* NOTICE OF LICENSE
4+
*
5+
* Copyright (c) 2019 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
6+
* All rights reserved
7+
*
8+
* This product includes proprietary software developed at TechDivision GmbH, Germany
9+
* For more information see https://www.techdivision.com/
10+
*
11+
* To obtain a valid license for using this software please contact us at license@techdivision.com
12+
*/
13+
14+
namespace Magento2\Sniffs\GraphQL;
15+
16+
/**
17+
* Kurze Beschreibung der Klasse
18+
*
19+
* Lange Beschreibung der Klasse (wenn vorhanden)...
20+
*
21+
* @copyright Copyright (c) 2019 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
22+
* @link https://www.techdivision.com/
23+
* @author Jean-Bernard Valentaten <j.valentaten@techdivision.com>
24+
*/
25+
class ValidArgumentNymeSniff
26+
{
27+
28+
}

Magento2/Sniffs/GraphQL/ValidTypeNameSniff.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
*/
66
namespace Magento2\Sniffs\GraphQL;
77

8-
use PHP_CodeSniffer\Standards\Squiz\Sniffs\Classes\ValidClassNameSniff;
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
use PHP_CodeSniffer\Util\Common;
911

1012
/**
1113
* Detects types (<kbd>type</kbd>, <kbd>interface</kbd> and <kbd>enum</kbd>) that are not specified in
1214
* <kbd>UpperCamelCase</kbd>.
1315
*/
14-
class ValidTypeNameSniff extends ValidClassNameSniff
16+
class ValidTypeNameSniff implements Sniff
1517
{
1618

1719
/**
@@ -26,7 +28,35 @@ class ValidTypeNameSniff extends ValidClassNameSniff
2628
*/
2729
public function register()
2830
{
29-
return [T_CLASS];
31+
return [T_CLASS, T_INTERFACE];
3032
}
3133

34+
/**
35+
* @inheritDoc
36+
*/
37+
public function process(File $phpcsFile, $stackPtr)
38+
{
39+
$tokens = $phpcsFile->getTokens();
40+
41+
//compose entity name by making use of the next strings the we find until we hit a non-string token
42+
$name = '';
43+
for ($i=$stackPtr+1; $tokens[$i]['code'] === T_STRING; ++$i) {
44+
$name .= $tokens[$i]['content'];
45+
}
46+
47+
$valid = Common::isCamelCaps($name, true, true, false);
48+
49+
if ($valid === false) {
50+
$type = ucfirst($tokens[$stackPtr]['content']);
51+
$error = '%s name "%s" is not in PascalCase format';
52+
$data = [
53+
$type,
54+
$name,
55+
];
56+
$phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data);
57+
$phpcsFile->recordMetric($stackPtr, 'PascalCase class name', 'no');
58+
} else {
59+
$phpcsFile->recordMetric($stackPtr, 'PascalCase class name', 'yes');
60+
}
61+
}
3262
}

PHP_CodeSniffer/Tokenizers/GraphQL.php

Lines changed: 157 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,63 @@
66

77
namespace PHP_CodeSniffer\Tokenizers;
88

9+
use GraphQL\Language\Lexer;
10+
use GraphQL\Language\Source;
11+
use GraphQL\Language\Token;
912
use PHP_CodeSniffer\Config;
13+
use PHP_CodeSniffer\Exceptions\TokenizerException;
1014

1115
/**
1216
* Implements a tokenizer for GraphQL files.
17+
*
18+
* @todo Reimplement using the official GraphQL implementation
1319
*/
14-
class GraphQL extends JS
20+
class GraphQL extends Tokenizer
1521
{
1622

17-
protected $additionalTokenValues = [
18-
'type' => 'T_CLASS',
19-
'interface' => 'T_CLASS',
20-
'enum' => 'T_CLASS',
21-
'#' => 'T_COMMENT',
23+
/**
24+
* Defines how GraphQL token types are mapped to PHP token types.
25+
*
26+
* @var array
27+
*/
28+
private $tokenTypeMap = [
29+
Token::AMP => null, //TODO
30+
Token::AT => 'T_DOC_COMMENT_TAG',
31+
Token::BANG => null, //TODO
32+
Token::BLOCK_STRING => 'T_COMMENT',
33+
Token::BRACE_L => 'T_OPEN_CURLY_BRACKET',
34+
Token::BRACE_R => 'T_CLOSE_CURLY_BRACKET',
35+
Token::BRACKET_L => 'T_OPEN_SQUARE_BRACKET',
36+
Token::BRACKET_R => 'T_CLOSE_CURLY_BRACKET',
37+
Token::COLON => 'T_COLON',
38+
Token::COMMENT => 'T_COMMENT',
39+
Token::DOLLAR => 'T_DOLLAR',
40+
Token::EOF => 'T_CLOSE_TAG',
41+
Token::EQUALS => 'T_EQUAL',
42+
Token::FLOAT => null, //TODO
43+
Token::INT => null, //TODO
44+
Token::NAME => 'T_STRING',
45+
Token::PAREN_L => 'T_OPEN_PARENTHESIS',
46+
Token::PAREN_R => 'T_CLOSE_PARENTHESIS',
47+
Token::PIPE => null, //TODO
48+
Token::SPREAD => 'T_ELLIPSIS',
49+
Token::SOF => 'T_OPEN_TAG',
50+
Token::STRING => 'T_STRING',
51+
];
52+
53+
/**
54+
* Defines how special keywords are mapped to PHP token types
55+
*
56+
* @var array
57+
*/
58+
private $keywordTokenTypeMap = [
59+
'enum' => 'T_CLASS',
60+
'extend' => 'T_EXTENDS', //TODO This might not be the appropriate equivalent
61+
'interface' => 'T_INTERFACE',
62+
'implements' => 'T_IMPLEMENTS',
63+
'type' => 'T_CLASS',
64+
'union' => 'T_CLASS',
65+
//TODO Add further types
2266
];
2367

2468
/**
@@ -27,17 +71,13 @@ class GraphQL extends JS
2771
* @param string $content
2872
* @param Config $config
2973
* @param string $eolChar
30-
* @throws \PHP_CodeSniffer\Exceptions\TokenizerException
74+
* @throws TokenizerException
3175
*/
3276
public function __construct($content, Config $config, $eolChar = '\n')
3377
{
34-
//add our token values
35-
$this->tokenValues = array_merge(
36-
$this->tokenValues,
37-
$this->additionalTokenValues
38-
);
78+
//TODO We might want to delete this unless we need the constructor to work totally different
3979

40-
//let parent do its job (which will start tokenizing)
80+
//let parent do its job
4181
parent::__construct($content, $config, $eolChar);
4282
}
4383

@@ -46,7 +86,110 @@ public function __construct($content, Config $config, $eolChar = '\n')
4686
*/
4787
public function processAdditional()
4888
{
49-
//NOP Does nothing intentionally
89+
//NOP: Does nothing intentionally
5090
}
5191

92+
/**
93+
* {@inheritDoc}
94+
*
95+
* @throws TokenizerException
96+
*/
97+
protected function tokenize($string)
98+
{
99+
$this->logVerbose('*** START GRAPHQL TOKENIZING ***');
100+
101+
$string = str_replace($this->eolChar, "\n", $string);
102+
$tokens = [];
103+
$lexer = new Lexer(
104+
new Source($string)
105+
);
106+
107+
do {
108+
$kind = $lexer->token->kind;
109+
$value = $lexer->token->value ?: '';
110+
111+
//if we have encountered a keyword, we convert it
112+
//otherwise we translate the token or default it to T_STRING
113+
if ($kind === Token::NAME && isset($this->keywordTokenTypeMap[$value])) {
114+
$tokenType = $this->keywordTokenTypeMap[$value];
115+
} elseif (isset($this->tokenTypeMap[$kind])) {
116+
$tokenType = $this->tokenTypeMap[$kind];
117+
} else {
118+
$tokenType = 'T_STRING';
119+
}
120+
121+
//some GraphQL tokens need special handling
122+
switch ($kind) {
123+
case Token::AT:
124+
case Token::BRACE_L:
125+
case Token::BRACE_R:
126+
case Token::PAREN_L:
127+
case Token::PAREN_R:
128+
$value = $kind;
129+
break;
130+
default:
131+
//NOP
132+
}
133+
134+
//finally we create the PHP token
135+
$token = [
136+
'code' => constant($tokenType),
137+
'type' => $tokenType,
138+
'content' => $value,
139+
];
140+
$line = $lexer->token->line;
141+
142+
$lexer->advance();
143+
144+
//if line has changed (and we're not on start of file) we have to append at least one line break to current
145+
//tokens content otherwise PHP_CodeSniffer will screw up line numbers
146+
if ($lexer->token->line !== $line && $kind !== Token::SOF) {
147+
$token['content'] .= $this->eolChar;
148+
}
149+
$tokens[] = $token;
150+
$tokens = array_merge(
151+
$tokens,
152+
$this->getNewLineTokens($line, $lexer->token->line)
153+
);
154+
} while ($lexer->token->kind !== Token::EOF);
155+
156+
$this->logVerbose('*** END GRAPHQL TOKENIZING ***');
157+
return $tokens;
158+
}
159+
160+
/**
161+
* Returns tokens of empty new lines for the range <var>$lineStart</var> to <var>$lineEnd</var>
162+
*
163+
* @param int $lineStart
164+
* @param int $lineEnd
165+
* @return array
166+
*/
167+
private function getNewLineTokens($lineStart, $lineEnd)
168+
{
169+
$amount = ($lineEnd - $lineStart) - 1;
170+
$tokens = [];
171+
172+
for ($i = 0; $i < $amount; ++$i) {
173+
$tokens[] = [
174+
'code' => T_WHITESPACE,
175+
'type' => 'T_WHITESPACE',
176+
'content' => $this->eolChar,
177+
];
178+
}
179+
180+
return $tokens;
181+
}
182+
183+
/**
184+
* Logs <var>$message</var> if {@link PHP_CODESNIFFER_VERBOSITY} is greater than <var>$level</var>.
185+
*
186+
* @param string $message
187+
* @param int $level
188+
*/
189+
private function logVerbose($message, $level = 1)
190+
{
191+
if (PHP_CODESNIFFER_VERBOSITY > $level) {
192+
printf("\t%s" . PHP_EOL, $message);
193+
}
194+
}
52195
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"version": "4",
1010
"require": {
1111
"php": ">=5.6.0",
12-
"squizlabs/php_codesniffer": "^3.4"
12+
"squizlabs/php_codesniffer": "^3.4",
13+
"webonyx/graphql-php": "^0.13.8"
1314
},
1415
"require-dev": {
1516
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"

composer.lock

Lines changed: 53 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)