import { createError } from '@cirrusct/error'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as ts from 'typescript'; import { isString } from 'util'; import { ParseConfigOptions, ProgramOptions } from './types'; export const formatDiagnostics = (d: ts.Diagnostic | ts.Diagnostic[], useColorAndContext = true): string => { const ds = Array.isArray(d) ? d : [d]; const host = { getCanonicalFileName: f => f, getCurrentDirectory: process.cwd, getNewLine: () => os.EOL, }; return useColorAndContext ? ts.formatDiagnosticsWithColorAndContext(ds, host) : ts.formatDiagnostics(ds, host); }; const throwDiagnosticError = (err: ts.Diagnostic | ts.Diagnostic[]) => { throw createError(formatDiagnostics(err)); }; export const defaultParseConfigHost: ts.ParseConfigHost = { fileExists: fs.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: true, }; export const createParsedConfig = (options: ParseConfigOptions): ts.ParsedCommandLine => { let tsConfig: object; let basePath = options.projectPath ? path.resolve(options.projectPath) : undefined; if (isString(options.tsConfig)) { const config = ts.readConfigFile(options.tsConfig, ts.sys.readFile); if (config.error !== undefined) { throw createError( ts.formatDiagnostics([config.error], { getCanonicalFileName: f => f, getCurrentDirectory: process.cwd, getNewLine: () => os.EOL, }) ); } tsConfig = config.config; basePath = basePath || path.dirname(options.tsConfig); } else { tsConfig = options.tsConfig; } if (!fs.existsSync(basePath)) { throw createError(`initConfig failed. Project path not defined`); } const parsedConfig = ts.parseJsonConfigFileContent( tsConfig, defaultParseConfigHost, basePath, options.compilerOptions || {} ); if (parsedConfig.errors && parsedConfig.errors.length > 0) { // ignore warnings and 'TS18003: No inputs were found in config file ...' const errors = parsedConfig.errors.filter(d => d.category === ts.DiagnosticCategory.Error && d.code !== 18003); if (errors.length > 0) { throwDiagnosticError(errors); } } return parsedConfig; }; export const createProgram = (options: ProgramOptions, compilerHost?: ts.CompilerHost) => { const parsedConfig = createParsedConfig(options); return ts.createProgram( parsedConfig.fileNames, parsedConfig.options, compilerHost || ts.createCompilerHost(parsedConfig.options, true) ); };