JS   F
last analyzed

Complexity

Total Complexity 82

Size/Duplication

Total Lines 344
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 344
rs 2
c 0
b 0
f 0
wmc 82
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A minify() 0 4 1
D min() 0 87 26
A peek() 0 5 1
B get() 0 24 7
D action() 0 79 33
B next() 0 39 10
A isAlphaNum() 0 4 3

How to fix   Complexity   

Complex Class

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

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 57 and the first side effect is on line 5.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace PH7\Framework\Compress\Minify;
4
5
defined('PH7') or exit('Restricted access');
6
7
/**
8
 * Js.class.php - PHP implementation of Douglas Crockford's JSMin. (Small changes by Pierre-Henry Soria)
9
 *
10
 * This is pretty much a direct port of jsmin.c to PHP with just a few
11
 * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
12
 * outputs to stdout, this library accepts a string as input and returns another
13
 * string as output.
14
 *
15
 * PHP 5 or higher is required.
16
 *
17
 * Permission is hereby granted to use this version of the library under the
18
 * same terms as jsmin.c, which has the following license:
19
 *
20
 * --
21
 * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
22
 *
23
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
24
 * this software and associated documentation files (the "Software"), to deal in
25
 * the Software without restriction, including without limitation the rights to
26
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
27
 * of the Software, and to permit persons to whom the Software is furnished to do
28
 * so, subject to the following conditions:
29
 *
30
 * The above copyright notice and this permission notice shall be included in all
31
 * copies or substantial portions of the Software.
32
 *
33
 * The Software shall be used for Good, not Evil.
34
 *
35
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
 * SOFTWARE.
42
 * --
43
 *
44
 * @package JSMin
45
 * @author Ryan Grove <[email protected]>
46
 * @copyright 2002 Douglas Crockford <[email protected]> (jsmin.c)
47
 * @copyright 2008 Ryan Grove <[email protected]> (PHP port)
48
 * @copyright 2012 Adam Goforth <[email protected]> (Updates)
49
 * @license http://opensource.org/licenses/mit-license.php MIT License
50
 * @version 1.1.2 (2012-05-01)
51
 * @link https://github.com/rgrove/jsmin-php
52
 *
53
 * THIS FILE HAS BEEN MODIFIED BY:
54
 * @author Pierre-Henry Soria <[email protected]>
55
 * @copyright (c) 2011-2019, Pierre-Henry Soria. All Rights Reserved.
56
 */
