ImportVisitor   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 10

Importance

Changes 0
Metric Value
wmc 43
cbo 10
dl 0
loc 350
rs 8.3157
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 3
A run() 0 7 1
B visitImport() 0 26 4
F processImportNode() 0 85 23
A visitRule() 0 10 2
A visitRuleOut() 0 8 2
A visitDirective() 0 6 1
A visitDirectiveOut() 0 6 1
A visitMixinDefinition() 0 6 1
A visitMixinDefinitionOut() 0 6 1
A visitRuleset() 0 6 1
A visitRulesetOut() 0 6 1
A visitMedia() 0 6 1
A visitMediaOut() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like ImportVisitor 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.

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

1
<?php
2
3
/*
4
 * This file is part of the ILess
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace ILess\Visitor;
11
12
use ILess\Context;
13
use ILess\Exception\Exception;
14
use ILess\Exception\ImportException;
15
use ILess\ImportedFile;
16
use ILess\Importer;
17
use ILess\ImportSequencer;
18
use ILess\Node\DetachedRulesetNode;
19
use ILess\Node\DirectiveNode;
20
use ILess\Node\ImportNode;
21
use ILess\Node\MediaNode;
22
use ILess\Node\MixinDefinitionNode;
23
use ILess\Node\RuleNode;
24
use ILess\Node\RulesetNode;
25
26
/**
27
 * Import visitor.
28
 */
