Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.05% covered (success)
91.05%
966 / 1061
66.67% covered (warning)
66.67%
70 / 105
CRAP
0.00% covered (danger)
0.00%
0 / 1
Less_Parser
91.05% covered (success)
91.05%
966 / 1061
66.67% covered (warning)
66.67%
70 / 105
710.61
0.00% covered (danger)
0.00%
0 / 1
 __construct
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 Reset
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 SetOptions
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 SetOption
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 registerFunction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unregisterFunction
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getCss
92.31% covered (success)
92.31%
24 / 26
0.00% covered (danger)
0.00%
0 / 1
6.02
 findValueOf
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
 getVariables
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
6.09
 findVarByName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 getVariableValue
42.86% covered (danger)
42.86%
9 / 21
0.00% covered (danger)
0.00%
0 / 1
94.64
 rgb2html
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 PreVisitors
25.00% covered (danger)
25.00%
1 / 4
0.00% covered (danger)
0.00%
0 / 1
10.75
 PostVisitors
57.14% covered (warning)
57.14%
8 / 14
0.00% covered (danger)
0.00%
0 / 1
15.38
 parse
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 parseFile
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 ModifyVars
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 SetFileInfo
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 SetCacheDir
n/a
0 / 0
n/a
0 / 0
5
 SetImportDirs
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
 _parse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 GetRules
