Skip to content

Commit ad7a297

Browse files
committedJun 30, 2024·
refactor(compiler): replace copied codes from ts source with public API
We use directly API `transpileModule` together with additional AST transformers from Angular. This can be achieved by creating a minimal `Program` (actually copied from ts source) and fetch this `Program` to AST transformers
1 parent 15d4c8c commit ad7a297

File tree

2 files changed

+28
-100
lines changed

2 files changed

+28
-100
lines changed
 

‎src/compiler/ng-jest-compiler.ts

+26-96
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import os from 'os';
2+
import path from 'path';
3+
14
import { type TsJestAstTransformer, TsCompiler, type ConfigSet } from 'ts-jest';
25
import type * as ts from 'typescript';
36

@@ -20,118 +23,45 @@ export class NgJestCompiler extends TsCompiler {
2023
* and we need `Program` to be able to use Angular `replace-resources` transformer.
2124
*/
2225
protected _transpileOutput(fileContent: string, filePath: string): ts.TranspileOutput {
23-
const diagnostics: ts.Diagnostic[] = [];
24-
const compilerOptions = { ...this._compilerOptions };
25-
const options: ts.CompilerOptions = compilerOptions
26-
? // @ts-expect-error internal TypeScript API
27-
this._ts.fixupCompilerOptions(compilerOptions, diagnostics)
28-
: {};
29-
30-
// mix in default options
31-
const defaultOptions = this._ts.getDefaultCompilerOptions();
32-
for (const key in defaultOptions) {
33-
// @ts-expect-error internal TypeScript API
34-
if (this._ts.hasProperty(defaultOptions, key) && options[key] === undefined) {
35-
options[key] = defaultOptions[key];
36-
}
37-
}
38-
39-
// @ts-expect-error internal TypeScript API
40-
for (const option of this._ts.transpileOptionValueCompilerOptions) {
41-
options[option.name] = option.transpileOptionValue;
42-
}
43-
44-
/**
45-
* transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between
46-
* input and output paths.
47-
*/
48-
options.suppressOutputPathCheck = true;
49-
50-
// Filename can be non-ts file.
51-
options.allowNonTsExtensions = true;
52-
53-
// if jsx is specified then treat file as .tsx
54-
const inputFileName = filePath || (compilerOptions && compilerOptions.jsx ? 'module.tsx' : 'module.ts');
5526
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
56-
const sourceFile = this._ts.createSourceFile(inputFileName, fileContent, options.target!); // TODO: GH#18217
57-
58-
// @ts-expect-error internal TypeScript API
59-
const newLine = this._ts.getNewLineCharacter(options);
60-
61-
// Output
62-
let outputText: string | undefined;
63-
let sourceMapText: string | undefined;
64-
65-
// Create a compilerHost object to allow the compiler to read and write files
27+
const sourceFile = this._ts.createSourceFile(filePath, fileContent, this._compilerOptions.target!);
6628
const compilerHost: ts.CompilerHost = {
67-
getSourceFile: (fileName) =>
68-
// @ts-expect-error internal TypeScript API
69-
fileName === this._ts.normalizePath(inputFileName) ? sourceFile : undefined,
70-
writeFile: (name, text) => {
71-
// @ts-expect-error internal TypeScript API
72-
if (this._ts.fileExtensionIs(name, '.map')) {
73-
// @ts-expect-error internal TypeScript API
74-
this._ts.Debug.assertEqual(
75-
sourceMapText,
76-
undefined,
77-
'Unexpected multiple source map outputs, file:',
78-
name,
79-
);
80-
sourceMapText = text;
81-
} else {
82-
// @ts-expect-error internal TypeScript API
83-
this._ts.Debug.assertEqual(outputText, undefined, 'Unexpected multiple outputs, file:', name);
84-
outputText = text;
85-
}
86-
},
29+
getSourceFile: (fileName) => (fileName === path.normalize(filePath) ? sourceFile : undefined),
30+
// eslint-disable-next-line @typescript-eslint/no-empty-function
31+
writeFile: () => {},
8732
getDefaultLibFileName: () => 'lib.d.ts',
8833
useCaseSensitiveFileNames: () => false,
8934
getCanonicalFileName: (fileName) => fileName,
9035
getCurrentDirectory: () => '',
91-
getNewLine: () => newLine,
92-
fileExists: (fileName): boolean => fileName === inputFileName,
36+
getNewLine: () => os.EOL,
37+
fileExists: (fileName): boolean => fileName === filePath,
9338
readFile: () => '',
9439
directoryExists: () => true,
9540
getDirectories: () => [],
9641
};
97-
98-
this.program = this._ts.createProgram([inputFileName], options, compilerHost);
99-
if (this.configSet.shouldReportDiagnostics(inputFileName)) {
100-
// @ts-expect-error internal TypeScript API
101-
this._ts.addRange(/*to*/ diagnostics, /*from*/ this.program.getSyntacticDiagnostics(sourceFile));
102-
// @ts-expect-error internal TypeScript API
103-
this._ts.addRange(/*to*/ diagnostics, /*from*/ this.program.getOptionsDiagnostics());
104-
}
105-
// Emit
106-
this.program.emit(
107-
/*targetSourceFile*/ undefined,
108-
/*writeFile*/ undefined,
109-
/*cancellationToken*/ undefined,
110-
/*emitOnlyDtsFiles*/ undefined,
111-
this._makeTransformers(this.configSet.resolvedTransformers),
112-
);
113-
if (outputText === undefined) {
114-
// @ts-expect-error internal TypeScript API
115-
return this._ts.Debug.fail('Output generation failed');
116-
}
117-
118-
return { outputText, diagnostics, sourceMapText };
42+
this.program = this._ts.createProgram([filePath], this._compilerOptions, compilerHost);
43+
44+
return this._ts.transpileModule(fileContent, {
45+
fileName: filePath,
46+
transformers: this._makeTransformers(this.configSet.resolvedTransformers),
47+
compilerOptions: this._compilerOptions,
48+
reportDiagnostics: this.configSet.shouldReportDiagnostics(filePath),
49+
});
11950
}
12051

12152
protected _makeTransformers(customTransformers: TsJestAstTransformer): ts.CustomTransformers {
122-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123-
const program = this.program!;
53+
const allTransformers = super._makeTransformers(customTransformers);
12454

12555
return {
126-
...super._makeTransformers(customTransformers).after,
127-
...super._makeTransformers(customTransformers).afterDeclarations,
56+
...allTransformers.after,
57+
...allTransformers.afterDeclarations,
12858
before: [
129-
...customTransformers.before.map((beforeTransformer) =>
130-
beforeTransformer.factory(this, beforeTransformer.options),
131-
),
132-
replaceResources(this),
133-
angularJitApplicationTransform(program),
134-
] as Array<ts.TransformerFactory<ts.SourceFile> | ts.CustomTransformerFactory>,
59+
...(allTransformers.before ?? []),
60+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
61+
replaceResources(this.program!),
62+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63+
angularJitApplicationTransform(this.program!),
64+
],
13565
};
13666
}
13767
}

‎src/transformers/replace-resources.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import type { TsCompilerInstance } from 'ts-jest';
98
import ts from 'typescript';
109

1110
import { STYLES, STYLE_URLS, TEMPLATE_URL, TEMPLATE, REQUIRE, COMPONENT, STYLE_URL } from '../constants';
@@ -59,10 +58,9 @@ const shouldTransform = (fileName: string) => !fileName.endsWith('.ngfactory.ts'
5958
* templateUrl: __NG_CLI_RESOURCE__0,
6059
* })
6160
*/
62-
export function replaceResources({ program }: TsCompilerInstance): ts.TransformerFactory<ts.SourceFile> {
61+
export function replaceResources(program: ts.Program): ts.TransformerFactory<ts.SourceFile> {
6362
return (context: ts.TransformationContext) => {
64-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
65-
const typeChecker = program!.getTypeChecker();
63+
const typeChecker = program.getTypeChecker();
6664
const resourceImportDeclarations: ts.ImportDeclaration[] = [];
6765
const moduleKind = context.getCompilerOptions().module;
6866
const nodeFactory = context.factory;

0 commit comments

Comments
 (0)
Please sign in to comment.