@@ -10,6 +10,7 @@ use crate::{ctx::Ctx, CompressOptions};
10
10
#[ derive( Default ) ]
11
11
pub struct NormalizeOptions {
12
12
pub convert_while_to_fors : bool ,
13
+ pub convert_const_to_let : bool ,
13
14
}
14
15
15
16
/// Normalize AST
@@ -19,6 +20,7 @@ pub struct NormalizeOptions {
19
20
/// * remove `Statement::EmptyStatement`
20
21
/// * remove `ParenthesizedExpression`
21
22
/// * convert whiles to fors
23
+ /// * convert `const` to `let` for non-exported variables
22
24
/// * convert `Infinity` to `f64::INFINITY`
23
25
/// * convert `NaN` to `f64::NaN`
24
26
/// * convert `var x; void x` to `void 0`
@@ -49,6 +51,16 @@ impl<'a> Traverse<'a> for Normalize {
49
51
} ) ;
50
52
}
51
53
54
+ fn exit_variable_declaration (
55
+ & mut self ,
56
+ decl : & mut VariableDeclaration < ' a > ,
57
+ ctx : & mut TraverseCtx < ' a > ,
58
+ ) {
59
+ if self . options . convert_const_to_let {
60
+ Self :: convert_const_to_let ( decl, ctx) ;
61
+ }
62
+ }
63
+
52
64
fn exit_statement ( & mut self , stmt : & mut Statement < ' a > , ctx : & mut TraverseCtx < ' a > ) {
53
65
match stmt {
54
66
Statement :: WhileStatement ( _) if self . options . convert_while_to_fors => {
@@ -136,6 +148,23 @@ impl<'a> Normalize {
136
148
* stmt = Statement :: ForStatement ( for_stmt) ;
137
149
}
138
150
151
+ fn convert_const_to_let ( decl : & mut VariableDeclaration < ' a > , ctx : & mut TraverseCtx < ' a > ) {
152
+ // checking whether the current scope is the root scope instead of
153
+ // checking whether any variables are exposed to outside (e.g. `export` in ESM)
154
+ if decl. kind . is_const ( ) && ctx. current_scope_id ( ) != ctx. scopes ( ) . root_scope_id ( ) {
155
+ let all_declarations_are_only_read = decl. declarations . iter ( ) . all ( |decl| {
156
+ decl. id . get_binding_identifiers ( ) . iter ( ) . all ( |id| {
157
+ ctx. symbols ( )
158
+ . get_resolved_references ( id. symbol_id ( ) )
159
+ . all ( |reference| reference. flags ( ) . is_read_only ( ) )
160
+ } )
161
+ } ) ;
162
+ if all_declarations_are_only_read {
163
+ decl. kind = VariableDeclarationKind :: Let ;
164
+ }
165
+ }
166
+ }
167
+
139
168
/// Transforms `undefined` => `void 0`, `Infinity` => `f64::Infinity`, `NaN` -> `f64::NaN`.
140
169
/// So subsequent passes don't need to look up whether these variables are shadowed or not.
141
170
fn try_compress_identifier (
@@ -179,14 +208,26 @@ impl<'a> Normalize {
179
208
180
209
#[ cfg( test) ]
181
210
mod test {
182
- use crate :: tester:: test;
211
+ use crate :: tester:: { test, test_same } ;
183
212
184
213
#[ test]
185
214
fn test_while ( ) {
186
215
// Verify while loops are converted to FOR loops.
187
216
test ( "while(c < b) foo()" , "for(; c < b;) foo()" ) ;
188
217
}
189
218
219
+ #[ test]
220
+ fn test_const_to_let ( ) {
221
+ test_same ( "const x = 1" ) ; // keep top-level (can be replaced with "let" if it's ESM and not exported)
222
+ test ( "{ const x = 1 }" , "{ let x = 1 }" ) ;
223
+ test_same ( "{ const x = 1; x = 2 }" ) ; // keep assign error
224
+ test ( "{ const x = 1, y = 2 }" , "{ let x = 1, y = 2 }" ) ;
225
+ test ( "{ const { x } = { x: 1 } }" , "{ let { x } = { x: 1 } }" ) ;
226
+ test ( "{ const [x] = [1] }" , "{ let [x] = [1] }" ) ;
227
+ test ( "{ const [x = 1] = [] }" , "{ let [x = 1] = [] }" ) ;
228
+ test ( "for (const x in y);" , "for (let x in y);" ) ;
229
+ }
230
+
190
231
#[ test]
191
232
fn test_void_ident ( ) {
192
233
test ( "var x; void x" , "var x" ) ;
0 commit comments