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