Completed
Pull Request — master (#2)
by Hans
02:22
created

Lexer   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 305
Duplicated Lines 49.84 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 84.77%

Importance

Changes 0
Metric Value
wmc 66
lcom 1
cbo 5
dl 152
loc 305
ccs 167
cts 197
cp 0.8477
rs 5.7474
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A emit() 0 4 1
A getLocation() 0 7 1
A getError() 0 7 1
C name() 7 38 13
A comment() 9 9 4
A spread() 0 20 3
B str() 23 23 5
C integerPart() 0 32 7
B fractionalPart() 21 21 5
C exponentPart() 26 26 7
C number() 26 26 8
C lex() 40 65 11

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Lexer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Lexer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace HansOtt\GraphQL\Query;
4
5
use HansOtt\GraphQL\Shared\ScannerGeneric;
6
use HansOtt\GraphQL\Shared\ScannerWithLocation;
7
use HansOtt\GraphQL\Shared\Lexer as LexerShared;
8
9
final class Lexer implements LexerShared
10
{
11
    /**
12
     * @var ScannerWithLocation
13
     */
14
    private $scanner;
15
    private $tokens;
16
17 66
    private function emit($type, $value, $location)
18
    {
19 66
        $this->tokens[] = new Token($type, $value, $location);
20 66
    }
21
22 93
    private function getLocation()
23
    {
24 93
        $line = $this->scanner->getLine();
25 93
        $column = $this->scanner->getColumn();
26
27 93
        return new Location($line, $column);
28
    }
29
30 27
    private function getError($message)
31
    {
32 27
        $line = $this->scanner->getLine();
33 27
        $column = $this->scanner->getColumn();
34
35 27
        return new SyntaxError($message . " (line {$line}, column {$column})");
36
    }
37
38 63
    private function name()
39
    {
40 63
        $name = $this->scanner->next();
41 63
        $location = $this->getLocation();
42
43 63
        if ($this->scanner->eof()) {
44
            $this->emit(Token::T_NAME, $name, $location);
45
            return;
46
        }
47
48 63
        $next = $this->scanner->peek();
49 63 View Code Duplication
        while ($next === '_' || ctype_alpha($next) || ctype_digit($next)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
50 63
            $name .= $this->scanner->next();
51 63
            if ($this->scanner->eof()) {
52 3
                break;
53
            }
54 63
            $next = $this->scanner->peek();
55 63
        }
56
57 63
        $type = Token::T_NAME;
58 63
        if ($name === 'query') {
59 18
            $type = Token::T_QUERY;
60 63
        } elseif ($name === 'mutation') {
61
            $type = Token::T_MUTATION;
62 63
        } elseif ($name === 'subscription') {
63
            $type = Token::T_SUBSCRIPTION;
64 63
        } elseif ($name === 'fragment') {
65 6
            $type = Token::T_FRAGMENT;
66 63
        } elseif ($name === 'true') {
67 3
            $type = Token::T_TRUE;
68 63
        } elseif ($name === 'false') {
69 3
            $type = Token::T_FALSE;
70 63
        } elseif ($name === 'null') {
71 3
            $type = Token::T_NULL;
72 3
        }
73
74 63
        $this->emit($type, $name, $location);
75 63
    }
76
77 View Code Duplication
    private function comment()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78
    {
79
        $this->scanner->next();
80
        $next = $this->scanner->peek();
81
        while ($this->scanner->eof() === false && $next !== "\n" && $next !== "\r") {
82
            $this->scanner->next();
83
            $next = $this->scanner->peek();
84
        }
85
    }
86
87 6
    private function spread()
88
    {
89 6
        $points = $this->scanner->next();
90 6
        $location = $this->getLocation();
91 6
        $next = $this->scanner->peek();
92
93 6
        if ($next !== '.') {
94
            throw $this->getError("Expected \".\" but instead found \"{$next}\"");
95
        }
96
97 6
        $points .= $this->scanner->next();
98 6
        $next = $this->scanner->peek();
99
100 6
        if ($next !== '.') {
101
            throw $this->getError("Expected \".\" but instead found \"{$next}\"");
102
        }
103
104 6
        $points .= $this->scanner->next();
105 6
        $this->emit(Token::T_SPREAD, $points, $location);
106 6
    }
107
108 24 View Code Duplication
    private function str()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
109
    {
110 24
        $this->scanner->next();
111 24
        $location = $this->getLocation();
112 24
        $string = '';
113 24
        $previousChar = false;
114
115 24
        while (true) {
116 24
            if ($this->scanner->eof()) {
117 3
                throw $this->getError('Unclosed quote');
118
            }
119 21
            $next = $this->scanner->peek();
120 21
            if ($previousChar !== '\\' && $next === '"') {
121 21
                $this->scanner->next();
122 21
                break;
123
            }
124 21
            $previousChar = $this->scanner->next();
125 21
            $string .= $previousChar;
126 21
        }
127
128 21
        $string = json_decode('"' . $string . '"');
129 21
        $this->emit(Token::T_STRING, $string, $location);
130 21
    }
131
132 48
    private function integerPart()
133
    {
134 48
        if ($this->scanner->eof()) {
135
            throw $this->getError('Expected an integer or a float but instead reached end');
136
        }
137
138 48
        $location = $this->getLocation();
139 48
        $next = $this->scanner->peek();
140 48
        $number = '';
141 48
        if ($next === '-') {
142 12
            $number .= $next;
143 12
            $this->scanner->next();
144 12
            if ($this->scanner->eof()) {
145 3
                throw $this->getError('Expected an integer or a float but instead reached end');
146
            }
147 9
        }
148
149 45
        $next = $this->scanner->peek();
150 45
        if ($next === '0') {
151 21
            $number .= $next;
152 21
            $this->scanner->next();
153 21
            return array($number, $location);
154
        }
155
156 24
        $next = $this->scanner->peek();
157 24
        while ($this->scanner->eof() === false && ctype_digit($next)) {
158 24
            $number .= $this->scanner->next();
159 24
            $next = $this->scanner->peek();
160 24
        }
161
162 24
        return array($number, $location);
163
    }
164
165 21 View Code Duplication
    private function fractionalPart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167 21
        $part = $this->scanner->next();
168
169 21
        if ($this->scanner->eof()) {
170 6
            throw $this->getError('Expected a digit but instead reached end');
171
        }
172
173 15
        $next = $this->scanner->peek();
174 15
        if (ctype_digit($next) === false) {
175
            throw $this->getError("Expected a digit but instead found \"{$next}\"");
176
        }
177
178 15
        $next = $this->scanner->peek();
179 15
        while ($this->scanner->eof() === false && ctype_digit($next)) {
180 15
            $part .= $this->scanner->next();
181 15
            $next = $this->scanner->peek();
182 15
        }
183
184 15
        return $part;
185
    }
186
187 15 View Code Duplication
    private function exponentPart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
    {
189 15
        $part = $this->scanner->next();
190
191 15
        if ($this->scanner->eof()) {
192 9
            throw $this->getError('Expected a digit but instead reached end');
193
        }
194
195 6
        $next = $this->scanner->peek();
196 6
        if ($next === '+' || $next === '-') {
197
            $part .= $this->scanner->next();
198
        }
199
200 6
        $next = $this->scanner->peek();
201 6
        if (ctype_digit($next) === false) {
202 6
            throw $this->getError("Expected a digit but instead found \"{$next}\"");
203
        }
204
205
        $next = $this->scanner->peek();
206
        while ($this->scanner->eof() === false && ctype_digit($next)) {
207
            $part .= $this->scanner->next();
208
            $next = $this->scanner->peek();
209
        }
210
211
        return $part;
212
    }
213
214 48 View Code Duplication
    private function number()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
    {
216 48
        list ($integerPart, $location) = $this->integerPart();
217 45
        if ($this->scanner->eof()) {
218
            $this->emit(Token::T_INT, $integerPart, $location);
219
            return;
220
        }
221
222 45
        $next = $this->scanner->peek();
223 45
        if ($next !== '.' && $next !== 'e' && $next !== 'E') {
224 24
            $this->emit(Token::T_INT, $integerPart, $location);
225 24
            return;
226
        }
227
228 24
        $number = $integerPart;
229 24
        if ($next === '.') {
230 21
            $number .= $this->fractionalPart();
231 15
        }
232
233 18
        $next = $this->scanner->peek();
234 18
        if ($next === 'e' || $next === 'E') {
235 15
            $number .= $this->exponentPart();
236
        }
237
238 3
        $this->emit(Token::T_FLOAT, $number, $location);
239 3
    }
240
241
    /**
242
     * @param string $query
243
     *
244
     * @throws SyntaxError
245
     *
246
     * @return Token[]
247
     */
248 96
    public function lex($query)
249
    {
250 96
        $flags = PREG_SPLIT_NO_EMPTY;
251 96
        $chars = preg_split('//u', $query, -1, $flags);
252 96
        $scanner = new ScannerGeneric($chars);
253 96
        $this->scanner = new ScannerWithLocation($scanner);
254 96
        $this->tokens = array();
255
        $punctuators = array(
256 96
            '!' => Token::T_EXCLAMATION,
257 96
            '$' => Token::T_DOLLAR,
258 96
            '(' => Token::T_PAREN_LEFT,
259 96
            ')' => Token::T_PAREN_RIGHT,
260 96
            '{' => Token::T_BRACE_LEFT,
261 96
            '}' => Token::T_BRACE_RIGHT,
262 96
            ':' => Token::T_COLON,
263 96
            ',' => Token::T_COMMA,
264 96
            '[' => Token::T_BRACKET_LEFT,
265 96
            ']' => Token::T_BRACKET_RIGHT,
266 96
            '=' => Token::T_EQUAL,
267 96
            '@' => Token::T_AT,
268 96
        );
269
270 96 View Code Duplication
        while ($this->scanner->eof() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271 93
            $next = $this->scanner->peek();
272
273 93
            if (ctype_space($next)) {
274 63
                $this->scanner->next();
275 63
                continue;
276
            }
277
278 93
            if ($next === '#') {
279
                $this->comment();
280
                continue;
281
            }
282
283 93
            if ($next === '_' || ctype_alpha($next)) {
284 63
                $this->name();
285 63
                continue;
286
            }
287
288 93
            if ($next === '.') {
289 6
                $this->spread();
290 6
                continue;
291
            }
292
293 93
            if ($next === '"') {
294 24
                $this->str();
295 21
                continue;
296
            }
297
298 90
            if ($next === '-' || ctype_digit($next)) {
299 48
                $this->number();
300 24
                continue;
301
            }
302
303 66
            if (isset($punctuators[$next])) {
304 66
                $this->emit($punctuators[$next], $this->scanner->next(), $this->getLocation());
305 66
                continue;
306
            }
307
308
            throw $this->getError("Unknown character: \"{$next}\"");
309
        }
310
311 69
        return $this->tokens;
312
    }
313
}
314