29
class ImportVisitor extends Visitor
30
{
31
    /**
32
     * The importer.
33
     *
34
     * @var Importer
35
     */
36
    protected $importer;
37
38
    /**
39
     * Finished flag.
40
     *
41
     * @var bool
42
     */
43
    protected $isFinished = false;
44
45
    /**
46
     * The context.
47
     *
48
     * @var Context
49
     */
50
    protected $context;
51
52
    /**
53
     * @var Exception
54
     */
55
    protected $error;
56
57
    /**
58
     * @var int
59
     */
60
    private $importCount = 0;
61
62
    /**
63
     * @var string
64
     */
65
    protected $type = VisitorInterface::TYPE_PRE_COMPILE;
66
67
    /**
68
     * Constructor.
69
     *
70
     * @param Context $context The context
71
     * @param Importer $importer The importer
72
     */
73
    public function __construct(Context $context, Importer $importer)
74
    {
75
        parent::__construct();
76
77
        $this->context = Context::createCopyForCompilation($context);
78
        $this->importer = $importer;
79
        $this->sequencer = new ImportSequencer(
80
            function () {
81
                if (!$this->isFinished) {
82
                    return;
83
                }
84
                if ($this->error) {
85
                    throw $this->error;
86
                }
87
            }
88
        );
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function run($root)
95
    {
96
        $this->visit($root);
97
98
        $this->isFinished = true;
99
        $this->sequencer->tryRun();
100
    }
101
102
    /**
103
     * Visits a import node.
104
     *
105
     * @param ImportNode $node The node
106
     * @param VisitorArguments $arguments The arguments
107
     *
108
     * @return ImportNode
109
     */
110
    public function visitImport(ImportNode $node, VisitorArguments $arguments)
111
    {
112
        if (!$node->css || $node->getOption('inline')) {
113
            $context = Context::createCopyForCompilation($this->context, $this->context->frames);
114
            $importParent = $context->frames[0];
115
            ++$this->importCount;
116
117
            if ($node->isVariableImport()) {
118
                $this->sequencer->addVariableImport(
119
                    function () use ($node, $context, $importParent) {
120
                        $this->processImportNode($node, $context, $importParent);
121
                    }
122
                );
123
            } else {
124
                $this->sequencer->addImport(
125
                    function () use ($node, $context, $importParent) {
126
                        $this->processImportNode($node, $context, $importParent);
127
                    }
128
                );
129
            }
130
        }
131
132
        $arguments->visitDeeper = false;
133
134
        return $node;
135
    }
136
137
    private function processImportNode(ImportNode $node, Context $context, $importParent)
138
    {
139
        $e = null;
140
        try {
141
            $compiledNode = $node->compileForImport($context);
142
        } catch (Exception $e) {
143
            $compiledNode = false;
144
            if (!$e->getCurrentFile()) {
145
                if ($node->currentFileInfo) {
146
                    $e->setCurrentFile($node->currentFileInfo, $node->index);
147
                } else {
148
                    $e->setIndex($node->index);
149
                }
150
            }
151
            $node->css = true;
152
            $node->error = $e;
153
        }
154
155
        $inlineCSS = $node->getOption('inline');
156
        $isPlugin = $node->getOption('plugin');
157
158
        if ($compiledNode && (!$compiledNode->css || $inlineCSS)) {
159
            if ($node->getOption('multiple')) {
160
                $context->importMultiple = true;
161
            }
162
163
            for ($i = 0; $i < count($importParent->rules); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
164
                if ($importParent->rules[$i] === $node) {
165
                    $importParent->rules[$i] = $compiledNode;
166
                    break;
167
                }
168
            }
169
170
            $tryAppendLessExtension = !$compiledNode->css;
171
172
            try {
173
                // import the file
174
                list($alreadyImported, $file) = $this->importer->import(
175
                    $compiledNode->getPath(),
176
                    $tryAppendLessExtension,
177
                    $compiledNode->currentFileInfo,
178
                    $compiledNode->options,
179
                    $compiledNode->index
180
                );
181
182
                /* @var $file ImportedFile */
183
                if (!$context->importMultiple) {
184
                    if ($alreadyImported) {
185
                        $compiledNode->skip = true;
186
                    }
187
                }
188
189
                if ($root = $file->getRuleset()) {
190
                    /* @var $root RulesetNode */
191
                    $compiledNode->root = $root;
192
                    $compiledNode->importedFilename = $file->getPath();
193
                    if (!$inlineCSS && !$isPlugin && ($context->importMultiple || !$alreadyImported)) {
194
                        $oldEnv = $this->context;
195
                        $this->context = $context;
196
                        try {
197
                            $this->visit($root);
198
                        } catch (Exception $e) {
199
                            $this->error = $e;
200
                        }
201
202
                        $this->end = $oldEnv;
203
                    }
204
                }
205
            } catch (ImportException $e) {
206
                // optional import
207
                if (isset($compiledNode->options['optional']) && $compiledNode->options['optional']) {
208
                    // optional import
209
                } else {
210
                    $this->error = $e;
211
                }
212
            } catch (Exception $e) {
213
                $this->error = $e;
214
            }
215
        }
216
217
        --$this->importCount;
218
        if ($this->isFinished) {
219
            $this->sequencer->tryRun();
220
        }
221
    }
222
223
    /**
224
     * Visits a rule node.
225
     *
226
     * @param RuleNode $node The node
227
     * @param VisitorArguments $arguments The arguments
228
     *
229
     * @return RuleNode
230
     */
231
    public function visitRule(RuleNode $node, VisitorArguments $arguments)
232
    {
233
        if ($node->value instanceof DetachedRulesetNode) {
234
            array_unshift($this->context->frames, $node);
235
        } else {
236
            $arguments->visitDeeper = false;
237
        }
238
239
        return $node;
240
    }
241
242
    /**
243
     * Visits a rule node (!again).
244
     *
245
     * @param RuleNode $node The node
246
     * @param VisitorArguments $arguments The arguments
247
     *
248
     * @return RuleNode
249
     */
250
    public function visitRuleOut(RuleNode $node, VisitorArguments $arguments)
251
    {
252
        if ($node->value instanceof DetachedRulesetNode) {
253
            array_shift($this->context->frames);
254
        }
255
256
        return $node;
257
    }
258
259
    /**
260
     * Visits a directive node.
261
     *
262
     * @param DirectiveNode $node The node
263
     * @param VisitorArguments $arguments The arguments
264
     *
265
     * @return DirectiveNode
266
     */
267
    public function visitDirective(DirectiveNode $node, VisitorArguments $arguments)
268
    {
269
        array_unshift($this->context->frames, $node);
270
271
        return $node;
272
    }
273
274
    /**
275
     * Visits a directive node (!again).
276
     *
277
     * @param DirectiveNode $node The node
278
     * @param VisitorArguments $arguments The arguments
279
     *
280
     * @return DirectiveNode
281
     */
282
    public function visitDirectiveOut(DirectiveNode $node, VisitorArguments $arguments)
283
    {
284
        array_shift($this->context->frames);
285
286
        return $node;
287
    }
288
289
    /**
290
     * Visits a mixin definition node.
291
     *
292
     * @param MixinDefinitionNode $node The node
293
     * @param VisitorArguments $arguments The arguments
294
     *
295
     * @return MixinDefinitionNode
296
     */
297
    public function visitMixinDefinition(MixinDefinitionNode $node, VisitorArguments $arguments)
298
    {
299
        array_unshift($this->context->frames, $node);
300
301
        return $node;
302
    }
303
304
    /**
305
     * Visits a mixin definition node (!again).
306
     *
307
     * @param MixinDefinitionNode $node The node
308
     * @param VisitorArguments $arguments The arguments
309
     *
310
     * @return MixinDefinitionNode
311
     */
312
    public function visitMixinDefinitionOut(MixinDefinitionNode $node, VisitorArguments $arguments)
313
    {
314
        array_shift($this->context->frames);
315
316
        return $node;
317
    }
318
319
    /**
320
     * Visits a ruleset node.
321
     *
322
     * @param RulesetNode $node The node
323
     * @param VisitorArguments $arguments The arguments
324
     *
325
     * @return RulesetNode
326
     */
327
    public function visitRuleset(RulesetNode $node, VisitorArguments $arguments)
328
    {
329
        array_unshift($this->context->frames, $node);
330
331
        return $node;
332
    }
333
334
    /**
335
     * Visits a ruleset node (!again).
336
     *
337
     * @param RulesetNode $node The node
338
     * @param VisitorArguments $arguments The arguments
339
     *
340
     * @return RulesetNode
341
     */
342
    public function visitRulesetOut(RulesetNode $node, VisitorArguments $arguments)
343
    {
344
        array_shift($this->context->frames);
345
346
        return $node;
347
    }
348
349
    /**
350
     * Visits a media node.
351
     *
352
     * @param MediaNode $node The node
353
     * @param VisitorArguments $arguments The arguments
354
     *
355
     * @return MediaNode
356
     */
357
    public function visitMedia(MediaNode $node, VisitorArguments $arguments)
358
    {
359
        array_unshift($this->context->frames, $node->rules[0]);
360
361
        return $node;
362
    }
363
364
    /**
365
     * Visits a media node (!again).
366
     *
367
     * @param MediaNode $node The node
368
     * @param VisitorArguments $arguments The arguments
369
     *
370
     * @return MediaNode
371
     */
372
    public function visitMediaOut(MediaNode $node, VisitorArguments $arguments)
373
    {
374
        array_shift($this->context->frames);
375
376
        return $node;
377
    }
378
}
379