| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136 |
- /***********************************************************************
- A JavaScript tokenizer / parser / beautifier / compressor.
- https://github.com/mishoo/UglifyJS2
- -------------------------------- (C) ---------------------------------
- Author: Mihai Bazon
- <mihai.bazon@gmail.com>
- http://mihai.bazon.net/blog
- Distributed under the BSD license:
- Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
- 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.
- ***********************************************************************/
- import {
- AST_Accessor,
- AST_Array,
- AST_Arrow,
- AST_Assign,
- AST_BigInt,
- AST_Binary,
- AST_Block,
- AST_BlockStatement,
- AST_Boolean,
- AST_Break,
- AST_Call,
- AST_Catch,
- AST_Chain,
- AST_Class,
- AST_ClassProperty,
- AST_ClassStaticBlock,
- AST_ConciseMethod,
- AST_Conditional,
- AST_Const,
- AST_Constant,
- AST_Debugger,
- AST_Default,
- AST_DefaultAssign,
- AST_Definitions,
- AST_Defun,
- AST_Destructuring,
- AST_Directive,
- AST_Do,
- AST_Dot,
- AST_DotHash,
- AST_DWLoop,
- AST_EmptyStatement,
- AST_Exit,
- AST_Expansion,
- AST_Export,
- AST_False,
- AST_For,
- AST_ForIn,
- AST_Function,
- AST_Hole,
- AST_If,
- AST_Import,
- AST_Infinity,
- AST_LabeledStatement,
- AST_Lambda,
- AST_Let,
- AST_NaN,
- AST_New,
- AST_Node,
- AST_Null,
- AST_Number,
- AST_Object,
- AST_ObjectKeyVal,
- AST_ObjectProperty,
- AST_PrefixedTemplateString,
- AST_PropAccess,
- AST_RegExp,
- AST_Return,
- AST_Scope,
- AST_Sequence,
- AST_SimpleStatement,
- AST_Statement,
- AST_String,
- AST_Sub,
- AST_Switch,
- AST_SwitchBranch,
- AST_Symbol,
- AST_SymbolClassProperty,
- AST_SymbolDeclaration,
- AST_SymbolDefun,
- AST_SymbolExport,
- AST_SymbolFunarg,
- AST_SymbolLambda,
- AST_SymbolLet,
- AST_SymbolMethod,
- AST_SymbolRef,
- AST_SymbolUsing,
- AST_TemplateString,
- AST_This,
- AST_Toplevel,
- AST_True,
- AST_Try,
- AST_Unary,
- AST_UnaryPostfix,
- AST_UnaryPrefix,
- AST_Undefined,
- AST_Using,
- AST_Var,
- AST_VarDef,
- AST_While,
- AST_With,
- AST_Yield,
- TreeTransformer,
- TreeWalker,
- walk,
- walk_abort,
- _NOINLINE,
- } from "../ast.js";
- import {
- defaults,
- HOP,
- make_node,
- make_void_0,
- makePredicate,
- MAP,
- remove,
- return_false,
- return_true,
- regexp_source_fix,
- has_annotation,
- regexp_is_safe,
- } from "../utils/index.js";
- import { first_in_statement } from "../utils/first_in_statement.js";
- import { equivalent_to } from "../equivalent-to.js";
- import {
- is_basic_identifier_string,
- JS_Parse_Error,
- parse,
- PRECEDENCE,
- } from "../parse.js";
- import { OutputStream } from "../output.js";
- import { base54, format_mangler_options } from "../scope.js";
- import "../size.js";
- import "./evaluate.js";
- import "./drop-side-effect-free.js";
- import "./drop-unused.js";
- import "./reduce-vars.js";
- import {
- is_undeclared_ref,
- bitwise_binop,
- lazy_op,
- is_nullish,
- is_undefined,
- is_lhs,
- aborts,
- is_used_in_expression,
- } from "./inference.js";
- import {
- SQUEEZED,
- OPTIMIZED,
- CLEAR_BETWEEN_PASSES,
- TOP,
- UNDEFINED,
- UNUSED,
- TRUTHY,
- FALSY,
- has_flag,
- set_flag,
- clear_flag,
- } from "./compressor-flags.js";
- import {
- make_sequence,
- best_of,
- best_of_expression,
- make_empty_function,
- make_node_from_constant,
- merge_sequence,
- get_simple_key,
- has_break_or_continue,
- maintain_this_binding,
- is_empty,
- is_identifier_atom,
- is_reachable,
- can_be_evicted_from_block,
- as_statement_array,
- is_func_expr,
- } from "./common.js";
- import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
- import { inline_into_symbolref, inline_into_call } from "./inline.js";
- import "./global-defs.js";
- import { is_pure_native_fn, is_pure_native_method, is_pure_native_static_fn, is_pure_native_static_property, pure_access_globals } from "./native-objects.js";
- class Compressor extends TreeWalker {
- constructor(options, { false_by_default = false, mangle_options = false }) {
- super();
- if (options.defaults !== undefined && !options.defaults) false_by_default = true;
- this.options = defaults(options, {
- arguments : false,
- arrows : !false_by_default,
- booleans : !false_by_default,
- booleans_as_integers : false,
- collapse_vars : !false_by_default,
- comparisons : !false_by_default,
- computed_props: !false_by_default,
- conditionals : !false_by_default,
- dead_code : !false_by_default,
- defaults : true,
- directives : !false_by_default,
- drop_console : false,
- drop_debugger : !false_by_default,
- ecma : 5,
- builtins_ecma : 5,
- builtins_pure : false,
- evaluate : !false_by_default,
- expression : false,
- global_defs : false,
- hoist_funs : false,
- hoist_props : !false_by_default,
- hoist_vars : false,
- ie8 : false,
- if_return : !false_by_default,
- inline : !false_by_default,
- join_vars : !false_by_default,
- keep_classnames: false,
- keep_fargs : true,
- keep_fnames : false,
- keep_infinity : false,
- lhs_constants : !false_by_default,
- loops : !false_by_default,
- module : false,
- negate_iife : !false_by_default,
- passes : 1,
- properties : !false_by_default,
- pure_getters : !false_by_default && "strict",
- pure_funcs : null,
- pure_new : false,
- reduce_funcs : !false_by_default,
- reduce_vars : !false_by_default,
- sequences : !false_by_default,
- side_effects : !false_by_default,
- switches : !false_by_default,
- top_retain : null,
- toplevel : !!(options && options["top_retain"]),
- typeofs : !false_by_default,
- unsafe : false,
- unsafe_arrows : false,
- unsafe_comps : false,
- unsafe_Function: false,
- unsafe_math : false,
- unsafe_symbols: false,
- unsafe_methods: false,
- unsafe_proto : false,
- unsafe_regexp : false,
- unsafe_undefined: false,
- unused : !false_by_default,
- warnings : false // legacy
- }, true);
- var global_defs = this.options["global_defs"];
- if (typeof global_defs == "object") for (var key in global_defs) {
- if (key[0] === "@" && HOP(global_defs, key)) {
- global_defs[key.slice(1)] = parse(global_defs[key], {
- expression: true
- });
- }
- }
- if (this.options["inline"] === true) this.options["inline"] = 3;
- var pure_funcs = this.options["pure_funcs"];
- if (typeof pure_funcs == "function") {
- this.pure_funcs = pure_funcs;
- } else {
- this.pure_funcs = pure_funcs ? function(node) {
- return !pure_funcs.includes(node.expression.print_to_string());
- } : return_true;
- }
- var top_retain = this.options["top_retain"];
- if (top_retain instanceof RegExp) {
- this.top_retain = function(def) {
- return top_retain.test(def.name);
- };
- } else if (typeof top_retain == "function") {
- this.top_retain = top_retain;
- } else if (top_retain) {
- if (typeof top_retain == "string") {
- top_retain = top_retain.split(/,/);
- }
- this.top_retain = function(def) {
- return top_retain.includes(def.name);
- };
- }
- if (this.options["module"]) {
- this.directives["use strict"] = true;
- this.options["toplevel"] = true;
- }
- var toplevel = this.options["toplevel"];
- this.toplevel = typeof toplevel == "string" ? {
- funcs: /funcs/.test(toplevel),
- vars: /vars/.test(toplevel)
- } : {
- funcs: toplevel,
- vars: toplevel
- };
- var sequences = this.options["sequences"];
- this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
- this.evaluated_regexps = new Map();
- this._toplevel = undefined;
- this._mangle_options = mangle_options
- ? format_mangler_options(mangle_options)
- : mangle_options;
- this.pure_access_globals = pure_access_globals(this);
- this.is_pure_native_fn = is_pure_native_fn(this);
- this.is_pure_native_method = is_pure_native_method(this);
- this.is_pure_native_static_fn = is_pure_native_static_fn(this);
- this.is_pure_native_static_property = is_pure_native_static_property(this);
- }
- mangle_options() {
- var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
- var module = this._mangle_options && this._mangle_options.module || this.option("module");
- return { ie8: this.option("ie8"), nth_identifier, module };
- }
- option(key) {
- return this.options[key];
- }
- exposed(def) {
- if (def.export) return true;
- if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
- if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
- return true;
- return false;
- }
- in_boolean_context() {
- if (!this.option("booleans")) return false;
- var self = this.self();
- for (var i = 0, p; p = this.parent(i); i++) {
- if (p instanceof AST_SimpleStatement
- || p instanceof AST_Conditional && p.condition === self
- || p instanceof AST_DWLoop && p.condition === self
- || p instanceof AST_For && p.condition === self
- || p instanceof AST_If && p.condition === self
- || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
- return true;
- }
- if (
- p instanceof AST_Binary
- && (
- p.operator == "&&"
- || p.operator == "||"
- || p.operator == "??"
- )
- || p instanceof AST_Conditional
- || p.tail_node() === self
- ) {
- self = p;
- } else {
- return false;
- }
- }
- }
- /** True if compressor.self()'s result will be turned into a 32-bit integer.
- * ex:
- * ~{expr}
- * (1, 2, {expr}) | 0
- **/
- in_32_bit_context(other_operand_must_be_number) {
- if (!this.option("evaluate")) return false;
- var self = this.self();
- for (var i = 0, p; p = this.parent(i); i++) {
- if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
- if (other_operand_must_be_number) {
- return (self === p.left ? p.right : p.left).is_number(this);
- } else {
- return true;
- }
- }
- if (p instanceof AST_UnaryPrefix) {
- return p.operator === "~";
- }
- if (
- p instanceof AST_Binary
- && (
- // Don't talk about p.left. Can change branch taken
- p.operator == "&&" && p.right === self
- || p.operator == "||" && p.right === self
- || p.operator == "??" && p.right === self
- )
- || p instanceof AST_Conditional && p.condition !== self
- || p.tail_node() === self
- ) {
- self = p;
- } else {
- return false;
- }
- }
- }
- in_computed_key() {
- if (!this.option("evaluate")) return false;
- var self = this.self();
- for (var i = 0, p; p = this.parent(i); i++) {
- if (p instanceof AST_ObjectProperty && p.key === self) {
- return true;
- }
- }
- return false;
- }
- get_toplevel() {
- return this._toplevel;
- }
- compress(toplevel) {
- toplevel = toplevel.resolve_defines(this);
- this._toplevel = toplevel;
- if (this.option("expression")) {
- this._toplevel.process_expression(true);
- }
- var passes = +this.options.passes || 1;
- var min_count = 1 / 0;
- var stopping = false;
- var mangle = this.mangle_options();
- for (var pass = 0; pass < passes; pass++) {
- this._toplevel.figure_out_scope(mangle);
- if (pass === 0 && this.option("drop_console")) {
- // must be run before reduce_vars and compress pass
- this._toplevel = this._toplevel.drop_console(this.option("drop_console"));
- }
- if (pass > 0 || this.option("reduce_vars")) {
- this._toplevel.reset_opt_flags(this);
- }
- this._toplevel = this._toplevel.transform(this);
- if (passes > 1) {
- let count = 0;
- walk(this._toplevel, () => { count++; });
- if (count < min_count) {
- min_count = count;
- stopping = false;
- } else if (stopping) {
- break;
- } else {
- stopping = true;
- }
- }
- }
- if (this.option("expression")) {
- this._toplevel.process_expression(false);
- }
- toplevel = this._toplevel;
- this._toplevel = undefined;
- return toplevel;
- }
- before(node, descend) {
- if (has_flag(node, SQUEEZED)) return node;
- var was_scope = false;
- if (node instanceof AST_Scope) {
- node = node.hoist_properties(this);
- node = node.hoist_declarations(this);
- was_scope = true;
- }
- // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
- // would call AST_Node.transform() if a different instance of AST_Node is
- // produced after def_optimize().
- // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
- // Migrate and defer all children's AST_Node.transform() to below, which
- // will now happen after this parent AST_Node has been properly substituted
- // thus gives a consistent AST snapshot.
- descend(node, this);
- // Existing code relies on how AST_Node.optimize() worked, and omitting the
- // following replacement call would result in degraded efficiency of both
- // output and performance.
- descend(node, this);
- var opt = node.optimize(this);
- if (was_scope && opt instanceof AST_Scope) {
- opt.drop_unused(this);
- descend(opt, this);
- }
- if (opt === node) set_flag(opt, SQUEEZED);
- return opt;
- }
- /** Alternative to plain is_lhs() which doesn't work within .optimize() */
- is_lhs() {
- const self = this.stack[this.stack.length - 1];
- const parent = this.stack[this.stack.length - 2];
- return is_lhs(self, parent);
- }
- }
- function def_optimize(node, optimizer) {
- node.DEFMETHOD("optimize", function(compressor) {
- var self = this;
- if (has_flag(self, OPTIMIZED)) return self;
- if (compressor.has_directive("use asm")) return self;
- var opt = optimizer(self, compressor);
- set_flag(opt, OPTIMIZED);
- return opt;
- });
- }
- def_optimize(AST_Node, function(self) {
- return self;
- });
- AST_Toplevel.DEFMETHOD("drop_console", function(options) {
- const isArray = Array.isArray(options);
- const tt = new TreeTransformer(function(self) {
- if (self.TYPE !== "Call") {
- return;
- }
- var exp = self.expression;
- if (!(exp instanceof AST_PropAccess)) {
- return;
- }
- var name = exp.expression;
- var property = exp.property;
- var depth = 2;
- while (name.expression) {
- property = name.property;
- name = name.expression;
- depth++;
- }
- if (isArray && !options.includes(property)) {
- return;
- }
- if (is_undeclared_ref(name) && name.name == "console") {
- if (
- depth === 3
- && !["call", "apply"].includes(exp.property)
- && is_used_in_expression(tt)
- ) {
- // a (used) call to Function.prototype methods (eg: console.log.bind(console))
- // but not .call and .apply which would also return undefined.
- exp.expression = make_empty_function(self);
- set_flag(exp.expression, SQUEEZED);
- self.args = [];
- } else {
- return make_void_0(self);
- }
- }
- });
- return this.transform(tt);
- });
- AST_Node.DEFMETHOD("equivalent_to", function(node) {
- return equivalent_to(this, node);
- });
- AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
- var self = this;
- var tt = new TreeTransformer(function(node) {
- if (insert && node instanceof AST_SimpleStatement) {
- return make_node(AST_Return, node, {
- value: node.body
- });
- }
- if (!insert && node instanceof AST_Return) {
- if (compressor) {
- var value = node.value && node.value.drop_side_effect_free(compressor, true);
- return value
- ? make_node(AST_SimpleStatement, node, { body: value })
- : make_node(AST_EmptyStatement, node);
- }
- return make_node(AST_SimpleStatement, node, {
- body: node.value || make_void_0(node)
- });
- }
- if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
- return node;
- }
- if (node instanceof AST_Block) {
- var index = node.body.length - 1;
- if (index >= 0) {
- node.body[index] = node.body[index].transform(tt);
- }
- } else if (node instanceof AST_If) {
- node.body = node.body.transform(tt);
- if (node.alternative) {
- node.alternative = node.alternative.transform(tt);
- }
- } else if (node instanceof AST_With) {
- node.body = node.body.transform(tt);
- }
- return node;
- });
- self.transform(tt);
- });
- AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
- const self = this;
- const reduce_vars = compressor.option("reduce_vars");
- const preparation = new TreeWalker(function(node, descend) {
- clear_flag(node, CLEAR_BETWEEN_PASSES);
- if (reduce_vars) {
- if (compressor.top_retain
- && node instanceof AST_Defun // Only functions are retained
- && preparation.parent() === self
- ) {
- set_flag(node, TOP);
- }
- return node.reduce_vars(preparation, descend, compressor);
- }
- });
- // Stack of look-up tables to keep track of whether a `SymbolDef` has been
- // properly assigned before use:
- // - `push()` & `pop()` when visiting conditional branches
- preparation.safe_ids = Object.create(null);
- preparation.in_loop = null;
- preparation.loop_ids = new Map();
- preparation.defs_to_safe_ids = new Map();
- self.walk(preparation);
- });
- AST_Symbol.DEFMETHOD("fixed_value", function() {
- var fixed = this.thedef.fixed;
- if (!fixed || fixed instanceof AST_Node) return fixed;
- return fixed();
- });
- AST_SymbolRef.DEFMETHOD("is_immutable", function() {
- var orig = this.definition().orig;
- return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
- });
- function find_variable(compressor, name) {
- var scope, i = 0;
- while (scope = compressor.parent(i++)) {
- if (scope instanceof AST_Scope) break;
- if (scope instanceof AST_Catch && scope.argname) {
- scope = scope.argname.definition().scope;
- break;
- }
- }
- return scope.find_variable(name);
- }
- AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
- return !this.definition().undeclared
- || (compressor.option("unsafe") || compressor.option("builtins_pure")) && compressor.pure_access_globals(this.name);
- });
- /* -----[ optimizers ]----- */
- var directives = new Set(["use asm", "use strict"]);
- def_optimize(AST_Directive, function(self, compressor) {
- if (compressor.option("directives")
- && (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) {
- return make_node(AST_EmptyStatement, self);
- }
- return self;
- });
- def_optimize(AST_Debugger, function(self, compressor) {
- if (compressor.option("drop_debugger"))
- return make_node(AST_EmptyStatement, self);
- return self;
- });
- def_optimize(AST_LabeledStatement, function(self, compressor) {
- if (self.body instanceof AST_Break
- && compressor.loopcontrol_target(self.body) === self.body) {
- return make_node(AST_EmptyStatement, self);
- }
- return self.label.references.length == 0 ? self.body : self;
- });
- def_optimize(AST_Block, function(self, compressor) {
- tighten_body(self.body, compressor);
- return self;
- });
- function can_be_extracted_from_if_block(node) {
- return !(
- node instanceof AST_Const
- || node instanceof AST_Let
- || node instanceof AST_Using
- || node instanceof AST_Class
- );
- }
- def_optimize(AST_BlockStatement, function(self, compressor) {
- tighten_body(self.body, compressor);
- switch (self.body.length) {
- case 1:
- if (!compressor.has_directive("use strict")
- && compressor.parent() instanceof AST_If
- && can_be_extracted_from_if_block(self.body[0])
- || can_be_evicted_from_block(self.body[0])) {
- return self.body[0];
- }
- break;
- case 0: return make_node(AST_EmptyStatement, self);
- }
- return self;
- });
- function opt_AST_Lambda(self, compressor) {
- tighten_body(self.body, compressor);
- if (compressor.option("side_effects")
- && self.body.length == 1
- && self.body[0] === compressor.has_directive("use strict")) {
- self.body.length = 0;
- }
- return self;
- }
- def_optimize(AST_Lambda, opt_AST_Lambda);
- AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
- var self = this;
- if (compressor.has_directive("use asm")) return self;
- var hoist_funs = compressor.option("hoist_funs");
- var hoist_vars = compressor.option("hoist_vars");
- if (hoist_funs || hoist_vars) {
- var dirs = [];
- var hoisted = [];
- var vars = new Map(), vars_found = 0, var_decl = 0;
- // let's count var_decl first, we seem to waste a lot of
- // space if we hoist `var` when there's only one.
- walk(self, node => {
- if (node instanceof AST_Scope && node !== self)
- return true;
- if (node instanceof AST_Var) {
- ++var_decl;
- return true;
- }
- });
- hoist_vars = hoist_vars && var_decl > 1;
- var tt = new TreeTransformer(
- function before(node) {
- if (node !== self) {
- if (node instanceof AST_Directive) {
- dirs.push(node);
- return make_node(AST_EmptyStatement, node);
- }
- if (hoist_funs && node instanceof AST_Defun
- && !(tt.parent() instanceof AST_Export)
- && tt.parent() === self) {
- hoisted.push(node);
- return make_node(AST_EmptyStatement, node);
- }
- if (
- hoist_vars
- && node instanceof AST_Var
- && !node.definitions.some(def => def.name instanceof AST_Destructuring)
- ) {
- node.definitions.forEach(function(def) {
- vars.set(def.name.name, def);
- ++vars_found;
- });
- var seq = node.to_assignments(compressor);
- var p = tt.parent();
- if (p instanceof AST_ForIn && p.init === node) {
- if (seq == null) {
- var def = node.definitions[0].name;
- return make_node(AST_SymbolRef, def, def);
- }
- return seq;
- }
- if (p instanceof AST_For && p.init === node) {
- return seq;
- }
- if (!seq) return make_node(AST_EmptyStatement, node);
- return make_node(AST_SimpleStatement, node, {
- body: seq
- });
- }
- if (node instanceof AST_Scope)
- return node; // to avoid descending in nested scopes
- }
- }
- );
- self = self.transform(tt);
- if (vars_found > 0) {
- // collect only vars which don't show up in self's arguments list
- var defs = [];
- const is_lambda = self instanceof AST_Lambda;
- const args_as_names = is_lambda ? self.args_as_names() : null;
- vars.forEach((def, name) => {
- if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) {
- vars.delete(name);
- } else {
- def = def.clone();
- def.value = null;
- defs.push(def);
- vars.set(name, def);
- }
- });
- if (defs.length > 0) {
- // try to merge in assignments
- for (var i = 0; i < self.body.length;) {
- if (self.body[i] instanceof AST_SimpleStatement) {
- var expr = self.body[i].body, sym, assign;
- if (expr instanceof AST_Assign
- && expr.operator == "="
- && (sym = expr.left) instanceof AST_Symbol
- && vars.has(sym.name)
- ) {
- var def = vars.get(sym.name);
- if (def.value) break;
- def.value = expr.right;
- remove(defs, def);
- defs.push(def);
- self.body.splice(i, 1);
- continue;
- }
- if (expr instanceof AST_Sequence
- && (assign = expr.expressions[0]) instanceof AST_Assign
- && assign.operator == "="
- && (sym = assign.left) instanceof AST_Symbol
- && vars.has(sym.name)
- ) {
- var def = vars.get(sym.name);
- if (def.value) break;
- def.value = assign.right;
- remove(defs, def);
- defs.push(def);
- self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
- continue;
- }
- }
- if (self.body[i] instanceof AST_EmptyStatement) {
- self.body.splice(i, 1);
- continue;
- }
- if (self.body[i] instanceof AST_BlockStatement) {
- self.body.splice(i, 1, ...self.body[i].body);
- continue;
- }
- break;
- }
- defs = make_node(AST_Var, self, {
- definitions: defs
- });
- hoisted.push(defs);
- }
- }
- self.body = dirs.concat(hoisted, self.body);
- }
- return self;
- });
- AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
- var self = this;
- if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
- var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
- var defs_by_id = new Map();
- var hoister = new TreeTransformer(function(node, descend) {
- if (node instanceof AST_VarDef) {
- const sym = node.name;
- let def;
- let value;
- if (sym.scope === self
- && !(sym instanceof AST_SymbolUsing)
- && (def = sym.definition()).escaped != 1
- && !def.assignments
- && !def.direct_access
- && !def.single_use
- && !compressor.exposed(def)
- && !top_retain(def)
- && (value = sym.fixed_value()) === node.value
- && value instanceof AST_Object
- && !value.properties.some(prop =>
- prop instanceof AST_Expansion || prop.computed_key()
- )
- ) {
- descend(node, this);
- const defs = new Map();
- const assignments = [];
- value.properties.forEach(({ key, value }) => {
- const scope = hoister.find_scope();
- const symbol = self.create_symbol(sym.CTOR, {
- source: sym,
- scope,
- conflict_scopes: new Set([
- scope,
- ...sym.definition().references.map(ref => ref.scope)
- ]),
- tentative_name: sym.name + "_" + key
- });
- defs.set(String(key), symbol.definition());
- assignments.push(make_node(AST_VarDef, node, {
- name: symbol,
- value
- }));
- });
- defs_by_id.set(def.id, defs);
- return MAP.splice(assignments);
- }
- } else if (node instanceof AST_PropAccess
- && node.expression instanceof AST_SymbolRef
- ) {
- const defs = defs_by_id.get(node.expression.definition().id);
- if (defs) {
- const def = defs.get(String(get_simple_key(node.property)));
- const sym = make_node(AST_SymbolRef, node, {
- name: def.name,
- scope: node.expression.scope,
- thedef: def
- });
- sym.reference({});
- return sym;
- }
- }
- });
- return self.transform(hoister);
- });
- def_optimize(AST_SimpleStatement, function(self, compressor) {
- if (compressor.option("side_effects")) {
- var body = self.body;
- var node = body.drop_side_effect_free(compressor, true);
- if (!node) {
- return make_node(AST_EmptyStatement, self);
- }
- if (node !== body) {
- return make_node(AST_SimpleStatement, self, { body: node });
- }
- }
- return self;
- });
- def_optimize(AST_While, function(self, compressor) {
- return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
- });
- def_optimize(AST_Do, function(self, compressor) {
- if (!compressor.option("loops")) return self;
- var cond = self.condition.tail_node().evaluate(compressor);
- if (!(cond instanceof AST_Node)) {
- if (cond) return make_node(AST_For, self, {
- body: make_node(AST_BlockStatement, self.body, {
- body: [
- self.body,
- make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- })
- ]
- })
- }).optimize(compressor);
- if (!has_break_or_continue(self, compressor.parent())) {
- return make_node(AST_BlockStatement, self.body, {
- body: [
- self.body,
- make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- })
- ]
- }).optimize(compressor);
- }
- }
- return self;
- });
- function if_break_in_loop(self, compressor) {
- var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
- if (compressor.option("dead_code") && is_break(first)) {
- var body = [];
- if (self.init instanceof AST_Statement) {
- body.push(self.init);
- } else if (self.init) {
- body.push(make_node(AST_SimpleStatement, self.init, {
- body: self.init
- }));
- }
- if (self.condition) {
- body.push(make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }));
- }
- extract_from_unreachable_code(compressor, self.body, body);
- return make_node(AST_BlockStatement, self, {
- body: body
- });
- }
- if (first instanceof AST_If) {
- if (is_break(first.body)) {
- if (self.condition) {
- self.condition = make_node(AST_Binary, self.condition, {
- left: self.condition,
- operator: "&&",
- right: first.condition.negate(compressor),
- });
- } else {
- self.condition = first.condition.negate(compressor);
- }
- drop_it(first.alternative);
- } else if (is_break(first.alternative)) {
- if (self.condition) {
- self.condition = make_node(AST_Binary, self.condition, {
- left: self.condition,
- operator: "&&",
- right: first.condition,
- });
- } else {
- self.condition = first.condition;
- }
- drop_it(first.body);
- }
- }
- return self;
- function is_break(node) {
- return node instanceof AST_Break
- && compressor.loopcontrol_target(node) === compressor.self();
- }
- function drop_it(rest) {
- rest = as_statement_array(rest);
- if (self.body instanceof AST_BlockStatement) {
- self.body = self.body.clone();
- self.body.body = rest.concat(self.body.body.slice(1));
- self.body = self.body.transform(compressor);
- } else {
- self.body = make_node(AST_BlockStatement, self.body, {
- body: rest
- }).transform(compressor);
- }
- self = if_break_in_loop(self, compressor);
- }
- }
- def_optimize(AST_For, function(self, compressor) {
- if (!compressor.option("loops")) return self;
- if (compressor.option("side_effects") && self.init) {
- self.init = self.init.drop_side_effect_free(compressor);
- }
- if (self.condition) {
- var cond = self.condition.evaluate(compressor);
- if (!(cond instanceof AST_Node)) {
- if (cond) self.condition = null;
- else if (!compressor.option("dead_code")) {
- var orig = self.condition;
- self.condition = make_node_from_constant(cond, self.condition);
- self.condition = best_of_expression(self.condition.transform(compressor), orig);
- }
- }
- if (compressor.option("dead_code")) {
- if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
- if (!cond) {
- var body = [];
- extract_from_unreachable_code(compressor, self.body, body);
- if (self.init instanceof AST_Statement) {
- body.push(self.init);
- } else if (self.init) {
- body.push(make_node(AST_SimpleStatement, self.init, {
- body: self.init
- }));
- }
- body.push(make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }));
- return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
- }
- }
- }
- return if_break_in_loop(self, compressor);
- });
- def_optimize(AST_If, function(self, compressor) {
- if (is_empty(self.alternative)) self.alternative = null;
- if (!compressor.option("conditionals")) return self;
- // if condition can be statically determined, drop
- // one of the blocks. note, statically determined implies
- // “has no side effects”; also it doesn't work for cases like
- // `x && true`, though it probably should.
- var cond = self.condition.evaluate(compressor);
- if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
- var orig = self.condition;
- self.condition = make_node_from_constant(cond, orig);
- self.condition = best_of_expression(self.condition.transform(compressor), orig);
- }
- if (compressor.option("dead_code")) {
- if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
- if (!cond) {
- var body = [];
- extract_from_unreachable_code(compressor, self.body, body);
- body.push(make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }));
- if (self.alternative) body.push(self.alternative);
- return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
- } else if (!(cond instanceof AST_Node)) {
- var body = [];
- body.push(make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }));
- body.push(self.body);
- if (self.alternative) {
- extract_from_unreachable_code(compressor, self.alternative, body);
- }
- return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
- }
- }
- var negated = self.condition.negate(compressor);
- var self_condition_length = self.condition.size();
- var negated_length = negated.size();
- var negated_is_best = negated_length < self_condition_length;
- if (self.alternative && negated_is_best) {
- negated_is_best = false; // because we already do the switch here.
- // no need to swap values of self_condition_length and negated_length
- // here because they are only used in an equality comparison later on.
- self.condition = negated;
- var tmp = self.body;
- self.body = self.alternative || make_node(AST_EmptyStatement, self);
- self.alternative = tmp;
- }
- if (is_empty(self.body) && is_empty(self.alternative)) {
- return make_node(AST_SimpleStatement, self.condition, {
- body: self.condition.clone()
- }).optimize(compressor);
- }
- if (self.body instanceof AST_SimpleStatement
- && self.alternative instanceof AST_SimpleStatement) {
- return make_node(AST_SimpleStatement, self, {
- body: make_node(AST_Conditional, self, {
- condition : self.condition,
- consequent : self.body.body,
- alternative : self.alternative.body
- })
- }).optimize(compressor);
- }
- if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
- if (self_condition_length === negated_length && !negated_is_best
- && self.condition instanceof AST_Binary && self.condition.operator == "||") {
- // although the code length of self.condition and negated are the same,
- // negated does not require additional surrounding parentheses.
- // see https://github.com/mishoo/UglifyJS2/issues/979
- negated_is_best = true;
- }
- if (negated_is_best) return make_node(AST_SimpleStatement, self, {
- body: make_node(AST_Binary, self, {
- operator : "||",
- left : negated,
- right : self.body.body
- })
- }).optimize(compressor);
- return make_node(AST_SimpleStatement, self, {
- body: make_node(AST_Binary, self, {
- operator : "&&",
- left : self.condition,
- right : self.body.body
- })
- }).optimize(compressor);
- }
- if (self.body instanceof AST_EmptyStatement
- && self.alternative instanceof AST_SimpleStatement) {
- return make_node(AST_SimpleStatement, self, {
- body: make_node(AST_Binary, self, {
- operator : "||",
- left : self.condition,
- right : self.alternative.body
- })
- }).optimize(compressor);
- }
- if (self.body instanceof AST_Exit
- && self.alternative instanceof AST_Exit
- && self.body.TYPE == self.alternative.TYPE) {
- return make_node(self.body.CTOR, self, {
- value: make_node(AST_Conditional, self, {
- condition : self.condition,
- consequent : self.body.value || make_void_0(self.body),
- alternative : self.alternative.value || make_void_0(self.alternative),
- }).transform(compressor)
- }).optimize(compressor);
- }
- if (self.body instanceof AST_If
- && !self.body.alternative
- && !self.alternative) {
- self = make_node(AST_If, self, {
- condition: make_node(AST_Binary, self.condition, {
- operator: "&&",
- left: self.condition,
- right: self.body.condition
- }),
- body: self.body.body,
- alternative: null
- });
- }
- if (aborts(self.body)) {
- if (self.alternative) {
- var alt = self.alternative;
- self.alternative = null;
- return make_node(AST_BlockStatement, self, {
- body: [ self, alt ]
- }).optimize(compressor);
- }
- }
- if (aborts(self.alternative)) {
- var body = self.body;
- self.body = self.alternative;
- self.condition = negated_is_best ? negated : self.condition.negate(compressor);
- self.alternative = null;
- return make_node(AST_BlockStatement, self, {
- body: [ self, body ]
- }).optimize(compressor);
- }
- return self;
- });
- def_optimize(AST_Switch, function(self, compressor) {
- if (!compressor.option("switches")) return self;
- var branch;
- var value = self.expression.evaluate(compressor);
- if (!(value instanceof AST_Node)) {
- var orig = self.expression;
- self.expression = make_node_from_constant(value, orig);
- self.expression = best_of_expression(self.expression.transform(compressor), orig);
- }
- if (!compressor.option("dead_code")) return self;
- if (value instanceof AST_Node) {
- value = self.expression.tail_node().evaluate(compressor);
- }
- var decl = [];
- var body = [];
- var default_branch;
- var exact_match;
- // - compress self.body into `body`
- // - find and deduplicate default branch
- // - find the exact match (`case 1234` inside `switch(1234)`)
- for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
- branch = self.body[i];
- if (branch instanceof AST_Default) {
- if (!default_branch) {
- default_branch = branch;
- } else {
- eliminate_branch(branch, body[body.length - 1]);
- }
- } else if (!(value instanceof AST_Node)) {
- var exp = branch.expression.evaluate(compressor);
- if (!(exp instanceof AST_Node) && exp !== value) {
- eliminate_branch(branch, body[body.length - 1]);
- continue;
- }
- if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
- exp = branch.expression.tail_node().evaluate(compressor);
- }
- if (exp === value) {
- exact_match = branch;
- if (default_branch) {
- var default_index = body.indexOf(default_branch);
- body.splice(default_index, 1);
- eliminate_branch(default_branch, body[default_index - 1]);
- default_branch = null;
- }
- }
- }
- body.push(branch);
- }
- // i < len if we found an exact_match. eliminate the rest
- while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
- self.body = body;
- let default_or_exact = default_branch || exact_match;
- default_branch = null;
- exact_match = null;
- // group equivalent branches so they will be located next to each other,
- // that way the next micro-optimization will merge them.
- // ** bail micro-optimization if not a simple switch case with breaks
- if (body.every((branch, i) =>
- (branch === default_or_exact || branch.expression instanceof AST_Constant)
- && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
- ) {
- for (let i = 0; i < body.length; i++) {
- const branch = body[i];
- for (let j = i + 1; j < body.length; j++) {
- const next = body[j];
- if (next.body.length === 0) continue;
- const last_branch = j === (body.length - 1);
- const equivalentBranch = branches_equivalent(next, branch, false);
- if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
- if (!equivalentBranch && last_branch) {
- next.body.push(make_node(AST_Break));
- }
- // let's find previous siblings with inert fallthrough...
- let x = j - 1;
- let fallthroughDepth = 0;
- while (x > i) {
- if (is_inert_body(body[x--])) {
- fallthroughDepth++;
- } else {
- break;
- }
- }
- const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
- body.splice(i + 1, 0, ...plucked);
- i += plucked.length;
- }
- }
- }
- }
- // merge equivalent branches in a row
- for (let i = 0; i < body.length; i++) {
- let branch = body[i];
- if (branch.body.length === 0) continue;
- if (!aborts(branch)) continue;
- for (let j = i + 1; j < body.length; i++, j++) {
- let next = body[j];
- if (next.body.length === 0) continue;
- if (
- branches_equivalent(next, branch, false)
- || (j === body.length - 1 && branches_equivalent(next, branch, true))
- ) {
- branch.body = [];
- branch = next;
- continue;
- }
- break;
- }
- }
- // Prune any empty branches at the end of the switch statement.
- {
- let i = body.length - 1;
- for (; i >= 0; i--) {
- let bbody = body[i].body;
- while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
- if (!is_inert_body(body[i])) break;
- }
- // i now points to the index of a branch that contains a body. By incrementing, it's
- // pointing to the first branch that's empty.
- i++;
- if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
- // The default behavior is to do nothing. We can take advantage of that to
- // remove all case expressions that are side-effect free that also do
- // nothing, since they'll default to doing nothing. But we can't remove any
- // case expressions before one that would side-effect, since they may cause
- // the side-effect to be skipped.
- for (let j = body.length - 1; j >= i; j--) {
- let branch = body[j];
- if (branch === default_or_exact) {
- default_or_exact = null;
- eliminate_branch(body.pop());
- } else if (!branch.expression.has_side_effects(compressor)) {
- eliminate_branch(body.pop());
- } else {
- break;
- }
- }
- }
- }
- // Prune side-effect free branches that fall into default.
- DEFAULT: if (default_or_exact) {
- let default_index = body.indexOf(default_or_exact);
- let default_body_index = default_index;
- for (; default_body_index < body.length - 1; default_body_index++) {
- if (!is_inert_body(body[default_body_index])) break;
- }
- if (default_body_index < body.length - 1) {
- break DEFAULT;
- }
- let side_effect_index = body.length - 1;
- for (; side_effect_index >= 0; side_effect_index--) {
- let branch = body[side_effect_index];
- if (branch === default_or_exact) continue;
- if (branch.expression.has_side_effects(compressor)) break;
- }
- // If the default behavior comes after any side-effect case expressions,
- // then we can fold all side-effect free cases into the default branch.
- // If the side-effect case is after the default, then any side-effect
- // free cases could prevent the side-effect from occurring.
- if (default_body_index > side_effect_index) {
- let prev_body_index = default_index - 1;
- for (; prev_body_index >= 0; prev_body_index--) {
- if (!is_inert_body(body[prev_body_index])) break;
- }
- let before = Math.max(side_effect_index, prev_body_index) + 1;
- let after = default_index;
- if (side_effect_index > default_index) {
- // If the default falls into the same body as a side-effect
- // case, then we need preserve that case and only prune the
- // cases after it.
- after = side_effect_index;
- body[side_effect_index].body = body[default_body_index].body;
- } else {
- // The default will be the last branch.
- default_or_exact.body = body[default_body_index].body;
- }
- // Prune everything after the default (or last side-effect case)
- // until the next case with a body.
- body.splice(after + 1, default_body_index - after);
- // Prune everything before the default that falls into it.
- body.splice(before, default_index - before);
- }
- }
- // See if we can remove the switch entirely if all cases (the default) fall into the same case body.
- DEFAULT: if (default_or_exact) {
- let i = body.findIndex(branch => !is_inert_body(branch));
- let caseBody;
- // `i` is equal to one of the following:
- // - `-1`, there is no body in the switch statement.
- // - `body.length - 1`, all cases fall into the same body.
- // - anything else, there are multiple bodies in the switch.
- if (i === body.length - 1) {
- // All cases fall into the case body.
- let branch = body[i];
- if (has_nested_break(self)) break DEFAULT;
- // This is the last case body, and we've already pruned any breaks, so it's
- // safe to hoist.
- caseBody = make_node(AST_BlockStatement, branch, {
- body: branch.body
- });
- branch.body = [];
- } else if (i !== -1) {
- // If there are multiple bodies, then we cannot optimize anything.
- break DEFAULT;
- }
- let sideEffect = body.find(
- branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
- );
- // If no cases cause a side-effect, we can eliminate the switch entirely.
- if (!sideEffect) {
- return make_node(AST_BlockStatement, self, {
- body: decl.concat(
- statement(self.expression),
- default_or_exact.expression ? statement(default_or_exact.expression) : [],
- caseBody || []
- )
- }).optimize(compressor);
- }
- // If we're this far, either there was no body or all cases fell into the same body.
- // If there was no body, then we don't need a default branch (because the default is
- // do nothing). If there was a body, we'll extract it to after the switch, so the
- // switch's new default is to do nothing and we can still prune it.
- const default_index = body.indexOf(default_or_exact);
- body.splice(default_index, 1);
- default_or_exact = null;
- if (caseBody) {
- // Recurse into switch statement one more time so that we can append the case body
- // outside of the switch. This recursion will only happen once since we've pruned
- // the default case.
- return make_node(AST_BlockStatement, self, {
- body: decl.concat(self, caseBody)
- }).optimize(compressor);
- }
- // If we fall here, there is a default branch somewhere, there are no case bodies,
- // and there's a side-effect somewhere. Just let the below paths take care of it.
- }
- // Reintegrate `decl` (var statements)
- if (body.length > 0) {
- body[0].body = decl.concat(body[0].body);
- }
- if (body.length == 0) {
- return make_node(AST_BlockStatement, self, {
- body: decl.concat(statement(self.expression))
- }).optimize(compressor);
- }
- if (body.length == 1 && !has_nested_break(self)) {
- // This is the last case body, and we've already pruned any breaks, so it's
- // safe to hoist.
- let branch = body[0];
- return make_node(AST_If, self, {
- condition: make_node(AST_Binary, self, {
- operator: "===",
- left: self.expression,
- right: branch.expression,
- }),
- body: make_node(AST_BlockStatement, branch, {
- body: branch.body
- }),
- alternative: null
- }).optimize(compressor);
- }
- if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
- let branch = body[0] === default_or_exact ? body[1] : body[0];
- let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
- if (aborts(body[0])) {
- // Only the first branch body could have a break (at the last statement)
- let first = body[0];
- if (is_break(first.body[first.body.length - 1], compressor)) {
- first.body.pop();
- }
- return make_node(AST_If, self, {
- condition: make_node(AST_Binary, self, {
- operator: "===",
- left: self.expression,
- right: branch.expression,
- }),
- body: make_node(AST_BlockStatement, branch, {
- body: branch.body
- }),
- alternative: make_node(AST_BlockStatement, default_or_exact, {
- body: [].concat(
- exact_exp || [],
- default_or_exact.body
- )
- })
- }).optimize(compressor);
- }
- let operator = "===";
- let consequent = make_node(AST_BlockStatement, branch, {
- body: branch.body,
- });
- let always = make_node(AST_BlockStatement, default_or_exact, {
- body: [].concat(
- exact_exp || [],
- default_or_exact.body
- )
- });
- if (body[0] === default_or_exact) {
- operator = "!==";
- let tmp = always;
- always = consequent;
- consequent = tmp;
- }
- return make_node(AST_BlockStatement, self, {
- body: [
- make_node(AST_If, self, {
- condition: make_node(AST_Binary, self, {
- operator: operator,
- left: self.expression,
- right: branch.expression,
- }),
- body: consequent,
- alternative: null,
- }),
- always,
- ],
- }).optimize(compressor);
- }
- return self;
- function eliminate_branch(branch, prev) {
- if (prev && !aborts(prev)) {
- prev.body = prev.body.concat(branch.body);
- } else {
- extract_from_unreachable_code(compressor, branch, decl);
- }
- }
- function branches_equivalent(branch, prev, insertBreak) {
- let bbody = branch.body;
- let pbody = prev.body;
- if (insertBreak) {
- bbody = bbody.concat(make_node(AST_Break));
- }
- if (bbody.length !== pbody.length) return false;
- let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
- let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
- return bblock.equivalent_to(pblock);
- }
- function statement(body) {
- return make_node(AST_SimpleStatement, body, { body });
- }
- function has_nested_break(root) {
- let has_break = false;
- let tw = new TreeWalker(node => {
- if (has_break) return true;
- if (node instanceof AST_Lambda) return true;
- if (node instanceof AST_SimpleStatement) return true;
- if (!is_break(node, tw)) return;
- let parent = tw.parent();
- if (
- parent instanceof AST_SwitchBranch
- && parent.body[parent.body.length - 1] === node
- ) {
- return;
- }
- has_break = true;
- });
- root.walk(tw);
- return has_break;
- }
- function is_break(node, stack) {
- return node instanceof AST_Break
- && stack.loopcontrol_target(node) === self;
- }
- function is_inert_body(branch) {
- return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
- body: branch.body
- }).has_side_effects(compressor);
- }
- });
- def_optimize(AST_Try, function(self, compressor) {
- if (self.bcatch && self.bfinally && self.bfinally.body.every(is_empty)) self.bfinally = null;
- if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
- var body = [];
- if (self.bcatch) {
- extract_from_unreachable_code(compressor, self.bcatch, body);
- }
- if (self.bfinally) body.push(...self.bfinally.body);
- return make_node(AST_BlockStatement, self, {
- body: body
- }).optimize(compressor);
- }
- return self;
- });
- AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
- var reduce_vars = compressor.option("reduce_vars");
- var assignments = [];
- for (const def of this.definitions) {
- if (def.value) {
- var name = make_node(AST_SymbolRef, def.name, def.name);
- assignments.push(make_node(AST_Assign, def, {
- operator : "=",
- logical: false,
- left : name,
- right : def.value
- }));
- if (reduce_vars) name.definition().fixed = false;
- }
- const thedef = def.name.definition();
- thedef.eliminated++;
- thedef.replaced--;
- }
- if (assignments.length == 0) return null;
- return make_sequence(this, assignments);
- });
- def_optimize(AST_Definitions, function(self) {
- if (self.definitions.length == 0) {
- return make_node(AST_EmptyStatement, self);
- }
- return self;
- });
- def_optimize(AST_VarDef, function(self, compressor) {
- if (
- self.name instanceof AST_SymbolLet
- && self.value != null
- && is_undefined(self.value, compressor)
- ) {
- self.value = null;
- }
- return self;
- });
- def_optimize(AST_Import, function(self) {
- return self;
- });
- def_optimize(AST_Call, function(self, compressor) {
- var exp = self.expression;
- var fn = exp;
- inline_array_like_spread(self.args);
- var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
- if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
- fn = fn.fixed_value();
- }
- var is_func = fn instanceof AST_Lambda;
- if (is_func && fn.pinned()) return self;
- if (compressor.option("unused")
- && simple_args
- && is_func
- && !fn.uses_arguments) {
- var pos = 0, last = 0;
- for (var i = 0, len = self.args.length; i < len; i++) {
- if (fn.argnames[i] instanceof AST_Expansion) {
- if (has_flag(fn.argnames[i].expression, UNUSED)) while (i < len) {
- var node = self.args[i++].drop_side_effect_free(compressor);
- if (node) {
- self.args[pos++] = node;
- }
- } else while (i < len) {
- self.args[pos++] = self.args[i++];
- }
- last = pos;
- break;
- }
- var trim = i >= fn.argnames.length;
- if (trim || has_flag(fn.argnames[i], UNUSED)) {
- var node = self.args[i].drop_side_effect_free(compressor);
- if (node) {
- self.args[pos++] = node;
- } else if (!trim) {
- self.args[pos++] = make_node(AST_Number, self.args[i], {
- value: 0
- });
- continue;
- }
- } else {
- self.args[pos++] = self.args[i];
- }
- last = pos;
- }
- self.args.length = last;
- }
- if (
- exp instanceof AST_Dot
- && exp.expression instanceof AST_SymbolRef
- && exp.expression.name === "console"
- && exp.expression.definition().undeclared
- && exp.property === "assert"
- ) {
- const condition = self.args[0];
- if (condition) {
- const value = condition.evaluate(compressor);
-
- if (value === 1 || value === true) {
- return make_void_0(self).optimize(compressor);
- }
- }
- }
- if (compressor.option("unsafe") && !exp.contains_optional()) {
- if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
- const [argument] = self.args;
- if (argument instanceof AST_Array) {
- return make_node(AST_Array, argument, {
- elements: argument.elements
- }).optimize(compressor);
- }
- }
- if (is_undeclared_ref(exp)) switch (exp.name) {
- case "Array":
- if (self.args.length != 1) {
- return make_node(AST_Array, self, {
- elements: self.args
- }).optimize(compressor);
- } else if (self.args[0] instanceof AST_Number && self.args[0].value <= 11) {
- const elements = [];
- for (let i = 0; i < self.args[0].value; i++) elements.push(new AST_Hole);
- return new AST_Array({ elements });
- }
- break;
- case "Object":
- if (self.args.length == 0) {
- return make_node(AST_Object, self, {
- properties: []
- });
- }
- break;
- case "String":
- if (self.args.length == 0) return make_node(AST_String, self, {
- value: ""
- });
- if (self.args.length <= 1) return make_node(AST_Binary, self, {
- left: self.args[0],
- operator: "+",
- right: make_node(AST_String, self, { value: "" })
- }).optimize(compressor);
- break;
- case "Number":
- if (self.args.length == 0) return make_node(AST_Number, self, {
- value: 0
- });
- if (self.args.length == 1 && compressor.option("unsafe_math")) {
- return make_node(AST_UnaryPrefix, self, {
- expression: self.args[0],
- operator: "+"
- }).optimize(compressor);
- }
- break;
- case "Symbol":
- if (self.args.length == 1 && self.args[0] instanceof AST_String && compressor.option("unsafe_symbols"))
- self.args.length = 0;
- break;
- case "Boolean":
- if (self.args.length == 0) return make_node(AST_False, self);
- if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
- expression: make_node(AST_UnaryPrefix, self, {
- expression: self.args[0],
- operator: "!"
- }),
- operator: "!"
- }).optimize(compressor);
- break;
- case "RegExp":
- var params = [];
- if (self.args.length >= 1
- && self.args.length <= 2
- && self.args.every((arg) => {
- var value = arg.evaluate(compressor);
- params.push(value);
- return arg !== value;
- })
- && regexp_is_safe(params[0])
- ) {
- let [ source, flags ] = params;
- source = regexp_source_fix(new RegExp(source).source);
- const rx = make_node(AST_RegExp, self, {
- value: { source, flags }
- });
- if (rx._eval(compressor) !== rx) {
- return rx;
- }
- }
- break;
- } else if (exp instanceof AST_Dot) switch(exp.property) {
- case "toString":
- if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
- return make_node(AST_Binary, self, {
- left: make_node(AST_String, self, { value: "" }),
- operator: "+",
- right: exp.expression
- }).optimize(compressor);
- }
- break;
- case "join":
- if (exp.expression instanceof AST_Array) EXIT: {
- var separator;
- if (self.args.length > 0) {
- separator = self.args[0].evaluate(compressor);
- if (separator === self.args[0]) break EXIT; // not a constant
- }
- var elements = [];
- var consts = [];
- for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
- var el = exp.expression.elements[i];
- if (el instanceof AST_Expansion) break EXIT;
- var value = el.evaluate(compressor);
- if (value !== el) {
- consts.push(value);
- } else {
- if (consts.length > 0) {
- elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
- }));
- consts.length = 0;
- }
- elements.push(el);
- }
- }
- if (consts.length > 0) {
- elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
- }));
- }
- if (elements.length == 0) return make_node(AST_String, self, { value: "" });
- if (elements.length == 1) {
- if (elements[0].is_string(compressor)) {
- return elements[0];
- }
- return make_node(AST_Binary, elements[0], {
- operator : "+",
- left : make_node(AST_String, self, { value: "" }),
- right : elements[0]
- });
- }
- if (separator == "") {
- var first;
- if (elements[0].is_string(compressor)
- || elements[1].is_string(compressor)) {
- first = elements.shift();
- } else {
- first = make_node(AST_String, self, { value: "" });
- }
- return elements.reduce(function(prev, el) {
- return make_node(AST_Binary, el, {
- operator : "+",
- left : prev,
- right : el
- });
- }, first).optimize(compressor);
- }
- // need this awkward cloning to not affect original element
- // best_of will decide which one to get through.
- var node = self.clone();
- node.expression = node.expression.clone();
- node.expression.expression = node.expression.expression.clone();
- node.expression.expression.elements = elements;
- return best_of(compressor, self, node);
- }
- break;
- case "charAt":
- if (exp.expression.is_string(compressor)) {
- var arg = self.args[0];
- var index = arg ? arg.evaluate(compressor) : 0;
- if (index !== arg) {
- return make_node(AST_Sub, exp, {
- expression: exp.expression,
- property: make_node_from_constant(index | 0, arg || exp)
- }).optimize(compressor);
- }
- }
- break;
- case "apply":
- if (self.args.length == 2 && self.args[1] instanceof AST_Array) {
- var args = self.args[1].elements.slice();
- args.unshift(self.args[0]);
- return make_node(AST_Call, self, {
- expression: make_node(AST_Dot, exp, {
- expression: exp.expression,
- optional: false,
- property: "call"
- }),
- args: args
- }).optimize(compressor);
- }
- break;
- case "call":
- var func = exp.expression;
- if (func instanceof AST_SymbolRef) {
- func = func.fixed_value();
- }
- if (func instanceof AST_Lambda && !func.contains_this()) {
- return (self.args.length ? make_sequence(this, [
- self.args[0],
- make_node(AST_Call, self, {
- expression: exp.expression,
- args: self.args.slice(1)
- })
- ]) : make_node(AST_Call, self, {
- expression: exp.expression,
- args: []
- })).optimize(compressor);
- }
- break;
- }
- }
- if (compressor.option("unsafe_Function")
- && is_undeclared_ref(exp)
- && exp.name == "Function") {
- // new Function() => function(){}
- if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
- if (self.args.every((x) => x instanceof AST_String)) {
- // quite a corner-case, but we can handle it:
- // https://github.com/mishoo/UglifyJS2/issues/203
- // if the code argument is a constant, then we can minify it.
- try {
- var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
- return arg.value;
- }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
- var ast = parse(code);
- var mangle = compressor.mangle_options();
- ast.figure_out_scope(mangle);
- var comp = new Compressor(compressor.options, {
- mangle_options: compressor._mangle_options
- });
- ast = ast.transform(comp);
- ast.figure_out_scope(mangle);
- ast.compute_char_frequency(mangle);
- ast.mangle_names(mangle);
- var fun;
- walk(ast, node => {
- if (is_func_expr(node)) {
- fun = node;
- return walk_abort;
- }
- });
- var code = OutputStream();
- AST_BlockStatement.prototype._codegen.call(fun, fun, code);
- self.args = [
- make_node(AST_String, self, {
- value: fun.argnames.map(function(arg) {
- return arg.print_to_string();
- }).join(",")
- }),
- make_node(AST_String, self.args[self.args.length - 1], {
- value: code.get().replace(/^{|}$/g, "")
- })
- ];
- return self;
- } catch (ex) {
- if (!(ex instanceof JS_Parse_Error)) {
- throw ex;
- }
- // Otherwise, it crashes at runtime. Or maybe it's nonstandard syntax.
- }
- }
- }
- return inline_into_call(self, compressor);
- });
- /** Does this node contain optional property access or optional call? */
- AST_Node.DEFMETHOD("contains_optional", function() {
- if (
- this instanceof AST_PropAccess
- || this instanceof AST_Call
- || this instanceof AST_Chain
- ) {
- if (this.optional) {
- return true;
- } else {
- return this.expression.contains_optional();
- }
- } else {
- return false;
- }
- });
- def_optimize(AST_New, function(self, compressor) {
- if (
- compressor.option("unsafe") &&
- is_undeclared_ref(self.expression) &&
- ["Object", "RegExp", "Function", "Error", "Array"].includes(self.expression.name)
- ) return make_node(AST_Call, self, self).transform(compressor);
- return self;
- });
- def_optimize(AST_Sequence, function(self, compressor) {
- if (!compressor.option("side_effects")) return self;
- var expressions = [];
- filter_for_side_effects();
- var end = expressions.length - 1;
- trim_right_for_undefined();
- if (end == 0) {
- self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
- if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
- return self;
- }
- self.expressions = expressions;
- return self;
- function filter_for_side_effects() {
- var first = first_in_statement(compressor);
- var last = self.expressions.length - 1;
- self.expressions.forEach(function(expr, index) {
- if (index < last) expr = expr.drop_side_effect_free(compressor, first);
- if (expr) {
- merge_sequence(expressions, expr);
- first = false;
- }
- });
- }
- function trim_right_for_undefined() {
- while (end > 0 && is_undefined(expressions[end], compressor)) end--;
- if (end < expressions.length - 1) {
- expressions[end] = make_node(AST_UnaryPrefix, self, {
- operator : "void",
- expression : expressions[end]
- });
- expressions.length = end + 1;
- }
- }
- });
- AST_Unary.DEFMETHOD("lift_sequences", function(compressor) {
- if (compressor.option("sequences")) {
- if (this.expression instanceof AST_Sequence) {
- var x = this.expression.expressions.slice();
- var e = this.clone();
- e.expression = x.pop();
- x.push(e);
- return make_sequence(this, x).optimize(compressor);
- }
- }
- return this;
- });
- def_optimize(AST_UnaryPostfix, function(self, compressor) {
- return self.lift_sequences(compressor);
- });
- def_optimize(AST_UnaryPrefix, function(self, compressor) {
- var e = self.expression;
- if (
- self.operator == "delete" &&
- !(
- e instanceof AST_SymbolRef ||
- e instanceof AST_PropAccess ||
- e instanceof AST_Chain ||
- is_identifier_atom(e)
- )
- ) {
- return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
- }
- // Short-circuit common `void 0`
- if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
- return unsafe_undefined_ref(self, compressor) || self;
- }
- var seq = self.lift_sequences(compressor);
- if (seq !== self) {
- return seq;
- }
- if (compressor.option("side_effects") && self.operator == "void") {
- e = e.drop_side_effect_free(compressor);
- if (e) {
- self.expression = e;
- return self;
- } else {
- return make_void_0(self).optimize(compressor);
- }
- }
- if (compressor.in_boolean_context()) {
- switch (self.operator) {
- case "!":
- if (e instanceof AST_UnaryPrefix && e.operator == "!") {
- // !!foo ==> foo, if we're in boolean context
- return e.expression;
- }
- if (e instanceof AST_Binary) {
- self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
- }
- break;
- case "typeof":
- // typeof always returns a non-empty string, thus it's
- // always true in booleans
- // And we don't need to check if it's undeclared, because in typeof, that's OK
- return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
- e,
- make_node(AST_True, self)
- ])).optimize(compressor);
- }
- }
- if (self.operator == "-" && e instanceof AST_Infinity) {
- e = e.transform(compressor);
- }
- if (e instanceof AST_Binary
- && (self.operator == "+" || self.operator == "-")
- && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
- return make_node(AST_Binary, self, {
- operator: e.operator,
- left: make_node(AST_UnaryPrefix, e.left, {
- operator: self.operator,
- expression: e.left
- }),
- right: e.right
- });
- }
- if (compressor.option("evaluate")) {
- // ~~x => x (in 32-bit context)
- // ~~{32 bit integer} => {32 bit integer}
- if (
- self.operator === "~"
- && self.expression instanceof AST_UnaryPrefix
- && self.expression.operator === "~"
- && (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor))
- ) {
- return self.expression.expression;
- }
- // ~(x ^ y) => x ^ ~y
- if (
- self.operator === "~"
- && e instanceof AST_Binary
- && e.operator === "^"
- ) {
- if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
- // ~(~x ^ y) => x ^ y
- e.left = e.left.bitwise_negate(compressor, true);
- } else {
- e.right = e.right.bitwise_negate(compressor, true);
- }
- return e;
- }
- }
- if (
- self.operator != "-"
- // avoid infinite recursion of numerals
- || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
- ) {
- var ev = self.evaluate(compressor);
- if (ev !== self) {
- ev = make_node_from_constant(ev, self).optimize(compressor);
- return best_of(compressor, ev, self);
- }
- }
- return self;
- });
- AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
- if (compressor.option("sequences")) {
- if (this.left instanceof AST_Sequence) {
- var x = this.left.expressions.slice();
- var e = this.clone();
- e.left = x.pop();
- x.push(e);
- return make_sequence(this, x).optimize(compressor);
- }
- if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
- var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
- var x = this.right.expressions;
- var last = x.length - 1;
- for (var i = 0; i < last; i++) {
- if (!assign && x[i].has_side_effects(compressor)) break;
- }
- if (i == last) {
- x = x.slice();
- var e = this.clone();
- e.right = x.pop();
- x.push(e);
- return make_sequence(this, x).optimize(compressor);
- } else if (i > 0) {
- var e = this.clone();
- e.right = make_sequence(this.right, x.slice(i));
- x = x.slice(0, i);
- x.push(e);
- return make_sequence(this, x).optimize(compressor);
- }
- }
- }
- return this;
- });
- var commutativeOperators = makePredicate("== === != !== * & | ^");
- function is_object(node) {
- return node instanceof AST_Array
- || node instanceof AST_Lambda
- || node instanceof AST_Object
- || node instanceof AST_Class;
- }
- def_optimize(AST_Binary, function(self, compressor) {
- function reversible() {
- return self.left.is_constant()
- || self.right.is_constant()
- || !self.left.has_side_effects(compressor)
- && !self.right.has_side_effects(compressor);
- }
- function reverse(op) {
- if (reversible()) {
- if (op) self.operator = op;
- var tmp = self.left;
- self.left = self.right;
- self.right = tmp;
- }
- }
- if (compressor.option("lhs_constants") && commutativeOperators.has(self.operator)) {
- if (self.right.is_constant()
- && !self.left.is_constant()) {
- // if right is a constant, whatever side effects the
- // left side might have could not influence the
- // result. hence, force switch.
- if (!(self.left instanceof AST_Binary
- && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
- reverse();
- }
- }
- }
- self = self.lift_sequences(compressor);
- if (compressor.option("comparisons")) switch (self.operator) {
- case "===":
- case "!==":
- var is_strict_comparison = true;
- if (
- (self.left.is_string(compressor) && self.right.is_string(compressor)) ||
- (self.left.is_number(compressor) && self.right.is_number(compressor)) ||
- (self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) ||
- (self.left.is_boolean() && self.right.is_boolean()) ||
- self.left.equivalent_to(self.right)
- ) {
- self.operator = self.operator.substr(0, 2);
- }
- // XXX: intentionally falling down to the next case
- case "==":
- case "!=":
- // void 0 == x => null == x
- if (!is_strict_comparison && is_undefined(self.left, compressor)) {
- self.left = make_node(AST_Null, self.left);
- // x == void 0 => x == null
- } else if (!is_strict_comparison && is_undefined(self.right, compressor)) {
- self.right = make_node(AST_Null, self.right);
- } else if (compressor.option("typeofs")
- // "undefined" == typeof x => undefined === x
- && self.left instanceof AST_String
- && self.left.value == "undefined"
- && self.right instanceof AST_UnaryPrefix
- && self.right.operator == "typeof") {
- var expr = self.right.expression;
- if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
- : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
- self.right = expr;
- self.left = make_void_0(self.left).optimize(compressor);
- if (self.operator.length == 2) self.operator += "=";
- }
- } else if (compressor.option("typeofs")
- // typeof x === "undefined" => x === undefined
- && self.left instanceof AST_UnaryPrefix
- && self.left.operator == "typeof"
- && self.right instanceof AST_String
- && self.right.value == "undefined") {
- var expr = self.left.expression;
- if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
- : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
- self.left = expr;
- self.right = make_void_0(self.right).optimize(compressor);
- if (self.operator.length == 2) self.operator += "=";
- }
- } else if (self.left instanceof AST_SymbolRef
- // obj !== obj => false
- && self.right instanceof AST_SymbolRef
- && self.left.definition() === self.right.definition()
- && is_object(self.left.fixed_value())) {
- return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
- } else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) {
- const not = node => make_node(AST_UnaryPrefix, node, {
- operator: "!",
- expression: node
- });
- const booleanify = (node, truthy) => {
- if (truthy) {
- return compressor.in_boolean_context()
- ? node
- : not(not(node));
- } else {
- return not(node);
- }
- };
- // The only falsy 32-bit integer is 0
- if (self.left instanceof AST_Number && self.left.value === 0) {
- return booleanify(self.right, self.operator[0] === "!");
- }
- if (self.right instanceof AST_Number && self.right.value === 0) {
- return booleanify(self.left, self.operator[0] === "!");
- }
- // Mask all-bits check
- // (x & 0xFF) != 0xFF => !(~x & 0xFF)
- let and_op, x, mask;
- if (
- (and_op =
- self.left instanceof AST_Binary ? self.left
- : self.right instanceof AST_Binary ? self.right : null)
- && (mask = and_op === self.left ? self.right : self.left)
- && and_op.operator === "&"
- && mask instanceof AST_Number
- && mask.is_32_bit_integer(compressor)
- && (x =
- and_op.left.equivalent_to(mask) ? and_op.right
- : and_op.right.equivalent_to(mask) ? and_op.left : null)
- ) {
- let optimized = booleanify(make_node(AST_Binary, self, {
- operator: "&",
- left: mask,
- right: make_node(AST_UnaryPrefix, self, {
- operator: "~",
- expression: x
- })
- }), self.operator[0] === "!");
- return best_of(compressor, optimized, self);
- }
- }
- break;
- case "&&":
- case "||":
- var lhs = self.left;
- if (lhs.operator == self.operator) {
- lhs = lhs.right;
- }
- if (lhs instanceof AST_Binary
- && lhs.operator == (self.operator == "&&" ? "!==" : "===")
- && self.right instanceof AST_Binary
- && lhs.operator == self.right.operator
- && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
- || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
- && !lhs.right.has_side_effects(compressor)
- && lhs.right.equivalent_to(self.right.right)) {
- var combined = make_node(AST_Binary, self, {
- operator: lhs.operator.slice(0, -1),
- left: make_node(AST_Null, self),
- right: lhs.right
- });
- if (lhs !== self.left) {
- combined = make_node(AST_Binary, self, {
- operator: self.operator,
- left: self.left.left,
- right: combined
- });
- }
- return combined;
- }
- break;
- }
- if (self.operator == "+" && compressor.in_boolean_context()) {
- var ll = self.left.evaluate(compressor);
- var rr = self.right.evaluate(compressor);
- if (ll && typeof ll == "string") {
- return make_sequence(self, [
- self.right,
- make_node(AST_True, self)
- ]).optimize(compressor);
- }
- if (rr && typeof rr == "string") {
- return make_sequence(self, [
- self.left,
- make_node(AST_True, self)
- ]).optimize(compressor);
- }
- }
- if (compressor.option("comparisons") && self.is_boolean()) {
- if (!(compressor.parent() instanceof AST_Binary)
- || compressor.parent() instanceof AST_Assign) {
- var negated = make_node(AST_UnaryPrefix, self, {
- operator: "!",
- expression: self.negate(compressor, first_in_statement(compressor))
- });
- self = best_of(compressor, self, negated);
- }
- if (compressor.option("unsafe_comps")) {
- switch (self.operator) {
- case "<": reverse(">"); break;
- case "<=": reverse(">="); break;
- }
- }
- }
- if (self.operator == "+") {
- if (self.right instanceof AST_String
- && self.right.getValue() == ""
- && self.left.is_string(compressor)) {
- return self.left;
- }
- if (self.left instanceof AST_String
- && self.left.getValue() == ""
- && self.right.is_string(compressor)) {
- return self.right;
- }
- if (self.left instanceof AST_Binary
- && self.left.operator == "+"
- && self.left.left instanceof AST_String
- && self.left.left.getValue() == ""
- && self.right.is_string(compressor)) {
- self.left = self.left.right;
- return self;
- }
- }
- if (compressor.option("evaluate")) {
- switch (self.operator) {
- case "&&":
- var ll = has_flag(self.left, TRUTHY)
- ? true
- : has_flag(self.left, FALSY)
- ? false
- : self.left.evaluate(compressor);
- if (!ll) {
- return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
- } else if (!(ll instanceof AST_Node)) {
- return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
- }
- var rr = self.right.evaluate(compressor);
- if (!rr) {
- if (compressor.in_boolean_context()) {
- return make_sequence(self, [
- self.left,
- make_node(AST_False, self)
- ]).optimize(compressor);
- } else {
- set_flag(self, FALSY);
- }
- } else if (!(rr instanceof AST_Node)) {
- var parent = compressor.parent();
- if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
- return self.left.optimize(compressor);
- }
- }
- // x || false && y ---> x ? y : false
- if (self.left.operator == "||") {
- var lr = self.left.right.evaluate(compressor);
- if (!lr) return make_node(AST_Conditional, self, {
- condition: self.left.left,
- consequent: self.right,
- alternative: self.left.right
- }).optimize(compressor);
- }
- break;
- case "||":
- var ll = has_flag(self.left, TRUTHY)
- ? true
- : has_flag(self.left, FALSY)
- ? false
- : self.left.evaluate(compressor);
- if (!ll) {
- return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
- } else if (!(ll instanceof AST_Node)) {
- return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
- }
- var rr = self.right.evaluate(compressor);
- if (!rr) {
- var parent = compressor.parent();
- if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
- return self.left.optimize(compressor);
- }
- } else if (!(rr instanceof AST_Node)) {
- if (compressor.in_boolean_context()) {
- return make_sequence(self, [
- self.left,
- make_node(AST_True, self)
- ]).optimize(compressor);
- } else {
- set_flag(self, TRUTHY);
- }
- }
- if (self.left.operator == "&&") {
- var lr = self.left.right.evaluate(compressor);
- if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
- condition: self.left.left,
- consequent: self.left.right,
- alternative: self.right
- }).optimize(compressor);
- }
- break;
- case "??":
- if (is_nullish(self.left, compressor)) {
- return self.right;
- }
- var ll = self.left.evaluate(compressor);
- if (!(ll instanceof AST_Node)) {
- // if we know the value for sure we can simply compute right away.
- return ll == null ? self.right : self.left;
- }
- if (compressor.in_boolean_context()) {
- const rr = self.right.evaluate(compressor);
- if (!(rr instanceof AST_Node) && !rr) {
- return self.left;
- }
- }
- }
- var associative = true;
- switch (self.operator) {
- case "+":
- // (x + "foo") + "bar" => x + "foobar"
- if (self.right instanceof AST_Constant
- && self.left instanceof AST_Binary
- && self.left.operator == "+"
- && self.left.is_string(compressor)) {
- var binary = make_node(AST_Binary, self, {
- operator: "+",
- left: self.left.right,
- right: self.right,
- });
- var r = binary.optimize(compressor);
- if (binary !== r) {
- self = make_node(AST_Binary, self, {
- operator: "+",
- left: self.left.left,
- right: r
- });
- }
- }
- // (x + "foo") + ("bar" + y) => (x + "foobar") + y
- if (self.left instanceof AST_Binary
- && self.left.operator == "+"
- && self.left.is_string(compressor)
- && self.right instanceof AST_Binary
- && self.right.operator == "+"
- && self.right.is_string(compressor)) {
- var binary = make_node(AST_Binary, self, {
- operator: "+",
- left: self.left.right,
- right: self.right.left,
- });
- var m = binary.optimize(compressor);
- if (binary !== m) {
- self = make_node(AST_Binary, self, {
- operator: "+",
- left: make_node(AST_Binary, self.left, {
- operator: "+",
- left: self.left.left,
- right: m
- }),
- right: self.right.right
- });
- }
- }
- // a + -b => a - b
- if (self.right instanceof AST_UnaryPrefix
- && self.right.operator == "-"
- && self.left.is_number_or_bigint(compressor)) {
- self = make_node(AST_Binary, self, {
- operator: "-",
- left: self.left,
- right: self.right.expression
- });
- break;
- }
- // -a + b => b - a
- if (self.left instanceof AST_UnaryPrefix
- && self.left.operator == "-"
- && reversible()
- && self.right.is_number_or_bigint(compressor)) {
- self = make_node(AST_Binary, self, {
- operator: "-",
- left: self.right,
- right: self.left.expression
- });
- break;
- }
- // `foo${bar}baz` + 1 => `foo${bar}baz1`
- if (self.left instanceof AST_TemplateString) {
- var l = self.left;
- var r = self.right.evaluate(compressor);
- if (r != self.right) {
- l.segments[l.segments.length - 1].value += String(r);
- return l;
- }
- }
- // 1 + `foo${bar}baz` => `1foo${bar}baz`
- if (self.right instanceof AST_TemplateString) {
- var r = self.right;
- var l = self.left.evaluate(compressor);
- if (l != self.left) {
- r.segments[0].value = String(l) + r.segments[0].value;
- return r;
- }
- }
- // `1${bar}2` + `foo${bar}baz` => `1${bar}2foo${bar}baz`
- if (self.left instanceof AST_TemplateString
- && self.right instanceof AST_TemplateString) {
- var l = self.left;
- var segments = l.segments;
- var r = self.right;
- segments[segments.length - 1].value += r.segments[0].value;
- for (var i = 1; i < r.segments.length; i++) {
- segments.push(r.segments[i]);
- }
- return l;
- }
- case "*":
- associative = compressor.option("unsafe_math");
- case "&":
- case "|":
- case "^":
- // a + +b => +b + a
- if (
- self.left.is_number_or_bigint(compressor)
- && self.right.is_number_or_bigint(compressor)
- && reversible()
- && !(self.left instanceof AST_Binary
- && self.left.operator != self.operator
- && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
- var reversed = make_node(AST_Binary, self, {
- operator: self.operator,
- left: self.right,
- right: self.left
- });
- if (self.right instanceof AST_Constant
- && !(self.left instanceof AST_Constant)) {
- self = best_of(compressor, reversed, self);
- } else {
- self = best_of(compressor, self, reversed);
- }
- }
- if (associative && self.is_number_or_bigint(compressor)) {
- // a + (b + c) => (a + b) + c
- if (self.right instanceof AST_Binary
- && self.right.operator == self.operator) {
- self = make_node(AST_Binary, self, {
- operator: self.operator,
- left: make_node(AST_Binary, self.left, {
- operator: self.operator,
- left: self.left,
- right: self.right.left,
- start: self.left.start,
- end: self.right.left.end
- }),
- right: self.right.right
- });
- }
- // (n + 2) + 3 => 5 + n
- // (2 * n) * 3 => 6 + n
- if (self.right instanceof AST_Constant
- && self.left instanceof AST_Binary
- && self.left.operator == self.operator) {
- if (self.left.left instanceof AST_Constant) {
- self = make_node(AST_Binary, self, {
- operator: self.operator,
- left: make_node(AST_Binary, self.left, {
- operator: self.operator,
- left: self.left.left,
- right: self.right,
- start: self.left.left.start,
- end: self.right.end
- }),
- right: self.left.right
- });
- } else if (self.left.right instanceof AST_Constant) {
- self = make_node(AST_Binary, self, {
- operator: self.operator,
- left: make_node(AST_Binary, self.left, {
- operator: self.operator,
- left: self.left.right,
- right: self.right,
- start: self.left.right.start,
- end: self.right.end
- }),
- right: self.left.left
- });
- }
- }
- // (a | 1) | (2 | d) => (3 | a) | b
- if (self.left instanceof AST_Binary
- && self.left.operator == self.operator
- && self.left.right instanceof AST_Constant
- && self.right instanceof AST_Binary
- && self.right.operator == self.operator
- && self.right.left instanceof AST_Constant) {
- self = make_node(AST_Binary, self, {
- operator: self.operator,
- left: make_node(AST_Binary, self.left, {
- operator: self.operator,
- left: make_node(AST_Binary, self.left.left, {
- operator: self.operator,
- left: self.left.right,
- right: self.right.left,
- start: self.left.right.start,
- end: self.right.left.end
- }),
- right: self.left.left
- }),
- right: self.right.right
- });
- }
- }
- }
- // bitwise ops
- if (bitwise_binop.has(self.operator)) {
- // Use De Morgan's laws
- // z & (X | y)
- // => z & X (given y & z === 0)
- // => z & X | {y & z} (given y & z !== 0)
- let y, z, x_node, y_node, z_node = self.left;
- if (
- self.operator === "&"
- && self.right instanceof AST_Binary
- && self.right.operator === "|"
- && typeof (z = self.left.evaluate(compressor)) === "number"
- ) {
- if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
- // z & (X | y)
- x_node = self.right.left;
- y_node = self.right.right;
- } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
- // z & (y | X)
- x_node = self.right.right;
- y_node = self.right.left;
- }
- if (x_node && y_node) {
- if ((y & z) === 0) {
- self = make_node(AST_Binary, self, {
- operator: self.operator,
- left: z_node,
- right: x_node
- });
- } else {
- const reordered_ops = make_node(AST_Binary, self, {
- operator: "|",
- left: make_node(AST_Binary, self, {
- operator: "&",
- left: x_node,
- right: z_node
- }),
- right: make_node_from_constant(y & z, y_node),
- });
- self = best_of(compressor, self, reordered_ops);
- }
- }
- }
- // x | x => 0 | x
- // x & x => 0 | x
- if (
- (self.operator === "|" || self.operator === "&")
- && self.left.equivalent_to(self.right)
- && !self.left.has_side_effects(compressor)
- && compressor.in_32_bit_context(true)
- ) {
- self.left = make_node(AST_Number, self, { value: 0 });
- self.operator = "|";
- }
- // ~x ^ ~y => x ^ y
- if (
- self.operator === "^"
- && self.left instanceof AST_UnaryPrefix
- && self.left.operator === "~"
- && self.right instanceof AST_UnaryPrefix
- && self.right.operator === "~"
- ) {
- self = make_node(AST_Binary, self, {
- operator: "^",
- left: self.left.expression,
- right: self.right.expression
- });
- }
- // Shifts that do nothing
- // {anything} >> 0 => {anything} | 0
- // {anything} << 0 => {anything} | 0
- if (
- (self.operator === "<<" || self.operator === ">>")
- && self.right instanceof AST_Number && self.right.value === 0
- ) {
- self.operator = "|";
- }
- // Find useless to-bitwise conversions
- // {32 bit integer} | 0 => {32 bit integer}
- // {32 bit integer} ^ 0 => {32 bit integer}
- const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
- : self.left instanceof AST_Number && self.left.value === 0 ? self.left
- : null;
- const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
- if (
- zero_side
- && (self.operator === "|" || self.operator === "^")
- && (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true))
- ) {
- return non_zero_side;
- }
- // {anything} & 0 => 0
- if (
- zero_side
- && self.operator === "&"
- && !non_zero_side.has_side_effects(compressor)
- && non_zero_side.is_32_bit_integer(compressor)
- ) {
- return zero_side;
- }
- // ~0 is all ones, as well as -1.
- // We can ellide some operations with it.
- const is_full_mask = (node) =>
- node instanceof AST_Number && node.value === -1
- ||
- node instanceof AST_UnaryPrefix
- && node.operator === "-"
- && node.expression instanceof AST_Number
- && node.expression.value === 1;
- const full_mask = is_full_mask(self.right) ? self.right
- : is_full_mask(self.left) ? self.left
- : null;
- const other_side = (full_mask === self.right ? self.left : self.right);
- // {32 bit integer} & -1 => {32 bit integer}
- if (
- full_mask
- && self.operator === "&"
- && (
- other_side.is_32_bit_integer(compressor)
- || compressor.in_32_bit_context(true)
- )
- ) {
- return other_side;
- }
- // {anything} ^ -1 => ~{anything}
- if (
- full_mask
- && self.operator === "^"
- && (
- other_side.is_32_bit_integer(compressor)
- || compressor.in_32_bit_context(true)
- )
- ) {
- return other_side.bitwise_negate(compressor);
- }
- }
- }
- // x && (y && z) ==> x && y && z
- // x || (y || z) ==> x || y || z
- // x + ("y" + z) ==> x + "y" + z
- // "x" + (y + "z")==> "x" + y + "z"
- if (self.right instanceof AST_Binary
- && self.right.operator == self.operator
- && (lazy_op.has(self.operator)
- || (self.operator == "+"
- && (self.right.left.is_string(compressor)
- || (self.left.is_string(compressor)
- && self.right.right.is_string(compressor)))))
- ) {
- self.left = make_node(AST_Binary, self.left, {
- operator : self.operator,
- left : self.left.transform(compressor),
- right : self.right.left.transform(compressor)
- });
- self.right = self.right.right.transform(compressor);
- return self.transform(compressor);
- }
- var ev = self.evaluate(compressor);
- if (ev !== self) {
- ev = make_node_from_constant(ev, self).optimize(compressor);
- return best_of(compressor, ev, self);
- }
- return self;
- });
- def_optimize(AST_SymbolExport, function(self) {
- return self;
- });
- def_optimize(AST_SymbolRef, function(self, compressor) {
- if (
- !compressor.option("ie8")
- && is_undeclared_ref(self)
- && !compressor.find_parent(AST_With)
- ) {
- switch (self.name) {
- case "undefined":
- return make_node(AST_Undefined, self).optimize(compressor);
- case "NaN":
- return make_node(AST_NaN, self).optimize(compressor);
- case "Infinity":
- return make_node(AST_Infinity, self).optimize(compressor);
- }
- }
- if (compressor.option("reduce_vars") && !compressor.is_lhs()) {
- return inline_into_symbolref(self, compressor);
- } else {
- return self;
- }
- });
- function is_atomic(lhs, self) {
- return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
- }
- /** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
- function unsafe_undefined_ref(self, compressor) {
- if (compressor.option("unsafe_undefined")) {
- var undef = find_variable(compressor, "undefined");
- if (undef) {
- var ref = make_node(AST_SymbolRef, self, {
- name : "undefined",
- scope : undef.scope,
- thedef : undef
- });
- set_flag(ref, UNDEFINED);
- return ref;
- }
- }
- return null;
- }
- def_optimize(AST_Undefined, function(self, compressor) {
- var symbolref = unsafe_undefined_ref(self, compressor);
- if (symbolref) return symbolref;
- var lhs = compressor.is_lhs();
- if (lhs && is_atomic(lhs, self)) return self;
- return make_void_0(self);
- });
- def_optimize(AST_Infinity, function(self, compressor) {
- var lhs = compressor.is_lhs();
- if (lhs && is_atomic(lhs, self)) return self;
- if (
- compressor.option("keep_infinity")
- && !(lhs && !is_atomic(lhs, self))
- && !find_variable(compressor, "Infinity")
- ) {
- return self;
- }
- return make_node(AST_Binary, self, {
- operator: "/",
- left: make_node(AST_Number, self, {
- value: 1
- }),
- right: make_node(AST_Number, self, {
- value: 0
- })
- });
- });
- def_optimize(AST_NaN, function(self, compressor) {
- var lhs = compressor.is_lhs();
- if (lhs && !is_atomic(lhs, self)
- || find_variable(compressor, "NaN")) {
- return make_node(AST_Binary, self, {
- operator: "/",
- left: make_node(AST_Number, self, {
- value: 0
- }),
- right: make_node(AST_Number, self, {
- value: 0
- })
- });
- }
- return self;
- });
- const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
- const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
- def_optimize(AST_Assign, function(self, compressor) {
- if (self.logical) {
- return self.lift_sequences(compressor);
- }
- var def;
- // x = x ---> x
- if (
- self.operator === "="
- && self.left instanceof AST_SymbolRef
- && self.left.name !== "arguments"
- && !(def = self.left.definition()).undeclared
- && self.right.equivalent_to(self.left)
- ) {
- return self.right;
- }
- if (compressor.option("dead_code")
- && self.left instanceof AST_SymbolRef
- && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
- var level = 0, node, parent = self;
- do {
- node = parent;
- parent = compressor.parent(level++);
- if (parent instanceof AST_Exit) {
- if (in_try(level, parent)) break;
- if (is_reachable(def.scope, [ def ])) break;
- if (self.operator == "=") return self.right;
- def.fixed = false;
- return make_node(AST_Binary, self, {
- operator: self.operator.slice(0, -1),
- left: self.left,
- right: self.right
- }).optimize(compressor);
- }
- } while (parent instanceof AST_Binary && parent.right === node
- || parent instanceof AST_Sequence && parent.tail_node() === node);
- }
- self = self.lift_sequences(compressor);
- if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
- // x = expr1 OP expr2
- if (self.right.left instanceof AST_SymbolRef
- && self.right.left.name == self.left.name
- && ASSIGN_OPS.has(self.right.operator)) {
- // x = x - 2 ---> x -= 2
- self.operator = self.right.operator + "=";
- self.right = self.right.right;
- } else if (self.right.right instanceof AST_SymbolRef
- && self.right.right.name == self.left.name
- && ASSIGN_OPS_COMMUTATIVE.has(self.right.operator)
- && !self.right.left.has_side_effects(compressor)) {
- // x = 2 & x ---> x &= 2
- self.operator = self.right.operator + "=";
- self.right = self.right.left;
- }
- }
- return self;
- function in_try(level, node) {
- function may_assignment_throw() {
- const right = self.right;
- self.right = make_node(AST_Null, right);
- const may_throw = node.may_throw(compressor);
- self.right = right;
- return may_throw;
- }
- var stop_at = self.left.definition().scope.get_defun_scope();
- var parent;
- while ((parent = compressor.parent(level++)) !== stop_at) {
- if (parent instanceof AST_Try) {
- if (parent.bfinally) return true;
- if (parent.bcatch && may_assignment_throw()) return true;
- }
- }
- }
- });
- def_optimize(AST_DefaultAssign, function(self, compressor) {
- if (!compressor.option("evaluate")) {
- return self;
- }
- var evaluateRight = self.right.evaluate(compressor);
- // `[x = undefined] = foo` ---> `[x] = foo`
- // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
- // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
- let lambda, iife;
- if (evaluateRight === undefined) {
- if (
- (lambda = compressor.parent()) instanceof AST_Lambda
- ? (
- compressor.option("keep_fargs") === false
- || (iife = compressor.parent(1)).TYPE === "Call"
- && iife.expression === lambda
- )
- : true
- ) {
- self = self.left;
- }
- } else if (evaluateRight !== self.right) {
- evaluateRight = make_node_from_constant(evaluateRight, self.right);
- self.right = best_of_expression(evaluateRight, self.right);
- }
- return self;
- });
- function is_nullish_check(check, check_subject, compressor) {
- if (check_subject.may_throw(compressor)) return false;
- let nullish_side;
- // foo == null
- if (
- check instanceof AST_Binary
- && check.operator === "=="
- // which side is nullish?
- && (
- (nullish_side = is_nullish(check.left, compressor) && check.left)
- || (nullish_side = is_nullish(check.right, compressor) && check.right)
- )
- // is the other side the same as the check_subject
- && (
- nullish_side === check.left
- ? check.right
- : check.left
- ).equivalent_to(check_subject)
- ) {
- return true;
- }
- // foo === null || foo === undefined
- if (check instanceof AST_Binary && check.operator === "||") {
- let null_cmp;
- let undefined_cmp;
- const find_comparison = cmp => {
- if (!(
- cmp instanceof AST_Binary
- && (cmp.operator === "===" || cmp.operator === "==")
- )) {
- return false;
- }
- let found = 0;
- let defined_side;
- if (cmp.left instanceof AST_Null) {
- found++;
- null_cmp = cmp;
- defined_side = cmp.right;
- }
- if (cmp.right instanceof AST_Null) {
- found++;
- null_cmp = cmp;
- defined_side = cmp.left;
- }
- if (is_undefined(cmp.left, compressor)) {
- found++;
- undefined_cmp = cmp;
- defined_side = cmp.right;
- }
- if (is_undefined(cmp.right, compressor)) {
- found++;
- undefined_cmp = cmp;
- defined_side = cmp.left;
- }
- if (found !== 1) {
- return false;
- }
- if (!defined_side.equivalent_to(check_subject)) {
- return false;
- }
- return true;
- };
- if (!find_comparison(check.left)) return false;
- if (!find_comparison(check.right)) return false;
- if (null_cmp && undefined_cmp && null_cmp !== undefined_cmp) {
- return true;
- }
- }
- return false;
- }
- def_optimize(AST_Conditional, function(self, compressor) {
- if (!compressor.option("conditionals")) return self;
- // This looks like lift_sequences(), should probably be under "sequences"
- if (self.condition instanceof AST_Sequence) {
- var expressions = self.condition.expressions.slice();
- self.condition = expressions.pop();
- expressions.push(self);
- return make_sequence(self, expressions);
- }
- var cond = self.condition.evaluate(compressor);
- if (cond !== self.condition) {
- if (cond) {
- return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
- } else {
- return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
- }
- }
- var negated = cond.negate(compressor, first_in_statement(compressor));
- if (best_of(compressor, cond, negated) === negated) {
- self = make_node(AST_Conditional, self, {
- condition: negated,
- consequent: self.alternative,
- alternative: self.consequent
- });
- }
- var condition = self.condition;
- var consequent = self.consequent;
- var alternative = self.alternative;
- // x?x:y --> x||y
- if (condition instanceof AST_SymbolRef
- && consequent instanceof AST_SymbolRef
- && condition.definition() === consequent.definition()) {
- return make_node(AST_Binary, self, {
- operator: "||",
- left: condition,
- right: alternative
- });
- }
- // if (foo) exp = something; else exp = something_else;
- // |
- // v
- // exp = foo ? something : something_else;
- if (
- consequent instanceof AST_Assign
- && alternative instanceof AST_Assign
- && consequent.operator === alternative.operator
- && consequent.logical === alternative.logical
- && consequent.left.equivalent_to(alternative.left)
- && (!self.condition.has_side_effects(compressor)
- || consequent.operator == "="
- && !consequent.left.has_side_effects(compressor))
- ) {
- return make_node(AST_Assign, self, {
- operator: consequent.operator,
- left: consequent.left,
- logical: consequent.logical,
- right: make_node(AST_Conditional, self, {
- condition: self.condition,
- consequent: consequent.right,
- alternative: alternative.right
- })
- });
- }
- // x ? y(a) : y(b) --> y(x ? a : b)
- var arg_index;
- if (consequent instanceof AST_Call
- && alternative.TYPE === consequent.TYPE
- && consequent.args.length > 0
- && consequent.args.length == alternative.args.length
- && consequent.expression.equivalent_to(alternative.expression)
- && !self.condition.has_side_effects(compressor)
- && !consequent.expression.has_side_effects(compressor)
- && typeof (arg_index = single_arg_diff()) == "number") {
- var node = consequent.clone();
- node.args[arg_index] = make_node(AST_Conditional, self, {
- condition: self.condition,
- consequent: consequent.args[arg_index],
- alternative: alternative.args[arg_index]
- });
- return node;
- }
- // a ? b : c ? b : d --> (a || c) ? b : d
- if (alternative instanceof AST_Conditional
- && consequent.equivalent_to(alternative.consequent)) {
- return make_node(AST_Conditional, self, {
- condition: make_node(AST_Binary, self, {
- operator: "||",
- left: condition,
- right: alternative.condition
- }),
- consequent: consequent,
- alternative: alternative.alternative
- }).optimize(compressor);
- }
- // a == null ? b : a -> a ?? b
- if (
- compressor.option("ecma") >= 2020 &&
- is_nullish_check(condition, alternative, compressor)
- ) {
- return make_node(AST_Binary, self, {
- operator: "??",
- left: alternative,
- right: consequent
- }).optimize(compressor);
- }
- // a ? b : (c, b) --> (a || c), b
- if (alternative instanceof AST_Sequence
- && consequent.equivalent_to(alternative.expressions[alternative.expressions.length - 1])) {
- return make_sequence(self, [
- make_node(AST_Binary, self, {
- operator: "||",
- left: condition,
- right: make_sequence(self, alternative.expressions.slice(0, -1))
- }),
- consequent
- ]).optimize(compressor);
- }
- // a ? b : (c && b) --> (a || c) && b
- if (alternative instanceof AST_Binary
- && alternative.operator == "&&"
- && consequent.equivalent_to(alternative.right)) {
- return make_node(AST_Binary, self, {
- operator: "&&",
- left: make_node(AST_Binary, self, {
- operator: "||",
- left: condition,
- right: alternative.left
- }),
- right: consequent
- }).optimize(compressor);
- }
- // x?y?z:a:a --> x&&y?z:a
- if (consequent instanceof AST_Conditional
- && consequent.alternative.equivalent_to(alternative)) {
- return make_node(AST_Conditional, self, {
- condition: make_node(AST_Binary, self, {
- left: self.condition,
- operator: "&&",
- right: consequent.condition
- }),
- consequent: consequent.consequent,
- alternative: alternative
- });
- }
- // x ? y : y --> x, y
- if (consequent.equivalent_to(alternative)) {
- return make_sequence(self, [
- self.condition,
- consequent
- ]).optimize(compressor);
- }
- // x ? y || z : z --> x && y || z
- if (consequent instanceof AST_Binary
- && consequent.operator == "||"
- && consequent.right.equivalent_to(alternative)) {
- return make_node(AST_Binary, self, {
- operator: "||",
- left: make_node(AST_Binary, self, {
- operator: "&&",
- left: self.condition,
- right: consequent.left
- }),
- right: alternative
- }).optimize(compressor);
- }
- const in_bool = compressor.in_boolean_context();
- if (is_true(self.consequent)) {
- if (is_false(self.alternative)) {
- // c ? true : false ---> !!c
- return booleanize(self.condition);
- }
- // c ? true : x ---> !!c || x
- return make_node(AST_Binary, self, {
- operator: "||",
- left: booleanize(self.condition),
- right: self.alternative
- });
- }
- if (is_false(self.consequent)) {
- if (is_true(self.alternative)) {
- // c ? false : true ---> !c
- return booleanize(self.condition.negate(compressor));
- }
- // c ? false : x ---> !c && x
- return make_node(AST_Binary, self, {
- operator: "&&",
- left: booleanize(self.condition.negate(compressor)),
- right: self.alternative
- });
- }
- if (is_true(self.alternative)) {
- // c ? x : true ---> !c || x
- return make_node(AST_Binary, self, {
- operator: "||",
- left: booleanize(self.condition.negate(compressor)),
- right: self.consequent
- });
- }
- if (is_false(self.alternative)) {
- // c ? x : false ---> !!c && x
- return make_node(AST_Binary, self, {
- operator: "&&",
- left: booleanize(self.condition),
- right: self.consequent
- });
- }
- return self;
- function booleanize(node) {
- if (node.is_boolean()) return node;
- // !!expression
- return make_node(AST_UnaryPrefix, node, {
- operator: "!",
- expression: node.negate(compressor)
- });
- }
- // AST_True or !0
- function is_true(node) {
- return node instanceof AST_True
- || in_bool
- && node instanceof AST_Constant
- && node.getValue()
- || (node instanceof AST_UnaryPrefix
- && node.operator == "!"
- && node.expression instanceof AST_Constant
- && !node.expression.getValue());
- }
- // AST_False or !1
- function is_false(node) {
- return node instanceof AST_False
- || in_bool
- && node instanceof AST_Constant
- && !node.getValue()
- || (node instanceof AST_UnaryPrefix
- && node.operator == "!"
- && node.expression instanceof AST_Constant
- && node.expression.getValue());
- }
- function single_arg_diff() {
- var a = consequent.args;
- var b = alternative.args;
- for (var i = 0, len = a.length; i < len; i++) {
- if (a[i] instanceof AST_Expansion) return;
- if (!a[i].equivalent_to(b[i])) {
- if (b[i] instanceof AST_Expansion) return;
- for (var j = i + 1; j < len; j++) {
- if (a[j] instanceof AST_Expansion) return;
- if (!a[j].equivalent_to(b[j])) return;
- }
- return i;
- }
- }
- }
- });
- def_optimize(AST_Boolean, function(self, compressor) {
- if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
- value: +self.value
- });
- var p = compressor.parent();
- if (compressor.option("booleans_as_integers")) {
- if (p instanceof AST_Binary && (p.operator == "===" || p.operator == "!==")) {
- p.operator = p.operator.replace(/=$/, "");
- }
- return make_node(AST_Number, self, {
- value: +self.value
- });
- }
- if (compressor.option("booleans")) {
- if (p instanceof AST_Binary && (p.operator == "=="
- || p.operator == "!=")) {
- return make_node(AST_Number, self, {
- value: +self.value
- });
- }
- return make_node(AST_UnaryPrefix, self, {
- operator: "!",
- expression: make_node(AST_Number, self, {
- value: 1 - self.value
- })
- });
- }
- return self;
- });
- function safe_to_flatten(value, compressor) {
- if (value instanceof AST_SymbolRef) {
- value = value.fixed_value();
- }
- if (!value) return false;
- if (!(value instanceof AST_Lambda || value instanceof AST_Class)) return true;
- if (!(value instanceof AST_Lambda && value.contains_this())) return true;
- return compressor.parent() instanceof AST_New;
- }
- AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
- if (!compressor.option("properties")) return;
- if (key === "__proto__") return;
- if (this instanceof AST_DotHash) return;
- var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015;
- var expr = this.expression;
- if (expr instanceof AST_Object) {
- var props = expr.properties;
- for (var i = props.length; --i >= 0;) {
- var prop = props[i];
- if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) {
- const all_props_flattenable = props.every((p) =>
- (p instanceof AST_ObjectKeyVal
- || arrows && p instanceof AST_ConciseMethod && !p.value.is_generator
- )
- && !p.computed_key()
- );
- if (!all_props_flattenable) return;
- if (!safe_to_flatten(prop.value, compressor)) return;
- return make_node(AST_Sub, this, {
- expression: make_node(AST_Array, expr, {
- elements: props.map(function(prop) {
- var v = prop.value;
- if (v instanceof AST_Accessor) {
- v = make_node(AST_Function, v, v);
- }
- var k = prop.key;
- if (k instanceof AST_Node && !(k instanceof AST_SymbolMethod)) {
- return make_sequence(prop, [ k, v ]);
- }
- return v;
- })
- }),
- property: make_node(AST_Number, this, {
- value: i
- })
- });
- }
- }
- }
- });
- def_optimize(AST_Sub, function(self, compressor) {
- var expr = self.expression;
- var prop = self.property;
- if (compressor.option("properties")) {
- var key = prop.evaluate(compressor);
- if (key !== prop) {
- if (typeof key == "string") {
- if (key == "undefined") {
- key = undefined;
- } else {
- var value = parseFloat(key);
- if (value.toString() == key) {
- key = value;
- }
- }
- }
- prop = self.property = best_of_expression(
- prop,
- make_node_from_constant(key, prop).transform(compressor)
- );
- var property = "" + key;
- if (is_basic_identifier_string(property)
- && property.length <= prop.size() + 1) {
- return make_node(AST_Dot, self, {
- expression: expr,
- optional: self.optional,
- property: property,
- quote: prop.quote,
- }).optimize(compressor);
- }
- }
- }
- var fn;
- OPT_ARGUMENTS: if (compressor.option("arguments")
- && expr instanceof AST_SymbolRef
- && expr.name == "arguments"
- && expr.definition().orig.length == 1
- && (fn = expr.scope) instanceof AST_Lambda
- && fn.uses_arguments
- && !(fn instanceof AST_Arrow)
- && prop instanceof AST_Number) {
- var index = prop.getValue();
- var params = new Set();
- var argnames = fn.argnames;
- for (var n = 0; n < argnames.length; n++) {
- if (!(argnames[n] instanceof AST_SymbolFunarg)) {
- break OPT_ARGUMENTS; // destructuring parameter - bail
- }
- var param = argnames[n].name;
- if (params.has(param)) {
- break OPT_ARGUMENTS; // duplicate parameter - bail
- }
- params.add(param);
- }
- var argname = fn.argnames[index];
- if (argname && compressor.has_directive("use strict")) {
- var def = argname.definition();
- if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
- argname = null;
- }
- } else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
- while (index >= fn.argnames.length) {
- argname = fn.create_symbol(AST_SymbolFunarg, {
- source: fn,
- scope: fn,
- tentative_name: "argument_" + fn.argnames.length,
- });
- fn.argnames.push(argname);
- }
- }
- if (argname) {
- var sym = make_node(AST_SymbolRef, self, argname);
- sym.reference({});
- clear_flag(argname, UNUSED);
- return sym;
- }
- }
- if (compressor.is_lhs()) return self;
- if (key !== prop) {
- var sub = self.flatten_object(property, compressor);
- if (sub) {
- expr = self.expression = sub.expression;
- prop = self.property = sub.property;
- }
- }
- if (compressor.option("properties") && compressor.option("side_effects")
- && prop instanceof AST_Number && expr instanceof AST_Array) {
- var index = prop.getValue();
- var elements = expr.elements;
- var retValue = elements[index];
- FLATTEN: if (safe_to_flatten(retValue, compressor)) {
- var flatten = true;
- var values = [];
- for (var i = elements.length; --i > index;) {
- var value = elements[i].drop_side_effect_free(compressor);
- if (value) {
- values.unshift(value);
- if (flatten && value.has_side_effects(compressor)) flatten = false;
- }
- }
- if (retValue instanceof AST_Expansion) break FLATTEN;
- retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
- if (!flatten) values.unshift(retValue);
- while (--i >= 0) {
- var value = elements[i];
- if (value instanceof AST_Expansion) break FLATTEN;
- value = value.drop_side_effect_free(compressor);
- if (value) values.unshift(value);
- else index--;
- }
- if (flatten) {
- values.push(retValue);
- return make_sequence(self, values).optimize(compressor);
- } else return make_node(AST_Sub, self, {
- expression: make_node(AST_Array, expr, {
- elements: values
- }),
- property: make_node(AST_Number, prop, {
- value: index
- })
- });
- }
- }
- var ev = self.evaluate(compressor);
- if (ev !== self) {
- ev = make_node_from_constant(ev, self).optimize(compressor);
- return best_of(compressor, ev, self);
- }
- return self;
- });
- def_optimize(AST_Chain, function (self, compressor) {
- if (is_nullish(self.expression, compressor)) {
- let parent = compressor.parent();
- // It's valid to delete a nullish optional chain, but if we optimized
- // this to `delete undefined` then it would appear to be a syntax error
- // when we try to optimize the delete. Thankfully, `delete 0` is fine.
- if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
- return make_node_from_constant(0, self);
- }
- return make_void_0(self).optimize(compressor);
- }
- if (
- self.expression instanceof AST_PropAccess
- || self.expression instanceof AST_Call
- ) {
- return self;
- } else {
- // Keep the AST valid, in case the child swapped itself
- return self.expression;
- }
- });
- def_optimize(AST_Dot, function(self, compressor) {
- const parent = compressor.parent();
- if (compressor.is_lhs()) return self;
- if (compressor.option("unsafe_proto")
- && self.expression instanceof AST_Dot
- && self.expression.property == "prototype") {
- var exp = self.expression.expression;
- if (is_undeclared_ref(exp)) switch (exp.name) {
- case "Array":
- self.expression = make_node(AST_Array, self.expression, {
- elements: []
- });
- break;
- case "Function":
- self.expression = make_empty_function(self.expression);
- break;
- case "Number":
- self.expression = make_node(AST_Number, self.expression, {
- value: 0
- });
- break;
- case "Object":
- self.expression = make_node(AST_Object, self.expression, {
- properties: []
- });
- break;
- case "RegExp":
- self.expression = make_node(AST_RegExp, self.expression, {
- value: { source: "t", flags: "" }
- });
- break;
- case "String":
- self.expression = make_node(AST_String, self.expression, {
- value: ""
- });
- break;
- }
- }
- if (!(parent instanceof AST_Call) || !has_annotation(parent, _NOINLINE)) {
- const sub = self.flatten_object(self.property, compressor);
- if (sub) return sub.optimize(compressor);
- }
- if (self.expression instanceof AST_PropAccess
- && parent instanceof AST_PropAccess) {
- return self;
- }
- let ev = self.evaluate(compressor);
- if (ev !== self) {
- ev = make_node_from_constant(ev, self).optimize(compressor);
- return best_of(compressor, ev, self);
- }
- return self;
- });
- function literals_in_boolean_context(self, compressor) {
- if (compressor.in_boolean_context()) {
- return best_of(compressor, self, make_sequence(self, [
- self,
- make_node(AST_True, self)
- ]).optimize(compressor));
- }
- return self;
- }
- function inline_array_like_spread(elements) {
- for (var i = 0; i < elements.length; i++) {
- var el = elements[i];
- if (el instanceof AST_Expansion) {
- var expr = el.expression;
- if (
- expr instanceof AST_Array
- && !expr.elements.some(elm => elm instanceof AST_Hole)
- ) {
- elements.splice(i, 1, ...expr.elements);
- // Step back one, as the element at i is now new.
- i--;
- }
- // In array-like spread, spreading a non-iterable value is TypeError.
- // We therefore can’t optimize anything else, unlike with object spread.
- }
- }
- }
- def_optimize(AST_Array, function(self, compressor) {
- var optimized = literals_in_boolean_context(self, compressor);
- if (optimized !== self) {
- return optimized;
- }
- inline_array_like_spread(self.elements);
- return self;
- });
- function inline_object_prop_spread(props) {
- for (var i = 0; i < props.length; i++) {
- var prop = props[i];
- if (prop instanceof AST_Expansion) {
- const expr = prop.expression;
- if (
- expr instanceof AST_Object
- && expr.properties.every(prop => prop instanceof AST_ObjectKeyVal)
- ) {
- props.splice(i, 1, ...expr.properties);
- // Step back one, as the property at i is now new.
- i--;
- } else if ((
- // `expr.is_constant()` returns `false` for `AST_RegExp`, so need both.
- expr instanceof AST_Constant
- || expr.is_constant()
- ) && !(expr instanceof AST_String)) {
- // Unlike array-like spread, in object spread, spreading a
- // non-iterable value silently does nothing; it is thus safe
- // to remove. AST_String is the only iterable constant.
- props.splice(i, 1);
- i--;
- }
- }
- }
- }
- def_optimize(AST_Object, function(self, compressor) {
- var optimized = literals_in_boolean_context(self, compressor);
- if (optimized !== self) {
- return optimized;
- }
- inline_object_prop_spread(self.properties);
- return self;
- });
- def_optimize(AST_RegExp, literals_in_boolean_context);
- def_optimize(AST_Return, function(self, compressor) {
- if (self.value && is_undefined(self.value, compressor)) {
- self.value = null;
- }
- return self;
- });
- def_optimize(AST_Arrow, opt_AST_Lambda);
- def_optimize(AST_Function, function(self, compressor) {
- self = opt_AST_Lambda(self, compressor);
- if (compressor.option("unsafe_arrows")
- && compressor.option("ecma") >= 2015
- && !self.name
- && !self.is_generator
- && !self.uses_arguments
- && !self.pinned()) {
- const uses_this = walk(self, node => {
- if (node instanceof AST_This) return walk_abort;
- });
- if (!uses_this) return make_node(AST_Arrow, self, self).optimize(compressor);
- }
- return self;
- });
- def_optimize(AST_Class, function(self) {
- for (let i = 0; i < self.properties.length; i++) {
- const prop = self.properties[i];
- if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
- self.properties.splice(i, 1);
- i--;
- }
- }
- return self;
- });
- def_optimize(AST_ClassStaticBlock, function(self, compressor) {
- tighten_body(self.body, compressor);
- return self;
- });
- def_optimize(AST_Yield, function(self, compressor) {
- if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
- self.expression = null;
- }
- return self;
- });
- def_optimize(AST_TemplateString, function(self, compressor) {
- if (
- !compressor.option("evaluate")
- || compressor.parent() instanceof AST_PrefixedTemplateString
- ) {
- return self;
- }
- var segments = [];
- for (var i = 0; i < self.segments.length; i++) {
- var segment = self.segments[i];
- if (segment instanceof AST_Node) {
- var result = segment.evaluate(compressor);
- // Evaluate to constant value
- // Constant value shorter than ${segment}
- if (result !== segment && (result + "").length <= segment.size() + "${}".length) {
- // There should always be a previous and next segment if segment is a node
- segments[segments.length - 1].value = segments[segments.length - 1].value + result + self.segments[++i].value;
- continue;
- }
- // `before ${`innerBefore ${any} innerAfter`} after` => `before innerBefore ${any} innerAfter after`
- // TODO:
- // `before ${'test' + foo} after` => `before innerBefore ${any} innerAfter after`
- // `before ${foo + 'test} after` => `before innerBefore ${any} innerAfter after`
- if (segment instanceof AST_TemplateString) {
- var inners = segment.segments;
- segments[segments.length - 1].value += inners[0].value;
- for (var j = 1; j < inners.length; j++) {
- segment = inners[j];
- segments.push(segment);
- }
- continue;
- }
- }
- segments.push(segment);
- }
- self.segments = segments;
- // `foo` => "foo"
- if (segments.length == 1) {
- return make_node(AST_String, self, segments[0]);
- }
- if (
- segments.length === 3
- && segments[1] instanceof AST_Node
- && (
- segments[1].is_string(compressor)
- || segments[1].is_number_or_bigint(compressor)
- || is_nullish(segments[1], compressor)
- || compressor.option("unsafe")
- )
- ) {
- // `foo${bar}` => "foo" + bar
- if (segments[2].value === "") {
- return make_node(AST_Binary, self, {
- operator: "+",
- left: make_node(AST_String, self, {
- value: segments[0].value,
- }),
- right: segments[1],
- });
- }
- // `${bar}baz` => bar + "baz"
- if (segments[0].value === "") {
- return make_node(AST_Binary, self, {
- operator: "+",
- left: segments[1],
- right: make_node(AST_String, self, {
- value: segments[2].value,
- }),
- });
- }
- }
- return self;
- });
- def_optimize(AST_PrefixedTemplateString, function(self) {
- return self;
- });
- // ["p"]:1 ---> p:1
- // [42]:1 ---> 42:1
- function lift_key(self, compressor) {
- if (!compressor.option("computed_props")) return self;
- // save a comparison in the typical case
- if (!(self.key instanceof AST_Constant)) return self;
- // allow certain acceptable props as not all AST_Constants are true constants
- if (self.key instanceof AST_String || self.key instanceof AST_Number) {
- const key = self.key.value.toString();
- if (key === "__proto__") return self;
- if (key == "constructor"
- && compressor.parent() instanceof AST_Class) return self;
- if (self instanceof AST_ObjectKeyVal) {
- self.quote = self.key.quote;
- self.key = key;
- } else if (self instanceof AST_ClassProperty) {
- self.quote = self.key.quote;
- self.key = make_node(AST_SymbolClassProperty, self.key, {
- name: key,
- });
- } else {
- self.quote = self.key.quote;
- self.key = make_node(AST_SymbolMethod, self.key, {
- name: key,
- });
- }
- }
- return self;
- }
- def_optimize(AST_ObjectProperty, lift_key);
- def_optimize(AST_ConciseMethod, function(self, compressor) {
- lift_key(self, compressor);
- // p(){return x;} ---> p:()=>x
- if (compressor.option("arrows")
- && compressor.parent() instanceof AST_Object
- && !self.value.is_generator
- && !self.value.uses_arguments
- && !self.value.pinned()
- && self.value.body.length == 1
- && self.value.body[0] instanceof AST_Return
- && self.value.body[0].value
- && !self.value.contains_this()) {
- var arrow = make_node(AST_Arrow, self.value, self.value);
- arrow.async = self.value.async;
- arrow.is_generator = self.value.is_generator;
- return make_node(AST_ObjectKeyVal, self, {
- key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key,
- value: arrow,
- quote: self.quote,
- });
- }
- return self;
- });
- def_optimize(AST_ObjectKeyVal, function(self, compressor) {
- lift_key(self, compressor);
- // p:function(){} ---> p(){}
- // p:function*(){} ---> *p(){}
- // p:async function(){} ---> async p(){}
- // p:()=>{} ---> p(){}
- // p:async()=>{} ---> async p(){}
- var unsafe_methods = compressor.option("unsafe_methods");
- if (unsafe_methods
- && compressor.option("ecma") >= 2015
- && (!(unsafe_methods instanceof RegExp) || unsafe_methods.test(self.key + ""))) {
- var key = self.key;
- var value = self.value;
- var is_arrow_with_block = value instanceof AST_Arrow
- && Array.isArray(value.body)
- && !value.contains_this();
- if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) {
- return make_node(AST_ConciseMethod, self, {
- key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, {
- name: key,
- }),
- value: make_node(AST_Accessor, value, value),
- quote: self.quote,
- });
- }
- }
- return self;
- });
- def_optimize(AST_Destructuring, function(self, compressor) {
- if (compressor.option("pure_getters") == true
- && compressor.option("unused")
- && !self.is_array
- && Array.isArray(self.names)
- && !is_destructuring_export_decl(compressor)
- && !(self.names[self.names.length - 1] instanceof AST_Expansion)) {
- var keep = [];
- for (var i = 0; i < self.names.length; i++) {
- var elem = self.names[i];
- if (!(elem instanceof AST_ObjectKeyVal
- && typeof elem.key == "string"
- && elem.value instanceof AST_SymbolDeclaration
- && !should_retain(compressor, elem.value.definition()))) {
- keep.push(elem);
- }
- }
- if (keep.length != self.names.length) {
- self.names = keep;
- }
- }
- return self;
- function is_destructuring_export_decl(compressor) {
- var ancestors = [/^VarDef$/, /^(Const|Let|Var)$/, /^Export$/];
- for (var a = 0, p = 0, len = ancestors.length; a < len; p++) {
- var parent = compressor.parent(p);
- if (!parent) return false;
- if (a === 0 && parent.TYPE == "Destructuring") continue;
- if (!ancestors[a].test(parent.TYPE)) {
- return false;
- }
- a++;
- }
- return true;
- }
- function should_retain(compressor, def) {
- if (def.references.length) return true;
- if (!def.global) return false;
- if (compressor.toplevel.vars) {
- if (compressor.top_retain) {
- return compressor.top_retain(def);
- }
- return false;
- }
- return true;
- }
- });
- export {
- Compressor,
- };
|