@@ -9,8 +9,8 @@ import * as ts from 'typescript';
9
9
10
10
interface ClassData {
11
11
name : string ;
12
- class : ts . VariableDeclaration ;
13
- classFunction : ts . FunctionExpression ;
12
+ declaration : ts . VariableDeclaration | ts . ClassDeclaration ;
13
+ function ? : ts . FunctionExpression ;
14
14
statements : StatementData [ ] ;
15
15
}
16
16
@@ -26,28 +26,34 @@ export function getFoldFileTransformer(program: ts.Program): ts.TransformerFacto
26
26
27
27
const transformer : ts . Transformer < ts . SourceFile > = ( sf : ts . SourceFile ) => {
28
28
29
- const classes = findClassDeclarations ( sf ) ;
30
- const statements = findClassStaticPropertyAssignments ( sf , checker , classes ) ;
29
+ const statementsToRemove : ts . ExpressionStatement [ ] = [ ] ;
30
+ const classesWithoutStatements = findClassDeclarations ( sf ) ;
31
+ let classes = findClassesWithStaticPropertyAssignments ( sf , checker , classesWithoutStatements ) ;
31
32
32
33
const visitor : ts . Visitor = ( node : ts . Node ) : ts . VisitResult < ts . Node > => {
34
+ if ( classes . length === 0 && statementsToRemove . length === 0 ) {
35
+ // There are no more statements to fold.
36
+ return ts . visitEachChild ( node , visitor , context ) ;
37
+ }
38
+
33
39
// Check if node is a statement to be dropped.
34
- if ( statements . find ( ( st ) => st . expressionStatement === node ) ) {
40
+ const stmtIdx = statementsToRemove . indexOf ( node as ts . ExpressionStatement ) ;
41
+ if ( stmtIdx != - 1 ) {
42
+ statementsToRemove . splice ( stmtIdx , 1 ) ;
43
+
35
44
return undefined ;
36
45
}
37
46
38
- // Check if node is a class to add statements to.
39
- const clazz = classes . find ( ( cl ) => cl . classFunction === node ) ;
47
+ // Check if node is a ES5 class to add statements to.
48
+ let clazz = classes . find ( ( cl ) => cl . function === node ) ;
40
49
if ( clazz ) {
41
- const functionExpression : ts . FunctionExpression = node as ts . FunctionExpression ;
42
-
43
- const newExpressions = clazz . statements . map ( ( st ) =>
44
- ts . createStatement ( st . expressionStatement . expression ) ) ;
50
+ const functionExpression = node as ts . FunctionExpression ;
45
51
46
52
// Create a new body with all the original statements, plus new ones,
47
53
// plus return statement.
48
54
const newBody = ts . createBlock ( [
49
55
...functionExpression . body . statements . slice ( 0 , - 1 ) ,
50
- ...newExpressions ,
56
+ ...clazz . statements . map ( st => st . expressionStatement ) ,
51
57
...functionExpression . body . statements . slice ( - 1 ) ,
52
58
] ) ;
53
59
@@ -61,8 +67,47 @@ export function getFoldFileTransformer(program: ts.Program): ts.TransformerFacto
61
67
newBody ,
62
68
) ;
63
69
70
+ // Update remaining classes and statements.
71
+ statementsToRemove . push ( ...clazz . statements . map ( st => st . expressionStatement ) ) ;
72
+ classes = classes . filter ( cl => cl != clazz ) ;
73
+
64
74
// Replace node with modified one.
65
- return ts . visitEachChild ( newNode , visitor , context ) ;
75
+ return newNode ;
76
+ }
77
+
78
+ // Check if node is a ES2015 class to replace with a pure IIFE.
79
+ clazz = classes . find ( ( cl ) => ! cl . function && cl . declaration === node ) ;
80
+ if ( clazz ) {
81
+ const classStatement = clazz . declaration as ts . ClassDeclaration ;
82
+ const innerReturn = ts . createReturn ( ts . createIdentifier ( clazz . name ) ) ;
83
+
84
+ const iife = ts . createImmediatelyInvokedFunctionExpression ( [
85
+ classStatement ,
86
+ ...clazz . statements . map ( st => st . expressionStatement ) ,
87
+ innerReturn ,
88
+ ] ) ;
89
+
90
+ const pureIife = ts . addSyntheticLeadingComment (
91
+ iife ,
92
+ ts . SyntaxKind . MultiLineCommentTrivia ,
93
+ '@__PURE__' ,
94
+ false ,
95
+ ) ;
96
+
97
+ // Move the original class modifiers to the var statement.
98
+ const newNode = ts . createVariableStatement (
99
+ clazz . declaration . modifiers ,
100
+ ts . createVariableDeclarationList ( [
101
+ ts . createVariableDeclaration ( clazz . name , undefined , pureIife ) ,
102
+ ] , ts . NodeFlags . Const ) ,
103
+ ) ;
104
+ clazz . declaration . modifiers = undefined ;
105
+
106
+ // Update remaining classes and statements.
107
+ statementsToRemove . push ( ...clazz . statements . map ( st => st . expressionStatement ) ) ;
108
+ classes = classes . filter ( cl => cl != clazz ) ;
109
+
110
+ return newNode ;
66
111
}
67
112
68
113
// Otherwise return node as is.
@@ -80,6 +125,19 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
80
125
const classes : ClassData [ ] = [ ] ;
81
126
// Find all class declarations, build a ClassData for each.
82
127
ts . forEachChild ( node , ( child ) => {
128
+ // Check if it is a named class declaration first.
129
+ // Technically it doesn't need a name in TS if it's the default export, but when downleveled
130
+ // it will be have a name (e.g. `default_1`).
131
+ if ( ts . isClassDeclaration ( child ) && child . name ) {
132
+ classes . push ( {
133
+ name : child . name . text ,
134
+ declaration : child ,
135
+ statements : [ ] ,
136
+ } ) ;
137
+
138
+ return ;
139
+ }
140
+
83
141
if ( child . kind !== ts . SyntaxKind . VariableStatement ) {
84
142
return ;
85
143
}
@@ -122,22 +180,20 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
122
180
}
123
181
classes . push ( {
124
182
name,
125
- class : varDecl ,
126
- classFunction : fn ,
183
+ declaration : varDecl ,
184
+ function : fn ,
127
185
statements : [ ] ,
128
186
} ) ;
129
187
} ) ;
130
188
131
189
return classes ;
132
190
}
133
191
134
- function findClassStaticPropertyAssignments (
192
+ function findClassesWithStaticPropertyAssignments (
135
193
node : ts . Node ,
136
194
checker : ts . TypeChecker ,
137
- classes : ClassData [ ] ) : StatementData [ ] {
138
-
139
- const statements : StatementData [ ] = [ ] ;
140
-
195
+ classes : ClassData [ ] ,
196
+ ) {
141
197
// Find each assignment outside of the declaration.
142
198
ts . forEachChild ( node , ( child ) => {
143
199
if ( child . kind !== ts . SyntaxKind . ExpressionStatement ) {
@@ -166,15 +222,15 @@ function findClassStaticPropertyAssignments(
166
222
return ;
167
223
}
168
224
169
- const hostClass = classes . find ( ( clazz => decls . includes ( clazz . class ) ) ) ;
225
+ const hostClass = classes . find ( ( clazz => decls . includes ( clazz . declaration ) ) ) ;
170
226
if ( ! hostClass ) {
171
227
return ;
172
228
}
173
229
const statement : StatementData = { expressionStatement, hostClass } ;
174
230
175
231
hostClass . statements . push ( statement ) ;
176
- statements . push ( statement ) ;
177
232
} ) ;
178
233
179
- return statements ;
234
+ // Only return classes that have static property assignments.
235
+ return classes . filter ( cl => cl . statements . length != 0 ) ;
180
236
}
0 commit comments