• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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