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

Compiler   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 442
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 442
rs 8.4864
wmc 48
lcom 2
cbo 4

How to fix   Complexity   

Complex Class

Complex classes like Compiler 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 Compiler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Jade;
4
5
use Jade\Compiler\CodeHandler;
6
use Jade\Compiler\MixinVisitor;
7
use Jade\Parser\Exception as ParserException;
8
9
/**
10
 * Class Jade Compiler.
11
 */
12
class Compiler extends MixinVisitor
13
{
14
    /**
15
     * Constants and configuration in Compiler/CompilerConfig.php.
16
     */
17
18
    /**
19
     * @var
20
     */
21
    protected $xml;
22
23
    /**
24
     * @var
25
     */
26
    protected $parentIndents;
27
28
    /**
29
     * @var array
30
     */
31
    protected $buffer = array();
32
    /**
33
     * @var array
34
     */
35
    protected $options = array();
36
    /**
37
     * @var array
38
     */
39
    protected $filters = array();
40
41
    /**
42
     * @var bool
43
     */
44
    protected $phpSingleLine = false;
45
    /**
46
     * @var bool
47
     */
48
    protected $allowMixinOverride = false;
49
    /**
50
     * @var bool
51
     */
52
    protected $keepNullAttributes = false;
53
    /**
54
     * @var bool
55
     */
56
    protected $filterAutoLoad = true;
57
    /**
58
     * @var bool
59
     */
60
    protected $terse = true;
61
    /**
62
     * @var bool
63
     */
64
    protected $restrictedScope = false;
65
    /**
66
     * @var array
67
     */
68
    protected $customKeywords = array();
69
    /**
70
     * @var Jade
71
     */
72
    protected $jade = null;
73
74
    /**
75
     * @var string
76
     */
77
    protected $quote;
78
79
    /**
80
     * @var string
81
     */
82
    protected $filename;
83
84
    /**
85
     * @param array/Jade $options
86
     * @param array      $filters
87
     */
88
    public function __construct($options = array(), array $filters = array(), $filename = null)
89
    {
90
        $this->options = $this->setOptions($options);
91
        $this->filters = $filters;
92
        $this->filename = $filename;
93
    }
94
95
    /**
96
     * Get a jade engine reference or an options array and return needed options.
97
     *
98
     * @param array/Jade $options
99
     *
100
     * @return array
101
     */
102
    protected function setOptions($options)
103
    {
104
        $optionTypes = array(
105
            'prettyprint' => 'boolean',
106
            'phpSingleLine' => 'boolean',
107
            'allowMixinOverride' => 'boolean',
108
            'keepNullAttributes' => 'boolean',
109
            'filterAutoLoad' => 'boolean',
110
            'restrictedScope' => 'boolean',
111
            'indentSize' => 'integer',
112
            'indentChar' => 'string',
113
            'customKeywords' => 'array',
114
        );
115
116
        if ($options instanceof Jade) {
117
            $this->jade = $options;
118
            $options = array();
119
120
            foreach ($optionTypes as $option => $type) {
121
                $this->$option = $this->jade->getOption($option);
122
                $options[$option] = $this->$option;
123
                settype($this->$option, $type);
124
            }
125
126
            $this->quote = $this->jade->getOption('singleQuote') ? '\'' : '"';
127
128
            return $options;
129
        }
130
131
        foreach (array_intersect_key($optionTypes, $options) as $option => $type) {
132
            $this->$option = $options[$option];
133
            settype($this->$option, $type);
134
        }
135
136
        $this->quote = isset($options['singleQuote']) && $options['singleQuote'] ? '\'' : '"';
137
138
        return $options;
139
    }
140
141
    /**
142
     * Get an option from the jade engine if set or from the options array else.
143
     *
144
     * @param string $option
145
     *
146
     * @throws \InvalidArgumentException
147
     *
148
     * @return mixed
149
     */
150
    public function getOption($option)
151
    {
152
        if (is_null($this->jade)) {
153
            if (!isset($this->options[$option])) {
154
                throw new \InvalidArgumentException("$option is not a valid option name.", 28);
155
            }
156
157
            return $this->options[$option];
158
        }
159
160
        return $this->jade->getOption($option);
161
    }
162
163
    /**
164
     * Get a compiler with the same settings.
165
     *
166
     * @return Compiler
167
     */
168
    public function subCompiler()
169
    {
170
        return new static($this->options, $this->filters);
171
    }
172
173
    /**
174
     * php closing tag depanding on the pretty print setting.
175
     *
176
     * @return string
177
     */
178
    protected function closingTag()
179
    {
180
        return '?>' . ($this->prettyprint ? ' ' : '');
181
    }
182
183
    /**
184
     * @param $node
185
     *
186
     * @return string
187
     */
188
    public function compile($node)
189
    {
190
        $this->visit($node);
191
192
        $code = ltrim(implode('', $this->buffer));
193
194
        // Separate in several lines to get a useable line number in case of an error occurs
195
        if ($this->phpSingleLine) {
196
            $code = str_replace(array('<?php', '?>'), array("<?php\n", "\n" . $this->closingTag()), $code);
197
        }
198
        // Remove the $ wich are not needed
199
        return $code;
200
    }
201
202
    /**
203
     * @param $method
204
     * @param $arguments
205
     *
206
     * @throws \BadMethodCallException If the 'apply' rely on non existing method
207
     *
208
     * @return mixed
209
     */
210
    protected function apply($method, $arguments)
211
    {
212
        if (!method_exists($this, $method)) {
213
            throw new \BadMethodCallException(sprintf('Method %s do not exists', $method), 7);
214
        }
215
216
        return call_user_func_array(array($this, $method), $arguments);
217
    }
218
219
    /**
220
     * @param      $line
221
     * @param null $indent
222
     */
223
    protected function buffer($line, $indent = null)
224
    {
225
        if ($indent === true || ($indent === null && $this->prettyprint)) {
226
            $line = $this->indent() . $line . $this->newline();
227
        }
228
229
        $this->buffer[] = $line;
230
    }
231
232
    /**
233
     * @param string $str
234
     *
235
     * @return bool|int
236
     */
237
    protected function isConstant($str)
238
    {
239
        return preg_match('/^' . static::CONSTANT_VALUE . '$/', trim($str));
240
    }
241
242
    /**
243
     * @param        $input
244
     * @param string $name
245
     *
246
     * @throws \ErrorException
247
     *
248
     * @return array
249
     */
250
    public function handleCode($input, $name = '')
251
    {
252
        $handler = new CodeHandler($input, $name);
253
254
        return $handler->parse();
255
    }
256
257
    /**
258
     * @param $input
259
     *
260
     * @throws \ErrorException
261
     *
262
     * @return array
263
     */
264
    public function handleString($input)
265
    {
266
        $result = array();
267
        $resultsString = array();
268
269
        $separators = preg_split(
270
            '/[+](?!\\()/', // concatenation operator - only js
271
            $input,
272
            -1,
273
            PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE
274
        );
275
276
        foreach ($separators as $part) {
277
            // $sep[0] - the separator string due to PREG_SPLIT_OFFSET_CAPTURE flag
278
            // $sep[1] - the offset due to PREG_SPLIT_OFFSET_CAPTURE
279
            // @todo: = find original usage of this
280
            //$sep = substr(
281
            //    $input,
282
            //    strlen($part[0]) + $part[1] + 1,
283
            //    isset($separators[$i+1]) ? $separators[$i+1][1] : strlen($input)
284
            //);
285
286
            // @todo: handleCode() in concat
287
            $part[0] = trim($part[0]);
288
289
            if (preg_match('/^("(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\')(.*)$/', $part[0], $match)) {
290
                $quote = substr($match[1], 0, 1);
291
292
                if (strlen(trim($match[2]))) {
293
                    throw new \ErrorException('Unexpected value: ' . $match[2], 8);
294
                }
295
296
                array_push($resultsString, $match[1]);
297
298
                continue;
299
            }
300
301
            $code = $this->handleCode($part[0]);
302
303
            $result = array_merge($result, array_slice($code, 0, -1));
304
            array_push($resultsString, array_pop($code));
305
        }
306
307
        array_push($result, implode(' . ', $resultsString));
308
309
        return $result;
310
    }
311
312
    /**
313
     * @param string $text
314
     *
315
     * @return mixed
316
     */
317
    public function interpolate($text)
318
    {
319
        return preg_replace_callback('/(\\\\)?([#!]){(.*?)}/', array($this, 'interpolateFromCapture'), $text);
320
    }
321
322
    /**
323
     * @param array $match
324
     *
325
     * @return string
326
     */
327
    protected function interpolateFromCapture($match)
328
    {
329
        if ($match[1] === '') {
330
            return $this->escapeIfNeeded($match[2] === '!', $match[3]);
331
        }
332
333
        return substr($match[0], 1);
334
    }
335
336
    /**
337
     * @throws \InvalidArgumentException
338
     *
339
     * @return array
340
     */
341
    protected function createStatements()
342
    {
343
        if (func_num_args() === 0) {
344
            throw new \InvalidArgumentException('No Arguments provided', 9);
345
        }
346
347
        $arguments = func_get_args();
348
        $statements = array();
349
        $variables = array();
350
351
        foreach ($arguments as $arg) {
352
            $arg = static::convertVarPath($arg);
353
354
            // add dollar if missing
355
            if (preg_match('/^' . static::VARNAME . '(\s*,.+)?$/', $arg)) {
356
                $arg = static::addDollarIfNeeded($arg);
357
            }
358
359
            // shortcut for constants
360
            if ($this->isConstant($arg)) {
361
                array_push($variables, $arg);
362
                continue;
363
            }
364
365
            // if we have a php variable assume that the string is good php
366
            if (strpos('{[', substr($arg, 0, 1)) === false && preg_match('/&?\${1,2}' . static::VARNAME . '|[A-Za-z0-9_\\\\]+::/', $arg)) {
367
                array_push($variables, $arg);
368
                continue;
369
            }
370
371
            $code = $this->handleArgumentValue($arg);
372
373
            $statements = array_merge($statements, array_slice($code, 0, -1));
374
            array_push($variables, array_pop($code));
375
        }
376
377
        array_push($statements, $variables);
378
379
        return $statements;
380
    }
381
382
    protected function handleArgumentValue($arg)
383
    {
384
        if (preg_match('/^"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'/', $arg)) {
385
            return $this->handleString(trim($arg));
386
        }
387
388
        try {
389
            return $this->handleCode($arg);
390
        } catch (\Exception $e) {
391
            // if a bug occur, try to remove comments
392
            try {
393
                return $this->handleCode(preg_replace('#/\*(.*)\*/#', '', $arg));
394
            } catch (\Exception $e) {
395
                throw new ParserException('Pug.php did not understand ' . $arg, 10, $e);
396
            }
397
        }
398
    }
399
400
    /**
401
     * @param      $code
402
     * @param null $statements
403
     *
404
     * @return string
405
     */
406
    protected function createPhpBlock($code, $statements = null)
407
    {
408
        if ($statements === null) {
409
            return '<?php ' . $code . ' ' . $this->closingTag();
410
        }
411
412
        $codeFormat = array_pop($statements);
413
        array_unshift($codeFormat, $code);
414
415
        if (count($statements) === 0) {
416
            $phpString = call_user_func_array('sprintf', $codeFormat);
417
418
            return '<?php ' . $phpString . ' ' . $this->closingTag();
419
        }
420
421
        $stmtString = '';
422
        foreach ($statements as $stmt) {
423
            $stmtString .= $this->newline() . $this->indent() . $stmt . ';';
424
        }
425
426
        $stmtString .= $this->newline() . $this->indent();
427
        $stmtString .= call_user_func_array('sprintf', $codeFormat);
428
429
        $phpString = '<?php ';
430
        $phpString .= $stmtString;
431
        $phpString .= $this->newline() . $this->indent() . ' ' . $this->closingTag();
432
433
        return $phpString;
434
    }
435
436
    /**
437
     * @param $code
438
     *
439
     * @return string
440
     */
441
    protected function createCode($code)
442
    {
443
        if (func_num_args() > 1) {
444
            $arguments = func_get_args();
445
            array_shift($arguments); // remove $code
446
            $statements = $this->apply('createStatements', $arguments);
447
448
            return $this->createPhpBlock($code, $statements);
449
        }
450
451
        return $this->createPhpBlock($code);
452
    }
453
}
454