• 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        const newLine = getNewLineCharacter(options);
50        // Create a compilerHost object to allow the compiler to read and write files
51        const compilerHost: CompilerHost = {
52            getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined,
53            writeFile: (name, text) => {
54                if (fileExtensionIs(name, ".map")) {
55                    Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name);
56                    sourceMapText = text;
57                }
58                else {
59                    Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name);
60                    outputText = text;
61                }
62            },
63            getDefaultLibFileName: () => "lib.d.ts",
64            useCaseSensitiveFileNames: () => false,
65            getCanonicalFileName: fileName => fileName,
66            getCurrentDirectory: () => "",
67            getNewLine: () => newLine,
68            fileExists: (fileName): boolean => fileName === inputFileName,
69            readFile: () => "",
70            directoryExists: () => true,
71            getDirectories: () => []
72        };
73
74        // if jsx is specified then treat file as .tsx
75        const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts");
76        const scriptKind = ensureScriptKind(inputFileName, /*setScriptKind*/ undefined);
77        const sourceFile = createSourceFile(
78            inputFileName,
79            input,
80            {
81                languageVersion: getEmitScriptTarget(options),
82                impliedNodeFormat: getImpliedNodeFormatForFile(toPath(inputFileName, "", compilerHost.getCanonicalFileName), /*cache*/ undefined, compilerHost, options),
83                setExternalModuleIndicator: getSetExternalModuleIndicator(options)
84            },
85            /* setParentNodes */ undefined,
86            scriptKind,
87            options
88        );
89        if (transpileOptions.moduleName) {
90            sourceFile.moduleName = transpileOptions.moduleName;
91        }
92
93        if (transpileOptions.renamedDependencies) {
94            sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies));
95        }
96
97        // Output
98        let outputText: string | undefined;
99        let sourceMapText: string | undefined;
100
101        const program = createProgram([inputFileName], options, compilerHost);
102
103        if (transpileOptions.reportDiagnostics) {
104            addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
105            addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
106        }
107        // Emit
108        program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers);
109
110        if (outputText === undefined) return Debug.fail("Output generation failed");
111
112        return { outputText, diagnostics, sourceMapText };
113    }
114
115    /*
116     * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result.
117     */
118    export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string {
119        const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName });
120        // addRange correctly handles cases when wither 'from' or 'to' argument is missing
121        addRange(diagnostics, output.diagnostics);
122        return output.outputText;
123    }
124
125    let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
126
127    /** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */
128    /*@internal*/
129    export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions {
130        // Lazily create this value to fix module loading errors.
131        commandLineOptionsStringToEnum = commandLineOptionsStringToEnum ||
132            filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[];
133
134        options = cloneCompilerOptions(options);
135
136        for (const opt of commandLineOptionsStringToEnum) {
137            if (!hasProperty(options, opt.name)) {
138                continue;
139            }
140
141            const value = options[opt.name];
142            // Value should be a key of opt.type
143            if (isString(value)) {
144                // If value is not a string, this will fail
145                options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
146            }
147            else {
148                if (!forEachEntry(opt.type, v => v === value)) {
149                    // Supplied value isn't a valid enum value.
150                    diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt));
151                }
152            }
153        }
154
155        return options;
156    }
157}
158