1import { 2 addRange, cloneCompilerOptions, CommandLineOptionOfCustomType, CompilerHost, CompilerOptions, 3 createCompilerDiagnosticForInvalidCustomType, createProgram, createSourceFile, CustomTransformers, Debug, 4 Diagnostic, ensureScriptKind, fileExtensionIs, filter, forEachEntry, getDefaultCompilerOptions, getEmitScriptTarget, 5 getEntries, getImpliedNodeFormatForFile, getNewLineCharacter, getSetExternalModuleIndicator, hasProperty, isString, 6 Map, MapLike, normalizePath, optionDeclarations, parseCustomTypeOption, toPath, transpileOptionValueCompilerOptions, 7} from "./_namespaces/ts"; 8 9export interface TranspileOptions { 10 compilerOptions?: CompilerOptions; 11 fileName?: string; 12 reportDiagnostics?: boolean; 13 moduleName?: string; 14 renamedDependencies?: MapLike<string>; 15 transformers?: CustomTransformers; 16} 17 18export interface TranspileOutput { 19 outputText: string; 20 diagnostics?: Diagnostic[]; 21 sourceMapText?: string; 22} 23 24/* 25 * This function will compile source text from 'input' argument using specified compiler options. 26 * If not options are provided - it will use a set of default compiler options. 27 * Extra compiler options that will unconditionally be used by this function are: 28 * - isolatedModules = true 29 * - allowNonTsExtensions = true 30 * - noLib = true 31 * - noResolve = true 32 */ 33export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput { 34 const diagnostics: Diagnostic[] = []; 35 36 const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : {}; 37 38 // mix in default options 39 const defaultOptions = getDefaultCompilerOptions(); 40 for (const key in defaultOptions) { 41 if (hasProperty(defaultOptions, key) && options[key] === undefined) { 42 options[key] = defaultOptions[key]; 43 } 44 } 45 46 for (const option of transpileOptionValueCompilerOptions) { 47 options[option.name] = option.transpileOptionValue; 48 } 49 50 // transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths. 51 options.suppressOutputPathCheck = true; 52 53 // Filename can be non-ts file. 54 options.allowNonTsExtensions = true; 55 56 const newLine = getNewLineCharacter(options); 57 // Create a compilerHost object to allow the compiler to read and write files 58 const compilerHost: CompilerHost = { 59 getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, 60 writeFile: (name, text) => { 61 if (fileExtensionIs(name, ".map")) { 62 Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); 63 sourceMapText = text; 64 } 65 else { 66 Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); 67 outputText = text; 68 } 69 }, 70 getDefaultLibFileName: () => "lib.d.ts", 71 useCaseSensitiveFileNames: () => false, 72 getCanonicalFileName: fileName => fileName, 73 getCurrentDirectory: () => "", 74 getNewLine: () => newLine, 75 fileExists: (fileName): boolean => fileName === inputFileName, 76 readFile: () => "", 77 directoryExists: () => true, 78 getDirectories: () => [] 79 }; 80 81 // if jsx is specified then treat file as .tsx 82 const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts"); 83 const scriptKind = ensureScriptKind(inputFileName, /*setScriptKind*/ undefined); 84 const sourceFile = createSourceFile( 85 inputFileName, 86 input, 87 { 88 languageVersion: getEmitScriptTarget(options), 89 impliedNodeFormat: getImpliedNodeFormatForFile(toPath(inputFileName, "", compilerHost.getCanonicalFileName), /*cache*/ undefined, compilerHost, options), 90 setExternalModuleIndicator: getSetExternalModuleIndicator(options) 91 }, 92 /* setParentNodes */ undefined, 93 scriptKind, 94 options 95 ); 96 if (transpileOptions.moduleName) { 97 sourceFile.moduleName = transpileOptions.moduleName; 98 } 99 100 if (transpileOptions.renamedDependencies) { 101 sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); 102 } 103 104 // Output 105 let outputText: string | undefined; 106 let sourceMapText: string | undefined; 107 108 const program = createProgram([inputFileName], options, compilerHost); 109 110 if (transpileOptions.reportDiagnostics) { 111 addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); 112 addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); 113 } 114 // Emit 115 program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); 116 117 if (outputText === undefined) return Debug.fail("Output generation failed"); 118 119 return { outputText, diagnostics, sourceMapText }; 120} 121 122/* 123 * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. 124 */ 125export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { 126 const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); 127 // addRange correctly handles cases when wither 'from' or 'to' argument is missing 128 addRange(diagnostics, output.diagnostics); 129 return output.outputText; 130} 131 132let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; 133 134/** 135 * JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. 136 * 137 * @internal 138 */ 139export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { 140 // Lazily create this value to fix module loading errors. 141 commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || 142 filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[]; 143 144 options = cloneCompilerOptions(options); 145 146 for (const opt of commandLineOptionsStringToEnum) { 147 if (!hasProperty(options, opt.name)) { 148 continue; 149 } 150 151 const value = options[opt.name]; 152 // Value should be a key of opt.type 153 if (isString(value)) { 154 // If value is not a string, this will fail 155 options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); 156 } 157 else { 158 if (!forEachEntry(opt.type, v => v === value)) { 159 // Supplied value isn't a valid enum value. 160 diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); 161 } 162 } 163 } 164 165 return options; 166} 167