@@ -9,8 +9,8 @@ import * as ts from 'typescript';
99
1010interface ClassData {
1111 name : string ;
12- class : ts . VariableDeclaration ;
13- classFunction : ts . FunctionExpression ;
12+ declaration : ts . VariableDeclaration | ts . ClassDeclaration ;
13+ function ? : ts . FunctionExpression ;
1414 statements : StatementData [ ] ;
1515}
1616
@@ -26,28 +26,34 @@ export function getFoldFileTransformer(program: ts.Program): ts.TransformerFacto
2626
2727 const transformer : ts . Transformer < ts . SourceFile > = ( sf : ts . SourceFile ) => {
2828
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 ) ;
3132
3233 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+
3339 // 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+
3544 return undefined ;
3645 }
3746
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 ) ;
4049 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 ;
4551
4652 // Create a new body with all the original statements, plus new ones,
4753 // plus return statement.
4854 const newBody = ts . createBlock ( [
4955 ...functionExpression . body . statements . slice ( 0 , - 1 ) ,
50- ...newExpressions ,
56+ ...clazz . statements . map ( st => st . expressionStatement ) ,
5157 ...functionExpression . body . statements . slice ( - 1 ) ,
5258 ] ) ;
5359
@@ -61,8 +67,47 @@ export function getFoldFileTransformer(program: ts.Program): ts.TransformerFacto
6167 newBody ,
6268 ) ;
6369
70+ // Update remaining classes and statements.
71+ statementsToRemove . push ( ...clazz . statements . map ( st => st . expressionStatement ) ) ;
72+ classes = classes . filter ( cl => cl != clazz ) ;
73+
6474 // 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 ;
66111 }
67112
68113 // Otherwise return node as is.
@@ -80,6 +125,19 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
80125 const classes : ClassData [ ] = [ ] ;
81126 // Find all class declarations, build a ClassData for each.
82127 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+
83141 if ( child . kind !== ts . SyntaxKind . VariableStatement ) {
84142 return ;
85143 }
@@ -122,22 +180,20 @@ function findClassDeclarations(node: ts.Node): ClassData[] {
122180 }
123181 classes . push ( {
124182 name,
125- class : varDecl ,
126- classFunction : fn ,
183+ declaration : varDecl ,
184+ function : fn ,
127185 statements : [ ] ,
128186 } ) ;
129187 } ) ;
130188
131189 return classes ;
132190}
133191
134- function findClassStaticPropertyAssignments (
192+ function findClassesWithStaticPropertyAssignments (
135193 node : ts . Node ,
136194 checker : ts . TypeChecker ,
137- classes : ClassData [ ] ) : StatementData [ ] {
138-
139- const statements : StatementData [ ] = [ ] ;
140-
195+ classes : ClassData [ ] ,
196+ ) {
141197 // Find each assignment outside of the declaration.
142198 ts . forEachChild ( node , ( child ) => {
143199 if ( child . kind !== ts . SyntaxKind . ExpressionStatement ) {
@@ -166,15 +222,15 @@ function findClassStaticPropertyAssignments(
166222 return ;
167223 }
168224
169- const hostClass = classes . find ( ( clazz => decls . includes ( clazz . class ) ) ) ;
225+ const hostClass = classes . find ( ( clazz => decls . includes ( clazz . declaration ) ) ) ;
170226 if ( ! hostClass ) {
171227 return ;
172228 }
173229 const statement : StatementData = { expressionStatement, hostClass } ;
174230
175231 hostClass . statements . push ( statement ) ;
176- statements . push ( statement ) ;
177232 } ) ;
178233
179- return statements ;
234+ // Only return classes that have static property assignments.
235+ return classes . filter ( cl => cl . statements . length != 0 ) ;
180236}
0 commit comments