All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uglify-1.3.3.lib.consolidator.js Maven / Gradle / Ivy

The newest version!
/**
 * @preserve Copyright 2012 Robert Gust-Bardon .
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/**
 * @fileoverview Enhances UglifyJS with consolidation of null, Boolean, and String values.
 * 

Also known as aliasing, this feature has been deprecated in the Closure Compiler since its * initial release, where it is unavailable from the CLI. The Closure Compiler allows one to log and * influence this process. In contrast, this implementation does not introduce * any variable declarations in global code and derives String values from * identifier names used as property accessors.

*

Consolidating literals may worsen the data compression ratio when an encoding * transformation is applied. For instance, jQuery 1.7.1 takes 248235 bytes. * Building it with * UglifyJS v1.2.5 results in 93647 bytes (37.73% of the original) which are * then compressed to 33154 bytes (13.36% of the original) using gzip(1). Building it with the same * version of UglifyJS 1.2.5 patched with the implementation of consolidation * results in 80784 bytes (a decrease of 12863 bytes, i.e. 13.74%, in comparison * to the aforementioned 93647 bytes) which are then compressed to 34013 bytes * (an increase of 859 bytes, i.e. 2.59%, in comparison to the aforementioned * 33154 bytes).

*

Written in the strict variant * of ECMA-262 5.1 Edition. Encoded in UTF-8. Follows Revision 2.28 of the Google JavaScript Style Guide (except for the * discouraged use of the {@code function} tag and the {@code namespace} tag). * 100% typed for the Closure Compiler Version 1741.

*

Should you find this software useful, please consider a donation.

* @author follow.me@RGustBardon (Robert Gust-Bardon) * @supported Tested with: * */ /*global console:false, exports:true, module:false, require:false */ /*jshint sub:true */ /** * Consolidates null, Boolean, and String values found inside an AST. * @param {!TSyntacticCodeUnit} oAbstractSyntaxTree An array-like object * representing an AST. * @return {!TSyntacticCodeUnit} An array-like object representing an AST with its null, Boolean, and * String values consolidated. */ // TODO(user) Consolidation of mathematical values found in numeric literals. // TODO(user) Unconsolidation. // TODO(user) Consolidation of ECMA-262 6th Edition programs. // TODO(user) Rewrite in ECMA-262 6th Edition. exports['ast_consolidate'] = function(oAbstractSyntaxTree) { 'use strict'; /*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, immed:true, latedef:true, newcap:true, noarge:true, noempty:true, nonew:true, onevar:true, plusplus:true, regexp:true, undef:true, strict:true, sub:false, trailing:true */ var _, /** * A record consisting of data about one or more source elements. * @constructor * @nosideeffects */ TSourceElementsData = function() { /** * The category of the elements. * @type {number} * @see ESourceElementCategories */ this.nCategory = ESourceElementCategories.N_OTHER; /** * The number of occurrences (within the elements) of each primitive * value that could be consolidated. * @type {!Array.>} */ this.aCount = []; this.aCount[EPrimaryExpressionCategories.N_IDENTIFIER_NAMES] = {}; this.aCount[EPrimaryExpressionCategories.N_STRING_LITERALS] = {}; this.aCount[EPrimaryExpressionCategories.N_NULL_AND_BOOLEAN_LITERALS] = {}; /** * Identifier names found within the elements. * @type {!Array.} */ this.aIdentifiers = []; /** * Prefixed representation Strings of each primitive value that could be * consolidated within the elements. * @type {!Array.} */ this.aPrimitiveValues = []; }, /** * A record consisting of data about a primitive value that could be * consolidated. * @constructor * @nosideeffects */ TPrimitiveValue = function() { /** * The difference in the number of terminal symbols between the original * source text and the one with the primitive value consolidated. If the * difference is positive, the primitive value is considered worthwhile. * @type {number} */ this.nSaving = 0; /** * An identifier name of the variable that will be declared and assigned * the primitive value if the primitive value is consolidated. * @type {string} */ this.sName = ''; }, /** * A record consisting of data on what to consolidate within the range of * source elements that is currently being considered. * @constructor * @nosideeffects */ TSolution = function() { /** * An object whose keys are prefixed representation Strings of each * primitive value that could be consolidated within the elements and * whose values are corresponding data about those primitive values. * @type {!Object.} * @see TPrimitiveValue */ this.oPrimitiveValues = {}; /** * The difference in the number of terminal symbols between the original * source text and the one with all the worthwhile primitive values * consolidated. * @type {number} * @see TPrimitiveValue#nSaving */ this.nSavings = 0; }, /** * The processor of ASTs found * in UglifyJS. * @namespace * @type {!TProcessor} */ oProcessor = (/** @type {!TProcessor} */ require('./process')), /** * A record consisting of a number of constants that represent the * difference in the number of terminal symbols between a source text with * a modified syntactic code unit and the original one. * @namespace * @type {!Object.} */ oWeights = { /** * The difference in the number of punctuators required by the bracket * notation and the dot notation. *

