Followup r83885: implement maximum line length and statement termination (each statem...
authorRoan Kattouw <catrope@users.mediawiki.org>
Mon, 14 Mar 2011 13:24:30 +0000 (13:24 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Mon, 14 Mar 2011 13:24:30 +0000 (13:24 +0000)
includes/DefaultSettings.php
includes/libs/JavaScriptMinifier.php
includes/resourceloader/ResourceLoader.php
maintenance/minify.php

index 6aff1d6..74d1b64 100644 (file)
@@ -2505,10 +2505,17 @@ $wgResourceLoaderDebug = false;
 $wgResourceLoaderUseESI = false;
 
 /**
- * Enable removal of some of the vertical whitespace (like \r and \n) from
- * JavaScript code when minifying.
+ * Put each statement on its own line when minifying JavaScript. This makes
+ * debugging in non-debug mode a bit easier.
  */
-$wgResourceLoaderMinifyJSVerticalSpace = false;
+$wgResourceLoaderMinifierStatementsOnOwnLine = false;
+
+/**
+ * Maximum line length when minifying JavaScript. This is not a hard maximum:
+ * the minifier will try not to produce lines longer than this, but may be
+ * forced to do so in certain cases.
+ */
+$wgResourceLoaderMinifierMaxLineLength = 1000;
 
 /**
  * Whether to include the mediawiki.legacy JS library (old wikibits.js), and its
index 4c7256d..710ebe3 100644 (file)
@@ -66,10 +66,16 @@ class JavaScriptMinifier {
        /**
         * Returns minified JavaScript code.
         *
+        * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when
+        *       literals (e.g. quoted strings) longer than $maxLineLength are encountered
+        *       or when required to guard against semicolon insertion.
+        *
         * @param $s String JavaScript code to minify
+        * @param $statementsOnOwnLine Bool Whether to put each statement on its own line
+        * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum.
         * @return String Minified code
         */
-       public static function minify( $s ) {
+       public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) {
                // First we declare a few tables that contain our parsing rules
 
                // $opChars : characters, which can be combined without whitespace in between them
@@ -377,6 +383,23 @@ class JavaScriptMinifier {
                                self::TYPE_LITERAL    => true
                        )
                );
+               
+               // Rules for when newlines should be inserted if
+               // $statementsOnOwnLine is enabled.
+               // $newlineBefore is checked before switching state,
+               // $newlineAfter is checked after
+               $newlineBefore = array(
+                       self::STATEMENT => array(
+                               self::TYPE_BRACE_CLOSE => true,
+                       ),
+               );
+               $newlineAfter = array(
+                       self::STATEMENT => array(
+                               self::TYPE_BRACE_OPEN => true,
+                               self::TYPE_PAREN_CLOSE => true,
+                               self::TYPE_SEMICOLON => true,
+                       ),
+               );
 
                // $divStates : Contains all states that can be followed by a division operator
                $divStates = array(
@@ -391,6 +414,7 @@ class JavaScriptMinifier {
                $out = '';
                $pos = 0;
                $length = strlen( $s );
+               $lineLength = 0;
                $newlineFound = true;
                $state = self::STATEMENT;
                $stack = array();
@@ -492,7 +516,7 @@ class JavaScriptMinifier {
                        }
 
                        // Now get the token type from our type array
-                       $token = substr( $s, $pos, $end - $pos );
+                       $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token )
                        $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL;
 
                        if( $newlineFound && isset( $semicolon[$state][$type] ) ) {
@@ -500,20 +524,38 @@ class JavaScriptMinifier {
                                // could add the ; token here ourselves, keeping the newline has a few advantages.
                                $out .= "\n";
                                $state = self::STATEMENT;
-                       } elseif( false /* Put your newline condition here */ ) {
+                               $lineLength = 0;
+                       } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength &&
+                                       !isset( $semicolon[$state][$type] ) )
+                       {
+                               // This line would get too long if we added $token, so add a newline first.
+                               // Only do this if it won't trigger semicolon insertion though.
                                $out .= "\n";
+                               $lineLength = 0;
                        // Check, whether we have to separate the token from the last one with whitespace
                        } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) {
                                $out .= ' ';
+                               $lineLength++;
                        // Don't accidentally create ++, -- or // tokens
                        } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) {
                                $out .= ' ';
+                               $lineLength++;
                        }
                        
                        $out .= $token;
+                       $lineLength += $end - $pos; // += strlen( $token )
                        $last = $s[$end - 1];
                        $pos = $end;
                        $newlineFound = false;
+                       
+                       // Output a newline after the token if required
+                       // This is checked before AND after switching state
+                       $newlineAdded = false;
+                       if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) {
+                               $out .= "\n";
+                               $lineLength = 0;
+                               $newlineAdded = true;
+                       }
 
                        // Now that we have output our token, transition into the new state.
                        if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) {
@@ -524,6 +566,12 @@ class JavaScriptMinifier {
                        } elseif( isset( $goto[$state][$type] ) ) {
                                $state = $goto[$state][$type];
                        }
+                       
+                       // Check for newline insertion again
+                       if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) {
+                               $out .= "\n";
+                               $lineLength = 0;
+                       }
                }
                return $out;
        }
