Same name in other branches
  1. 5.0.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
  2. 6.0.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
  3. 8.x-2.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
  4. 8.x-3.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze
  5. 8.x-4.x advagg_js_minify/jsqueeze.inc \Patchwork\JSqueeze

Hierarchy

Expanded class hierarchy of JSqueeze

1 file declares its use of JSqueeze
advagg_js_compress.php53.inc dans advagg_js_compress/advagg_js_compress.php53.inc
Advanced CSS/JS aggregation js compression php 5.3+ functions.
4 string references to 'JSqueeze'
advagg_js_compress_configuration dans advagg_js_compress/advagg_js_compress.module
Generate the js compress configuration.
advagg_js_compress_jsqueeze dans advagg_js_compress/advagg_js_compress.php53.inc
Compress a JS string using jsqueeze.
advagg_js_compress_libraries_info dans advagg_js_compress/advagg_js_compress.module
Implements hook_libraries_info().
advagg_js_compress_redo_files dans advagg_js_compress/advagg_js_compress.module
Get all js files and js files that are not compressed.

Fichier

advagg_js_compress/jsqueeze.inc, line 63

Namespace

Patchwork
View source
class JSqueeze {
    const SPECIAL_VAR_PACKER = '(\\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
    public $charFreq;
    protected $strings, $closures, $str0, $str1, $argFreq, $specialVarRx, $keepImportantComments, $varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*', $reserved = array(
        // Literals
'true',
        'false',
        'null',
        // ES6
'break',
        'case',
        'class',
        'catch',
        'const',
        'continue',
        'debugger',
        'default',
        'delete',
        'do',
        'else',
        'export',
        'extends',
        'finally',
        'for',
        'function',
        'if',
        'import',
        'in',
        'instanceof',
        'new',
        'return',
        'super',
        'switch',
        'this',
        'throw',
        'try',
        'typeof',
        'var',
        'void',
        'while',
        'with',
        'yield',
        // Future
'enum',
        // Strict mode
'implements',
        'package',
        'protected',
        'static',
        'let',
        'interface',
        'private',
        'public',
        // Module
'await',
        // Older standards
'abstract',
        'boolean',
        'byte',
        'char',
        'double',
        'final',
        'float',
        'goto',
        'int',
        'long',
        'native',
        'short',
        'synchronized',
        'throws',
        'transient',
        'volatile',
    );
    public function __construct() {
        $this->reserved = array_flip($this->reserved);
        $this->charFreq = array_fill(0, 256, 0);
    }
    
    /**
     * Squeezes a JavaScript source code.
     *
     * Set $singleLine to false if you want optional
     * semi-colons to be replaced by line feeds.
     *
     * Set $keepImportantComments to false if you want /*! comments to be removed.
     *
     * $specialVarRx defines the regular expression of special variables names
     * for global vars, methods, properties and in string substitution.
     * Set it to false if you don't want any.
     *
     * If the analysed javascript source contains a single line comment like
     * this one, then the directive will overwrite $specialVarRx:
     *
     * // jsqueeze.specialVarRx = your_special_var_regexp_here
     *
     * Only the first directive is parsed, others are ignored. It is not possible
     * to redefine $specialVarRx in the middle of the javascript source.
     *
     * Example:
     * $parser = new JSqueeze;
     * $squeezed_js = $parser->squeeze($fat_js);
     */
    public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) {
        $code = trim($code);
        if ('' === $code) {
            return '';
        }
        $this->argFreq = array(
            -1 => 0,
        );
        $this->specialVarRx = $specialVarRx;
        $this->keepImportantComments = !!$keepImportantComments;
        if (preg_match("#//[ \t]*jsqueeze\\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) {
            if (!$key[1]) {
                $key[2] = trim($key[2]);
                $key[1] = strtolower($key[2]);
                $key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
            }
            $this->specialVarRx = $key[1] ? $key[2] : false;
        }
        // Remove capturing parentheses
        $this->specialVarRx && ($this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\\((?!\\?)/', '(?:', $this->specialVarRx));
        false !== strpos($code, "\r") && ($code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"));
        false !== strpos($code, "…") && ($code = str_replace("…", "\n", $code));
        // Next Line
        false !== strpos($code, "
") && ($code = str_replace("
", "\n", $code));
        // Line Separator
        false !== strpos($code, "
") && ($code = str_replace("
", "\n", $code));
        // Paragraph Separator
        list($code, $this->strings) = $this->extractStrings($code);
        list($code, $this->closures) = $this->extractClosures($code);
        $key = "//''\"\"#0'";
        // This crap has a wonderful property: it can not happen in any valid javascript, even in strings
        $this->closures[$key] =& $code;
        $tree = array(
            $key => array(
                'parent' => false,
            ),
        );
        $this->makeVars($code, $tree[$key], $key);
        $this->renameVars($tree[$key], true);
        $code = substr($tree[$key]['code'], 1);
        $code = preg_replace("'\\breturn !'", 'return!', $code);
        $code = preg_replace("'\\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
        // preg_replace is much more efficient than str_replace here
        // because we don't need to scan the entire code each time for every replacement
        $code = preg_replace_callback('#//\'\'""\\d++(?:/?+\'|\\])#', array(
            $this,
            'restoreString',
        ), $code);
        if ($singleLine) {
            $code = strtr($code, "\n", ';');
        }
        else {
            $code = str_replace("\n", ";\n", $code);
        }
        false !== strpos($code, "\r") && ($code = strtr(trim($code), "\r", "\n"));
        // Cleanup memory
        $this->charFreq = array_fill(0, 256, 0);
        $this->strings = $this->closures = $this->argFreq = array();
        $this->str0 = $this->str1 = '';
        return $code;
    }
    protected function extractStrings($f) {
        if ($cc_on = false !== strpos($f, '@cc_on')) {
            // Protect conditional comments from being removed
            $f = str_replace('#', '##', $f);
            $f = str_replace('/*@', '1#@', $f);
            $f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
            $f = str_replace('@*/', '@#1', $f);
        }
        $len = strlen($f);
        $code = str_repeat(' ', $len);
        $j = 0;
        $strings = array();
        $K = 0;
        $instr = false;
        $q = array(
            "'",
            '"',
            "'" => 0,
            '"' => 0,
        );
        // Extract strings, removes comments
        for ($i = 0; $i < $len; ++$i) {
            if ($instr) {
                if ('//' == $instr) {
                    if ("\n" == $f[$i]) {
                        $f[$i--] = ' ';
                        $instr = false;
                    }
                }
                elseif ($f[$i] == $instr || '/' == $f[$i] && "/'" == $instr) {
                    if ('!' == $instr) {
                    }
                    elseif ('*' == $instr) {
                        if ('/' == $f[$i + 1]) {
                            ++$i;
                            $instr = false;
                        }
                    }
                    else {
                        if ("/'" == $instr) {
                            while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
                                $s[] = $f[$i++];
                            }
                            $s[] = $f[$i];
                        }
                        $instr = false;
                    }
                }
                elseif ('*' == $instr) {
                }
                elseif ('!' == $instr) {
                    if ('*' == $f[$i] && '/' == $f[$i + 1]) {
                        $s[] = "*/\r";
                        ++$i;
                        $instr = false;
                    }
                    elseif ("\n" == $f[$i]) {
                        $s[] = "\r";
                    }
                    else {
                        $s[] = $f[$i];
                    }
                }
                elseif ('\\' == $f[$i]) {
                    ++$i;
                    if ("\n" != $f[$i]) {
                        isset($q[$f[$i]]) && ++$q[$f[$i]];
                        $s[] = '\\' . $f[$i];
                    }
                }
                elseif ('[' == $f[$i] && "/'" == $instr) {
                    $instr = '/[';
                    $s[] = '[';
                }
                elseif (']' == $f[$i] && '/[' == $instr) {
                    $instr = "/'";
                    $s[] = ']';
                }
                elseif ("'" == $f[$i] || '"' == $f[$i]) {
                    ++$q[$f[$i]];
                    $s[] = '\\' . $f[$i];
                }
                else {
                    $s[] = $f[$i];
                }
            }
            else {
                switch ($f[$i]) {
                    case ';':
                        // Remove triple semi-colon
                        if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
                            $f[$i] = $f[$i + 1] = '/';
                        }
                        else {
                            $code[++$j] = ';';
                            break;
                        }
                    case '/':
                        if ('*' == $f[$i + 1]) {
                            ++$i;
                            $instr = '*';
                            if ($this->keepImportantComments && '!' == $f[$i + 1]) {
                                ++$i;
                                // no break here
                            }
                            else {
                                break;
                            }
                        }
                        elseif ('/' == $f[$i + 1]) {
                            ++$i;
                            $instr = '//';
                            break;
                        }
                        else {
                            $a = $j && (' ' == $code[$j] || "" == $code[$j]) ? $code[$j - 1] : $code[$j];
                            if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a) || false !== strpos('oenfd', $a) && preg_match("'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ ]?\\*?)[ ]?\$'", substr($code, $j - 7, 8))) {
                                if (')' === $a && $j > 1) {
                                    $a = 1;
                                    $k = $j - (' ' == $code[$j] || "" == $code[$j]) - 1;
                                    while ($k >= 0 && $a) {
                                        if ('(' === $code[$k]) {
                                            --$a;
                                        }
                                        elseif (')' === $code[$k]) {
                                            ++$a;
                                        }
                                        --$k;
                                    }
                                    if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ ]?\$'", substr($code, 0, $k + 1))) {
                                        $code[++$j] = '/';
                                        break;
                                    }
                                }
                                $key = "//''\"\"" . $K++ . ($instr = "/'");
                                $a = $j;
                                $code .= $key;
                                while (isset($key[++$j - $a - 1])) {
                                    $code[$j] = $key[$j - $a - 1];
                                }
                                --$j;
                                isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
                                $strings[$key] = array(
                                    '/',
                                );
                                $s =& $strings[$key];
                            }
                            else {
                                $code[++$j] = '/';
                            }
                            break;
                        }
                    case "'":
                    case '"':
                        $instr = $f[$i];
                        $key = "//''\"\"" . $K++ . ('!' == $instr ? ']' : "'");
                        $a = $j;
                        $code .= $key;
                        while (isset($key[++$j - $a - 1])) {
                            $code[$j] = $key[$j - $a - 1];
                        }
                        --$j;
                        isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
                        $strings[$key] = array();
                        $s =& $strings[$key];
                        '!' == $instr && ($s[] = "\r/*!");
                        break;
                    case "\n":
                        if ($j > 3) {
                            if (' ' == $code[$j] || "" == $code[$j]) {
                                --$j;
                            }
                            if (false === strpos('oefd', $code[$j]) || !preg_match("'(?<![\$.a-zA-Z0-9_])(?:do|else|typeof|void)[ ]?\$'", substr($code, $j - 6, 8))) {
                                $code[++$j] = false !== strpos('kend', $code[$j - 1]) && preg_match("'(?<![\$.a-zA-Z0-9_])(?:break|continue|return|yield[ ]?\\*?)[ ]?\$'", substr($code, $j - 9, 10)) ? ';' : "";
                                break;
                            }
                        }
                    case "\t":
                        $f[$i] = ' ';
                    case ' ':
                        if (!$j || ' ' == $code[$j] || "" == $code[$j]) {
                            break;
                        }
                    default:
                        $code[++$j] = $f[$i];
                }
            }
        }
        isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
        unset($s);
        $code = substr($code, 0, $j + 1);
        $cc_on && $this->restoreCc($code, false);
        // Deal with newlines before/after postfix/prefix operators
        // (a string literal starts with `//` and ends with `'` at this stage)
        // http://inimino.org/~inimino/blog/javascript_semicolons
        // Newlines before prefix are a new statement when a completed expression precedes because postfix is a "restrictd production"
        // A closing bracket `)` from if/for/while does not complete an expression, so mark possible `;` as `#` to deal with later
        $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]}])(--|\\+\\+)#", ';$1', $code);
        $code = preg_replace("#(?<=\\))(--|\\+\\+)#", '#$1', $code);
        // Newlines after postfix are a new statement if the following token can't be parsed otherwise
        // i.e. it's a keyword, identifier, string or number literal, prefix operator, opening brace
        // But a prefix operator can have a newline before its operand, so check a completed expression precedes to be sure it's a postfix
        // Again mark case after closing bracket with `#` to deal with later
        // Also ensure keywords that may be followed by an expression aren't mistaken for the end of a completed expression
        // (note that postfix cannot apply to an expression completed with `}`)
        $code = preg_replace("#(?<![\$.a-zA-Z0-9_])(do|else|return|throw|typeof|void|yield) ?+(--|\\+\\+)#", '$1$2 ', $code);
        $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]]) ?+(--|\\+\\+)(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1;', $code);
        $code = preg_replace("#(?<=\\)) ?+(--|\\+\\+)(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1#', $code);
        // Protect wanted spaces and remove unwanted ones
        $code = strtr($code, "", ' ');
        $code = str_replace('- -', "--", $code);
        $code = str_replace('+ +', "++", $code);
        $code = preg_replace("'(\\d)\\s+\\.\\s*([a-zA-Z\$_[(])'", "\$1.\$2", $code);
        $code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\\]{}/']+)#", '$1', $code);
        $code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\\]{}/]+) #", '$1', $code);
        $cc_on && ($code = preg_replace_callback("'//[^\\'].*?@#3'", function ($m) {
            return strtr($m[0], ' ', "");
        }, $code));
        // Replace new Array/Object by []/{}
        false !== strpos($code, 'new Array') && ($code = preg_replace("'new Array(?:\\(\\)|([;\\])},:]))'", '[]$1', $code));
        false !== strpos($code, 'new Object') && ($code = preg_replace("'new Object(?:\\(\\)|([;\\])},:]))'", '{}$1', $code));
        // Add missing semi-colons after curly braces
        // This adds more semi-colons than strictly needed,
        // but it seems that later gzipping is favorable to the repetition of "};"
        $code = preg_replace("'\\}(?![:,;.()\\[\\]}\\|&?]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
        // Tag possible empty instruction for easy detection
        $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\\('", '1#(', $code);
        $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\\('", '2#(', $code);
        $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do while\\('", '4#(', $code);
        $code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\\('", '3#(', $code);
        $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do(?![\$a-zA-Z0-9_])'", '5#', $code);
        $forPool = array();
        $instrPool = array();
        $doPool = array();
        $s = 0;
        $d = 0;
        $f = array();
        $j = -1;
        // Remove as much semi-colon as possible
        $len = strlen($code);
        for ($i = 0; $i < $len; ++$i) {
            switch ($code[$i]) {
                case '(':
                    if ($j >= 0 && "\n" == $f[$j]) {
                        $f[$j] = ';';
                    }
                    ++$s;
                    if ($i > 1 && '#' == $code[$i - 1]) {
                        switch ($code[$i - 2]) {
                            case '3':
                                if (isset($doPool[$d])) {
                                    $instrPool[$s - 1] = 5;
                                    // `while` corresponds to `do`
                                    unset($doPool[$d]);
                                }
                                else {
                                    $instrPool[$s - 1] = 1;
                                }
                                break;
                            case '2':
                                $forPool[$s] = 1;
                            // also set $instrPool
                            case '1':
                            case '4':
                                $instrPool[$s - 1] = 1;
                        }
                    }
                    $f[++$j] = '(';
                    break;
                case ']':
                case ')':
                    if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
                        $f[$j] .= $code[$i];
                        $f[++$j] = "\n";
                    }
                    else {
                        $f[++$j] = $code[$i];
                    }
                    if (')' == $code[$i]) {
                        unset($forPool[$s]);
                        --$s;
                        if (isset($instrPool[$s]) && 5 === $instrPool[$s]) {
                            $f[$j - 1] .= ')';
                            $f[$j] = ';';
                        }
                    }
                    continue 2;
                case '{':
                    ++$d;
                    $f[++$j] = '{';
                    break;
                case '}':
                    --$d;
                    if ("\n" == $f[$j]) {
                        $f[$j] = '}';
                    }
                    else {
                        $f[++$j] = '}';
                    }
                    break;
                case '+':
                case '-':
                    $f[++$j] = $code[$i];
                    if ($i + 1 < $len && ($code[$i] === $code[$i + 1] || '#' === $code[$i + 1])) {
                        // delay unsetting $instrPool[$s]
                        continue 2;
                    }
                    break;
                case '#':
                    switch ($f[$j]) {
                        case '1':
                            $f[$j] = 'if';
                            break 2;
                        case '2':
                            $f[$j] = 'for';
                            break 2;
                        case '3':
                            $f[$j] = 'while';
                            break 2;
                        case '4':
                            // special case `while` that doesn't correspond to the `do`
                            $f[$j] = 'do while';
                            $doPool[$d] = 1;
                            break 2;
                        case '5':
                            $f[$j] = 'do';
                            $doPool[$d] = 1;
                        case ';':
                            // added after `do..while` - no extra `;` needed
                            break 2;
                        case ')':
                        case '+':
                        case '-':
                            if (isset($instrPool[$s])) {
                                // prefix operator in conditional/loop statement - no `;`
                                break 2;
                            }
                    }
                case ';':
                    if (isset($forPool[$s]) || isset($instrPool[$s]) && 5 !== $instrPool[$s]) {
                        $f[++$j] = ';';
                    }
                    elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
                        $f[++$j] = "\n";
                    }
                    break;
                case '[':
                    if ($j >= 0 && "\n" == $f[$j]) {
                        $f[$j] = ';';
                    }
                default:
                    $f[++$j] = $code[$i];
            }
            unset($instrPool[$s]);
        }
        $f = implode('', $f);
        $cc_on && ($f = str_replace('@#3', "\r", $f));
        // Fix "else ;" empty instructions
        $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else([\n}])'", '$1', $f);
        $r1 = array(
            // keywords with a direct object
'case',
            'delete',
            'do',
            'else',
            'function',
            'in',
            'instanceof',
            'of',
            'break',
            'new',
            'return',
            'throw',
            'typeof',
            'var',
            'void',
            'yield',
            'let',
            'if',
            'const',
            'get',
            'set',
            'continue',
        );
        $r2 = array(
            // keywords with a subject
'in',
            'instanceof',
            'of',
        );
        // Fix missing semi-colons
        $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\\$])', $r1) . ') (?!(' . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f);
        $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\\('", "\nif(", $f);
        $f = preg_replace("'(?<=--|\\+\\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n\$1", $f);
        $f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\\('", 'for each(', $f);
        $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f);
        // Merge strings
        if ($q["'"] > $q['"']) {
            $q = array(
                $q[1],
                $q[0],
            );
        }
        $f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f);
        strpos($f, $q[0] . '+' . $q[0]) && ($f = str_replace($q[0] . '+' . $q[0], '', $f));
        $len = count($strings);
        foreach ($strings as $r1 => &$r2) {
            $r2 = "/'" == substr($r1, -2) ? str_replace(array(
                "\\'",
                '\\"',
            ), array(
                "'",
                '"',
            ), $r2) : str_replace('\\' . $q[1], $q[1], $r2);
        }
        // Restore wanted spaces
        $f = strtr($f, "", ' ');
        return array(
            $f,
            $strings,
        );
    }
    protected function extractClosures($code) {
        $code = ';' . $code;
        $this->argFreq[-1] += substr_count($code, '}catch(');
        if ($this->argFreq[-1]) {
            // Special catch scope handling
            // FIXME: this implementation doesn't work with nested catch scopes who need
            // access to their parent's caught variable (but who needs that?).
            $f = preg_split("@}catch\\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
            $code = 'catch$scope$var' . mt_rand();
            $this->specialVarRx = $this->specialVarRx ? '(?:' . $this->specialVarRx . '|' . preg_quote($code) . ')' : preg_quote($code);
            $i = count($f) - 1;
            while ($i) {
                $c = 1;
                $j = 0;
                $l = strlen($f[$i]);
                while ($c && $j < $l) {
                    $s = $f[$i][$j++];
                    $c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
                }
                if (!$c) {
                    do {
                        $s = $f[$i][$j++];
                        $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
                    } while ($c && $j < $l);
                }
                $c = preg_quote($f[$i - 1], '#');
                $f[$i - 2] .= '}catch(' . preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1' . $code, $f[$i - 1] . substr($f[$i], 0, $j)) . substr($f[$i], $j);
                unset($f[$i--], $f[$i--]);
            }
            $code = $f[0];
        }
        $f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
        $i = count($f) - 1;
        $closures = array();
        while ($i) {
            $c = 1;
            $j = 0;
            $l = strlen($f[$i]);
            while ($c && $j < $l) {
                $s = $f[$i][$j++];
                $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
            }
            switch (substr($f[$i - 2], -1)) {
                default:
                    if (false !== ($c = strpos($f[$i - 1], ' ', 8))) {
                        break;
                    }
                case false:
                case "\n":
                case ';':
                case '{':
                case '}':
                case ')':
                case ']':
                    $c = strpos($f[$i - 1], '(', 4);
            }
            $l = "//''\"\"#{$i}'";
            $code = substr($f[$i - 1], $c);
            $closures[$l] = $code . substr($f[$i], 0, $j);
            $f[$i - 2] .= substr($f[$i - 1], 0, $c) . $l . substr($f[$i], $j);
            if ('(){' !== $code) {
                $j = substr_count($code, ',');
                do {
                    isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : ($this->argFreq[$j] = 1);
                } while ($j--);
            }
            $i -= 2;
        }
        return array(
            $f[0],
            $closures,
        );
    }
    protected function makeVars($closure, &$tree, $key) {
        $tree['code'] =& $closure;
        $tree['nfe'] = false;
        $tree['used'] = array();
        $tree['local'] = array();
        // Replace multiple "var" declarations by a single one
        $closure = preg_replace_callback("'(?<=[\n\\{\\}])var [^\n\\{\\};]+(?:\nvar [^\n\\{\\};]+)+'", array(
            $this,
            'mergeVarDeclarations',
        ), $closure);
        // Get all local vars (functions, arguments and "var" prefixed)
        $vars =& $tree['local'];
        if (preg_match("'^( [^(]*)?\\((.*?)\\)\\{'", $closure, $v)) {
            if ($v[1]) {
                $vars[$tree['nfe'] = substr($v[1], 1)] = -1;
                $tree['parent']['local'][';' . $key] =& $vars[$tree['nfe']];
            }
            if ($v[2]) {
                $i = 0;
                $v = explode(',', $v[2]);
                foreach ($v as $w) {
                    $vars[$w] = $this->argFreq[$i++] - 1;
                    // Give a bonus to argument variables
                }
            }
        }
        $v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
        if ($i = count($v) - 1) {
            $w = array();
            while ($i) {
                $j = $c = 0;
                $l = strlen($v[$i]);
                while ($j < $l) {
                    switch ($v[$i][$j]) {
                        case '(':
                        case '[':
                        case '{':
                            ++$c;
                            break;
                        case ')':
                        case ']':
                        case '}':
                            if ($c-- <= 0) {
                                break 2;
                            }
                            break;
                        case ';':
                        case "\n":
                            if (!$c) {
                                break 2;
                            }
                        default:
                            $c || ($w[] = $v[$i][$j]);
                    }
                    ++$j;
                }
                $w[] = ',';
                --$i;
            }
            $v = explode(',', implode('', $w));
            foreach ($v as $w) {
                if (preg_match("'^{$this->varRx}'", $w, $v)) {
                    isset($vars[$v[0]]) || ($vars[$v[0]] = 0);
                }
            }
        }
        if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) {
            foreach ($v[1] as $w) {
                isset($vars[$w]) || ($vars[$w] = 0);
            }
        }
        if ($this->argFreq[-1] && preg_match_all("@}catch\\(({$this->varRx})@", $closure, $v)) {
            $v[0] = array();
            foreach ($v[1] as $w) {
                isset($v[0][$w]) ? ++$v[0][$w] : ($v[0][$w] = 1);
            }
            foreach ($v[0] as $w => $v) {
                $vars[$w] = $this->argFreq[-1] - $v;
            }
        }
        // Get all used vars, local and non-local
        $vars =& $tree['used'];
        if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) {
            foreach ($w as $k) {
                if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) {
                    if (':' === $k[3]) {
                        $k = '.' . $k[2];
                    }
                    elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) {
                        ++$this->charFreq[ord($k[1][1])];
                        // "g" or "s"
                        ++$this->charFreq[101];
                        // "e"
                        ++$this->charFreq[116];
                        // "t"
                        $k = '.' . $k[2];
                    }
                    else {
                        $k = $k[2];
                    }
                }
                else {
                    $k = $k[1] . $k[2];
                }
                isset($vars[$k]) ? ++$vars[$k] : ($vars[$k] = 1);
            }
        }
        if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) {
            foreach ($w[0] as $a) {
                $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx ? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) : array(
                    $this->strings[$a],
                );
                $a = count($v);
                for ($i = 0; $i < $a; ++$i) {
                    $k = $v[$i];
                    if (1 === $i % 2) {
                        if (',' === $k[0] || '{' === $k[0]) {
                            if (':' === substr($k, -1)) {
                                $k = '.' . substr($k, 1, -1);
                            }
                            elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) {
                                ++$this->charFreq[ord($k[1])];
                                // "g" or "s"
                                ++$this->charFreq[101];
                                // "e"
                                ++$this->charFreq[116];
                                // "t"
                                $k = '.' . substr($k, 5);
                            }
                            else {
                                $k = substr($k, 1);
                            }
                        }
                        elseif (':' === substr($k, -1)) {
                            $k = substr($k, 0, -1);
                        }
                        $w =& $tree;
                        while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) {
                            $w =& $w['parent'];
                        }
                        (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : ($vars[$k] = 1));
                        unset($w);
                    }
                    if (0 === $i % 2 || !isset($vars[$k])) {
                        foreach (count_chars($v[$i], 1) as $k => $w) {
                            $this->charFreq[$k] += $w;
                        }
                    }
                }
            }
        }
        // Propagate the usage number to parents
        foreach ($vars as $w => $a) {
            $k =& $tree;
            $chain = array();
            do {
                $vars =& $k['local'];
                $chain[] =& $k;
                if (isset($vars[$w])) {
                    unset($k['used'][$w]);
                    if (isset($vars[$w])) {
                        $vars[$w] += $a;
                    }
                    else {
                        $vars[$w] = $a;
                    }
                    $a = false;
                    break;
                }
            } while ($k['parent'] && ($k =& $k['parent']));
            if ($a && !$k['parent']) {
                if (isset($vars[$w])) {
                    $vars[$w] += $a;
                }
                else {
                    $vars[$w] = $a;
                }
            }
            if (isset($tree['used'][$w]) && isset($vars[$w])) {
                foreach ($chain as &$b) {
                    isset($b['local'][$w]) || ($b['used'][$w] =& $vars[$w]);
                }
            }
        }
        // Analyse children
        $tree['children'] = array();
        $vars =& $tree['children'];
        if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) {
            foreach ($w[0] as $a) {
                $vars[$a] = array(
                    'parent' => &$tree,
                );
                $this->makeVars($this->closures[$a], $vars[$a], $a);
            }
        }
    }
    protected function mergeVarDeclarations($m) {
        return str_replace("\nvar ", ',', $m[0]);
    }
    protected function renameVars(&$tree, $root) {
        if ($root) {
            $tree['local'] += $tree['used'];
            $tree['used'] = array();
            foreach ($tree['local'] as $k => $v) {
                if ('.' == $k[0]) {
                    $k = substr($k, 1);
                }
                if ('true' === $k) {
                    $this->charFreq[48] += $v;
                }
                elseif ('false' === $k) {
                    $this->charFreq[49] += $v;
                }
                elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}\$#", $k)) {
                    foreach (count_chars($k, 1) as $k => $w) {
                        $this->charFreq[$k] += $w * $v;
                    }
                }
                elseif (2 == strlen($k)) {
                    $tree['used'][] = $k[1];
                }
            }
            $this->charFreq = $this->rsort($this->charFreq);
            $this->str0 = '';
            $this->str1 = '';
            foreach ($this->charFreq as $k => $v) {
                if (!$v) {
                    break;
                }
                $v = chr($k);
                if (64 < $k && $k < 91 || 96 < $k && $k < 123) {
                    // A-Z a-z
                    $this->str0 .= $v;
                    $this->str1 .= $v;
                }
                elseif (47 < $k && $k < 58) {
                    // 0-9
                    $this->str1 .= $v;
                }
            }
            if ('' === $this->str0) {
                $this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
                $this->str1 = $this->str0 . '0123456789';
            }
            foreach ($tree['local'] as $var => $root) {
                if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) {
                    $tree['local'][$var] += $tree['local'][".{$var}"];
                }
            }
            foreach ($tree['local'] as $var => $root) {
                if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) {
                    $tree['local'][$var] = $tree['local'][substr($var, 1)];
                }
            }
            $tree['local'] = $this->rsort($tree['local']);
            foreach ($tree['local'] as $var => $root) {
                switch (substr($var, 0, 1)) {
                    case '.':
                        if (!isset($tree['local'][substr($var, 1)])) {
                            $tree['local'][$var] = '#' . ($this->specialVarRx && 3 < strlen($var) && preg_match("'^\\.{$this->specialVarRx}\$'", $var) ? $this->getNextName($tree) . '$' : substr($var, 1));
                        }
                        break;
                    case ';':
                        $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
                    case '#':
                        break;
                    default:
                        $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}\$'", $var) ? $this->getNextName($tree) . '$' : $var;
                        $tree['local'][$var] = $root;
                        if (isset($tree['local'][".{$var}"])) {
                            $tree['local'][".{$var}"] = '#' . $root;
                        }
                }
            }
            foreach ($tree['local'] as $var => $root) {
                $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
            }
        }
        else {
            $tree['local'] = $this->rsort($tree['local']);
            if (false !== $tree['nfe']) {
                $tree['used'][] = $tree['local'][$tree['nfe']];
            }
            foreach ($tree['local'] as $var => $root) {
                if ($tree['nfe'] !== $var) {
                    $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
                }
            }
        }
        $this->local_tree =& $tree['local'];
        $this->used_tree =& $tree['used'];
        $tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array(
            $this,
            'getNewName',
        ), $tree['code']);
        if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) {
            foreach ($b[0] as $a) {
                $this->strings[$a] = preg_replace_callback("#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", array(
                    $this,
                    'getNewName',
                ), $this->strings[$a]);
            }
        }
        foreach ($tree['children'] as $a => &$b) {
            $this->renameVars($b, false);
            $tree['code'] = str_replace($a, $b['code'], $tree['code']);
            unset($tree['children'][$a]);
        }
    }
    protected function getNewName($m) {
        $m = $m[0];
        $pre = '.' === $m[0] ? '.' : '';
        $post = '';
        if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) {
            $pre = $m[0];
            if (':' === substr($m, -1)) {
                $post = ':';
                $m = (' ' !== $m[0] ? '.' : '') . substr($m, 1, -1);
            }
            elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) {
                $pre .= substr($m, 1, 4);
                $m = '.' . substr($m, 5);
            }
            else {
                $m = substr($m, 1);
            }
        }
        elseif (':' === substr($m, -1)) {
            $post = ':';
            $m = substr($m, 0, -1);
        }
        $post = (isset($this->reserved[$m]) ? 'true' === $m ? '!0' : ('false' === $m ? '!1' : $m) : (isset($this->local_tree[$m]) ? $this->local_tree[$m] : (isset($this->used_tree[$m]) ? $this->used_tree[$m] : $m))) . $post;
        return '' === $post ? '' : $pre . ('.' === $post[0] ? substr($post, 1) : $post);
    }
    protected function getNextName(&$tree = array(), &$counter = false) {
        if (false === $counter) {
            $counter =& $tree['counter'];
            isset($counter) || ($counter = -1);
            $exclude = array_flip($tree['used']);
        }
        else {
            $exclude = $tree;
        }
        ++$counter;
        $len0 = strlen($this->str0);
        $len1 = strlen($this->str0);
        $name = $this->str0[$counter % $len0];
        $i = intval($counter / $len0) - 1;
        while ($i >= 0) {
            $name .= $this->str1[$i % $len1];
            $i = intval($i / $len1) - 1;
        }
        return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
    }
    protected function restoreCc(&$s, $lf = true) {
        $lf && ($s = str_replace('@#3', '', $s));
        $s = str_replace('@#1', '@*/', $s);
        $s = str_replace('2#@', '//@', $s);
        $s = str_replace('1#@', '/*@', $s);
        $s = str_replace('##', '#', $s);
    }
    protected function restoreString($m) {
        return $this->strings[$m[0]];
    }
    private function rsort($array) {
        if (!$array) {
            return $array;
        }
        $i = 0;
        $tuples = array();
        foreach ($array as $k => &$v) {
            $tuples[] = array(
                ++$i,
                $k,
                &$v,
            );
        }
        usort($tuples, function ($a, $b) {
            if ($b[2] > $a[2]) {
                return 1;
            }
            if ($b[2] < $a[2]) {
                return -1;
            }
            if ($b[0] > $a[0]) {
                return -1;
            }
            if ($b[0] < $a[0]) {
                return 1;
            }
            return 0;
        });
        $array = array();
        foreach ($tuples as $t) {
            $array[$t[1]] =& $t[2];
        }
        return $array;
    }

}

Members

Titre Trier par ordre décroissant Modifiers Object type Résumé
JSqueeze::$charFreq public property
JSqueeze::$strings protected property
JSqueeze::extractClosures protected function
JSqueeze::extractStrings protected function
JSqueeze::getNewName protected function
JSqueeze::getNextName protected function
JSqueeze::makeVars protected function
JSqueeze::mergeVarDeclarations protected function
JSqueeze::renameVars protected function
JSqueeze::restoreCc protected function
JSqueeze::restoreString protected function
JSqueeze::rsort private function
JSqueeze::SPECIAL_VAR_PACKER constant
JSqueeze::squeeze public function Squeezes a JavaScript source code.
JSqueeze::__construct public function