66
77namespace PHP_CodeSniffer \Tokenizers ;
88
9+ use GraphQL \Language \Lexer ;
10+ use GraphQL \Language \Source ;
11+ use GraphQL \Language \Token ;
912use 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}
0 commit comments