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