• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-cli / 10055681579

23 Jul 2024 08:42AM UTC coverage: 70.793% (+4.0%) from 66.802%
10055681579

push

github

web-flow
Merge pull request #1243 from IgniteUI/bpenkov/angular-ts-file-update

Add an AngularTypeScriptFileUpdate

1135 of 1677 branches covered (67.68%)

Branch coverage included in aggregate %.

348 of 415 new or added lines in 19 files covered. (83.86%)

76 existing lines in 39 files now uncovered.

4687 of 6547 relevant lines covered (71.59%)

170.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.11
/packages/core/typescript/TypeScriptASTTransformer.ts
1
import * as ts from 'typescript';
12✔
2
import * as crypto from 'crypto';
12✔
3
import {
12✔
4
  KeyValuePair,
5
  FormattingService,
6
  PropertyAssignment,
7
  Identifier,
8
  ImportDeclarationMeta,
9
  FormatSettings,
10
  ChangeRequest,
11
  ChangeType,
12
  SyntaxKind,
13
} from '../types';
14
import { TypeScriptFormattingService } from './TypeScriptFormattingService';
12✔
15
import {
12✔
16
  createImportDeclaration,
17
  importDeclarationCollides,
18
  isPropertyAssignment,
19
  newArgumentInMethodCallExpressionTransformerFactory,
20
  newImportDeclarationTransformerFactory,
21
  newMemberInArrayLiteralTransformerFactory,
22
  newMemberInObjectLiteralTransformerFactory,
23
  updateForObjectLiteralMemberTransformerFactory,
24
} from './TransformerFactories';
25
import { TypeScriptExpressionCollector } from './TypeScriptExpressionCollector';
12✔
26

