@@ -12,6 +12,90 @@ const getQuery = (request) => {
1212 return request . includes ( "?" ) ? request . substr ( i ) : "" ;
1313} ;
1414
15+ const collectDeclaration = ( declarations , pattern ) => {
16+ const stack = [ pattern ] ;
17+ while ( stack . length > 0 ) {
18+ const node = stack . pop ( ) ;
19+ switch ( node . type ) {
20+ case "Identifier" :
21+ declarations . add ( node . name ) ;
22+ break ;
23+ case "ArrayPattern" :
24+ for ( const element of node . elements )
25+ if ( element ) stack . push ( element ) ;
26+ break ;
27+ case "AssignmentPattern" :
28+ stack . push ( node . left ) ;
29+ break ;
30+ case "ObjectPattern" :
31+ for ( const property of node . properties )
32+ stack . push ( property . value ) ;
33+ break ;
34+ case "RestElement" :
35+ stack . push ( node . argument ) ;
36+ break ;
37+ }
38+ }
39+ } ;
40+
41+ const getHoistedDeclarations = ( branch , includeFunctionDeclarations ) => {
42+ const declarations = new Set ( ) ;
43+ const stack = [ branch ] ;
44+ while ( stack . length > 0 ) {
45+ const node = stack . pop ( ) ;
46+ // Some node could be `null` or `undefined`.
47+ if ( ! node )
48+ continue ;
49+ switch ( node . type ) {
50+ // Walk through control statements to look for hoisted declarations.
51+ // Some branches are skipped since they do not allow declarations.
52+ case "BlockStatement" :
53+ for ( const stmt of node . body )
54+ stack . push ( stmt ) ;
55+ break ;
56+ case "IfStatement" :
57+ stack . push ( node . consequent ) ;
58+ stack . push ( node . alternate ) ;
59+ break ;
60+ case "ForStatement" :
61+ stack . push ( node . init ) ;
62+ stack . push ( node . body ) ;
63+ break ;
64+ case "ForInStatement" :
65+ case "ForOfStatement" :
66+ stack . push ( node . left ) ;
67+ stack . push ( node . body ) ;
68+ break ;
69+ case "DoWhileStatement" :
70+ case "WhileStatement" :
71+ case "LabeledStatement" :
72+ stack . push ( node . body ) ;
73+ break ;
74+ case "SwitchStatement" :
75+ for ( const cs of node . cases )
76+ for ( const consequent of cs . consequent )
77+ stack . push ( consequent ) ;
78+ break ;
79+ case "TryStatement" :
80+ stack . push ( node . block ) ;
81+ if ( node . handler )
82+ stack . push ( node . handler . body ) ;
83+ stack . push ( node . finalizer ) ;
84+ break ;
85+ case "FunctionDeclaration" :
86+ if ( includeFunctionDeclarations )
87+ collectDeclaration ( declarations , node . id ) ;
88+ break ;
89+ case "VariableDeclaration" :
90+ if ( node . kind === "var" )
91+ for ( const decl of node . declarations )
92+ collectDeclaration ( declarations , decl . id ) ;
93+ break ;
94+ }
95+ }
96+ return Array . from ( declarations ) ;
97+ } ;
98+
1599class ConstPlugin {
16100 apply ( compiler ) {
17101 compiler . hooks . compilation . tap ( "ConstPlugin" , ( compilation , {
@@ -30,6 +114,57 @@ class ConstPlugin {
30114 dep . loc = statement . loc ;
31115 parser . state . current . addDependency ( dep ) ;
32116 }
117+ const branchToRemove = bool ? statement . alternate : statement . consequent ;
118+ if ( branchToRemove ) {
119+ // Before removing the dead branch, the hoisted declarations
120+ // must be collected.
121+ //
122+ // Given the following code:
123+ //
124+ // if (true) f() else g()
125+ // if (false) {
126+ // function f() {}
127+ // const g = function g() {}
128+ // if (someTest) {
129+ // let a = 1
130+ // var x, {y, z} = obj
131+ // }
132+ // } else {
133+ // …
134+ // }
135+ //
136+ // the generated code is:
137+ //
138+ // if (true) f() else {}
139+ // if (false) {
140+ // var f, x, y, z; (in loose mode)
141+ // var x, y, z; (in strict mode)
142+ // } else {
143+ // …
144+ // }
145+ //
146+ // NOTE: When code runs in strict mode, `var` declarations
147+ // are hoisted but `function` declarations don't.
148+ //
149+ let declarations ;
150+ if ( parser . scope . isStrict ) {
151+ // If the code runs in strict mode, variable declarations
152+ // using `var` must be hoisted.
153+ declarations = getHoistedDeclarations ( branchToRemove , false ) ;
154+ } else {
155+ // Otherwise, collect all hoisted declaration.
156+ declarations = getHoistedDeclarations ( branchToRemove , true ) ;
157+ }
158+ let replacement ;
159+ if ( declarations . length > 0 ) {
160+ replacement = `{ var ${ declarations . join ( ", " ) } ; }` ;
161+ } else {
162+ replacement = "{}" ;
163+ }
164+ const dep = new ConstDependency ( replacement , branchToRemove . range ) ;
165+ dep . loc = branchToRemove . loc ;
166+ parser . state . current . addDependency ( dep ) ;
167+ }
33168 return bool ;
34169 }
35170 } ) ;
@@ -42,6 +177,21 @@ class ConstPlugin {
42177 dep . loc = expression . loc ;
43178 parser . state . current . addDependency ( dep ) ;
44179 }
180+ // Expressions do not hoist.
181+ // It is safe to remove the dead branch.
182+ //
183+ // Given the following code:
184+ //
185+ // false ? someExpression() : otherExpression();
186+ //
187+ // the generated code is:
188+ //
189+ // false ? undefined : otherExpression();
190+ //
191+ const branchToRemove = bool ? expression . alternate : expression . consequent ;
192+ const dep = new ConstDependency ( "undefined" , branchToRemove . range ) ;
193+ dep . loc = branchToRemove . loc ;
194+ parser . state . current . addDependency ( dep ) ;
45195 return bool ;
46196 }
47197 } ) ;
0 commit comments