57
class JS
58
{
59
    const ORD_LF = 10;
60
    const ORD_SPACE = 32;
61
    const ACTION_KEEP_A = 1;
62
    const ACTION_DELETE_A = 2;
63
    const ACTION_DELETE_A_B = 3;
64
65
    protected $a = '';
66
    protected $b = '';
67
    protected $input = '';
68
    protected $inputIndex = 0;
69
    protected $inputLength = 0;
70
    protected $lookAhead = null;
71
    protected $output = '';
72
73
    // -- Public Static Methods --------------------------------------------------
74
75
    /**
76
     * Constructor
77
     *
78
     * @param string $input Javascript to be minified
79
     */
80
    public function __construct($input)
81
    {
82
        $this->input = str_replace("\r\n", "\n", $input);
83
        $this->inputLength = strlen($this->input);
84
    }
85
86
    // -- Public Instance Methods ------------------------------------------------
87
88
    /**
89
     * Minify Javascript
90
     *
91
     * @uses __construct()
92
     * @uses min()
93
     *
94
     * @param string $js Javascript to be minified
95
     *
96
     * @return string
97
     */
98
    public static function minify($js)
99
    {
100
        return (new self($js))->min();
101
    }
102
103
    // -- Protected Instance Methods ---------------------------------------------
104
105
    /**
106
     * Perform minification, return result
107
     *
108
     * @uses action()
109
     * @uses isAlphaNum()
110
     * @uses get()
111
     * @uses peek()
112
     * @return string
113
     */
114
    protected function min()
115
    {
116
        if (0 == strncmp($this->peek(), "\xef", 1)) {
117
            $this->get();
118
            $this->get();
119
            $this->get();
120
        }
121
122
        $this->a = "\n";
123
        $this->action(self::ACTION_DELETE_A_B);
124
125
        while ($this->a !== null) {
126
            switch ($this->a) {
127
                case ' ':
128
                    if ($this->isAlphaNum($this->b)) {
129
                        $this->action(self::ACTION_KEEP_A);
130
                    } else {
131
                        $this->action(self::ACTION_DELETE_A);
132
                    }
133
                    break;
134
135
                case "\n":
136
                    switch ($this->b) {
137
                        case '{':
138
                        case '[':
139
                        case '(':
140
                        case '+':
141
                        case '-':
142
                        case '!':
143
                        case '~':
144
                            $this->action(self::ACTION_KEEP_A);
145
                            break;
146
147
                        case ' ':
148
                            $this->action(self::ACTION_DELETE_A_B);
149
                            break;
150
151
                        default:
152
                            if ($this->isAlphaNum($this->b)) {
153
                                $this->action(self::ACTION_KEEP_A);
154
                            } else {
155
                                $this->action(self::ACTION_DELETE_A);
156
                            }
157
                    }
158
                    break;
159
160
                default:
161
                    switch ($this->b) {
162
                        case ' ':
163
                            if ($this->isAlphaNum($this->a)) {
164
                                $this->action(self::ACTION_KEEP_A);
165
                                break;
166
                            }
167
168
                            $this->action(self::ACTION_DELETE_A_B);
169
                            break;
170
171
                        case "\n":
172
                            switch ($this->a) {
173
                                case '}':
174
                                case ']':
175
                                case ')':
176
                                case '+':
177
                                case '-':
178
                                case '"':
179
                                case "'":
180
                                    $this->action(self::ACTION_KEEP_A);
181
                                    break;
182
183
                                default:
184
                                    if ($this->isAlphaNum($this->a)) {
185
                                        $this->action(self::ACTION_KEEP_A);
186
                                    } else {
187
                                        $this->action(self::ACTION_DELETE_A_B);
188
                                    }
189
                            }
190
                            break;
191
192
                        default:
193
                            $this->action(self::ACTION_KEEP_A);
194
                            break;
195
                    }
196
            }
197
        }
198
199
        return $this->output;
200
    }
201
202
    /**
203
     * Get next char. If is ctrl character, translate to a space or newline.
204
     *
205
     * @uses get()
206
     * @return string|null
207
     */
208
    protected function peek()
209
    {
210
        $this->lookAhead = $this->get();
211
        return $this->lookAhead;
212
    }
213
214
    /**
215
     * Get next char. Convert ctrl char to space.
216
     *
217
     * @return string|null
218
     */
219
    protected function get()
220
    {
221
        $c = $this->lookAhead;
222
        $this->lookAhead = null;
223
224
        if ($c === null) {
225
            if ($this->inputIndex < $this->inputLength) {
226
                $c = substr($this->input, $this->inputIndex, 1);
227
                $this->inputIndex += 1;
228
            } else {
229
                $c = null;
230
            }
231
        }
232
233
        if ($c === "\r") {
234
            return "\n";
235
        }
236
237
        if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
238
            return $c;
239
        }
240
241
        return ' ';
242
    }
243
244
    /**
245
     * Action -- do something! What to do is determined by the $command argument.
246
     *
247
     * action treats a string as a single character. Wow!
248
     * action recognizes a regular expression if it is preceded by ( or , or =.
249
     *
250
     * @uses next()
251
     * @uses get()
252
     * @throws \PH7\Framework\Compress\Exception If parser errors are found:
253
     *         - Unterminated string literal
254
     *         - Unterminated regular expression set in regex literal
255
     *         - Unterminated regular expression literal
256
     *
257
     * @param int $command One of class constants:
258
     *      ACTION_KEEP_A      Output A. Copy B to A. Get the next B.
259
     *      ACTION_DELETE_A    Copy B to A. Get the next B. (Delete A).
260
     *      ACTION_DELETE_A_B  Get the next B. (Delete B).
261
     */