'[]'.length - '.'.length

* @const * @type {number} */ N_PROPERTY_ACCESSOR: 1, /** * The number of punctuators required by a variable declaration with an * initialiser. *

':'.length + ';'.length

* @const * @type {number} */ N_VARIABLE_DECLARATION: 2, /** * The number of terminal symbols required to introduce a variable * statement (excluding its variable declaration list). *

'var '.length

* @const * @type {number} */ N_VARIABLE_STATEMENT_AFFIXATION: 4, /** * The number of terminal symbols needed to enclose source elements * within a function call with no argument values to a function with an * empty parameter list. *

'(function(){}());'.length

* @const * @type {number} */ N_CLOSURE: 17 }, /** * Categories of primary expressions from which primitive values that * could be consolidated are derivable. * @namespace * @enum {number} */ EPrimaryExpressionCategories = { /** * Identifier names used as property accessors. * @type {number} */ N_IDENTIFIER_NAMES: 0, /** * String literals. * @type {number} */ N_STRING_LITERALS: 1, /** * Null and Boolean literals. * @type {number} */ N_NULL_AND_BOOLEAN_LITERALS: 2 }, /** * Prefixes of primitive values that could be consolidated. * The String values of the prefixes must have same number of characters. * The prefixes must not be used in any properties defined in any version * of ECMA-262. * @namespace * @enum {string} */ EValuePrefixes = { /** * Identifies String values. * @type {string} */ S_STRING: '#S', /** * Identifies null and Boolean values. * @type {string} */ S_SYMBOLIC: '#O' }, /** * Categories of source elements in terms of their appropriateness of * having their primitive values consolidated. * @namespace * @enum {number} */ ESourceElementCategories = { /** * Identifies a source element that includes the {@code with} statement. * @type {number} */ N_WITH: 0, /** * Identifies a source element that includes the {@code eval} identifier name. * @type {number} */ N_EVAL: 1, /** * Identifies a source element that must be excluded from the process * unless its whole scope is examined. * @type {number} */ N_EXCLUDABLE: 2, /** * Identifies source elements not posing any problems. * @type {number} */ N_OTHER: 3 }, /** * The list of literals (other than the String ones) whose primitive * values can be consolidated. * @const * @type {!Array.} */ A_OTHER_SUBSTITUTABLE_LITERALS = [ 'null', // The null literal. 'false', // The Boolean literal {@code false}. 'true' // The Boolean literal {@code true}. ]; (/** * Consolidates all worthwhile primitive values in a syntactic code unit. * @param {!TSyntacticCodeUnit} oSyntacticCodeUnit An array-like object * representing the branch of the abstract syntax tree representing the * syntactic code unit along with its scope. * @see TPrimitiveValue#nSaving */ function fExamineSyntacticCodeUnit(oSyntacticCodeUnit) { var _, /** * Indicates whether the syntactic code unit represents global code. * @type {boolean} */ bIsGlobal = 'toplevel' === oSyntacticCodeUnit[0], /** * Indicates whether the whole scope is being examined. * @type {boolean} */ bIsWhollyExaminable = !bIsGlobal, /** * An array-like object representing source elements that constitute a * syntactic code unit. * @type {!TSyntacticCodeUnit} */ oSourceElements, /** * A record consisting of data about the source element that is * currently being examined. * @type {!TSourceElementsData} */ oSourceElementData, /** * The scope of the syntactic code unit. * @type {!TScope} */ oScope, /** * An instance of an object that allows the traversal of an AST. * @type {!TWalker} */ oWalker, /** * An object encompassing collections of functions used during the * traversal of an AST. * @namespace * @type {!Object.>} */ oWalkers = { /** * A collection of functions used during the surveyance of source * elements. * @namespace * @type {!Object.} */ oSurveySourceElement: { /**#nocode+*/ // JsDoc Toolkit 2.4.0 hides some of the keys. /** * Classifies the source element as excludable if it does not * contain a {@code with} statement or the {@code eval} identifier * name. Adds the identifier of the function and its formal * parameters to the list of identifier names found. * @param {string} sIdentifier The identifier of the function. * @param {!Array.} aFormalParameterList Formal parameters. * @param {!TSyntacticCodeUnit} oFunctionBody Function code. */ 'defun': function( sIdentifier, aFormalParameterList, oFunctionBody) { fClassifyAsExcludable(); fAddIdentifier(sIdentifier); aFormalParameterList.forEach(fAddIdentifier); }, /** * Increments the count of the number of occurrences of the String * value that is equivalent to the sequence of terminal symbols * that constitute the encountered identifier name. * @param {!TSyntacticCodeUnit} oExpression The nonterminal * MemberExpression. * @param {string} sIdentifierName The identifier name used as the * property accessor. * @return {!Array} The encountered branch of an AST with its nonterminal * MemberExpression traversed. */ 'dot': function(oExpression, sIdentifierName) { fCountPrimaryExpression( EPrimaryExpressionCategories.N_IDENTIFIER_NAMES, EValuePrefixes.S_STRING + sIdentifierName); return ['dot', oWalker.walk(oExpression), sIdentifierName]; }, /** * Adds the optional identifier of the function and its formal * parameters to the list of identifier names found. * @param {?string} sIdentifier The optional identifier of the * function. * @param {!Array.} aFormalParameterList Formal parameters. * @param {!TSyntacticCodeUnit} oFunctionBody Function code. */ 'function': function( sIdentifier, aFormalParameterList, oFunctionBody) { if ('string' === typeof sIdentifier) { fAddIdentifier(sIdentifier); } aFormalParameterList.forEach(fAddIdentifier); }, /** * Either increments the count of the number of occurrences of the * encountered null or Boolean value or classifies a source element * as containing the {@code eval} identifier name. * @param {string} sIdentifier The identifier encountered. */ 'name': function(sIdentifier) { if (-1 !== A_OTHER_SUBSTITUTABLE_LITERALS.indexOf(sIdentifier)) { fCountPrimaryExpression( EPrimaryExpressionCategories.N_NULL_AND_BOOLEAN_LITERALS, EValuePrefixes.S_SYMBOLIC + sIdentifier); } else { if ('eval' === sIdentifier) { oSourceElementData.nCategory = ESourceElementCategories.N_EVAL; } fAddIdentifier(sIdentifier); } }, /** * Classifies the source element as excludable if it does not * contain a {@code with} statement or the {@code eval} identifier * name. * @param {TSyntacticCodeUnit} oExpression The expression whose * value is to be returned. */ 'return': function(oExpression) { fClassifyAsExcludable(); }, /** * Increments the count of the number of occurrences of the * encountered String value. * @param {string} sStringValue The String value of the string * literal encountered. */ 'string': function(sStringValue) { if (sStringValue.length > 0) { fCountPrimaryExpression( EPrimaryExpressionCategories.N_STRING_LITERALS, EValuePrefixes.S_STRING + sStringValue); } }, /** * Adds the identifier reserved for an exception to the list of * identifier names found. * @param {!TSyntacticCodeUnit} oTry A block of code in which an * exception can occur. * @param {Array} aCatch The identifier reserved for an exception * and a block of code to handle the exception. * @param {TSyntacticCodeUnit} oFinally An optional block of code * to be evaluated regardless of whether an exception occurs. */ 'try': function(oTry, aCatch, oFinally) { if (Array.isArray(aCatch)) { fAddIdentifier(aCatch[0]); } }, /** * Classifies the source element as excludable if it does not * contain a {@code with} statement or the {@code eval} identifier * name. Adds the identifier of each declared variable to the list * of identifier names found. * @param {!Array.} aVariableDeclarationList Variable * declarations. */ 'var': function(aVariableDeclarationList) { fClassifyAsExcludable(); aVariableDeclarationList.forEach(fAddVariable); }, /** * Classifies a source element as containing the {@code with} * statement. * @param {!TSyntacticCodeUnit} oExpression An expression whose * value is to be converted to a value of type Object and * become the binding object of a new object environment * record of a new lexical environment in which the statement * is to be executed. * @param {!TSyntacticCodeUnit} oStatement The statement to be * executed in the augmented lexical environment. * @return {!Array} An empty array to stop the traversal. */ 'with': function(oExpression, oStatement) { oSourceElementData.nCategory = ESourceElementCategories.N_WITH; return []; } /**#nocode-*/ // JsDoc Toolkit 2.4.0 hides some of the keys. }, /** * A collection of functions used while looking for nested functions. * @namespace * @type {!Object.} */ oExamineFunctions: { /**#nocode+*/ // JsDoc Toolkit 2.4.0 hides some of the keys. /** * Orders an examination of a nested function declaration. * @this {!TSyntacticCodeUnit} An array-like object representing * the branch of an AST representing the syntactic code unit along with * its scope. * @return {!Array} An empty array to stop the traversal. */ 'defun': function() { fExamineSyntacticCodeUnit(this); return []; }, /** * Orders an examination of a nested function expression. * @this {!TSyntacticCodeUnit} An array-like object representing * the branch of an AST representing the syntactic code unit along with * its scope. * @return {!Array} An empty array to stop the traversal. */ 'function': function() { fExamineSyntacticCodeUnit(this); return []; } /**#nocode-*/ // JsDoc Toolkit 2.4.0 hides some of the keys. } }, /** * Records containing data about source elements. * @type {Array.} */ aSourceElementsData = [], /** * The index (in the source text order) of the source element * immediately following a Directive Prologue. * @type {number} */ nAfterDirectivePrologue = 0, /** * The index (in the source text order) of the source element that is * currently being considered. * @type {number} */ nPosition, /** * The index (in the source text order) of the source element that is * the last element of the range of source elements that is currently * being considered. * @type {(undefined|number)} */ nTo, /** * Initiates the traversal of a source element. * @param {!TWalker} oWalker An instance of an object that allows the * traversal of an abstract syntax tree. * @param {!TSyntacticCodeUnit} oSourceElement A source element from * which the traversal should commence. * @return {function(): !TSyntacticCodeUnit} A function that is able to * initiate the traversal from a given source element. */ cContext = function(oWalker, oSourceElement) { /** * @return {!TSyntacticCodeUnit} A function that is able to * initiate the traversal from a given source element. */ var fLambda = function() { return oWalker.walk(oSourceElement); }; return fLambda; }, /** * Classifies the source element as excludable if it does not * contain a {@code with} statement or the {@code eval} identifier * name. */ fClassifyAsExcludable = function() { if (oSourceElementData.nCategory === ESourceElementCategories.N_OTHER) { oSourceElementData.nCategory = ESourceElementCategories.N_EXCLUDABLE; } }, /** * Adds an identifier to the list of identifier names found. * @param {string} sIdentifier The identifier to be added. */ fAddIdentifier = function(sIdentifier) { if (-1 === oSourceElementData.aIdentifiers.indexOf(sIdentifier)) { oSourceElementData.aIdentifiers.push(sIdentifier); } }, /** * Adds the identifier of a variable to the list of identifier names * found. * @param {!Array} aVariableDeclaration A variable declaration. */ fAddVariable = function(aVariableDeclaration) { fAddIdentifier(/** @type {string} */ aVariableDeclaration[0]); }, /** * Increments the count of the number of occurrences of the prefixed * String representation attributed to the primary expression. * @param {number} nCategory The category of the primary expression. * @param {string} sName The prefixed String representation attributed * to the primary expression. */ fCountPrimaryExpression = function(nCategory, sName) { if (!oSourceElementData.aCount[nCategory].hasOwnProperty(sName)) { oSourceElementData.aCount[nCategory][sName] = 0; if (-1 === oSourceElementData.aPrimitiveValues.indexOf(sName)) { oSourceElementData.aPrimitiveValues.push(sName); } } oSourceElementData.aCount[nCategory][sName] += 1; }, /** * Consolidates all worthwhile primitive values in a range of source * elements. * @param {number} nFrom The index (in the source text order) of the * source element that is the first element of the range. * @param {number} nTo The index (in the source text order) of the * source element that is the last element of the range. * @param {boolean} bEnclose Indicates whether the range should be * enclosed within a function call with no argument values to a * function with an empty parameter list if any primitive values * are consolidated. * @see TPrimitiveValue#nSaving */ fExamineSourceElements = function(nFrom, nTo, bEnclose) { var _, /** * The index of the last mangled name. * @type {number} */ nIndex = oScope.cname, /** * The index of the source element that is currently being * considered. * @type {number} */ nPosition, /** * A collection of functions used during the consolidation of * primitive values and identifier names used as property * accessors. * @namespace * @type {!Object.} */ oWalkersTransformers = { /** * If the String value that is equivalent to the sequence of * terminal symbols that constitute the encountered identifier * name is worthwhile, a syntactic conversion from the dot * notation to the bracket notation ensues with that sequence * being substituted by an identifier name to which the value * is assigned. * Applies to property accessors that use the dot notation. * @param {!TSyntacticCodeUnit} oExpression The nonterminal * MemberExpression. * @param {string} sIdentifierName The identifier name used as * the property accessor. * @return {!Array} A syntactic code unit that is equivalent to * the one encountered. * @see TPrimitiveValue#nSaving */ 'dot': function(oExpression, sIdentifierName) { /** * The prefixed String value that is equivalent to the * sequence of terminal symbols that constitute the * encountered identifier name. * @type {string} */ var sPrefixed = EValuePrefixes.S_STRING + sIdentifierName; return oSolutionBest.oPrimitiveValues.hasOwnProperty( sPrefixed) && oSolutionBest.oPrimitiveValues[sPrefixed].nSaving > 0 ? ['sub', oWalker.walk(oExpression), ['name', oSolutionBest.oPrimitiveValues[sPrefixed].sName]] : ['dot', oWalker.walk(oExpression), sIdentifierName]; }, /** * If the encountered identifier is a null or Boolean literal * and its value is worthwhile, the identifier is substituted * by an identifier name to which that value is assigned. * Applies to identifier names. * @param {string} sIdentifier The identifier encountered. * @return {!Array} A syntactic code unit that is equivalent to * the one encountered. * @see TPrimitiveValue#nSaving */ 'name': function(sIdentifier) { /** * The prefixed representation String of the identifier. * @type {string} */ var sPrefixed = EValuePrefixes.S_SYMBOLIC + sIdentifier; return [ 'name', oSolutionBest.oPrimitiveValues.hasOwnProperty(sPrefixed) && oSolutionBest.oPrimitiveValues[sPrefixed].nSaving > 0 ? oSolutionBest.oPrimitiveValues[sPrefixed].sName : sIdentifier ]; }, /** * If the encountered String value is worthwhile, it is * substituted by an identifier name to which that value is * assigned. * Applies to String values. * @param {string} sStringValue The String value of the string * literal encountered. * @return {!Array} A syntactic code unit that is equivalent to * the one encountered. * @see TPrimitiveValue#nSaving */ 'string': function(sStringValue) { /** * The prefixed representation String of the primitive value * of the literal. * @type {string} */ var sPrefixed = EValuePrefixes.S_STRING + sStringValue; return oSolutionBest.oPrimitiveValues.hasOwnProperty( sPrefixed) && oSolutionBest.oPrimitiveValues[sPrefixed].nSaving > 0 ? ['name', oSolutionBest.oPrimitiveValues[sPrefixed].sName] : ['string', sStringValue]; } }, /** * Such data on what to consolidate within the range of source * elements that is currently being considered that lead to the * greatest known reduction of the number of the terminal symbols * in comparison to the original source text. * @type {!TSolution} */ oSolutionBest = new TSolution(), /** * Data representing an ongoing attempt to find a better * reduction of the number of the terminal symbols in comparison * to the original source text than the best one that is * currently known. * @type {!TSolution} * @see oSolutionBest */ oSolutionCandidate = new TSolution(), /** * A record consisting of data about the range of source elements * that is currently being examined. * @type {!TSourceElementsData} */ oSourceElementsData = new TSourceElementsData(), /** * Variable declarations for each primitive value that is to be * consolidated within the elements. * @type {!Array.} */ aVariableDeclarations = [], /** * Augments a list with a prefixed representation String. * @param {!Array.} aList A list that is to be augmented. * @return {function(string)} A function that augments a list * with a prefixed representation String. */ cAugmentList = function(aList) { /** * @param {string} sPrefixed Prefixed representation String of * a primitive value that could be consolidated within the * elements. */ var fLambda = function(sPrefixed) { if (-1 === aList.indexOf(sPrefixed)) { aList.push(sPrefixed); } }; return fLambda; }, /** * Adds the number of occurrences of a primitive value of a given * category that could be consolidated in the source element with * a given index to the count of occurrences of that primitive * value within the range of source elements that is currently * being considered. * @param {number} nPosition The index (in the source text order) * of a source element. * @param {number} nCategory The category of the primary * expression from which the primitive value is derived. * @return {function(string)} A function that performs the * addition. * @see cAddOccurrencesInCategory */ cAddOccurrences = function(nPosition, nCategory) { /** * @param {string} sPrefixed The prefixed representation String * of a primitive value. */ var fLambda = function(sPrefixed) { if (!oSourceElementsData.aCount[nCategory].hasOwnProperty( sPrefixed)) { oSourceElementsData.aCount[nCategory][sPrefixed] = 0; } oSourceElementsData.aCount[nCategory][sPrefixed] += aSourceElementsData[nPosition].aCount[nCategory][ sPrefixed]; }; return fLambda; }, /** * Adds the number of occurrences of each primitive value of a * given category that could be consolidated in the source * element with a given index to the count of occurrences of that * primitive values within the range of source elements that is * currently being considered. * @param {number} nPosition The index (in the source text order) * of a source element. * @return {function(number)} A function that performs the * addition. * @see fAddOccurrences */ cAddOccurrencesInCategory = function(nPosition) { /** * @param {number} nCategory The category of the primary * expression from which the primitive value is derived. */ var fLambda = function(nCategory) { Object.keys( aSourceElementsData[nPosition].aCount[nCategory] ).forEach(cAddOccurrences(nPosition, nCategory)); }; return fLambda; }, /** * Adds the number of occurrences of each primitive value that * could be consolidated in the source element with a given index * to the count of occurrences of that primitive values within * the range of source elements that is currently being * considered. * @param {number} nPosition The index (in the source text order) * of a source element. */ fAddOccurrences = function(nPosition) { Object.keys(aSourceElementsData[nPosition].aCount).forEach( cAddOccurrencesInCategory(nPosition)); }, /** * Creates a variable declaration for a primitive value if that * primitive value is to be consolidated within the elements. * @param {string} sPrefixed Prefixed representation String of a * primitive value that could be consolidated within the * elements. * @see aVariableDeclarations */ cAugmentVariableDeclarations = function(sPrefixed) { if (oSolutionBest.oPrimitiveValues[sPrefixed].nSaving > 0) { aVariableDeclarations.push([ oSolutionBest.oPrimitiveValues[sPrefixed].sName, [0 === sPrefixed.indexOf(EValuePrefixes.S_SYMBOLIC) ? 'name' : 'string', sPrefixed.substring(EValuePrefixes.S_SYMBOLIC.length)] ]); } }, /** * Sorts primitive values with regard to the difference in the * number of terminal symbols between the original source text * and the one with those primitive values consolidated. * @param {string} sPrefixed0 The prefixed representation String * of the first of the two primitive values that are being * compared. * @param {string} sPrefixed1 The prefixed representation String * of the second of the two primitive values that are being * compared. * @return {number} *
*
-1
*
if the first primitive value must be placed before * the other one,
*
0
*
if the first primitive value may be placed before * the other one,
*
1
*
if the first primitive value must not be placed * before the other one.
*
* @see TSolution.oPrimitiveValues */ cSortPrimitiveValues = function(sPrefixed0, sPrefixed1) { /** * The difference between: *
    *
  1. the difference in the number of terminal symbols * between the original source text and the one with the * first primitive value consolidated, and
  2. *
  3. the difference in the number of terminal symbols * between the original source text and the one with the * second primitive value consolidated.
  4. *
* @type {number} */ var nDifference = oSolutionCandidate.oPrimitiveValues[sPrefixed0].nSaving - oSolutionCandidate.oPrimitiveValues[sPrefixed1].nSaving; return nDifference > 0 ? -1 : nDifference < 0 ? 1 : 0; }, /** * Assigns an identifier name to a primitive value and calculates * whether instances of that primitive value are worth * consolidating. * @param {string} sPrefixed The prefixed representation String * of a primitive value that is being evaluated. */ fEvaluatePrimitiveValue = function(sPrefixed) { var _, /** * The index of the last mangled name. * @type {number} */ nIndex, /** * The representation String of the primitive value that is * being evaluated. * @type {string} */ sName = sPrefixed.substring(EValuePrefixes.S_SYMBOLIC.length), /** * The number of source characters taken up by the * representation String of the primitive value that is * being evaluated. * @type {number} */ nLengthOriginal = sName.length, /** * The number of source characters taken up by the * identifier name that could substitute the primitive * value that is being evaluated. * substituted. * @type {number} */ nLengthSubstitution, /** * The number of source characters taken up by by the * representation String of the primitive value that is * being evaluated when it is represented by a string * literal. * @type {number} */ nLengthString = oProcessor.make_string(sName).length; oSolutionCandidate.oPrimitiveValues[sPrefixed] = new TPrimitiveValue(); do { // Find an identifier unused in this or any nested scope. nIndex = oScope.cname; oSolutionCandidate.oPrimitiveValues[sPrefixed].sName = oScope.next_mangled(); } while (-1 !== oSourceElementsData.aIdentifiers.indexOf( oSolutionCandidate.oPrimitiveValues[sPrefixed].sName)); nLengthSubstitution = oSolutionCandidate.oPrimitiveValues[ sPrefixed].sName.length; if (0 === sPrefixed.indexOf(EValuePrefixes.S_SYMBOLIC)) { // foo:null, or foo:null; oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving -= nLengthSubstitution + nLengthOriginal + oWeights.N_VARIABLE_DECLARATION; // null vs foo oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving += oSourceElementsData.aCount[ EPrimaryExpressionCategories. N_NULL_AND_BOOLEAN_LITERALS][sPrefixed] * (nLengthOriginal - nLengthSubstitution); } else { // foo:'fromCharCode'; oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving -= nLengthSubstitution + nLengthString + oWeights.N_VARIABLE_DECLARATION; // .fromCharCode vs [foo] if (oSourceElementsData.aCount[ EPrimaryExpressionCategories.N_IDENTIFIER_NAMES ].hasOwnProperty(sPrefixed)) { oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving += oSourceElementsData.aCount[ EPrimaryExpressionCategories.N_IDENTIFIER_NAMES ][sPrefixed] * (nLengthOriginal - nLengthSubstitution - oWeights.N_PROPERTY_ACCESSOR); } // 'fromCharCode' vs foo if (oSourceElementsData.aCount[ EPrimaryExpressionCategories.N_STRING_LITERALS ].hasOwnProperty(sPrefixed)) { oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving += oSourceElementsData.aCount[ EPrimaryExpressionCategories.N_STRING_LITERALS ][sPrefixed] * (nLengthString - nLengthSubstitution); } } if (oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving > 0) { oSolutionCandidate.nSavings += oSolutionCandidate.oPrimitiveValues[sPrefixed].nSaving; } else { oScope.cname = nIndex; // Free the identifier name. } }, /** * Adds a variable declaration to an existing variable statement. * @param {!Array} aVariableDeclaration A variable declaration * with an initialiser. */ cAddVariableDeclaration = function(aVariableDeclaration) { (/** @type {!Array} */ oSourceElements[nFrom][1]).unshift( aVariableDeclaration); }; if (nFrom > nTo) { return; } // If the range is a closure, reuse the closure. if (nFrom === nTo && 'stat' === oSourceElements[nFrom][0] && 'call' === oSourceElements[nFrom][1][0] && 'function' === oSourceElements[nFrom][1][1][0]) { fExamineSyntacticCodeUnit(oSourceElements[nFrom][1][1]); return; } // Create a list of all derived primitive values within the range. for (nPosition = nFrom; nPosition <= nTo; nPosition += 1) { aSourceElementsData[nPosition].aPrimitiveValues.forEach( cAugmentList(oSourceElementsData.aPrimitiveValues)); } if (0 === oSourceElementsData.aPrimitiveValues.length) { return; } for (nPosition = nFrom; nPosition <= nTo; nPosition += 1) { // Add the number of occurrences to the total count. fAddOccurrences(nPosition); // Add identifiers of this or any nested scope to the list. aSourceElementsData[nPosition].aIdentifiers.forEach( cAugmentList(oSourceElementsData.aIdentifiers)); } // Distribute identifier names among derived primitive values. do { // If there was any progress, find a better distribution. oSolutionBest = oSolutionCandidate; if (Object.keys(oSolutionCandidate.oPrimitiveValues).length > 0) { // Sort primitive values descending by their worthwhileness. oSourceElementsData.aPrimitiveValues.sort(cSortPrimitiveValues); } oSolutionCandidate = new TSolution(); oSourceElementsData.aPrimitiveValues.forEach( fEvaluatePrimitiveValue); oScope.cname = nIndex; } while (oSolutionCandidate.nSavings > oSolutionBest.nSavings); // Take the necessity of adding a variable statement into account. if ('var' !== oSourceElements[nFrom][0]) { oSolutionBest.nSavings -= oWeights.N_VARIABLE_STATEMENT_AFFIXATION; } if (bEnclose) { // Take the necessity of forming a closure into account. oSolutionBest.nSavings -= oWeights.N_CLOSURE; } if (oSolutionBest.nSavings > 0) { // Create variable declarations suitable for UglifyJS. Object.keys(oSolutionBest.oPrimitiveValues).forEach( cAugmentVariableDeclarations); // Rewrite expressions that contain worthwhile primitive values. for (nPosition = nFrom; nPosition <= nTo; nPosition += 1) { oWalker = oProcessor.ast_walker(); oSourceElements[nPosition] = oWalker.with_walkers( oWalkersTransformers, cContext(oWalker, oSourceElements[nPosition])); } if ('var' === oSourceElements[nFrom][0]) { // Reuse the statement. (/** @type {!Array.} */ aVariableDeclarations.reverse( )).forEach(cAddVariableDeclaration); } else { // Add a variable statement. Array.prototype.splice.call( oSourceElements, nFrom, 0, ['var', aVariableDeclarations]); nTo += 1; } if (bEnclose) { // Add a closure. Array.prototype.splice.call( oSourceElements, nFrom, 0, ['stat', ['call', ['function', null, [], []], []]]); // Copy source elements into the closure. for (nPosition = nTo + 1; nPosition > nFrom; nPosition -= 1) { Array.prototype.unshift.call( oSourceElements[nFrom][1][1][3], oSourceElements[nPosition]); } // Remove source elements outside the closure. Array.prototype.splice.call( oSourceElements, nFrom + 1, nTo - nFrom + 1); } } if (bEnclose) { // Restore the availability of identifier names. oScope.cname = nIndex; } }; oSourceElements = (/** @type {!TSyntacticCodeUnit} */ oSyntacticCodeUnit[bIsGlobal ? 1 : 3]); if (0 === oSourceElements.length) { return; } oScope = bIsGlobal ? oSyntacticCodeUnit.scope : oSourceElements.scope; // Skip a Directive Prologue. while (nAfterDirectivePrologue < oSourceElements.length && 'directive' === oSourceElements[nAfterDirectivePrologue][0]) { nAfterDirectivePrologue += 1; aSourceElementsData.push(null); } if (oSourceElements.length === nAfterDirectivePrologue) { return; } for (nPosition = nAfterDirectivePrologue; nPosition < oSourceElements.length; nPosition += 1) { oSourceElementData = new TSourceElementsData(); oWalker = oProcessor.ast_walker(); // Classify a source element. // Find its derived primitive values and count their occurrences. // Find all identifiers used (including nested scopes). oWalker.with_walkers( oWalkers.oSurveySourceElement, cContext(oWalker, oSourceElements[nPosition])); // Establish whether the scope is still wholly examinable. bIsWhollyExaminable = bIsWhollyExaminable && ESourceElementCategories.N_WITH !== oSourceElementData.nCategory && ESourceElementCategories.N_EVAL !== oSourceElementData.nCategory; aSourceElementsData.push(oSourceElementData); } if (bIsWhollyExaminable) { // Examine the whole scope. fExamineSourceElements( nAfterDirectivePrologue, oSourceElements.length - 1, false); } else { // Examine unexcluded ranges of source elements. for (nPosition = oSourceElements.length - 1; nPosition >= nAfterDirectivePrologue; nPosition -= 1) { oSourceElementData = (/** @type {!TSourceElementsData} */ aSourceElementsData[nPosition]); if (ESourceElementCategories.N_OTHER === oSourceElementData.nCategory) { if ('undefined' === typeof nTo) { nTo = nPosition; // Indicate the end of a range. } // Examine the range if it immediately follows a Directive Prologue. if (nPosition === nAfterDirectivePrologue) { fExamineSourceElements(nPosition, nTo, true); } } else { if ('undefined' !== typeof nTo) { // Examine the range that immediately follows this source element. fExamineSourceElements(nPosition + 1, nTo, true); nTo = void 0; // Obliterate the range. } // Examine nested functions. oWalker = oProcessor.ast_walker(); oWalker.with_walkers( oWalkers.oExamineFunctions, cContext(oWalker, oSourceElements[nPosition])); } } } }(oAbstractSyntaxTree = oProcessor.ast_add_scope(oAbstractSyntaxTree))); return oAbstractSyntaxTree; }; /*jshint sub:false */ /* Local Variables: */ /* mode: js */ /* coding: utf-8 */ /* indent-tabs-mode: nil */ /* tab-width: 2 */ /* End: */ /* vim: set ft=javascript fenc=utf-8 et ts=2 sts=2 sw=2: */ /* :mode=javascript:noTabs=true:tabSize=2:indentSize=2:deepIndent=true: */




© 2015 - 2025 Weber Informatics LLC | Privacy Policy