@@ -11,6 +11,7 @@ import {
11
11
SyntaxKind ,
12
12
VariableDeclarationKind ,
13
13
} from 'ts-morph' ;
14
+ import { isFunctionDeclaration } from 'typescript' ;
14
15
15
16
const SCRIPT_TAG_REGEX = / < s c r i p t l a n g = " t s " > ( [ \s \S ] * ?) < \/ s c r i p t > / i;
16
17
const TEMPLATE_TAG_REGEX = / < t e m p l a t e > ( [ \s \S ] * ?) < \/ t e m p l a t e > / i;
@@ -19,19 +20,24 @@ const STYLE_TAG_REGEX = /<style>([\s\S]*?)<\/style>/i;
19
20
const ON_INIT = 'onInit' ;
20
21
const ON_DESTROY = 'onDestroy' ;
21
22
23
+ const HOOKS_MAP = {
24
+ [ ON_INIT ] : 'ngOnInit' ,
25
+ [ ON_DESTROY ] : 'ngOnDestroy' ,
26
+ } as const ;
27
+
22
28
export function compileNgFile (
23
29
filePath : string ,
24
30
fileContent : string ,
25
31
shouldFormat = false
26
32
) {
27
- // eslint-disable-next-line @typescript-eslint/no-var-requires
28
- const { names } = require ( '@nx/devkit' ) ;
29
-
30
33
const componentName = filePath . split ( '/' ) . pop ( ) ?. split ( '.' ) [ 0 ] ;
31
34
if ( ! componentName ) {
32
35
throw new Error ( `[Analog] Missing component name ${ filePath } ` ) ;
33
36
}
34
37
38
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
39
+ const { names } = require ( '@nx/devkit' ) ;
40
+
35
41
const {
36
42
fileName : componentFileName ,
37
43
className,
@@ -52,6 +58,7 @@ export function compileNgFile(
52
58
}
53
59
54
60
const ngType = templateContent ? 'Component' : 'Directive' ;
61
+ const entityName = `${ className } Analog${ ngType } ` ;
55
62
56
63
if ( styleContent ) {
57
64
templateContent = `<style>${ styleContent . replace ( / \n / g, '' ) } </style>
@@ -73,7 +80,7 @@ import { ${ngType}${
73
80
: ''
74
81
}
75
82
})
76
- export default class AnalogNgEntity {
83
+ export default class ${ entityName } {
77
84
constructor() {}
78
85
}` ;
79
86
@@ -83,7 +90,7 @@ export default class AnalogNgEntity {
83
90
project . createSourceFile ( filePath , scriptContent ) ;
84
91
project . createSourceFile ( `${ filePath } .virtual.ts` , source ) ;
85
92
86
- return processNgScript ( filePath , project , ngType , shouldFormat ) ;
93
+ return processNgScript ( filePath , project , ngType , entityName , shouldFormat ) ;
87
94
}
88
95
89
96
return source ;
@@ -93,6 +100,7 @@ function processNgScript(
93
100
fileName : string ,
94
101
project : Project ,
95
102
ngType : 'Component' | 'Directive' ,
103
+ entityName : string ,
96
104
isProd ?: boolean
97
105
) {
98
106
const ngSourceFile = project . getSourceFile ( fileName ) ;
@@ -103,7 +111,7 @@ function processNgScript(
103
111
}
104
112
105
113
const targetClass = targetSourceFile . getClass (
106
- ( classDeclaration ) => classDeclaration . getName ( ) === 'AnalogNgEntity'
114
+ ( classDeclaration ) => classDeclaration . getName ( ) === entityName
107
115
) ;
108
116
109
117
if ( ! targetClass ) {
@@ -146,38 +154,47 @@ function processNgScript(
146
154
targetSourceFile . addImportDeclaration ( node . getStructure ( ) ) ;
147
155
}
148
156
157
+ const nodeFullText = node . getText ( ) ;
158
+
149
159
// for VariableStatement (e.g: const ... = ..., let ... = ...)
150
160
if ( Node . isVariableStatement ( node ) ) {
151
161
// NOTE: we do not support multiple declarations (i.e: const a, b, c)
152
- const declarations = node . getDeclarations ( ) ;
153
- const declaration = declarations [ 0 ] ;
154
- const isLet = node . getDeclarationKind ( ) === VariableDeclarationKind . Let ;
155
- const initializer = declaration . getInitializer ( ) ;
162
+ const [ declaration , isLet ] = [
163
+ node . getDeclarations ( ) [ 0 ] ,
164
+ node . getDeclarationKind ( ) === VariableDeclarationKind . Let ,
165
+ ] ;
156
166
167
+ const [ name , initializer ] = [
168
+ declaration . getName ( ) ,
169
+ declaration . getInitializer ( ) ,
170
+ ] ;
171
+
172
+ // let variable; // no initializer
157
173
if ( ! initializer && isLet ) {
158
- targetConstructor . addStatements ( node . getText ( ) ) ;
159
- getters . push ( {
160
- propertyName : declaration . getName ( ) ,
161
- isFunction : false ,
162
- } ) ;
174
+ // transfer the whole line `let variable;` over
175
+ addVariableToConstructor ( targetConstructor , '' , name , 'let' ) ;
176
+ // populate getters array for Object.defineProperties
177
+ getters . push ( { propertyName : name , isFunction : false } ) ;
163
178
} else if ( initializer ) {
164
- const declarationNameNode = declaration . getNameNode ( ) ;
179
+ // with initializer
180
+ const nameNode = declaration . getNameNode ( ) ;
165
181
166
- // destructures
182
+ // if destructured.
183
+ // TODO: we don't have a good abstraction for handling destructured variables yet.
167
184
if (
168
- Node . isArrayBindingPattern ( declarationNameNode ) ||
169
- Node . isObjectBindingPattern ( declarationNameNode )
185
+ Node . isArrayBindingPattern ( nameNode ) ||
186
+ Node . isObjectBindingPattern ( nameNode )
170
187
) {
171
- targetConstructor . addStatements ( node . getText ( ) ) ;
188
+ targetConstructor . addStatements ( nodeFullText ) ;
172
189
173
- const bindingElements = declarationNameNode
190
+ const bindingElements = nameNode
174
191
. getDescendantsOfKind ( SyntaxKind . BindingElement )
175
192
. map ( ( bindingElement ) => bindingElement . getName ( ) ) ;
176
193
177
194
if ( isLet ) {
178
195
getters . push (
179
- ...bindingElements . map ( ( bindingElement ) => ( {
180
- propertyName : bindingElement ,
196
+ ...bindingElements . map ( ( propertyName ) => ( {
197
+ propertyName,
181
198
isFunction : false ,
182
199
} ) )
183
200
) ;
@@ -194,42 +211,52 @@ function processNgScript(
194
211
}
195
212
}
196
213
} else {
197
- addPropertyToClass (
198
- targetClass ,
199
- targetConstructor ,
200
- declaration . getName ( ) ,
201
- initializer ,
202
- isLet ,
203
- ( isFunction , propertyName ) => {
204
- targetConstructor . addStatements ( node . getText ( ) ) ;
205
- if ( isLet ) {
206
- getters . push ( { propertyName, isFunction } ) ;
207
- }
208
- }
209
- ) ;
214
+ const isFunctionInitializer = isFunction ( initializer ) ;
215
+
216
+ if ( ! isLet ) {
217
+ /**
218
+ * normal property
219
+ * const variable = initializer;
220
+ * We'll create a class property with the same variable name
221
+ */
222
+ addVariableToConstructor (
223
+ targetConstructor ,
224
+ initializer . getText ( ) ,
225
+ name ,
226
+ 'const' ,
227
+ true
228
+ ) ;
229
+ } else {
230
+ /**
231
+ * let variable = initializer;
232
+ * We will NOT create a class property with the name because we will add it to the getters
233
+ */
234
+ addVariableToConstructor (
235
+ targetConstructor ,
236
+ initializer . getText ( ) ,
237
+ name ,
238
+ 'let'
239
+ ) ;
240
+ getters . push ( {
241
+ propertyName : name ,
242
+ isFunction : isFunctionInitializer ,
243
+ } ) ;
244
+ }
210
245
}
211
246
}
212
247
}
213
248
249
+ // function fnName() {}
214
250
if ( Node . isFunctionDeclaration ( node ) ) {
215
- addPropertyToClass (
216
- targetClass ,
217
- targetConstructor ,
218
- node . getName ( ) || '' ,
219
- node ,
220
- false ,
221
- ( _ , propertyName ) => {
222
- targetConstructor . addFunction ( {
223
- name : propertyName ,
224
- parameters : node
225
- . getParameters ( )
226
- . map ( ( parameter ) => parameter . getStructure ( ) ) ,
227
- statements : node
228
- . getStatements ( )
229
- . map ( ( statement ) => statement . getText ( ) ) ,
230
- } ) ;
231
- }
232
- ) ;
251
+ const functionName = node . getName ( ) ;
252
+ if ( functionName ) {
253
+ targetConstructor . addStatements ( [
254
+ // bring the function over
255
+ nodeFullText ,
256
+ // assign class property
257
+ `this.${ functionName } = ${ functionName } .bind(this);` ,
258
+ ] ) ;
259
+ }
233
260
}
234
261
235
262
if ( Node . isExpressionStatement ( node ) ) {
@@ -244,45 +271,38 @@ function processNgScript(
244
271
} else if ( functionName === ON_INIT || functionName === ON_DESTROY ) {
245
272
const initFunction = expression . getArguments ( ) [ 0 ] ;
246
273
if ( Node . isArrowFunction ( initFunction ) ) {
247
- addPropertyToClass (
248
- targetClass ,
249
- targetConstructor ,
250
- functionName ,
251
- initFunction ,
252
- false ,
253
- ( _isFunction , propertyName ) => {
254
- targetConstructor . addFunction ( {
255
- name : propertyName ,
256
- statements : initFunction
257
- . getStatements ( )
258
- . map ( ( statement ) => statement . getText ( ) ) ,
259
- } ) ;
260
-
261
- targetClass . addMethod ( {
262
- name : functionName === ON_INIT ? 'ngOnInit' : 'ngOnDestroy' ,
263
- statements : `this.${ propertyName } ();` ,
264
- } ) ;
265
- }
274
+ // add the function to constructor
275
+ targetConstructor . addStatements (
276
+ `this.${ functionName } = ${ initFunction . getText ( ) } `
266
277
) ;
278
+
279
+ // add life-cycle method to class
280
+ targetClass . addMethod ( {
281
+ name : HOOKS_MAP [ functionName ] ,
282
+ statements : `this.${ functionName } ();` ,
283
+ } ) ;
267
284
}
268
285
} else {
286
+ // just add the entire node to the constructor. i.e: effect()
269
287
targetConstructor . addStatements ( node . getText ( ) ) ;
270
288
}
271
289
}
272
290
}
273
291
} ) ;
274
292
275
- if ( ngType === 'Component' ) {
293
+ if ( ngType === 'Component' && declarations . length ) {
276
294
const importsMetadata = targetMetadataArguments . getProperty ( 'imports' ) ;
295
+ const declarationSymbols = declarations . filter ( Boolean ) . join ( ', ' ) ;
296
+
277
297
if ( importsMetadata && Node . isPropertyAssignment ( importsMetadata ) ) {
278
298
const importsInitializer = importsMetadata . getInitializer ( ) ;
279
299
if ( Node . isArrayLiteralExpression ( importsInitializer ) ) {
280
- importsInitializer . addElement ( declarations . filter ( Boolean ) . join ( ', ' ) ) ;
300
+ importsInitializer . addElement ( declarationSymbols ) ;
281
301
}
282
302
} else {
283
303
targetMetadataArguments . addPropertyAssignment ( {
284
304
name : 'imports' ,
285
- initializer : `[${ declarations . filter ( Boolean ) . join ( ', ' ) } ]` ,
305
+ initializer : `[${ declarationSymbols } ]` ,
286
306
} ) ;
287
307
}
288
308
}
@@ -353,38 +373,37 @@ function processMetadata(
353
373
} ) ;
354
374
}
355
375
356
- function addPropertyToClass <
357
- TInitializer extends Expression | FunctionDeclaration
358
- > (
359
- targetClass : ClassDeclaration ,
376
+ /**
377
+ * const variable = initializer;
378
+ * |
379
+ * v
380
+ * constructor() {
381
+ * const variable = (this.variable = initializer);
382
+ * }
383
+ *
384
+ */
385
+ function addVariableToConstructor (
360
386
targetConstructor : ConstructorDeclaration ,
361
- propertyName : string ,
362
- propertyInitializer : TInitializer ,
363
- isLet : boolean ,
364
- constructorUpdater : ( isFunction : boolean , propertyName : string ) => void
387
+ initializerText : string ,
388
+ variableName : string ,
389
+ kind : 'let' | 'const' ,
390
+ withClassProperty = false
365
391
) {
366
- if ( ! isLet ) {
367
- // add the empty property the class (e.g: protected propertyName;)
368
- targetClass . addProperty ( {
369
- name : propertyName ,
370
- kind : StructureKind . Property ,
371
- scope : Scope . Protected ,
372
- } ) ;
373
- }
374
-
375
- const isFunction =
376
- Node . isArrowFunction ( propertyInitializer ) ||
377
- Node . isFunctionDeclaration ( propertyInitializer ) ;
392
+ let statement = `${ kind } ${ variableName } ` ;
378
393
379
- // update the constructor
380
- constructorUpdater ( isFunction , propertyName ) ;
394
+ if ( initializerText ) {
395
+ statement += `= ${ initializerText } ` ;
381
396
382
- if ( ! isLet ) {
383
- // assign the variable to the property
384
- targetConstructor . addStatements (
385
- isFunction
386
- ? `this.${ propertyName } = ${ propertyName } .bind(this);`
387
- : `this.${ propertyName } = ${ propertyName } ;`
388
- ) ;
397
+ if ( withClassProperty ) {
398
+ statement = `${ kind } ${ variableName } = (this.${ variableName } = ${ initializerText } )` ;
399
+ }
389
400
}
401
+
402
+ targetConstructor . addStatements ( ( statement += ';' ) ) ;
403
+ }
404
+
405
+ function isFunction ( initializer : Node ) {
406
+ return (
407
+ Node . isArrowFunction ( initializer ) || Node . isFunctionDeclaration ( initializer )
408
+ ) ;
390
409
}
0 commit comments