Completed
Push — master ( 895d73...5bc057 )
by Federico
02:36
created

Scanner   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 283
Duplicated Lines 4.24 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 12
loc 283
rs 8.439
wmc 47
lcom 1
cbo 2

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 Scanner 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 Scanner, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Jade\Lexer;
4
5
/**
6
 * Class Jade\Lexer\Scanner.
7
 */
8
abstract class Scanner extends MixinScanner
9
{
10
    /**
11
     *  Helper to create tokens.
12
     */
13
    protected function scan($regex, $type, $captureIndex = 1)
14
    {
15
        if (preg_match($regex, $this->input, $matches)) {
16
            $this->consume($matches[0]);
17
18
            return $this->token($type, isset($matches[$captureIndex]) && strlen($matches[$captureIndex]) > 0 ? $matches[$captureIndex] : '');
19
        }
20
    }
21
22
    /**
23
     * Scan comment from input & return it if found.
24
     *
25
     * @return object|null
26
     */
27
    protected function scanComment()
28
    {
29
        $indent = count($this->indentStack) ? $this->indentStack[0] : 0;
30
        if (preg_match('/^ *\/\/(-)?([^\n]*(\n+[ \t]{' . ($indent + 1) . ',}[^\n]*)*)/', $this->input, $matches)) {
31
            $this->consume($matches[0]);
32
            $value = isset($matches[2]) ? $matches[2] : '';
33
            if (isset($matches[3])) {
34
                $value .= "\n";
35
            }
36
            $token = $this->token('comment', $value);
37
            $token->buffer = '-' !== $matches[1];
38
39
            return $token;
40
        }
41
    }
42
43
    /**
44
     * @return object
45
     */
46
    protected function scanInterpolation()
47
    {
48
        return $this->scan('/^#{(.*?)}/', 'interpolation');
49
    }
50
51
    /**
52
     * @return object
53
     */
54
    protected function scanTag()
55
    {
56
        if (preg_match('/^(\w[-:\w]*)(\/?)/', $this->input, $matches)) {
57
            $this->consume($matches[0]);
58
            $name = $matches[1];
59
60
            if (':' === substr($name, -1) && ':' !== substr($name, -2, 1)) {
61
                $name = substr($name, 0, -1);
62
                $this->defer($this->token(':'));
63
64
                while (' ' === substr($this->input, 0, 1)) {
65
                    $this->consume(' ');
66
                }
67
            }
68
69
            $token = $this->token('tag', $name);
70
            $token->selfClosing = ($matches[2] === '/');
71
72
            return $token;
73
        }
74
    }
75
76
    /**
77
     * @return object
78
     */
79
    protected function scanFilter()
80
    {
81
        return $this->scan('/^(?<!:):(?!:)(\w+(?:-\w+)*)/', 'filter');
82
    }
83
84
    /**
85
     * @return object
86
     */
87
    protected function scanDoctype()
88
    {
89
        return $this->scan('/^(?:!!!|doctype) *([^\n]+)?/', 'doctype');
90
    }
91
92
    /**
93
     * @return object
94
     */
95
    protected function scanId()
96
    {
97
        return $this->scan('/^#([\w-]+)/', 'id');
98
    }
99
100
    /**
101
     * @return object
102
     */
103
    protected function scanClassName()
104
    {
105
        // http://www.w3.org/TR/CSS21/grammar.html#scanner
106
        //
107
        // ident:
108
        //      -?{nmstart}{nmchar}*
109
        // nmstart:
110
        //      [_a-z]|{nonascii}|{escape}
111
        // nonascii:
112
        //      [\240-\377]
113
        // escape:
114
        //      {unicode}|\\[^\r\n\f0-9a-f]
115
        // unicode:
116
        //      \\{h}{1,6}(\r\n|[ \t\r\n\f])?
117
        // nmchar:
118
        //      [_a-z0-9-]|{nonascii}|{escape}
119
        //
120
        // /^(-?(?!=[0-9-])(?:[_a-z0-9-]|[\240-\377]|\\{h}{1,6}(?:\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])+)/
121
        return $this->scan('/^\.([\w-]+)/', 'class');
122
    }
123
124
    /**
125
     * @return object
126
     */
127
    protected function scanText()
128
    {
129
        return $this->scan('/^(?:\| ?| ?)?([^\n]+)/', 'text');
130
    }
131
132
    /**
133
     * @return object
134
     */
135
    protected function scanAssignment()
136
    {
137
        if (preg_match('/^(\$?\w+) += *([^;\n]+|\'[^\']+\'|"[^"]+")( *;? *)/', $this->input, $matches)) {
138
            $this->consume($matches[0]);
139
140
            return $this->token('code', (substr($matches[1], 0, 1) === '$' ? '' : '$') . $matches[1] . '=' . $matches[2]);
141
        }
142
    }
143
144
    /**
145
     * @return object
146
     */
147
    protected function scanConditional()
148
    {
149
        if (preg_match('/^(if|unless|else if|elseif|else|while)\b([^\n]*)/', $this->input, $matches)) {
150
            $this->consume($matches[0]);
151
152
            /*switch ($matches[1]) {
153
                case 'if': $code = 'if (' . $matches[2] . '):'; break;
154
                case 'unless': $code = 'if (!(' . $matches[2] . ')):'; break;
155
                case 'else if': $code = 'elseif (' . $matches[2] . '):'; break;
156
                case 'else': $code = 'else (' . $matches[2] . '):'; break;
157
            }*/
158
            $code = $this->normalizeCode($matches[0]);
159
            $token = $this->token('code', $code);
160
            $token->buffer = false;
161
162
            return $token;
163
        }
164
    }
165
166
    /**
167
     * @return object
168
     */
169
    protected function scanEach()
170
    {
171
        if (preg_match('/^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? +in *([^\n]+)/', $this->input, $matches)) {
172
            $this->consume($matches[0]);
173
174
            $token = $this->token('each', $matches[1]);
175
            $token->key = $matches[2];
176
            $token->code = $this->normalizeCode($matches[3]);
177
178
            return $token;
179
        }
180
    }
181
182
    /**
183
     * @return object
184
     */
185
    protected function scanCustomKeyword()
186
    {
187
        if (
188
            count($this->customKeywords) &&
189
            preg_match('/^([\w-]+)([^\n]*)/', $this->input, $matches) &&
190
            isset($this->customKeywords[$matches[1]]) &&
191
            is_callable($this->customKeywords[$matches[1]])
192
        ) {
193
            $this->consume($matches[0]);
194
195
            $token = $this->token('customKeyword', $matches[1]);
196
            $token->args = trim($matches[2]);
197
198
            return $token;
199
        }
200
    }
201
202
    /**
203
     * @return object
204
     */
205
    protected function scanCode()
206
    {
207
        if (preg_match('/^(!?=|-)([^\n]+)/', $this->input, $matches)) {
208
            $this->consume($matches[0]);
209
            $flags = $matches[1];
210
            $code = $this->normalizeCode($matches[2]);
211
212
            $token = $this->token('code', $code);
213
            $token->escape = $flags[0] === '=';
214
            $token->buffer = '=' === $flags[0] || (isset($flags[1]) && '=' === $flags[1]);
215
216
            return $token;
217
        }
218
    }
219
220
    /**
221
     * @throws \ErrorException
222
     *
223
     * @return object
224
     */
225
    protected function scanAttributes()
226
    {
227
        if (substr($this->input, 0, 1) === '(') {
228
            // cant use ^ anchor in the regex because the pattern is recursive
229
            // but this restriction is asserted by the if above
230
            //$this->input = preg_replace('/([a-zA-Z0-9\'"\\]\\}\\)])([\t ]+[a-zA-Z])/', '$1,$2', $this->input);
231
            if (!preg_match('/\((?:"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|[^()\'"]++|(?R))*+\)/', $this->input, $matches)) {
232
                throw new \ErrorException('Unable to find attributes closing parenthesis.', 21);
233
            }
234
            $this->consume($matches[0]);
235
236
            //$str = preg_replace('/()([a-zA-Z0-9_\\x7f-\\xff\\)\\]\\}"\'])(\s+[a-zA-Z_])/', '$1,$2', $str);
237
238
            $token = $this->token('attributes');
239
            $token->attributes = array();
240
            $token->escaped = array();
241
            $token->selfClosing = false;
242
243
            $parser = new Attributes($token);
244
            $parser->parseWith(substr($matches[0], 1, strlen($matches[0]) - 2));
245
246
            if ($this->length() && '/' === $this->input[0]) {
247
                $this->consume(1);
248
                $token->selfClosing = true;
249
            }
250
251
            return $token;
252
        }
253
    }
254
255
    /**
256
     * @return object
257
     */
258
    protected function scanPipelessText()
259
    {
260
        if ($this->pipeless && "\n" !== substr($this->input, 0, 1)) {
261
            $pos = strpos($this->input, "\n");
262
263
            if ($pos === false) {
264
                $pos = $this->length();
265
            }
266
267
            $str = substr($this->input, 0, $pos); // do not include the \n char
268
269
            $this->consume($str);
270
271
            return $this->token('text', ltrim($str));
272
        }
273
    }
274
275
    /**
276
     * @return object
277
     */
278
    protected function scanColon()
279
    {
280
        return $this->scan('/^:(?!:) */', ':');
281
    }
282
283
    /**
284
     * @return object
285
     */
286
    protected function scanAndAttributes()
287
    {
288
        return $this->scan('/^&attributes(\(((?>[^()]+|(?1))*)\))/', '&attributes', 2);
289
    }
290
}
291