262
    protected function action($command)
263
    {
264
        switch ($command) {
265
            case self::ACTION_KEEP_A:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
266
                $this->output .= $this->a;
267
268
            case self::ACTION_DELETE_A:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
269
                $this->a = $this->b;
270
271
                if ($this->a === "'" || $this->a === '"') {
272
                    for (; ;) {
273
                        $this->output .= $this->a;
274
                        $this->a = $this->get();
275
276
                        if ($this->a === $this->b) {
277
                            break;
278
                        }
279
280
                        if (ord($this->a) <= self::ORD_LF) {
281
                            throw new \PH7\Framework\Compress\Exception('Unterminated string literal.');
282
                        }
283
284
                        if ($this->a === '\\') {
285
                            $this->output .= $this->a;
286
                            $this->a = $this->get();
287
                        }
288
                    }
289
                }
290
291
            case self::ACTION_DELETE_A_B:
292
                $this->b = $this->next();
293
294
                if ($this->b === '/' && (
295
                        $this->a === '(' || $this->a === ',' || $this->a === '=' ||
296
                        $this->a === ':' || $this->a === '[' || $this->a === '!' ||
297
                        $this->a === '&' || $this->a === '|' || $this->a === '?' ||
298
                        $this->a === '{' || $this->a === '}' || $this->a === ';' ||
299
                        $this->a === "\n")
300
                ) {
301
302
                    $this->output .= $this->a . $this->b;
303
304
                    for (; ;) {
305
                        $this->a = $this->get();
306
307
                        if ($this->a === '[') {
308
                            /*
309
                              inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
310
                                return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
311
                            */
312
                            for (; ;) {
313
                                $this->output .= $this->a;
314
                                $this->a = $this->get();
315
316
                                if ($this->a === ']') {
317
                                    break;
318
                                } elseif ($this->a === '\\') {
319
                                    $this->output .= $this->a;
320
                                    $this->a = $this->get();
321
                                } elseif (ord($this->a) <= self::ORD_LF) {
322
                                    throw new \PH7\Framework\Compress\Exception('Unterminated regular expression set in regex literal.');
323
                                }
324
                            }
325
                        } elseif ($this->a === '/') {
326
                            break;
327
                        } elseif ($this->a === '\\') {
328
                            $this->output .= $this->a;
329
                            $this->a = $this->get();
330
                        } elseif (ord($this->a) <= self::ORD_LF) {
331
                            throw new \PH7\Framework\Compress\Exception('Unterminated regular expression literal.');
332
                        }
333
334
                        $this->output .= $this->a;
335
                    }
336
337
                    $this->b = $this->next();
338
                }
339
        }
340
    }
341
342
    /**
343
     * Get the next character, skipping over comments. peek() is used to see
344
     *  if a '/' is followed by a '/' or '*'.
345
     *
346
     * @uses get()
347
     * @uses peek()
348
     * @throws \PH7\Framework\Compress\Exception On unterminated comment.
349
     * @return string
350
     */
351
    protected function next()
352
    {
353
        $c = $this->get();
354
355
        if ($c === '/') {
356
            switch ($this->peek()) {
357
                case '/':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
358
                    for (; ;) {
359
                        $c = $this->get();
360
361
                        if (ord($c) <= self::ORD_LF) {
362
                            return $c;
363
                        }
364
                    }
365
366
                case '*':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
367
                    $this->get();
368
369
                    for (; ;) {
370
                        switch ($this->get()) {
371
                            case '*':
372
                                if ($this->peek() === '/') {
373
                                    $this->get();
374
                                    return ' ';
375
                                }
376
                                break;
377
378
                            case null:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->get() of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
379
                                throw new \PH7\Framework\Compress\Exception('Unterminated comment.');
380
                        }
381
                    }
382
383
                default:
384
                    return $c;
385
            }
386
        }
387
388
        return $c;
389
    }
390
391
    /**
392
     * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
393
     *
394
     * @return bool
395
     */
396
    protected function isAlphaNum($c)
397
    {
398
        return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
399
    }
400
}
401