|
| 1 | +import * as fs from 'fs'; |
| 2 | +import { cwd } from 'process'; |
| 3 | +import * as ts from 'typescript'; |
| 4 | +import { logOnce } from './logger'; |
| 5 | +import { TsJestConfig } from './jest-types'; |
| 6 | + |
| 7 | +// Takes the typescript code and by whatever method configured, makes it into javascript code. |
| 8 | +export function transpileTypescript( |
| 9 | + filePath: string, |
| 10 | + fileSrc: string, |
| 11 | + compilerOptions: ts.CompilerOptions, |
| 12 | + tsJestConfig: TsJestConfig, |
| 13 | +): string { |
| 14 | + if (tsJestConfig.useExperimentalLanguageServer) { |
| 15 | + logOnce('Using experimental language server.'); |
| 16 | + return transpileViaLanguageServer(filePath, fileSrc, compilerOptions); |
| 17 | + } |
| 18 | + logOnce('Compiling via normal transpileModule call'); |
| 19 | + return transpileViaTranspileModile(filePath, fileSrc, compilerOptions); |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * This is slower, but can properly parse enums and deal with reflect metadata. |
| 24 | + * This is an experimental approach from our side. Potentially we should cache |
| 25 | + * the languageServer between calls. |
| 26 | + */ |
| 27 | +function transpileViaLanguageServer( |
| 28 | + filePath: string, |
| 29 | + fileSrc: string, |
| 30 | + compilerOptions: ts.CompilerOptions, |
| 31 | +) { |
| 32 | + const serviceHost: ts.LanguageServiceHost = { |
| 33 | + // Returns an array of the files we need to consider |
| 34 | + getScriptFileNames: () => { |
| 35 | + return [filePath]; |
| 36 | + }, |
| 37 | + |
| 38 | + getScriptVersion: fileName => { |
| 39 | + // We're not doing any watching or changing files, so versioning is not relevant for us |
| 40 | + return undefined; |
| 41 | + }, |
| 42 | + |
| 43 | + getCurrentDirectory: () => { |
| 44 | + return cwd(); |
| 45 | + }, |
| 46 | + |
| 47 | + getScriptSnapshot: fileName => { |
| 48 | + if (fileName === filePath) { |
| 49 | + // jest has already served this file for us, so no need to hit disk again. |
| 50 | + return ts.ScriptSnapshot.fromString(fileSrc); |
| 51 | + } |
| 52 | + // Read file from disk. I think this could be problematic if the files are not saved as utf8. |
| 53 | + const result = fs.readFileSync(fileName, 'utf8'); |
| 54 | + return ts.ScriptSnapshot.fromString(result); |
| 55 | + }, |
| 56 | + |
| 57 | + getCompilationSettings: () => { |
| 58 | + return compilerOptions; |
| 59 | + }, |
| 60 | + |
| 61 | + getDefaultLibFileName: () => { |
| 62 | + return ts.getDefaultLibFilePath(compilerOptions); |
| 63 | + }, |
| 64 | + fileExists: ts.sys.fileExists, |
| 65 | + readFile: ts.sys.readFile, |
| 66 | + readDirectory: ts.sys.readDirectory, |
| 67 | + getDirectories: ts.sys.getDirectories, |
| 68 | + directoryExists: ts.sys.directoryExists, |
| 69 | + }; |
| 70 | + const service = ts.createLanguageService(serviceHost); |
| 71 | + const serviceOutput = service.getEmitOutput(filePath); |
| 72 | + const files = serviceOutput.outputFiles.filter(file => { |
| 73 | + // Service outputs both d.ts and .js files - we're not interested in the declarations. |
| 74 | + return file.name.endsWith('js'); |
| 75 | + }); |
| 76 | + logOnce('JS files parsed', files.map(f => f.name)); |
| 77 | + |
| 78 | + // Log some diagnostics here: |
| 79 | + const diagnostics = service |
| 80 | + .getCompilerOptionsDiagnostics() |
| 81 | + .concat(service.getSyntacticDiagnostics(filePath)) |
| 82 | + .concat(service.getSemanticDiagnostics(filePath)); |
| 83 | + |
| 84 | + if (diagnostics.length > 0) { |
| 85 | + const errors = `${diagnostics.map(d => d.messageText)}\n`; |
| 86 | + logOnce(`Diagnostic errors from TSC: ${errors}`); |
| 87 | + // Maybe we should keep compiling even though there are errors. This can possibly be configured. |
| 88 | + throw Error( |
| 89 | + `TSC language server encountered errors while transpiling. Errors: ${errors}`, |
| 90 | + ); |
| 91 | + } |
| 92 | + |
| 93 | + return files[0].text; |
| 94 | +} |
| 95 | + |
| 96 | +/** |
| 97 | + * This is faster, and considers the modules in isolation |
| 98 | + */ |
| 99 | +function transpileViaTranspileModile( |
| 100 | + filePath: string, |
| 101 | + fileSource: string, |
| 102 | + compilerOptions: ts.CompilerOptions, |
| 103 | +) { |
| 104 | + return ts.transpileModule(fileSource, { |
| 105 | + compilerOptions, |
| 106 | + fileName: filePath, |
| 107 | + }).outputText; |
| 108 | +} |
0 commit comments