|
9 | 9 | import path from 'node:path'; |
10 | 10 | import ts from 'typescript'; |
11 | 11 | import type { AngularHostOptions } from '../angular-host'; |
| 12 | +import { ensurePreDeclarationTransformers } from './aot-compilation'; |
12 | 13 | import { CompilerOptionOverrides, transformCompilerOptions } from './compiler-options'; |
13 | 14 | import { TypeScriptCompilation } from './typescript-compilation'; |
14 | 15 | import { |
@@ -509,4 +510,141 @@ describe('AngularCompilation', () => { |
509 | 510 | expect(warnings).toEqual([]); |
510 | 511 | }); |
511 | 512 | }); |
| 513 | + |
| 514 | + describe('ensurePreDeclarationTransformers', () => { |
| 515 | + function createTestProgram( |
| 516 | + fileName: string, |
| 517 | + content: string, |
| 518 | + ): { |
| 519 | + program: ts.Program; |
| 520 | + sourceFile: ts.SourceFile; |
| 521 | + } { |
| 522 | + const options: ts.CompilerOptions = { |
| 523 | + target: ts.ScriptTarget.ES2022, |
| 524 | + module: ts.ModuleKind.ES2022, |
| 525 | + declaration: true, |
| 526 | + noLib: true, |
| 527 | + }; |
| 528 | + const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES2022, true); |
| 529 | + const host = ts.createCompilerHost(options); |
| 530 | + host.getSourceFile = (name) => (name === fileName ? sourceFile : undefined); |
| 531 | + host.fileExists = (name) => name === fileName; |
| 532 | + host.readFile = (name) => (name === fileName ? content : undefined); |
| 533 | + host.writeFile = () => {}; |
| 534 | + |
| 535 | + const program = ts.createProgram([fileName], options, host); |
| 536 | + |
| 537 | + return { program, sourceFile }; |
| 538 | + } |
| 539 | + |
| 540 | + it('transforms each SourceFile only once when emitting .d.ts followed by .js and reuses the cached SourceFile', () => { |
| 541 | + const { program, sourceFile } = createTestProgram('/index.ts', 'export const value = 1;'); |
| 542 | + |
| 543 | + let preDeclStatePopulated = false; |
| 544 | + const beforeSpy1 = jasmine |
| 545 | + .createSpy('beforeTransformer1') |
| 546 | + .and.callFake((sf: ts.SourceFile) => { |
| 547 | + preDeclStatePopulated = true; |
| 548 | + const addedStmt = ts.factory.createVariableStatement( |
| 549 | + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], |
| 550 | + ts.factory.createVariableDeclarationList( |
| 551 | + [ |
| 552 | + ts.factory.createVariableDeclaration( |
| 553 | + 'fromBefore1', |
| 554 | + undefined, |
| 555 | + undefined, |
| 556 | + ts.factory.createNumericLiteral(2), |
| 557 | + ), |
| 558 | + ], |
| 559 | + ts.NodeFlags.Const, |
| 560 | + ), |
| 561 | + ); |
| 562 | + |
| 563 | + return ts.factory.updateSourceFile(sf, [...sf.statements, addedStmt]); |
| 564 | + }); |
| 565 | + |
| 566 | + const beforeSpy2 = jasmine |
| 567 | + .createSpy('beforeTransformer2') |
| 568 | + .and.callFake((sf: ts.SourceFile) => { |
| 569 | + const addedStmt = ts.factory.createVariableStatement( |
| 570 | + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], |
| 571 | + ts.factory.createVariableDeclarationList( |
| 572 | + [ |
| 573 | + ts.factory.createVariableDeclaration( |
| 574 | + 'fromBefore2', |
| 575 | + undefined, |
| 576 | + undefined, |
| 577 | + ts.factory.createNumericLiteral(3), |
| 578 | + ), |
| 579 | + ], |
| 580 | + ts.NodeFlags.Const, |
| 581 | + ), |
| 582 | + ); |
| 583 | + |
| 584 | + return ts.factory.updateSourceFile(sf, [...sf.statements, addedStmt]); |
| 585 | + }); |
| 586 | + |
| 587 | + let sawPopulatedPreDeclState = false; |
| 588 | + const afterDeclSpy = jasmine |
| 589 | + .createSpy('afterDeclarationsTransformer') |
| 590 | + .and.callFake((sf: ts.SourceFile) => { |
| 591 | + sawPopulatedPreDeclState = preDeclStatePopulated; |
| 592 | + |
| 593 | + return sf; |
| 594 | + }); |
| 595 | + |
| 596 | + const transformers: ts.CustomTransformers = { |
| 597 | + before: [() => beforeSpy1, () => beforeSpy2], |
| 598 | + afterDeclarations: [() => afterDeclSpy], |
| 599 | + }; |
| 600 | + |
| 601 | + ensurePreDeclarationTransformers(transformers); |
| 602 | + |
| 603 | + const emittedFiles = new Map<string, string>(); |
| 604 | + const writeFile: ts.WriteFileCallback = (fileName, text) => { |
| 605 | + emittedFiles.set(fileName, text); |
| 606 | + }; |
| 607 | + |
| 608 | + // Emit .d.ts first (emitOnlyDtsFiles = true) |
| 609 | + program.emit(sourceFile, writeFile, undefined, true, transformers); |
| 610 | + |
| 611 | + expect(sawPopulatedPreDeclState).toBeTrue(); |
| 612 | + expect(beforeSpy1).toHaveBeenCalledTimes(1); |
| 613 | + expect(beforeSpy2).toHaveBeenCalledTimes(1); |
| 614 | + expect(afterDeclSpy).toHaveBeenCalledTimes(1); |
| 615 | + expect(emittedFiles.has('/index.d.ts')).toBeTrue(); |
| 616 | + expect(emittedFiles.has('/index.js')).toBeFalse(); |
| 617 | + |
| 618 | + // Emit .js second; before transformers must not execute again and cached AST must be emitted |
| 619 | + program.emit(sourceFile, writeFile, undefined, 0 as unknown as boolean, transformers); |
| 620 | + |
| 621 | + expect(beforeSpy1).toHaveBeenCalledTimes(1); |
| 622 | + expect(beforeSpy2).toHaveBeenCalledTimes(1); |
| 623 | + expect(emittedFiles.get('/index.js')).toContain('export const fromBefore1 = 2;'); |
| 624 | + expect(emittedFiles.get('/index.js')).toContain('export const fromBefore2 = 3;'); |
| 625 | + }); |
| 626 | + |
| 627 | + it('transforms each SourceFile only once when emitting .js and .d.ts together', () => { |
| 628 | + const { program, sourceFile } = createTestProgram('/index.ts', 'export const value = 1;'); |
| 629 | + |
| 630 | + const beforeSpy = jasmine |
| 631 | + .createSpy('beforeTransformer') |
| 632 | + .and.callFake((sf: ts.SourceFile) => sf); |
| 633 | + const afterDeclSpy = jasmine |
| 634 | + .createSpy('afterDeclarationsTransformer') |
| 635 | + .and.callFake((sf: ts.SourceFile) => sf); |
| 636 | + |
| 637 | + const transformers: ts.CustomTransformers = { |
| 638 | + before: [() => beforeSpy], |
| 639 | + afterDeclarations: [() => afterDeclSpy], |
| 640 | + }; |
| 641 | + |
| 642 | + ensurePreDeclarationTransformers(transformers); |
| 643 | + |
| 644 | + program.emit(sourceFile, () => {}, undefined, false, transformers); |
| 645 | + |
| 646 | + expect(beforeSpy).toHaveBeenCalledTimes(1); |
| 647 | + expect(afterDeclSpy).toHaveBeenCalledTimes(1); |
| 648 | + }); |
| 649 | + }); |
512 | 650 | }); |
0 commit comments