73.53% covered (warning)
73.53%
25 / 34
0.00% covered (danger)
0.00%
0 / 1
16.13
 SetInput
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 UnsetInput
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 CacheFile
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 AddParsedFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 AllParsedFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 FileParsed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 restore
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 forget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isWhitespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 matcher
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 MatchChar
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 MatchReg
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 PeekReg
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 PeekChar
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 skipWhitespace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 expect
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 expectChar
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 parsePrimary
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 parseComment
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 parseComments
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 parseEntitiesQuoted
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 parseEntitiesKeyword
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 FromKeyword
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 parseEntitiesCall
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
7
 parseEntitiesArguments
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 parseEntitiesLiteral
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseEntitiesAssignment
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 parseEntitiesUrl
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
6.04
 parseEntitiesVariable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 parseEntitiesVariableCurly
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 parseEntitiesColor
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseEntitiesDimension
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 parseUnicodeDescriptor
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 parseEntitiesJavascript
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
6.17
 parseVariable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseRulesetCall
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseExtend
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
8
 parseMixinCall
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 parseMixinCallElements
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 parseMixinArgs
91.14% covered (success)
91.14%
72 / 79
0.00% covered (danger)
0.00%
0 / 1
34.80
 parseMixinDefinition
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
9
 parseEntity
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 parseEnd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 parseAlpha
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 parseElement
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
6
 parseCombinator
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
10
 parseLessSelector
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseSelector
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
17
 parseTag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 parseAttribute
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 parseBlock
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 parseBlockRuleset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseDetachedRuleset
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseRuleset
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
9.07
 parseNameValue
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 parseRule
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
20
 parseAnonymousValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseImport
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 parseImportOptions
85.00% covered (warning)
85.00%
17 / 20
0.00% covered (danger)
0.00%
0 / 1
6.12
 parseImportOption
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseMediaFeature
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
8.01
 parseMediaFeatures
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
6.44
 parseMedia
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 parseDirective
96.61% covered (success)
96.61%
57 / 59
0.00% covered (danger)
0.00%
0 / 1
24
 parseValue
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 parseImportant
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseSub
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 parseMultiplication
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
7.01
 parseAddition
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
7.01
 parseConditions
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
6.03
 parseCondition
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 parseOperand
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 parseExpression
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 parseProperty
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseRuleProperty
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
9
 rulePropertyMatch
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 serializeVars
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 is_method
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 round
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 Error
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 WinPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 AbsPath
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 CacheEnabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * Parse and compile Less files into CSS
5 */
6class Less_Parser {
7
8    /**
9     * Default parser options
10     * @var array<string,mixed>
11     */
12    public static $default_options = [
13        'compress'                => false,            // option - whether to compress
14        'strictUnits'            => false,            // whether units need to evaluate correctly
15        'strictMath'            => false,            // whether math has to be within parenthesis
16        'relativeUrls'            => true,            // option - whether to adjust URL's to be relative
17        'urlArgs'                => '',                // whether to add args into url tokens
18        'numPrecision'            => 8,
19
20        'import_dirs'            => [],
21        'import_callback'        => null,
22        'cache_dir'                => null,
23        'cache_method'            => 'serialize',     // false, 'serialize', 'callback';
24        'cache_callback_get'    => null,
25        'cache_callback_set'    => null,
26
27        'sourceMap'                => false,            // whether to output a source map
28        'sourceMapBasepath'        => null,
29        'sourceMapWriteTo'        => null,
30        'sourceMapURL'            => null,
31
32        'indentation'             => '  ',
33
34        'plugins'                => [],
35
36    ];
37
38    /** @var array{compress:bool,strictUnits:bool,strictMath:bool,numPrecision:int,import_dirs:array,import_callback:null|callable,indentation:string} */
39    public static $options = [];
40
41    private $input;                    // Less input string
42    private $input_len;                // input string length
43    private $pos;                    // current index in `input`
44    private $saveStack = [];    // holds state for backtracking
45    private $furthest;
46    private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
47
48    /**
49     * @var Less_Environment
50     */
51    private $env;
52
53    protected $rules = [];
54
55    private static $imports = [];
56
57    public static $has_extends = false;
58
59    public static $next_id = 0;
60
61    /**
62     * Filename to contents of all parsed the files
63     *
64     * @var array
65     */
66    public static $contentsMap = [];
67
68    /**
69     * @param Less_Environment|array|null $env
70     */
71    public function __construct( $env = null ) {
72        // Top parser on an import tree must be sure there is one "env"
73        // which will then be passed around by reference.
74        if ( $env instanceof Less_Environment ) {
75            $this->env = $env;
76        } else {
77            $this->SetOptions( self::$default_options );
78            $this->Reset( $env );
79        }
80
81        // mbstring.func_overload > 1 bugfix
82        // The encoding value must be set for each source file,
83        // therefore, to conserve resources and improve the speed of this design is taken here
84        if ( ini_get( 'mbstring.func_overload' ) ) {
85            $this->mb_internal_encoding = ini_get( 'mbstring.internal_encoding' );
86            @ini_set( 'mbstring.internal_encoding', 'ascii' );
87        }
88    }
89
90    /**
91     * Reset the parser state completely
92     */
93    public function Reset( $options = null ) {
94        $this->rules = [];
95        self::$imports = [];
96        self::$has_extends = false;
97        self::$imports = [];
98        self::$contentsMap = [];
99
100        $this->env = new Less_Environment();
101
102        // set new options
103        if ( is_array( $options ) ) {
104            $this->SetOptions( self::$default_options );
105            $this->SetOptions( $options );
106        }
107
108        $this->env->Init();
109    }
110
111    /**
112     * Set one or more compiler options
113     *  options: import_dirs, cache_dir, cache_method
114     */
115    public function SetOptions( $options ) {
116        foreach ( $options as $option => $value ) {
117            $this->SetOption( $option, $value );
118        }
119    }
120
121    /**
122     * Set one compiler option
123     */
124    public function SetOption( $option, $value ) {
125        switch ( $option ) {
126
127            case 'import_dirs':
128                $this->SetImportDirs( $value );
129                return;
130
131            case 'cache_dir':
132                if ( is_string( $value ) ) {
133                    Less_Cache::SetCacheDir( $value );
134                    Less_Cache::CheckCacheDir();
135                }
136                return;
137        }
138
139        self::$options[$option] = $value;
140    }
141
142    /**
143     * Registers a new custom function
144     *
145     * @param string $name function name
146     * @param callable $callback callback
147     */
148    public function registerFunction( $name, $callback ) {
149        $this->env->functions[$name] = $callback;
150    }
151
152    /**
153     * Removed an already registered function
154     *
155     * @param string $name function name
156     */
157    public function unregisterFunction( $name ) {
158        if ( isset( $this->env->functions[$name] ) ) {
159            unset( $this->env->functions[$name] );
160        }
161    }
162
163    /**
164     * Get the current css buffer
165     *
166     * @return string
167     */
168    public function getCss() {
169        $precision = ini_get( 'precision' );
170        @ini_set( 'precision', '16' );
171        $locale = setlocale( LC_NUMERIC, 0 );
172        setlocale( LC_NUMERIC, "C" );
173
174        try {
175            $root = new Less_Tree_Ruleset( null, $this->rules );
176            $root->root = true;
177            $root->firstRoot = true;
178
179            $this->PreVisitors( $root );
180
181            self::$has_extends = false;
182            $evaldRoot = $root->compile( $this->env );
183
184            $this->PostVisitors( $evaldRoot );
185
186            if ( self::$options['sourceMap'] ) {
187                $generator = new Less_SourceMap_Generator( $evaldRoot, self::$contentsMap, self::$options );
188                // will also save file
189                // FIXME: should happen somewhere else?
190                $css = $generator->generateCSS();
191            } else {
192                $css = $evaldRoot->toCSS();
193            }
194
195            if ( self::$options['compress'] ) {
196                $css = preg_replace( '/(^(\s)+)|((\s)+$)/', '', $css );
197            }
198
199        } catch ( Exception $exc ) {
200            // Intentional fall-through so we can reset environment
201        }
202
203        // reset php settings
204        @ini_set( 'precision', $precision );
205        setlocale( LC_NUMERIC, $locale );
206
207        // If you previously defined $this->mb_internal_encoding
208        // is required to return the encoding as it was before
209        if ( $this->mb_internal_encoding != '' ) {
210            @ini_set( "mbstring.internal_encoding", $this->mb_internal_encoding );
211            $this->mb_internal_encoding = '';
212        }
213
214        // Rethrow exception after we handled resetting the environment
215        if ( !empty( $exc ) ) {
216            throw $exc;
217        }
218
219        return $css;
220    }
221
222    public function findValueOf( $varName ) {
223        foreach ( $this->rules as $rule ) {
224            if ( isset( $rule->variable ) && ( $rule->variable == true ) && ( str_replace( "@", "", $rule->name ) == $varName ) ) {
225                return $this->getVariableValue( $rule );
226            }
227        }
228        return null;
229    }
230
231    /**
232     * Gets the private rules variable and returns an array of the found variables
233     * it uses a helper method getVariableValue() that contains the logic ot fetch the value
234     * from the rule object
235     *
236     * @return array
237     */
238    public function getVariables() {
239        $variables = [];
240
241        $not_variable_type = [
242            Less_Tree_Comment::class,   // this include less comments ( // ) and css comments (/* */)
243            Less_Tree_Import::class,    // do not search variables in included files @import
244            Less_Tree_Ruleset::class,   // selectors (.someclass, #someid, …)
245            Less_Tree_Operation::class,
246        ];
247
248        // @TODO run compilation if not runned yet
249        foreach ( $this->rules as $key => $rule ) {
250            if ( in_array( get_class( $rule ), $not_variable_type ) ) {
251                continue;
252            }
253
254            // Note: it seems $rule is always Less_Tree_Rule when variable = true
255            if ( $rule instanceof Less_Tree_Rule && $rule->variable ) {
256                $variables[$rule->name] = $this->getVariableValue( $rule );
257            } else {
258                if ( $rule instanceof Less_Tree_Comment ) {
259                    $variables[] = $this->getVariableValue( $rule );
260                }
261            }
262        }
263        return $variables;
264    }
265
266    public function findVarByName( $var_name ) {
267        foreach ( $this->rules as $rule ) {
268            if ( isset( $rule->variable ) && ( $rule->variable == true ) ) {
269                if ( $rule->name == $var_name ) {
270                    return $this->getVariableValue( $rule );
271                }
272            }
273        }
274        return null;
275    }
276
277    /**
278     * This method gets the value of the less variable from the rules object.
279     * Since the objects vary here we add the logic for extracting the css/less value.
280     *
281     * @param Less_Tree $var
282     * @return string
283     */
284    private function getVariableValue( Less_Tree $var ) {
285        switch ( get_class( $var ) ) {
286            case Less_Tree_Color::class:
287                return $this->rgb2html( $var->rgb );
288            case Less_Tree_Variable::class:
289                return $this->findVarByName( $var->name );
290            case Less_Tree_Keyword::class:
291                return $var->value;
292            case Less_Tree_Url::class:
293                // Based on Less_Tree_Url::genCSS()
294                // Recurse to serialize the Less_Tree_Quoted value
295                return 'url(' . $this->getVariableValue( $var->value ) . ')';
296            case Less_Tree_Rule::class:
297                return $this->getVariableValue( $var->value );
298            case Less_Tree_Value::class:
299                $value = '';
300                foreach ( $var->value as $sub_value ) {
301                    $value .= $this->getVariableValue( $sub_value ) . ' ';
302                }
303                return $value;
304            case Less_Tree_Quoted::class:
305                return $var->quote . $var->value . $var->quote;
306            case Less_Tree_Dimension::class:
307                $value = $var->value;
308                if ( $var->unit && $var->unit->numerator ) {
309                    $value .= $var->unit->numerator[0];
310                }
311                return $value;
312            case Less_Tree_Expression::class:
313                $value = '';
314                foreach ( $var->value as $item ) {
315                    $value .= $this->getVariableValue( $item ) . " ";
316                }
317                return $value;
318            case Less_Tree_Operation::class:
319                throw new Exception( 'getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()' );
320            case Less_Tree_Unit::class:
321            case Less_Tree_Comment::class:
322            case Less_Tree_Import::class:
323            case Less_Tree_Ruleset::class:
324            default:
325                throw new Exception( "type missing in switch/case getVariableValue for " . get_class( $var ) );
326        }
327    }
328
329    private function rgb2html( $r, $g = -1, $b = -1 ) {
330        if ( is_array( $r ) && count( $r ) == 3 ) {
331            list( $r, $g, $b ) = $r;
332        }
333
334        return sprintf( '#%02x%02x%02x', $r, $g, $b );
335    }
336
337    /**
338     * Run pre-compile visitors
339     */
340    private function PreVisitors( $root ) {
341        if ( self::$options['plugins'] ) {
342            foreach ( self::$options['plugins'] as $plugin ) {
343                if ( !empty( $plugin->isPreEvalVisitor ) ) {
344                    $plugin->run( $root );
345                }
346            }
347        }
348    }
349
350    /**
351     * Run post-compile visitors
352     */
353    private function PostVisitors( $evaldRoot ) {
354        $visitors = [];
355        $visitors[] = new Less_Visitor_joinSelector();
356        if ( self::$has_extends ) {
357            $visitors[] = new Less_Visitor_processExtends();
358        }
359        $visitors[] = new Less_Visitor_toCSS();
360
361        if ( self::$options['plugins'] ) {
362            foreach ( self::$options['plugins'] as $plugin ) {
363                if ( property_exists( $plugin, 'isPreEvalVisitor' ) && $plugin->isPreEvalVisitor ) {
364                    continue;
365                }
366
367                if ( property_exists( $plugin, 'isPreVisitor' ) && $plugin->isPreVisitor ) {
368                    array_unshift( $visitors, $plugin );
369                } else {
370                    $visitors[] = $plugin;
371                }
372            }
373        }
374
375        for ( $i = 0; $i < count( $visitors ); $i++ ) {