UNPKG

jsx

Version:

a faster, safer, easier JavaScript

1,561 lines (1,301 loc) 137 kB
/* * Copyright (c) 2012 DeNA Co., Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ import "js.jsx"; import "./meta.jsx"; import "./analysis.jsx"; import "./classdef.jsx"; import "./type.jsx"; import "./expression.jsx"; import "./statement.jsx"; import "./emitter.jsx"; import "./jssourcemap.jsx"; import "./util.jsx"; import "./optimizer.jsx"; import "./parser.jsx"; import "./platform.jsx"; import _UnclassifyOptimizationCommand, _NoDebugCommand from "./optimizer.jsx"; // utilify functions specific to jsemitter class _Util { static const OUTPUTNAME_IDENTIFIER = "emitter.outputname"; class OutputNameStash extends Stash { var outputName : string; function constructor(outputName : string) { this.outputName = outputName; } override function clone() : Stash { throw new Error("not supported"); } } static function getOutputClassName(classDef : ClassDefinition) : string { return (classDef.getStash(_Util.OUTPUTNAME_IDENTIFIER) as _Util.OutputNameStash).outputName; } static function getOutputConstructorName(ctor : MemberFunctionDefinition) : string { if ((ctor.getClassDef().flags() & ClassDefinition.IS_NATIVE) != 0) { return _Util.getNameOfNativeConstructor(ctor.getClassDef()); } return (ctor.getStash(_Util.OUTPUTNAME_IDENTIFIER) as _Util.OutputNameStash).outputName; } static function getOutputConstructorName(classDef : ClassDefinition, argTypes : Type[]) : string { var ctor = Util.findFunctionInClass(classDef, "constructor", argTypes, false); assert ctor; return _Util.getOutputConstructorName(ctor); } static function getNameOfNativeConstructor(classDef : ClassDefinition) : string { if (classDef instanceof InstantiatedClassDefinition) { if ((classDef as InstantiatedClassDefinition).getTemplateClassName() == "Map") { return "Object"; } else { return (classDef as InstantiatedClassDefinition).getTemplateClassName(); } } return classDef.className(); } static function setOutputClassNames(classDefs : ClassDefinition[]) : void { function setOutputName(stashable : Stashable, name : string) : void { stashable.setStash(_Util.OUTPUTNAME_IDENTIFIER, new _Util.OutputNameStash(name)); } function escapeClassNameIfInstantiated(name : string) : string { // escape the instantiated class names (note: template classes are never emitted (since they are all native) but their names are used for mangling of function arguments) return name.replace(/\.</g, "$$").replace(/>/g, "$E").replace(/[^A-Za-z0-9_]/g,"$"); } var countByName = new Map.<number>; function newUniqueName(className : string) : string { if (countByName[className]) { var name = className + "$" + (countByName[className] - 1) as string; ++countByName[className]; } else { name = className; countByName[className] = 1; } return escapeClassNameIfInstantiated(name); } // rename the classes with conflicting names for (var i = 0; i < classDefs.length; ++i) { var classDef = classDefs[i]; if ((classDef.flags() & ClassDefinition.IS_NATIVE) != 0) { // check that the names of native classes do not conflict, and register the ocurrences var className = classDef.className(); assert ! countByName[className]; setOutputName(classDef, escapeClassNameIfInstantiated(className)); countByName[className] = 1; } } for (var i = 0; i < classDefs.length; ++i) { var classDef = classDefs[i]; if ((classDef.flags() & ClassDefinition.IS_NATIVE) == 0) { // decide the className if (classDef.getOuterClassDef() != null) var className = _Util.getOutputClassName(classDef.getOuterClassDef()) + "$C" + classDef.className(); else className = classDef.className(); // list the constructors var ctors = _Util.findFunctions(classDef, "constructor", false); if (ctors.length != 0) { // move exported ctor to the top (so that it would not get mangled) for (var j = 0; j < ctors.length; ++j) { if ((ctors[j].flags() & ClassDefinition.IS_EXPORT) != 0) { var exportedCtor = ctors[j]; ctors.splice(j, 1); ctors.unshift(exportedCtor); break; } } var n = newUniqueName(className); setOutputName(classDef, n); setOutputName(ctors[0], n); for (var j = 1; j < ctors.length; ++j) { setOutputName(ctors[j], newUniqueName(className)); } } else { setOutputName(classDef, newUniqueName(className)); } } } } static function encodeObjectLiteralKey(s : string) : string { if (s.length == 0 || s.match(/^[A-Za-z_$][A-Za-z0-9_$]*$/)) { return s; } return Util.encodeStringLiteral(s); } static function findFunctions(classDef : ClassDefinition, name : string, isStatic : boolean) : MemberFunctionDefinition[] { var functions = new MemberFunctionDefinition[]; var members = classDef.members(); for (var i = 0; i < members.length; ++i) { var member = members[i]; if ((member instanceof MemberFunctionDefinition) && member.name() == name && (member.flags() & ClassDefinition.IS_STATIC) == (isStatic ? ClassDefinition.IS_STATIC : 0)) functions.push(member as MemberFunctionDefinition); } return functions; } } class _Mangler { function mangleFunctionName (name : string, argTypes : Type[]) : string { return name + this.mangleFunctionArguments(argTypes); } function mangleTypeName (type : Type) : string { if (type.equals(Type.voidType)) return "V"; else if (type.equals(Type.booleanType)) return "B"; else if (type.equals(Type.integerType)) return "I"; else if (type.equals(Type.numberType)) return "N"; else if (type.equals(Type.stringType)) return "S"; else if (type instanceof ObjectType) { var classDef = type.getClassDef(); if (classDef instanceof InstantiatedClassDefinition) { var typeArgs = (classDef as InstantiatedClassDefinition).getTypeArguments(); switch ((classDef as InstantiatedClassDefinition).getTemplateClassName()) { case "Array": return "A" + this.mangleTypeName(typeArgs[0]); case "Map": return "H" + this.mangleTypeName(typeArgs[0]); default: // fall through } } return "L" + _Util.getOutputClassName(type.getClassDef()) + "$"; } else if (type instanceof StaticFunctionType) return "F" + this.mangleFunctionArguments((type as StaticFunctionType).getArgumentTypes()) + this.mangleTypeName((type as StaticFunctionType).getReturnType()) + "$"; else if (type instanceof MemberFunctionType) return "M" + this.mangleTypeName((type as MemberFunctionType).getObjectType()) + this.mangleFunctionArguments((type as MemberFunctionType).getArgumentTypes()) + this.mangleTypeName((type as MemberFunctionType).getReturnType()) + "$"; else if (type instanceof NullableType) return "U" + this.mangleTypeName((type as NullableType).getBaseType()); else if (type.equals(Type.variantType)) return "X"; else throw new Error("FIXME " + type.toString()); } function mangleFunctionArguments (argTypes : Type[]) : string { var s = "$"; for (var i = 0; i < argTypes.length; ++i) s += this.mangleTypeName(argTypes[i]); return s; } function requiresMangling(classDef : ClassDefinition, name : string, argTypes : Type[], isStatic : boolean) : boolean { assert argTypes != null; return ! Util.memberRootIsNative(classDef, name, argTypes, isStatic); } function requiresMangling(expr : PropertyExpression) : boolean { if (! Util.isReferringToFunctionDefinition(expr)) { return false; } return ! Util.propertyRootIsNative(expr); } function requiresMangling(member : MemberFunctionDefinition) : boolean { return this.requiresMangling(member.getClassDef(), member.name(), member.getArgumentTypes(), (member.flags() & ClassDefinition.IS_STATIC) != 0); } } class _Namer { static const IDENTIFIER = "namer"; class _TryStash extends Stash { var catchName : string; function constructor(catchName : string) { this.catchName = catchName; } override function clone() : Stash { throw new Error("operation not supported"); } } class _CatchTargetStash extends Stash { var tryStmt : TryStatement; function constructor(tryStmt : TryStatement) { this.tryStmt = tryStmt; } override function clone() : Stash { throw new Error("operation not supported"); } } var _emitter : JavaScriptEmitter; var _catchLevel = -1; function setup(emitter : JavaScriptEmitter) : _Namer { this._emitter = emitter; return this; } function getNameOfProperty(classDef : ClassDefinition, name : string) : string { return name; } function getNameOfMethod(classDef : ClassDefinition, name : string, argTypes : Type[]) : string { if (Util.memberRootIsNative(classDef, name, argTypes, false)) { return name; } return this._emitter.getMangler().mangleFunctionName(name, argTypes); } function getNameOfStaticVariable(classDef : ClassDefinition, name : string) : string { return name; } function getNameOfStaticFunction(classDef : ClassDefinition, name : string, argTypes : Type[]) : string { var className = _Util.getOutputClassName(classDef); if (Util.memberRootIsNative(classDef, name, argTypes, true)) { return className + "." + name; } return className + "$" + this._emitter.getMangler().mangleFunctionName(name, argTypes); } function getNameOfConstructor(classDef : ClassDefinition, argTypes : Type[]) : string { return _Util.getOutputConstructorName(classDef, argTypes); } function getNameOfClass(classDef : ClassDefinition) : string { return _Util.getOutputClassName(classDef); } function enterScope(local : LocalVariable, cb : function () : void) : void { cb(); } function enterFunction(funcDef : MemberFunctionDefinition, cb : function () : void) : void { cb(); } function enterCatch(tryStmt : TryStatement, cb : function (getCatchName : function () : string) : void) : void { // adjust level ++this._catchLevel; // doit this._enterCatch(tryStmt, cb, "$__jsx_catch_" + this._catchLevel as string); // exit --this._catchLevel; } function _enterCatch(tryStmt : TryStatement, cb : function (getCatchName : function () : string) : void, catchName : string) : void { tryStmt.setStash(_Namer.IDENTIFIER, new _Namer._TryStash(catchName)); var catchStmts = tryStmt.getCatchStatements(); for (var i in catchStmts) { catchStmts[i].getLocal().setStash(_Namer.IDENTIFIER, new _Namer._CatchTargetStash(tryStmt)); } cb(function () { return this._getCatchName(tryStmt); }); } function getNameOfLocalVariable(local : LocalVariable) : string { if (local instanceof CaughtVariable) { return this._getCatchName(local as CaughtVariable); } else { return local.getName().getValue(); } } function _getCatchName(caught : CaughtVariable) : string { return this._getCatchName((caught.getStash(_Namer.IDENTIFIER) as _Namer._CatchTargetStash).tryStmt); } function _getCatchName(tryStmt : TryStatement) : string { return (tryStmt.getStash(_Namer.IDENTIFIER) as _Namer._TryStash).catchName; } } class _MinifiedNameGenerator { static const _MINIFY_CHARS = "$_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // every object has eval, so it is listed as a keyword static const KEYWORDS = ( "break else new var case finally return void catch for switch while continue function this with default if throw" + " delete in try do instanceof typeof abstract enum int" + " boolean export interface byte extends long char final native class float package const goto private debugger implements protected double import public" + " NaN Infinity undefined eval" ).split(/\s+/); static const GLOBALS = ( "parseInt parseFloat isNaN isFinite decodeURI decodeURIComponent encodeURI encodeURIComponent" + " Object Function Array String Boolean Number Date RegExp Error EvalError RangeError ReferenceError SyntaxError TypeError URIError Math" ).split(/\s+/); var _skipWords = new Map.<boolean>; var _memo = new string[]; var _counter = 0; function constructor(skipWords : string[]) { for (var i in skipWords) { this._skipWords[skipWords[i]] = true; } } function get(n : number) : string { while (this._memo.length <= n) { do { var candidate = _MinifiedNameGenerator._stringify(this._counter++); } while (this._skipWords.hasOwnProperty(candidate) || candidate.match(/^[0-9$]/)); this._memo.push(candidate); } return this._memo[n]; } static function _stringify(n : number) : string { var name = ""; do { var colIndex = n % _MinifiedNameGenerator._MINIFY_CHARS.length; name += _MinifiedNameGenerator._MINIFY_CHARS.charAt(colIndex); n = (n - colIndex) / _MinifiedNameGenerator._MINIFY_CHARS.length; } while (n != 0); return name; } } class _Minifier { static const CLASSSTASH_IDENTIFIER = "minifier.class"; static const SCOPESTASH_IDENTIFIER = "minifier.scope"; static const LOCALSTASH_IDENTIFIER = "minifier.local"; class _ClassStash extends Stash { var staticVariableUseCount = new Map.<number>; var staticVariableConversionTable = new Map.<string>; override function clone() : Stash { throw new Error("operation not supported"); } } class _ScopeStash extends Stash { var usedGlobals = new Map.<boolean>; var usedOuterLocals = new LocalVariable[]; override function clone() : Stash { throw new Error("operation not supported"); } } class _LocalStash extends Stash { var useCount = 0; var minifiedName : Nullable.<string>; override function clone() : Stash { throw new Error("operation not supported"); } } var _emitter : JavaScriptEmitter; var _classDefs : ClassDefinition[]; var _propertyUseCount = new Map.<number>(); var _propertyConversionTable : Map.<string>; var _globalUseCount = new Map.<number>(); var _globalConversionTable : Map.<string>; var _outerLocals = new LocalVariable[]; class _MinifyingNamer extends _Namer { var _minifier : _Minifier; function setup(minifier : _Minifier) : _Minifier._MinifyingNamer { this._minifier = minifier; super.setup(minifier._emitter); return this; } function _getMangler() : _Mangler { return this._minifier._emitter.getMangler(); } function _isCounting() : boolean { return this._minifier._isCounting(); } override function getNameOfProperty(classDef : ClassDefinition, name : string) : string { if (Util.memberRootIsNative(classDef, name, null, false) || Util.memberIsExported(classDef, name, null, false)) { return name; } if (this._isCounting()) { _Minifier._incr(this._minifier._propertyUseCount, name); } else { name = this._minifier._propertyConversionTable[name]; } return name; } override function getNameOfMethod(classDef : ClassDefinition, name : string, argTypes : Type[]) : string { if (Util.memberRootIsNative(classDef, name, argTypes, false)) { return name; } var mangledName = this._getMangler().mangleFunctionName(name, argTypes); if (this._isCounting()) { _Minifier._incr(this._minifier._propertyUseCount, mangledName); } else { mangledName = this._minifier._propertyConversionTable[mangledName]; } return mangledName; } override function getNameOfStaticVariable(classDef : ClassDefinition, name : string) : string { if (Util.memberRootIsNative(classDef, name, null, true) || Util.memberIsExported(classDef, name, null, true)) { return name; } if (this._isCounting()) { _Minifier._incr(_Minifier._getClassStash(classDef).staticVariableUseCount, name); } else { name = _Minifier._getClassStash(classDef).staticVariableConversionTable[name]; } return name; } override function getNameOfStaticFunction(classDef : ClassDefinition, name : string, argTypes : Type[]) : string { if (Util.memberRootIsNative(classDef, name, argTypes, true)) { return this.getNameOfClass(classDef) + "." + name; } var mangledName = _Util.getOutputClassName(classDef) + "$" + this._getMangler().mangleFunctionName(name, argTypes); if (this._isCounting()) { _Minifier._incr(this._minifier._globalUseCount, mangledName); } else { mangledName = this._minifier._globalConversionTable[mangledName]; } return mangledName; } override function getNameOfConstructor(classDef : ClassDefinition, argTypes : Type[]) : string { if ((classDef.flags() & ClassDefinition.IS_NATIVE) != 0) { var name = _Util.getNameOfNativeConstructor(classDef); if (this._isCounting()) { _Minifier._incr(this._minifier._globalUseCount, name); } return name; } var mangledName = _Util.getOutputConstructorName(classDef, argTypes); if (this._isCounting()) { _Minifier._incr(this._minifier._globalUseCount, mangledName); } else { mangledName = this._minifier._globalConversionTable[mangledName]; } return mangledName; } override function getNameOfClass(classDef : ClassDefinition) : string { var name = _Util.getOutputClassName(classDef); if (this._isCounting()) { _Minifier._incr(this._minifier._globalUseCount, name); } if ((classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FAKE)) == 0 && ! this._isCounting()) { return this._minifier._globalConversionTable[name]; } return name; } override function enterScope(local : LocalVariable, cb : function () : void) : void { if (local == null) { // to support unnamed function expressions cb(); } else { if (this._isCounting()) { this._minifier._recordUsedIdentifiers(local, function () { this._minifier._outerLocals.push(local); cb(); this._minifier._outerLocals.pop(); }); } else { this._minifier._buildConversionTable([ local ], _Minifier._getScopeStash(local)); cb(); } } } override function enterFunction(funcDef : MemberFunctionDefinition, cb : function () : void) : void { if (this._isCounting()) { this._minifier._recordUsedIdentifiers(funcDef, function () { this._minifier._outerLocals = this._minifier._outerLocals.concat(_Minifier._getArgsAndLocals(funcDef)); cb(); this._minifier._outerLocals.length -= funcDef.getArguments().length + funcDef.getLocals().length; }); } else { this._minifier._buildConversionTable(_Minifier._getArgsAndLocals(funcDef), _Minifier._getScopeStash(funcDef)); cb(); } } override function getNameOfLocalVariable(local : LocalVariable) : string { if (local instanceof CaughtVariable) { return this._getCatchName(local as CaughtVariable); } if (this._isCounting()) { ++_Minifier._getLocalStash(local).useCount; return local.getName().getValue(); } else { return _Minifier._getLocalStash(local).minifiedName; } } } function constructor(emitter : JavaScriptEmitter, classDefs : ClassDefinition[]) { this._emitter = emitter; this._classDefs = classDefs; } function getCountingNamer() : _Namer { assert this._isCounting(); return (new _Minifier._MinifyingNamer).setup(this); } function getMinifyingNamer() : _Namer { // build minification tables this._minifyProperties(); this._minifyStaticVariables(); this._minifyGlobals(); // and return return (new _Minifier._MinifyingNamer).setup(this); } function _isCounting() : boolean { return this._propertyConversionTable == null; } function _recordUsedIdentifiers(stashable : Stashable, cb : function () : void) : void { assert this._isCounting(); // prepare var globalUseCountBackup = new Map.<number>; for (var k in this._globalUseCount) { globalUseCountBackup[k] = this._globalUseCount[k]; } var outerLocalUseCount = new number[]; for (var i in this._outerLocals) { outerLocalUseCount[i] = _Minifier._getLocalStash(this._outerLocals[i]).useCount; } // execute cb(); assert outerLocalUseCount.length == this._outerLocals.length; // collect and store info var scopeStash = _Minifier._getScopeStash(stashable); for (var k in this._globalUseCount) { if (this._globalUseCount[k] != globalUseCountBackup[k]) { scopeStash.usedGlobals[k] = true; } } for (var i in this._outerLocals) { if (outerLocalUseCount[i] != _Minifier._getLocalStash(this._outerLocals[i]).useCount) { scopeStash.usedOuterLocals.push(this._outerLocals[i]); } } } function _minifyProperties() : void { this._log("minifying properties"); this._propertyConversionTable = _Minifier._buildConversionTable( this._propertyUseCount, new _MinifiedNameGenerator( ([] : string[]).concat( _MinifiedNameGenerator.KEYWORDS, (function () : string[] { var nativePropertyNames = new Map.<boolean>; this._classDefs.forEach(function (classDef) { classDef.forEachMember(function (member) { if ((member.flags() & ClassDefinition.IS_STATIC) == 0 && ((member.flags() | classDef.flags()) & ClassDefinition.IS_NATIVE) != 0) { nativePropertyNames[member.name()] = true; } return true; }); }); return nativePropertyNames.keys(); })() ))); for (var k in this._propertyConversionTable) { this._log(" " + k + " => " + this._propertyConversionTable[k]); } } function _minifyStaticVariables() : void { this._log("minifying static variables"); this._classDefs.forEach(function (classDef) { if ((classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FAKE)) == 0) { var exportedStaticVarNames = new string[]; classDef.forEachMemberVariable(function (member) { if ((member.flags() & (ClassDefinition.IS_STATIC | ClassDefinition.IS_EXPORT)) == (ClassDefinition.IS_STATIC | ClassDefinition.IS_EXPORT)) { exportedStaticVarNames.push(member.name()); } return true; }); var stash = _Minifier._getClassStash(classDef); stash.staticVariableConversionTable = _Minifier._buildConversionTable( stash.staticVariableUseCount, new _MinifiedNameGenerator(_MinifiedNameGenerator.KEYWORDS.concat(exportedStaticVarNames))); } }); } function _minifyGlobals() : void { this._log("minifying classes and static functions"); // build useCount list wo. native class names var useCount = new Map.<number>; for (var k in this._globalUseCount) { useCount[k] = this._globalUseCount[k]; } this._classDefs.forEach(function (classDef) { if ((classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FAKE)) != 0) { delete useCount[classDef.className()]; } }); // build conversion table this._globalConversionTable = _Minifier._buildConversionTable( useCount, new _MinifiedNameGenerator( ([] : string[]).concat( _MinifiedNameGenerator.KEYWORDS, _MinifiedNameGenerator.GLOBALS, (function () : string[] { var nativeClassNames = new string[]; this._classDefs.forEach(function (classDef) { if ((classDef.flags() & ClassDefinition.IS_NATIVE) != 0) { nativeClassNames.push(classDef.className()); } }); return nativeClassNames; })() ))); for (var k in this._globalConversionTable) { this._log(" " + k + " => " + this._globalConversionTable[k]); } } function _log(message : string) : void { // log message; } function _buildConversionTable(locals : LocalVariable[], scopeStash : _Minifier._ScopeStash) : void { // build useCount var useCount = new Map.<number>; locals.forEach(function (local) { useCount[local.getName().getValue()] = _Minifier._getLocalStash(local).useCount; }); // build list of reserved words var reserved = [] : string[]; for (var k in scopeStash.usedGlobals) { // if k does not exist in globalConversionTable then it is a native class name reserved.push(this._globalConversionTable.hasOwnProperty(k) ? this._globalConversionTable[k] : k); } for (var i in scopeStash.usedOuterLocals) { reserved.push(_Minifier._getLocalStash(scopeStash.usedOuterLocals[i]).minifiedName); } this._log("local minification, preserving: " + reserved.join(",")); reserved = reserved.concat(_MinifiedNameGenerator.KEYWORDS); // doit var conversionTable = _Minifier._buildConversionTable(useCount, new _MinifiedNameGenerator(reserved)); // store the result locals.forEach(function (local) { _Minifier._getLocalStash(local).minifiedName = conversionTable[local.getName().getValue()]; }); } static function _buildConversionTable(useCount : Map.<number>, nameGenerator : _MinifiedNameGenerator) : Map.<string> { // sort property names by use count (in descending order) var propertyNames = useCount.keys().sort(function (x, y) { var delta = useCount[y] - useCount[x]; if (delta != 0) { return delta; } if (x < y) { return -1; } else { return 1; } }); // build conversion map var conversionTable = new Map.<string>(); for (var i = 0; i < propertyNames.length; ++i) { conversionTable[propertyNames[i]] = nameGenerator.get(i); } return conversionTable; } static function _getClassStash(classDef : ClassDefinition) : _Minifier._ClassStash { var stash = classDef.getStash(_Minifier.CLASSSTASH_IDENTIFIER); if (stash == null) { stash = classDef.setStash(_Minifier.CLASSSTASH_IDENTIFIER, new _Minifier._ClassStash()); } return stash as _Minifier._ClassStash; } static function _getScopeStash(stashable : Stashable) : _Minifier._ScopeStash { var stash = stashable.getStash(_Minifier.SCOPESTASH_IDENTIFIER); if(stash == null) { stash = stashable.setStash(_Minifier.SCOPESTASH_IDENTIFIER, new _Minifier._ScopeStash()); } return stash as _Minifier._ScopeStash; } static function _getLocalStash(local : LocalVariable) : _Minifier._LocalStash { var stash = local.getStash(_Minifier.LOCALSTASH_IDENTIFIER); if(stash == null) { stash = local.setStash(_Minifier.LOCALSTASH_IDENTIFIER, new _Minifier._LocalStash()); } return stash as _Minifier._LocalStash; } static function _incr(useCount : Map.<number>, name : string) : void { if (useCount.hasOwnProperty(name)) { ++useCount[name]; } else { useCount[name] = 1; } } static function _getArgsAndLocals(funcDef : MemberFunctionDefinition) : LocalVariable[] { var list = new LocalVariable[]; funcDef.getArguments().forEach(function (a) { list.push(a); }); return list.concat(funcDef.getLocals()); } static function minifyJavaScript(src : string) : string { var ast = esprima.parse(src); ast = esmangle.mangle(ast, { destructive: true } : Map.<variant>); return escodegen.generate(ast, { format: { renumber: true, hexadecimal: true, escapeless: true, compact: true, semicolons: false, parentheses: false } : Map.<variant>, directive: true } : Map.<variant>); } } native("require('esprima')") class esprima { static function parse(src : string) : variant; } native("require('esmangle')") class esmangle { static function mangle(ast : variant, opts : Map.<variant>) : variant; } native("require('escodegen')") class escodegen { static function generate(ast : variant, opts : Map.<variant>) : string; } // statement emitter abstract class _StatementEmitter { var _emitter : JavaScriptEmitter; function constructor (emitter : JavaScriptEmitter) { this._emitter = emitter; } abstract function emit () : void; function emitLabelOfStatement (statement : LabellableStatement) : void { var label = statement.getLabel(); if (label != null) { this._emitter._reduceIndent(); this._emitter._emit(label.getValue() + ":\n", label); this._emitter._advanceIndent(); } } } class _ConstructorInvocationStatementEmitter extends _StatementEmitter { var _statement : ConstructorInvocationStatement; function constructor (emitter : JavaScriptEmitter, statement : ConstructorInvocationStatement) { super(emitter); this._statement = statement; } override function emit () : void { var ctorType = this._statement.getConstructorType() as ResolvedFunctionType; var argTypes = ctorType != null ? ctorType.getArgumentTypes() : new Type[]; var ctorName = this._emitter.getNamer().getNameOfConstructor(this._statement.getConstructingClassDef(), argTypes); var token = this._statement.getToken(); if (ctorName == "Error" && this._statement.getArguments().length == 1) { /* At least v8 does not support "Error.call(this, message)"; it not only does not setup the stacktrace but also does not set the message property. So we set the message property. We continue to call "Error" hoping that it would have some positive effect on other platforms (like setting the stacktrace, etc.). FIXME check that doing "Error.call(this);" does not have any negative effect on other platforms */ this._emitter._emit("Error.call(this);\n", token); this._emitter._emit("this.message = ", token); this._emitter._getExpressionEmitterFor(this._statement.getArguments()[0]).emit(_AssignmentExpressionEmitter._operatorPrecedence["="]); this._emitter._emit(";\n", token); } else { this._emitter._emitCallArguments(token, ctorName + ".call(this", this._statement.getArguments(), argTypes); this._emitter._emit(";\n", token); } } } class _ExpressionStatementEmitter extends _StatementEmitter { var _statement : ExpressionStatement; function constructor (emitter : JavaScriptEmitter, statement : ExpressionStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(";\n", null); } } class _FunctionStatementEmitter extends _StatementEmitter { var _statement : FunctionStatement; function constructor (emitter : JavaScriptEmitter, statement : FunctionStatement) { super(emitter); this._statement = statement; } override function emit () : void { var funcDef = this._statement.getFuncDef(); assert funcDef.getFuncLocal() != null; this._emitter._emit("function " + this._emitter.getNamer().getNameOfLocalVariable(funcDef.getFuncLocal()) + "(", funcDef.getToken()); this._emitter.getNamer().enterFunction(funcDef, function () { var args = funcDef.getArguments(); for (var i = 0; i < args.length; ++i) { if (i != 0) this._emitter._emit(", ", funcDef.getToken()); this._emitter._emit(this._emitter.getNamer().getNameOfLocalVariable(args[i]), funcDef.getToken()); } this._emitter._emit(") {\n", funcDef.getToken()); this._emitter._advanceIndent(); this._emitter._emitFunctionBody(funcDef); this._emitter._reduceIndent(); this._emitter._emit("}\n", funcDef.getToken()); }); } } class _ReturnStatementEmitter extends _StatementEmitter { var _statement : ReturnStatement; function constructor (emitter : JavaScriptEmitter, statement : ReturnStatement) { super(emitter); this._statement = statement; } override function emit () : void { var expr = this._statement.getExpr(); if (expr != null) { this._emitter._emit("return ", null); if (this._emitter._enableProfiler) { this._emitter._emit("$__jsx_profiler.exit(", null); } this._emitter._emitRHSOfAssignment(this._statement.getExpr(), this._emitter._emittingFunction.getReturnType()); if (this._emitter._enableProfiler) { this._emitter._emit(")", null); } this._emitter._emit(";\n", null); } else { if (this._emitter._enableProfiler) { this._emitter._emit("return $__jsx_profiler.exit();\n", this._statement.getToken()); } else { this._emitter._emit("return;\n", this._statement.getToken()); } } } } class _DeleteStatementEmitter extends _StatementEmitter { var _statement : DeleteStatement; function constructor (emitter : JavaScriptEmitter, statement : DeleteStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("delete ", this._statement.getToken()); this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(";\n", null); } } class _BreakStatementEmitter extends _StatementEmitter { var _statement : BreakStatement; function constructor (emitter : JavaScriptEmitter, statement : BreakStatement) { super(emitter); this._statement = statement; } override function emit () : void { if (this._statement.getLabel() != null) this._emitter._emit("break " + this._statement.getLabel().getValue() + ";\n", this._statement.getToken()); else this._emitter._emit("break;\n", this._statement.getToken()); } } class _ContinueStatementEmitter extends _StatementEmitter { var _statement : ContinueStatement; function constructor (emitter : JavaScriptEmitter, statement : ContinueStatement) { super(emitter); this._statement = statement; } override function emit () : void { if (this._statement.getLabel() != null) this._emitter._emit("continue " + this._statement.getLabel().getValue() + ";\n", this._statement.getToken()); else this._emitter._emit("continue;\n", this._statement.getToken()); } } class _DoWhileStatementEmitter extends _StatementEmitter { var _statement : DoWhileStatement; function constructor (emitter : JavaScriptEmitter, statement : DoWhileStatement) { super(emitter); this._statement = statement; } override function emit () : void { this.emitLabelOfStatement(this._statement); this._emitter._emit("do {\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("} while (", null); this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(");\n", null); } } class _ForInStatementEmitter extends _StatementEmitter { var _statement : ForInStatement; function constructor (emitter : JavaScriptEmitter, statement : ForInStatement) { super(emitter); this._statement = statement; } override function emit () : void { this.emitLabelOfStatement(this._statement); this._emitter._emit("for (", null); this._emitter._getExpressionEmitterFor(this._statement.getLHSExpr()).emit(0); this._emitter._emit(" in ", null); this._emitter._getExpressionEmitterFor(this._statement.getListExpr()).emit(0); this._emitter._emit(") {\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("}\n", null); } } class _ForStatementEmitter extends _StatementEmitter { var _statement : ForStatement; function constructor (emitter : JavaScriptEmitter, statement : ForStatement) { super(emitter); this._statement = statement; } override function emit () : void { this.emitLabelOfStatement(this._statement); this._emitter._emit("for (", this._statement.getToken()); var initExpr = this._statement.getInitExpr(); if (initExpr != null) this._emitter._getExpressionEmitterFor(initExpr).emit(0); this._emitter._emit("; ", null); var condExpr = this._statement.getCondExpr(); if (condExpr != null) this._emitter._getExpressionEmitterFor(condExpr).emit(0); this._emitter._emit("; ", null); var postExpr = this._statement.getPostExpr(); if (postExpr != null) this._emitter._getExpressionEmitterFor(postExpr).emit(0); this._emitter._emit(") {\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("}\n", null); } } class _IfStatementEmitter extends _StatementEmitter { var _statement : IfStatement; function constructor (emitter : JavaScriptEmitter, statement : IfStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("if (", this._statement.getToken()); this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(") {\n", null); this._emitter._emitStatements(this._statement.getOnTrueStatements()); var ifFalseStatements = this._statement.getOnFalseStatements(); if (ifFalseStatements.length != 0) { this._emitter._emit("} else {\n", null); this._emitter._emitStatements(ifFalseStatements); } this._emitter._emit("}\n", null); } } class _SwitchStatementEmitter extends _StatementEmitter { var _statement : SwitchStatement; function constructor (emitter : JavaScriptEmitter, statement : SwitchStatement) { super(emitter); this._statement = statement; } override function emit () : void { this.emitLabelOfStatement(this._statement); this._emitter._emit("switch (", this._statement.getToken()); this._emitter._emitWithNullableGuard(this._statement.getExpr(), 0); this._emitter._emit(") {\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("}\n", null); } } class _CaseStatementEmitter extends _StatementEmitter { var _statement : CaseStatement; function constructor (emitter : JavaScriptEmitter, statement : CaseStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._reduceIndent(); this._emitter._emit("case ", null); this._emitter._emitWithNullableGuard(this._statement.getExpr(), 0); this._emitter._emit(":\n", null); this._emitter._advanceIndent(); } } class _DefaultStatementEmitter extends _StatementEmitter { var _statement : DefaultStatement; function constructor (emitter : JavaScriptEmitter, statement : DefaultStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._reduceIndent(); this._emitter._emit("default:\n", null); this._emitter._advanceIndent(); } } class _WhileStatementEmitter extends _StatementEmitter { var _statement : WhileStatement; function constructor (emitter : JavaScriptEmitter, statement : WhileStatement) { super(emitter); this._statement = statement; } override function emit () : void { this.emitLabelOfStatement(this._statement); this._emitter._emit("while (", this._statement.getToken()); this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(") {\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("}\n", null); } } class _TryStatementEmitter extends _StatementEmitter { var _statement : TryStatement; function constructor (emitter : JavaScriptEmitter, statement : TryStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("try {\n", this._statement.getToken()); this._emitter._emitStatements(this._statement.getTryStatements()); this._emitter._emit("}", null); var catchStatements = this._statement.getCatchStatements(); if (catchStatements.length != 0) { this._emitter.getNamer().enterCatch(this._statement, function (getCatchName) { this._emitter._emit(" catch (" + getCatchName() + ") {\n", null); if (this._emitter._enableProfiler) { this._emitter._advanceIndent(); this._emitter._emit("$__jsx_profiler.resume($__jsx_profiler_ctx);\n", null); this._emitter._reduceIndent(); } this._emitter._emitStatements(catchStatements.map.<Statement>((s) -> { return s; })); if (! catchStatements[catchStatements.length - 1].getLocal().getType().equals(Type.variantType)) { this._emitter._advanceIndent(); this._emitter._emit("{\n", null); this._emitter._advanceIndent(); this._emitter._emit("throw " + getCatchName() + ";\n", null); this._emitter._reduceIndent(); this._emitter._emit("}\n", null); this._emitter._reduceIndent(); } this._emitter._emit("}", null); }); } var finallyStatements = this._statement.getFinallyStatements(); if (finallyStatements.length != 0 || catchStatements.length == 0) { this._emitter._emit(" finally {\n", null); this._emitter._emitStatements(finallyStatements); this._emitter._emit("}", null); } this._emitter._emit("\n", null); } } class _CatchStatementEmitter extends _StatementEmitter { var _statement : CatchStatement; function constructor (emitter : JavaScriptEmitter, statement : CatchStatement) { super(emitter); this._statement = statement; } override function emit () : void { var localType = this._statement.getLocal().getType(); if (localType instanceof ObjectType) { this._emitter._emit("if (" + this._emitter.getNamer().getNameOfLocalVariable(this._statement.getLocal()) + " instanceof " + this._emitter.getNamer().getNameOfClass(localType.getClassDef()) + ") {\n", this._statement.getToken()); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("} else ", null); } else { this._emitter._emit("{\n", null); this._emitter._emitStatements(this._statement.getStatements()); this._emitter._emit("}\n", null); } } } class _ThrowStatementEmitter extends _StatementEmitter { var _statement : ThrowStatement; function constructor (emitter : JavaScriptEmitter, statement : ThrowStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("throw ", this._statement.getToken()); this._emitter._getExpressionEmitterFor(this._statement.getExpr()).emit(0); this._emitter._emit(";\n", null); } } class _AssertStatementEmitter extends _StatementEmitter { var _statement : AssertStatement; function constructor (emitter : JavaScriptEmitter, statement : AssertStatement) { super(emitter); this._statement = statement; } override function emit () : void { var condExpr = this._statement._expr; this._emitter._emitAssertion(function () { this._emitter._getExpressionEmitterFor(condExpr).emit(0); }, condExpr.getToken(), "assertion failure"); } } class _LogStatementEmitter extends _StatementEmitter { var _statement : LogStatement; function constructor (emitter : JavaScriptEmitter, statement : LogStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("console.log(", this._statement.getToken()); var exprs = this._statement.getExprs(); for (var i = 0; i < exprs.length; ++i) { if (i != 0) this._emitter._emit(", ", null); this._emitter._getExpressionEmitterFor(exprs[i]).emit(0); } this._emitter._emit(");\n", null); } } class _DebuggerStatementEmitter extends _StatementEmitter { var _statement : DebuggerStatement; function constructor (emitter : JavaScriptEmitter, statement : DebuggerStatement) { super(emitter); this._statement = statement; } override function emit () : void { this._emitter._emit("debugger;\n", this._statement.getToken()); } } // expression emitter abstract class _ExpressionEmitter { var _emitter : JavaScriptEmitter; function constructor (emitter : JavaScriptEmitter) { this._emitter = emitter; } abstract function emit (outerOpPrecedence : number) : void; function emitWithPrecedence (outerOpPrecedence : number, precedence : number, callback : function():void) : void { if (precedence > outerOpPrecedence) { this._emitter._emit("(", null); callback(); this._emitter._emit(")", null); } else { callback(); } } } class _LocalExpressionEmitter extends _ExpressionEmitter { var _expr : LocalExpression; function constructor (emitter : JavaScriptEmitter, expr : LocalExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var local = this._expr.getLocal(); this._emitter._emit(this._emitter.getNamer().getNameOfLocalVariable(local), this._expr.getToken()); } } class _ClassExpressionEmitter extends _ExpressionEmitter { var _expr : ClassExpression; function constructor (emitter : JavaScriptEmitter, expr : ClassExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var type = this._expr.getType(); this._emitter._emit(this._emitter.getNamer().getNameOfClass(type.getClassDef()), null); } } class _NullExpressionEmitter extends _ExpressionEmitter { var _expr : NullExpression; function constructor (emitter : JavaScriptEmitter, expr : NullExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); this._emitter._emit("null", token); } } class _BooleanLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : BooleanLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : BooleanLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); this._emitter._emit(token.getValue(), token); } } class _IntegerLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : IntegerLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : IntegerLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); this._emitter._emit("" + token.getValue(), token); } } class _NumberLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : NumberLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : NumberLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); var str = token.getValue(); if (outerOpPrecedence == _PropertyExpressionEmitter._operatorPrecedence && str.indexOf(".") == -1) { this._emitter._emit("(" + str + ")", token); } else { this._emitter._emit("" + str, token); } } } class _StringLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : StringLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : StringLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); // FIXME escape this._emitter._emit(token.getValue(), token); } } class _RegExpLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : RegExpLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : RegExpLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var token = this._expr.getToken(); this._emitter._emit(token.getValue(), token); } } class _ArrayLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : ArrayLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : ArrayLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { this._emitter._emit("[ ", null); var exprs = this._expr.getExprs(); for (var i = 0; i < exprs.length; ++i) { if (i != 0) this._emitter._emit(", ", null); this._emitter._getExpressionEmitterFor(exprs[i]).emit(0); } this._emitter._emit(" ]", null); } } class _MapLiteralExpressionEmitter extends _ExpressionEmitter { var _expr : MapLiteralExpression; function constructor (emitter : JavaScriptEmitter, expr : MapLiteralExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { this._emitter._emit("({ ", null); var elements = this._expr.getElements(); for (var i = 0; i < elements.length; ++i) { var element = elements[i]; if (i != 0) this._emitter._emit(", ", null); this._emitter._emit(element.getKey().getValue(), element.getKey()); this._emitter._emit(": ", null); this._emitter._getExpressionEmitterFor(element.getExpr()).emit(0); } this._emitter._emit(" })", null); } } class _ThisExpressionEmitter extends _ExpressionEmitter { var _expr : ThisExpression; function constructor (emitter : JavaScriptEmitter, expr : ThisExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var emittingFunction = this._emitter._emittingFunction; if ((emittingFunction.flags() & ClassDefinition.IS_STATIC) != 0) this._emitter._emit("$this", this._expr.getToken()); else this._emitter._emit("this", this._expr.getToken()); } } class _AsExpressionEmitter extends _ExpressionEmitter { var _expr : AsExpression; function constructor (emitter : JavaScriptEmitter, expr : AsExpression) { super(emitter); this._expr = expr; } override function emit (outerOpPrecedence : number) : void { var srcType = this._expr.getExpr().getType(); var destType = this._expr.getType(); if (srcType instanceof ObjectType || srcType.equals(Type.variantType)) { if (srcType.isConvertibleTo(destType)) { this._emitter._getExpressionEmitterFor(this._expr.getExpr()).emit(outerOpPrecedence); return; } if (destType instanceof ObjectType || destType instanceof FunctionType) { // unsafe cast new _AsNoConvertExpressionEmitter(this._emitter, new AsNoConvertExpression(this._expr.getToken(), this._expr.getExpr(), this._expr.getType())).emit(outerOpPrecedence); return; } } if (srcType.resolveIfNullabl