index e376b6a..8903e5b 100644 (file)
@@ -121,6 +121,7 @@ class ResourceLoader {
         * @return String: Filtered data, or a comment containing an error message
         */
        protected function filter( $filter, $data ) {
+               global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
                wfProfileIn( __METHOD__ );
 
                // For empty/whitespace-only data or for unknown filters, don't perform 
@@ -147,7 +148,10 @@ class ResourceLoader {
                try {
                        switch ( $filter ) {
                                case 'minify-js':
-                                       $result = JavaScriptMinifier::minify( $data );
+                                       $result = JavaScriptMinifier::minify( $data,
+                                               $wgResourceLoaderMinifierStatementsOnOwnLine,
+                                               $wgResourceLoaderMinifierMaxLineLength
+                                       );
                                        $result .= "\n\n/* cache key: $key */\n";
                                        break;
                                case 'minify-css':
index de9c53a..c50f941 100644 (file)
@@ -35,8 +35,11 @@ class MinifyScript extends Maintenance {
                        "Directory for output. If this is not specified, and neither is --outfile, then the\n" .
                        "output files will be sent to the same directories as the input files.",
                        false, true );
-               $this->addOption( 'minify-vertical-space',
-                       "Boolean value for minifying the vertical space for javascript.",
+               $this->addOption( 'js-statements-on-own-line',
+                       "Boolean value for putting statements on their own line when minifying JavaScript.",
+                       false, true );
+               $this->addOption( 'js-max-line-length',
+                       "Maximum line length for JavaScript minification.",
                        false, true );
                $this->mDescription = "Minify a file or set of files.\n\n" .
                        "If --outfile is not specified, then the output file names will have a .min extension\n" .
@@ -99,7 +102,7 @@ class MinifyScript extends Maintenance {
        }
 
        public function minify( $inPath, $outPath ) {
-               global $wgResourceLoaderMinifyJSVerticalSpace;
+               global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
 
                $extension = $this->getExtension( $inPath );
                $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' );
@@ -117,7 +120,10 @@ class MinifyScript extends Maintenance {
 
                switch ( $extension ) {
                        case 'js':
-                               $outText = JavaScriptDistiller::stripWhiteSpace( $inText, $this->getOption( 'minify-vertical-space', $wgResourceLoaderMinifyJSVerticalSpace ) );
+                               $outText = JavaScriptMinifier::minify( $inText,
+                                       $this->getOption( 'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ),
+                                       $this->getOption( 'js-max-line-length', $wgResourceLoaderMinifierMaxLineLength )
+                               );
                                break;
                        case 'css':
                                $outText = CSSMin::minify( $inText );