27
export class TypeScriptASTTransformer {
12✔
28
  private _printer: ts.Printer | undefined;
29
  private _expressionCollector: TypeScriptExpressionCollector;
30
  private _flatNodeRelations: Map<ts.Node, ts.Node>;
31
  private _defaultCompilerOptions: ts.CompilerOptions = {
160✔
32
    pretty: true,
33
  };
34

35
  /**
36
   * Create a new source update instance for the given source file.
37
   * @param sourceFile The source file to update.
38
   * @param printerOptions Options to use when printing the source file.
39
   * @param customCompilerOptions Custom compiler options to use when transforming the source file.
40
   * @param formatSettings Custom formatting settings to apply. If provided, a {@link TypeScriptFormattingService} will be initialized.
41
   */
42
  constructor(
43
    public readonly sourceFile: ts.SourceFile,
160✔
44
    protected readonly printerOptions?: ts.PrinterOptions,
160✔
45
    protected readonly customCompilerOptions?: ts.CompilerOptions,
160✔
46
    private readonly formatSettings?: FormatSettings
160✔
47
  ) {
48
    if (formatSettings) {
160✔
49
      this.formatter = new TypeScriptFormattingService(
82✔
50
        sourceFile.fileName,
51
        formatSettings
52
      );
53
    }
54

55
    this._flatNodeRelations = this.createNodeRelationsMap(this.sourceFile);
160✔
56
    this._expressionCollector = new TypeScriptExpressionCollector();
160✔
57
  }
58

59
  /** Map of all transformations to apply to the source file. */
60
  public readonly transformations = new Map<string, ChangeRequest<ts.Node>>();
160✔
61

62
  /** The formatting service to use when printing the source file. */
63
  public formatter: FormattingService;
64

65
  /** A map of nodes with their parents. */
66
  public get flatNodeRelations() {
67
    return this._flatNodeRelations;
524✔
68
  }
69

70
  /**
71
   * The printer instance to use to print the source file after modifications.
72
   */
73
  public get printer(): ts.Printer {
74
    if (!this._printer) {
46✔
75
      this._printer = ts.createPrinter(this.printerOptions);
42✔
76
    }
77

78
    return this._printer;
46✔
79
  }
80

81
  /**
82
   * The compiler options to use when transforming the source file.
83
   */
84
  public get compilerOptions(): ts.CompilerOptions {
85
    return Object.assign(
262✔
86
      {},
87
      this._defaultCompilerOptions,
88
      this.customCompilerOptions
89
    );
90
  }
91

92
  /**
93
   * Looks up a property assignment in the AST.
94
   * @param visitCondition The condition by which the property assignment is found.
95
   * @param lastMatch Whether to return the last match found. If not set, the first match will be returned.
96
   */
97
  public findPropertyAssignment(
98
    visitCondition: (node: ts.PropertyAssignment) => boolean,
99
    lastMatch: boolean = false
64✔
100
  ): ts.PropertyAssignment | undefined {
101
    let propertyAssignment: ts.PropertyAssignment | undefined;
102
    const visitor: ts.Visitor = (node) => {
66✔
103
      if (ts.isPropertyAssignment(node) && visitCondition(node)) {
1,538✔
104
        return (propertyAssignment = node);
36✔
105
      }
106
      if (!propertyAssignment || lastMatch) {
1,502✔
107
        return ts.visitEachChild(node, visitor, undefined);
1,404✔
108
      }
109
      return undefined;
98✔
110
    };
111

112
    ts.visitNode(this.sourceFile, visitor, ts.isPropertyAssignment);
66✔
113
    return propertyAssignment;
66✔
114
  }
115

116
  /**
117
   * Searches the AST for a variable declaration with the given name and type.
118
   * @param name The name of the variable to look for.
119
   * @param type The type of the variable to look for.
120
   * @returns The variable declaration if found, otherwise `undefined`.
121
   */
122
  public findVariableDeclaration(
123
    name: string,
124
    type: string
125
  ): ts.VariableDeclaration | undefined {
126
    let declaration;
127
    ts.forEachChild(this.sourceFile, (node) => {
6✔
128
      if (
12!
129
        ts.isVariableDeclaration(node) &&
12!
130
        node.name.getText() === name &&
131
        node.type?.getText() === type
×
132
      ) {
133
        declaration = node;
×
134
      } else if (ts.isVariableStatement(node)) {
12✔
135
        declaration = node.declarationList.declarations.find(
6✔
136
          (declaration) =>
137
            declaration.name.getText() === name &&
6✔
138
            declaration.type?.getText() === type
18!
139
        );
140
      }
141
      // handle variable declaration lists (ts.isVariableDeclarationList)?
142
      // const a = 5, b = 6...;
143
    });
144

145
    return declaration;
6✔
146
  }
147

148
  /**
149
   * Traverses the {@link flatNodeRelations} up to find a node that satisfies the given condition.
150
   * @param node The starting point of the search.
151
   * @param condition The condition to satisfy.
152
   * @returns The node's ancestor that satisfies the condition, `undefined` if none is found.
153
   */
154
  public findNodeAncestor(
155
    node: ts.Node,
156
    condition: (node: ts.Node) => boolean
157
  ): ts.Node | undefined {
158
    if (condition(node)) {
674✔
159
      return node;
150✔
160
    }
161

162
    const parent = this.flatNodeRelations.get(node);
524✔
163
    if (parent) {
524✔
164
      return this.findNodeAncestor(parent, condition);
494✔
165
    }
166

167
    // no parent node satisfies the condition
168
    return undefined;
30✔
169
  }
170

171
  /**
172
   * Creates a request that will resolve during {@link finalize} for a new property assignment in an object literal expression.
173
   * @param visitCondition The condition by which the object literal expression is found.
174
   * @param propertyAssignment The property that will be added.
175
   */
176
  public requestNewMemberInObjectLiteral(
177
    visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
178
    propertyAssignment: PropertyAssignment
179
  ): void;
180
  /**
181
   * Creates a request that will resolve during {@link finalize} for a new property assignment in an object literal expression.
182
   * @param visitCondition The condition by which the object literal expression is found.
183
   * @param propertyName The name of the property that will be added.
184
   * @param propertyValue The value of the property that will be added.
185
   * @param multiline Whether the object literal should be multiline.
186
   */
187
  public requestNewMemberInObjectLiteral(
188
    visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
189
    propertyName: string,
190
    propertyValue: ts.Expression,
191
    multiline?: boolean
192
  ): void;
193
  public requestNewMemberInObjectLiteral(
194
    visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
195
    propertyNameOrAssignment: string | PropertyAssignment,
196
    propertyValue?: ts.Expression,
197
    multiline?: boolean
198
  ): void {
199
    let newProperty: ts.PropertyAssignment;
200
    if (propertyNameOrAssignment instanceof Object) {
82✔
201
      newProperty = ts.factory.createPropertyAssignment(
2✔
202
        propertyNameOrAssignment.name,
203
        propertyNameOrAssignment.value
204
      );
205
    } else if (propertyValue && typeof propertyValue !== 'string') {
80!
206
      newProperty = ts.factory.createPropertyAssignment(
80✔
207
        ts.factory.createIdentifier(propertyNameOrAssignment as string),
208
        propertyValue
209
      );
210
    } else {
211
      throw new Error('Must provide property value.');
×
212
    }
213

214
    const transformerFactory = newMemberInObjectLiteralTransformerFactory(
82✔
215
      newProperty,
216
      visitCondition,
217
      multiline,
218
      this._expressionCollector
219
    );
220
    this.requestChange(
82✔
221
      ChangeType.NewNode,
222
      transformerFactory,
223
      SyntaxKind.PropertyAssignment,
224
      newProperty
225
    );
226
  }
227

228
  /**
229
   * Creates a request that will resolve during {@link finalize} for a new property assignment that has a JSX value.
230
   * The member is added in an object literal expression.
231
   * @param visitCondition The condition by which the object literal expression is found.
232
   * @param propertyName The name of the property that will be added.
233
   * @param propertyValue The value of the property that will be added.
234
   * @param jsxAttributes The JSX attributes to add to the JSX element.
235
   *
236
   * @remarks Creates a property assignment of the form `{ propertyName: <propertyValue /> }` in the object literal.
237
   */
238
  public requestJsxMemberInObjectLiteral(
239
    visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
240
    propertyName: string,
241
    propertyValue: string,
242
    jsxAttributes?: ts.JsxAttributes
243
  ): void {
244
    const jsxElement = ts.factory.createJsxSelfClosingElement(
×
245
      ts.factory.createIdentifier(propertyValue),
246
      undefined, // type arguments
247
      jsxAttributes
248
    );
249

250
    this.requestNewMemberInObjectLiteral(
×
251
      visitCondition,
252
      propertyName,
253
      jsxElement
254
    );
255
  }
256

257
  /**
258
   * Creates a request that will resolve during {@link finalize} for an update to the value of a member in an object literal expression.
259
   * @param visitCondition The condition by which the object literal expression is found.
260
   * @param targetMember The member that will be updated. The value should be the new value to set.
261
   */
262
  public requestUpdateForObjectLiteralMember(
263
    visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
264
    targetMember: PropertyAssignment
265
  ): void {
266
    const transformerFactory = updateForObjectLiteralMemberTransformerFactory(
6✔
267
      visitCondition,
268
      targetMember
269
    );
270

271
    this.requestChange(
6✔
272
      ChangeType.NodeUpdate,
273
      transformerFactory,
274
      SyntaxKind.PropertyAssignment,
275
      ts.factory.createPropertyAssignment(targetMember.name, targetMember.value)
276
    );
277
  }
278

279
  /**
280
   * Creates a new object literal expression with the given properties.
281
   * @param properties The properties to add to the object literal.
282
   * @param multiline Whether the object literal should be multiline.
283
   * @param transform A function to transform the value of the property.
284
   * @remarks A `transform` function should be provided if the `properties` are of type `KeyValuePair<T>`.
285
   */
286
  public createObjectLiteralExpression(
287
    properties: PropertyAssignment[] | KeyValuePair<string>[],
288
    multiline: boolean = false,
4✔
289
    transform?: (value: string) => ts.Expression
290
  ): ts.ObjectLiteralExpression {
291
    let propertyAssignments: ts.ObjectLiteralElementLike[] = [];
70✔
292
    if (properties.every(isPropertyAssignment)) {
70✔
293
      propertyAssignments = properties.map((property) =>
60✔
294
        ts.factory.createPropertyAssignment(property.name, property.value)
130✔
295
      );
296
    } else {
297
      for (const property of properties) {
10✔
298
        propertyAssignments.push(
10✔
299
          ...this.mapKeyValuePairToObjectLiteral(property, (value) =>
300
            transform
10✔
301
              ? transform(value)
10!
302
              : ts.factory.createStringLiteral(
303
                  value,
304
                  this.formatSettings?.singleQuotes
×
305
                )
306
          )
307
        );
308
      }
309
    }
310

311
    return ts.factory.createObjectLiteralExpression(
70✔
312
      propertyAssignments,
313
      multiline
314
    );
315
  }
316

317
  /**
318
   * Creates a request that will resolve during {@link finalize} which adds a new element to an array literal expression.
319
   * @param visitCondition The condition by which the array literal expression is found.
320
   * @param elements The elements that will be added to the array literal.
321
   * @param prepend If the elements should be added at the beginning of the array.
322
   * @anchorElement The element to anchor the new elements to.
323
   * @multiline Whether the array literal should be multiline.
324
   * @remarks The `anchorElement` must match the type of the elements in the collection.
325
   */
326
  public requestNewMembersInArrayLiteral(
327
    visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
328
    elements: ts.Expression[],
329
    prepend?: boolean,
330
    anchorElement?: ts.Expression | PropertyAssignment,
331
    multiline?: boolean
332
  ): void;
333
  /**
334
   * Creates a request that will resolve during {@link finalize} which adds a new element to an array literal expression.
335
   * @param visitCondition The condition by which the array literal expression is found.
336
   * @param elements The elements that will be added to the array literal
337
   * @prepend If the elements should be added at the beginning of the array.
338
   * @anchorElement The element to anchor the new elements to.
339
   * @multiline Whether the array literal should be multiline.
340
   * @remarks The `anchorElement` must match the type of the elements in the collection.
341
   */
342
  public requestNewMembersInArrayLiteral(
343
    visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
344
    elements: PropertyAssignment[],
345
    prepend?: boolean,
346
    anchorElement?: ts.Expression | PropertyAssignment,
347
    multiline?: boolean
348
  ): void;
349
  public requestNewMembersInArrayLiteral(
350
    visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
351
    expressionOrPropertyAssignment: ts.Expression[] | PropertyAssignment[],
352
    prepend: boolean = false,
8✔
353
    anchorElement?: ts.StringLiteral | ts.NumericLiteral | PropertyAssignment,
354
    multiline: boolean = false
52✔
355
  ): void {
356
    let elements: ts.Expression[] | PropertyAssignment[];
357
    const isExpression = expressionOrPropertyAssignment.every((e) =>
52✔
358
      ts.isExpression(e as ts.Node)
52✔
359
    );
360
    if (isExpression) {
52!
361
      elements = expressionOrPropertyAssignment as ts.Expression[];
52✔
362
    } else {
363
      elements = (expressionOrPropertyAssignment as PropertyAssignment[]).map(
×
NEW
364
        (property) => this.createObjectLiteralExpression([property], multiline)
×
365
      );
366
    }
367

368
    const transformerFactory = newMemberInArrayLiteralTransformerFactory(
52✔
369
      visitCondition,
370
      elements,
371
      prepend,
372
      anchorElement
373
    );
374
    this.requestChange(
52✔
375
      ChangeType.NewNode,
376
      transformerFactory,
377
      isExpression ? SyntaxKind.Expression : SyntaxKind.PropertyAssignment,
52!
378
      ts.factory.createNodeArray(elements)
379
    );
380
  }
381

382
  /**
383
   * Creates an array literal expression with the given elements.
384
   * @param elements The elements to include in the array literal.
385
   * @param multiline Whether the array literal should be multiline.
386
   */
387
  public createArrayLiteralExpression(
388
    elements: ts.Expression[],
389
    multiline?: boolean
390
  ): ts.ArrayLiteralExpression;
391
  public createArrayLiteralExpression(
392
    elements: PropertyAssignment[],
393
    multiline?: boolean
394
  ): ts.ArrayLiteralExpression;
395
  public createArrayLiteralExpression(
396
    elementsOrProperties: ts.Expression[] | PropertyAssignment[],
397
    multiline: boolean = false
4✔
398
  ): ts.ArrayLiteralExpression {
399
    if (
78✔
400
      elementsOrProperties.every((element) =>
401
        ts.isExpression(element as ts.Node)
86✔
402
      )
403
    ) {
404
      return ts.factory.createArrayLiteralExpression(
74✔
405
        elementsOrProperties as ts.Expression[],
406
        multiline
407
      );
408
    }
409

410
    const propertyAssignments = (
411
      elementsOrProperties as PropertyAssignment[]
4✔
412
    ).map((property) =>
413
      this.createObjectLiteralExpression([property], multiline)
8✔
414
    );
415
    return ts.factory.createArrayLiteralExpression(
4✔
416
      propertyAssignments,
417
      multiline
418
    );
419
  }
420

421
  /**
422
   * Creates a `ts.CallExpression` for an identifier with a method call.
423
   * @param identifierName Identifier text.
424
   * @param call Method to call, creating `<identifierName>.<call>()`.
425
   * @param typeArgs Type arguments for the call, translates to type arguments for generic methods - `myMethod<T, T1, ...>`.
426
   * @param args Arguments for the call, translates to arguments for the method - `myMethod(arg1, arg2, ...)`.
427
   * @remarks Create `typeArgs` with methods like `ts.factory.createXXXTypeNode`.
428
   *
429
   * ```
430
   * const typeArg = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
431
   * const arg = ts.factory.createNumericLiteral('5');
432
   * const callExpression = astTransformer.createCallExpression(
433
   *    'x',
434
   *    'myGenericFunction',
435
   *    [typeArg],
436
   *    [arg]
437
   * );
438
   *
439
   * // This would create the function call
440
   * x.myGenericFunction<number>(5)
441
   * ```
442
   */
443
  public createCallExpression(
444
    identifierName: string,
445
    call: string,
446
    typeArgs?: ts.TypeNode[],
447
    args?: ts.Expression[]
448
  ): ts.CallExpression {
449
    return ts.factory.createCallExpression(
4✔
450
      ts.factory.createPropertyAccessExpression(
451
        ts.factory.createIdentifier(identifierName),
452
        call
453
      ),
454
      typeArgs,
455
      args
456
    );
457
  }
458

459
  /**
460
   * Creates a request that will resolve during {@link finalize} which adds a new argument to a method call expression.
461
   * @param visitCondition The condition by which the method call expression is found.
462
   * @param argument The argument to add to the method call.
463
   * @param position The position in the argument list to add the new argument.
464
   * @param override Whether to override the argument at the given position.
465
   * @remarks If `position` is not provided or is less than zero, the argument will be added at the end of the argument list.
466
   */
467
  public requestNewArgumentInMethodCallExpression(
468
    visitCondition: (node: ts.CallExpression) => boolean,
469
    argument: ts.Expression,
470
    position: number = -1,
6✔
471
    override: boolean = false
8✔
472
  ): void {
473
    const transformerFactory =
474
      newArgumentInMethodCallExpressionTransformerFactory(
10✔
475
        visitCondition,
476
        argument,
477
        position,
478
        override
479
      );
480

481
    this.requestChange(
10✔
482
      ChangeType.NewNode,
483
      transformerFactory,
484
      SyntaxKind.Expression,
485
      argument
486
    );
487
  }
488

489
  /**
490
   * Creates a node for a named import declaration.
491
   * @param importDeclarationMeta Metadata for the new import declaration.
492
   * @param isDefault Whether the import is a default import.
493
   * @param isSideEffects Whether the import is a side effects import.
494
   * @returns A named import declaration of the form `import { MyClass } from "my-module"`.
495
   * @remarks If `isDefault` is `true`, the first element of `identifiers` will be used and
496
   * the import will be a default import of the form `import MyClass from "my-module"`.
497
   * @remarks
498
   * If `isSideEffects` is `true`, all other options are ignored
499
   * and the import will be a side effects import of the form `import "my-module"`.
500
   * @reference {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#description|MDN}
501
   */
502
  public createImportDeclaration(
503
    importDeclarationMeta: ImportDeclarationMeta,
504
    isDefault: boolean = false,
4✔
505
    isSideEffects: boolean = false
8✔
506
  ): ts.ImportDeclaration {
507
    return createImportDeclaration(
8✔
508
      importDeclarationMeta,
509
      isDefault,
510
      isSideEffects,
511
      this.formatSettings?.singleQuotes || false
40!
512
    );
513
  }
514

515
  /**
516
   * Checks if an import declaration's identifier or alias would collide with an existing one.
517
   * @param identifier The identifier to check for collisions.
518
   * @param moduleName The module that the import is for, used for side effects imports.
519
   * @param isSideEffects If the import is strictly a side effects import.
520
   * @param sourceFile The source file to check for collisions.
521
   */
522
  public importDeclarationCollides(
523
    identifier: Identifier,
524
    moduleName?: string,
525
    isSideEffects: boolean = false,
4✔
526
    sourceFile: ts.SourceFile = this.sourceFile
4✔
527
  ): boolean {
528
    return importDeclarationCollides(
4✔
529
      identifier,
530
      sourceFile,
531
      moduleName,
532
      isSideEffects
533
    );
534
  }
535

536
  /**
537
   * Creates a request that will resolve during {@link finalize} which adds an import declaration to the source file.
538
   * @param importDeclarationMeta Metadata for the new import declaration.
539
   * @param isDefault Whether the import is a default import.
540
   * @remarks If `isDefault` is `true`, the first identifier will be used and
541
   * the import will be a default import of the form `import MyClass from "my-module"`.
542
   * @remarks If `isSideEffects` is `true`, all other options are ignored
543
   * and the import will be a side effects import of the form `import "my-module"`.
544
   * @reference {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#description|MDN}
545
   */
546
  public requestNewImportDeclaration(
547
    importDeclarationMeta: ImportDeclarationMeta,
548
    isDefault: boolean = false,
66✔
549
    isSideEffects: boolean = false
104✔
550
  ): void {
551
    const transformerFactory = newImportDeclarationTransformerFactory(
112✔
552
      importDeclarationMeta,
553
      isDefault,
554
      isSideEffects,
555
      this.formatSettings?.singleQuotes || false
478✔
556
    );
557

558
    const identifiers = Array.isArray(importDeclarationMeta.identifiers)
112✔
559
      ? ts.factory.createNodeArray(
112✔
560
          importDeclarationMeta.identifiers.map((i) =>
561
            ts.factory.createIdentifier(i.name || i.alias)
70!
562
          )
563
        )
564
      : ts.factory.createIdentifier(
565
          importDeclarationMeta.identifiers.name ||
62!
566
            importDeclarationMeta.identifiers.alias
567
        );
568

569
    this.requestChange(
112✔
570
      ChangeType.NewNode,
571
      transformerFactory,
572
      SyntaxKind.ImportDeclaration,
573
      identifiers
574
    );
575
  }
576

577
  /**
578
   * Applies the requested changes to the source file.
579
   * @remarks Does not mutate the original `ts.SourceFile`. Instead, it creates a new one with the changes applied.
580
   */
581
  public applyChanges(): ts.SourceFile {
582
    let clone = this.sourceFile.getSourceFile();
118✔
583
    for (const [_id, transformer] of this.transformations) {
118✔
584
      clone = ts.transform(
262✔
585
        clone,
586
        [transformer.transformerFactory],
587
        this.compilerOptions
588
      ).transformed[0];
589

590
      this._flatNodeRelations = this.createNodeRelationsMap(clone);
262✔
591
    }
592

593
    this.transformations.clear();
118✔
594
    return clone;
118✔
595
  }
596

597
  /**
598
   * Applies all transformations, parses the AST and returns the resulting source code.
599
   * @remarks This method should be called after all modifications are ready to be applied to the AST.
600
   * If a {@link formatter} is present, it will be used to format the source code and update the file on the FS.
601
   */
602
  public finalize(): string {
603
    const finalSource = this.applyChanges();
118✔
604
    if (this.formatter) {
118✔
605
      return this.formatter.applyFormatting(finalSource);
74✔
606
    }
607

608
    return this.printer.printFile(finalSource);
44✔
609
  }
610

611
  /**
612
   * Requests a change to the source file.
613
   * @param type The type of change to request.
614
   * @param transformerFactory The transformer to apply to the source file during finalization.
615
   * @param syntaxKind The syntax kind of the node to change.
616
   * @param node The affected node.
617
   * @param relatedChangeId The ID of the change request that should be applied before this one.
618
   * @remarks All aggregated changes will be applied during {@link finalize}.
619
   */
620
  private requestChange<T extends ts.Node>(
621
    type: ChangeType,
622
    transformerFactory: ts.TransformerFactory<ts.SourceFile>,
623
    syntaxKind: SyntaxKind,
624
    node?: T | ts.NodeArray<T>
625
  ): void {
626
    const id = crypto.randomUUID();
262✔
627
    this.transformations.set(id, {
262✔
628
      id,
629
      type,
630
      transformerFactory,
631
      syntaxKind,
632
      node,
633
    });
634
  }
635

636
  /**
637
   * Maps a `KeyValuePair` type to a `ts.ObjectLiteralElementLike` type.
638
   * @param kvp The key-value pair to map.
639
   * @param transform Resolves the `ts.Expression` for the the initializer of the `ts.ObjectLiteralElementLike`.
640
   */
641
  private mapKeyValuePairToObjectLiteral<T>(
642
    kvp: KeyValuePair<T>,
643
    transform: (value: T) => ts.Expression
644
  ): ts.ObjectLiteralElementLike[] {
645
    return Object.entries(kvp).map(([key, value]) =>
10✔
646
      ts.factory.createPropertyAssignment(key, transform(value))
10✔
647
    );
648
  }
649

650
  /**
651
   * Creates a flat map of nodes with their parent nodes.
652
   * @param rootNode The node to create a map for.
653
   */
654
  private createNodeRelationsMap(rootNode: ts.Node): Map<ts.Node, ts.Node> {
655
    const flatNodeRelations = new Map<ts.Node, ts.Node>();
422✔
656
    const visit = (node: ts.Node, parent: ts.Node | null): void => {
422✔
657
      if (parent) {
14,678✔
658
        flatNodeRelations.set(node, parent);
14,256✔
659
      }
660
      ts.forEachChild(node, (child) => visit(child, node));
14,678✔
661
    };
662

663
    visit(rootNode, null);
422✔
664
    return flatNodeRelations;
422✔
665
  }
666
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc