@@ -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,49 @@ 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
+ // There doesn't seem to be a way to make a statement from a class declaration.
82
+ // But it seems to work if you just jam it in there.
83
+ const classStatement = clazz . declaration as { } as ts . ExpressionStatement ;
84
+ const innerReturn = ts . createReturn ( ts . createIdentifier ( clazz . name ) ) ;
85
+
86
+ const iife = ts . createImmediatelyInvokedFunctionExpression ( [
87
+ classStatement ,
88
+ ...clazz . statements . map ( st => st . expressionStatement ) ,
89
+ innerReturn ,
90
+ ] ) ;
91
+
92
+ const pureIife = ts . addSyntheticLeadingComment (
93
+ iife ,
94
+ ts . SyntaxKind . MultiLineCommentTrivia ,
95
+ '@__PURE__' ,
96
+ false ,
97
+ ) ;
98
+
99
+ // Move the original class modifiers to the var statement.
100
+ const newNode = ts . createVariableStatement (
101
+ clazz . declaration . modifiers ,
102
+ ts . createVariableDeclarationList ( [
103
+ ts . createVariableDeclaration ( clazz . name , undefined , pureIife ) ,
104
+ ] ) ,
105
+ ) ;
106
+ clazz . declaration . modifiers = undefined ;
107
+
108
+ // Update remaining classes and statements.
109
+ statementsToRemove . push ( ...clazz . statements . map ( st => st . expressionStatement ) ) ;
110
+ classes = classes . filter ( cl => cl != clazz ) ;
111
+
112
+ return newNode ;
66
113
}
67
114
68
115
// Otherwise return node as is.
@@ -80,6 +127,19 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
80
127
const classes : ClassData [ ] = [ ] ;
81
128
// Find all class declarations, build a ClassData for each.
82
129
ts . forEachChild ( node , ( child ) => {
130
+ // Check if it is a named class declaration first.
131
+ // Technically it doesn't need a name in TS if it's the default export, but when downleveled
132
+ // it will be have a name (e.g. `default_1`).
133
+ if ( ts . isClassDeclaration ( child ) && child . name && ts . isIdentifier ( child . name ) ) {
134
+ classes . push ( {
135
+ name : child . name . text ,
136
+ declaration : child ,
137
+ statements : [ ] ,
138
+ } ) ;
139
+
140
+ return ;
141
+ }
142
+
83
143
if ( child . kind !== ts . SyntaxKind . VariableStatement ) {
84
144
return ;
85
145
}
@@ -122,22 +182,20 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
122
182
}
123
183
classes . push ( {
124
184
name,
125
- class : varDecl ,
126
- classFunction : fn ,
185
+ declaration : varDecl ,
186
+ function : fn ,
127
187
statements : [ ] ,
128
188
} ) ;
129
189
} ) ;
130
190
131
191
return classes ;
132
192
}
133
193
134
- function findClassStaticPropertyAssignments (
194
+ function findClassesWithStaticPropertyAssignments (
135
195
node : ts . Node ,
136
196
checker : ts . TypeChecker ,
137
- classes : ClassData [ ] ) : StatementData [ ] {
138
-
139
- const statements : StatementData [ ] = [ ] ;
140
-
197
+ classes : ClassData [ ] ,
198
+ ) {
141
199
// Find each assignment outside of the declaration.
142
200
ts . forEachChild ( node , ( child ) => {
143
201
if ( child . kind !== ts . SyntaxKind . ExpressionStatement ) {
@@ -166,15 +224,15 @@ function findClassStaticPropertyAssignments(
166
224
return ;
167
225
}
168
226
169
- const hostClass = classes . find ( ( clazz => decls . includes ( clazz . class ) ) ) ;
227
+ const hostClass = classes . find ( ( clazz => decls . includes ( clazz . declaration ) ) ) ;
170
228
if ( ! hostClass ) {
171
229
return ;
172
230
}
173
231
const statement : StatementData = { expressionStatement, hostClass } ;
174
232
175
233
hostClass . statements . push ( statement ) ;
176
- statements . push ( statement ) ;
177
234
} ) ;
178
235
179
- return statements ;
236
+ // Only return classes that have static property assignments.
237
+ return classes . filter ( cl => cl . statements . length != 0 ) ;
180
238
}
0 commit comments