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