UNPKG

alm

Version:

The best IDE for TypeScript

361 lines (360 loc) 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var fmc = require("../../../disk/fileModelCache"); var fsu = require("../../../utils/fsu"); var fs = require("fs"); var json = require("../../../../common/json"); var utils_1 = require("../../../../common/utils"); var types_1 = require("../../../../common/types"); var compilationContextExpander_1 = require("./compilationContextExpander"); var tsconfigValidation_1 = require("./tsconfigValidation"); ////////////////////////////////////////////////////////////////////// exports.errors = { GET_PROJECT_INVALID_PATH: 'The path used to query for tsconfig.json does not exist', GET_PROJECT_NO_PROJECT_FOUND: 'No Project Found', GET_PROJECT_FAILED_TO_OPEN_PROJECT_FILE: 'Failed to fs.readFileSync the project file', GET_PROJECT_PROJECT_FILE_INVALID_OPTIONS: 'Project file contains invalid options', CREATE_FOLDER_MUST_EXIST: 'The folder must exist on disk in order to create a tsconfig.json', CREATE_PROJECT_ALREADY_EXISTS: 'tsconfig.json file already exists', }; var path = require("path"); var os = require("os"); var formatting = require("./formatCodeOptions"); var projectFileName = 'tsconfig.json'; /** * This is what we use when the user doesn't specify a files / include */ var invisibleFilesInclude = ["./**/*.ts", "./**/*.tsx"]; var invisibleFilesIncludeWithJS = ["./**/*.ts", "./**/*.tsx", "./**/*.js"]; /** * What we use to * - create a new tsconfig on disk * - create an in memory project * - default values for a tsconfig file read from disk. Therefore it must match ts defaults */ var defaultCompilerOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeJs, jsx: ts.JsxEmit.None, experimentalDecorators: false, emitDecoratorMetadata: false, declaration: false, noImplicitAny: false, suppressImplicitAnyIndexErrors: false, strictNullChecks: false, }; /** * If you want to create a project on the fly */ function getDefaultInMemoryProject(srcFile) { var dir = path.dirname(srcFile); var allowJs = utils_1.isJs(srcFile); var files = [srcFile]; var typings = compilationContextExpander_1.getDefinitionsForNodeModules(dir, files); files = compilationContextExpander_1.increaseCompilationContext(files, allowJs); files = utils_1.uniq(files.map(fsu.consistentPath)); var project = { compilerOptions: utils_1.extend(defaultCompilerOptions, { allowJs: allowJs, lib: [ 'dom', 'es2017' ] }), files: files, typings: typings.ours.concat(typings.implicit), formatCodeOptions: formatting.defaultFormatCodeOptions(), compileOnSave: true, buildOnSave: false, }; return { projectFileDirectory: dir, projectFilePath: srcFile, project: project, inMemory: true }; } exports.getDefaultInMemoryProject = getDefaultInMemoryProject; function getProjectSync(pathOrSrcFile) { if (!fsu.existsSync(pathOrSrcFile)) { return { error: types_1.makeBlandError(pathOrSrcFile, exports.errors.GET_PROJECT_INVALID_PATH, 'tsconfig') }; } // Get the path directory var dir = fs.lstatSync(pathOrSrcFile).isDirectory() ? pathOrSrcFile : path.dirname(pathOrSrcFile); // Keep going up till we find the project file var projectFile = ''; try { projectFile = fsu.travelUpTheDirectoryTreeTillYouFind(dir, projectFileName); } catch (e) { var err = e; if (err.message == "not found") { var bland = types_1.makeBlandError(fsu.consistentPath(pathOrSrcFile), exports.errors.GET_PROJECT_NO_PROJECT_FOUND, 'tsconfig'); return { error: bland }; } } projectFile = path.normalize(projectFile); var projectFileDirectory = path.dirname(projectFile); var projectFilePath = fsu.consistentPath(projectFile); // We now have a valid projectFile. Parse it: var projectSpec; try { var projectFileTextContent = fmc.getOrCreateOpenFile(projectFile).getContents(); } catch (ex) { return { error: types_1.makeBlandError(pathOrSrcFile, exports.errors.GET_PROJECT_FAILED_TO_OPEN_PROJECT_FILE, 'tsconfig') }; } var res = json.parse(projectFileTextContent); if (res.data) { projectSpec = res.data; } else { var bland = json.parseErrorToCodeError(projectFilePath, res.error, 'tsconfig'); return { error: bland }; } // Setup default project options if (!projectSpec.compilerOptions) projectSpec.compilerOptions = {}; // Additional global level validations if (projectSpec.files && projectSpec.exclude) { var bland = types_1.makeBlandError(projectFilePath, 'You cannot use both "files" and "exclude" in tsconfig.json', 'tsconfig'); return { error: bland }; } if (projectSpec.compilerOptions.allowJs && !projectSpec.compilerOptions.outDir) { var bland = types_1.makeBlandError(projectFilePath, 'You must use an `outDir` if you are using `allowJs` in tsconfig.json', 'tsconfig'); return { error: bland }; } if (projectSpec.compilerOptions.allowJs && projectSpec.compilerOptions.allowNonTsExtensions) { // Bad because otherwise all `package.json`s in the `files` start to give *JavaScript parsing* errors. var bland = types_1.makeBlandError(projectFilePath, 'If you are using `allowJs` you should not specify `allowNonTsExtensions` in tsconfig.json', 'tsconfig'); return { error: bland }; } /** * Always add `outDir`(if any) to exclude */ if (projectSpec.compilerOptions.outDir) { projectSpec.exclude = (projectSpec.exclude || []).concat(projectSpec.compilerOptions.outDir); } /** * Finally expand whatever needs expanding * See : https://github.com/TypeStrong/tsconfig/issues/19 */ try { var tsResult = ts.parseJsonConfigFileContent(projectSpec, ts.sys, path.dirname(projectFile), null, projectFile); // console.log(tsResult); // DEBUG projectSpec.files = tsResult.fileNames || []; } catch (ex) { return { error: types_1.makeBlandError(projectFilePath, ex.message, 'tsconfig') }; } var pkg = null; try { var packageJSONPath = fsu.travelUpTheDirectoryTreeTillYouFind(projectFileDirectory, 'package.json'); if (packageJSONPath) { var parsedPackage = JSON.parse(fmc.getOrCreateOpenFile(packageJSONPath).getContents()); pkg = { main: parsedPackage.main, name: parsedPackage.name, directory: path.dirname(packageJSONPath), definition: parsedPackage.typescript && parsedPackage.typescript.definition }; } } catch (ex) { // console.error('no package.json found', projectFileDirectory, ex.message); } var project = { compilerOptions: {}, files: projectSpec.files.map(function (x) { return path.resolve(projectFileDirectory, x); }), formatCodeOptions: formatting.makeFormatCodeOptions(projectSpec.formatCodeOptions), compileOnSave: projectSpec.compileOnSave == undefined ? true : projectSpec.compileOnSave, package: pkg, typings: [], buildOnSave: !!projectSpec.buildOnSave, }; // Validate the raw compiler options before converting them to TS compiler options var validationResult = tsconfigValidation_1.validate(projectSpec.compilerOptions); if (validationResult.errorMessage) { return { error: types_1.makeBlandError(projectFilePath, validationResult.errorMessage, 'tsconfig') }; } // Convert the raw options to TS options project.compilerOptions = rawToTsCompilerOptions(projectSpec.compilerOptions, projectFileDirectory); // Expand files to include references project.files = compilationContextExpander_1.increaseCompilationContext(project.files, !!project.compilerOptions.allowJs); // Expand files to include node_modules / package.json / typescript.definition var typings = compilationContextExpander_1.getDefinitionsForNodeModules(dir, project.files); project.files = project.files.concat(typings.implicit); project.typings = typings.ours.concat(typings.implicit); project.files = project.files.concat(typings.packagejson); // Normalize to "/" for all files // And take the uniq values project.files = utils_1.uniq(project.files.map(fsu.consistentPath)); projectFileDirectory = fsu.consistentPath(projectFileDirectory); return { result: { projectFileDirectory: projectFileDirectory, projectFilePath: projectFileDirectory + '/' + projectFileName, project: project, inMemory: false } }; } exports.getProjectSync = getProjectSync; /** Creates a project by source file location. Defaults are assumed unless overriden by the optional spec. */ function createProjectRootSync(srcFolder, defaultOptions, overWrite) { if (defaultOptions === void 0) { defaultOptions = utils_1.extend(defaultCompilerOptions, { jsx: ts.JsxEmit.React, declaration: true, experimentalDecorators: true, emitDecoratorMetadata: true, outDir: 'lib', lib: [ 'dom', 'es2017', ], }); } if (overWrite === void 0) { overWrite = true; } if (!fs.existsSync(srcFolder)) { throw new Error(exports.errors.CREATE_FOLDER_MUST_EXIST); } var projectFilePath = path.normalize(srcFolder + '/' + projectFileName); if (!overWrite && fs.existsSync(projectFilePath)) throw new Error(exports.errors.CREATE_PROJECT_ALREADY_EXISTS); // We need to write the raw spec var projectSpec = {}; projectSpec.compilerOptions = tsToRawCompilerOptions(defaultOptions); projectSpec.compileOnSave = true; projectSpec.exclude = ["node_modules"]; projectSpec.include = ["src"]; fs.writeFileSync(projectFilePath, json.stringify(projectSpec, os.EOL)); return getProjectSync(srcFolder); } exports.createProjectRootSync = createProjectRootSync; ////////////////////////////////////////////////////////////////////// /** * ENUM to String and String to ENUM */ var typescriptEnumMap = { target: { 'es3': ts.ScriptTarget.ES3, 'es5': ts.ScriptTarget.ES5, 'es6': ts.ScriptTarget.ES2015, 'es2015': ts.ScriptTarget.ES2015, 'es2016': ts.ScriptTarget.ES2016, 'es2017': ts.ScriptTarget.ES2017, 'esnext': ts.ScriptTarget.ESNext, 'next': ts.ScriptTarget.ESNext, 'latest': ts.ScriptTarget.Latest }, module: { 'none': ts.ModuleKind.None, 'commonjs': ts.ModuleKind.CommonJS, 'amd': ts.ModuleKind.AMD, 'umd': ts.ModuleKind.UMD, 'system': ts.ModuleKind.System, 'es6': ts.ModuleKind.ES2015, 'es2015': ts.ModuleKind.ES2015, }, moduleResolution: { 'node': ts.ModuleResolutionKind.NodeJs, 'classic': ts.ModuleResolutionKind.Classic }, jsx: { 'none': ts.JsxEmit.None, 'preserve': ts.JsxEmit.Preserve, 'react': ts.JsxEmit.React, 'react-native': ts.JsxEmit.ReactNative, }, newLine: { 'CRLF': ts.NewLineKind.CarriageReturnLineFeed, 'LF': ts.NewLineKind.LineFeed } }; /** * These are options that are relative paths to tsconfig.json * Note: There is also `rootDirs` that is handled manually */ var pathResolveTheseOptions = [ 'out', 'outFile', 'outDir', 'rootDir', 'baseUrl', ]; ////////////////////////////////////////////////////////////////////// /** * Raw To Compiler */ function rawToTsCompilerOptions(jsonOptions, projectDir) { var compilerOptions = utils_1.extend(defaultCompilerOptions); /** Parse the enums */ for (var key in jsonOptions) { if (typescriptEnumMap[key]) { var name_1 = jsonOptions[key]; var map = typescriptEnumMap[key]; compilerOptions[key] = map[name_1.toLowerCase()] || map[name_1.toUpperCase()]; } else { compilerOptions[key] = jsonOptions[key]; } } /** * Parse all paths to not be relative */ pathResolveTheseOptions.forEach(function (option) { if (compilerOptions[option] !== undefined) { compilerOptions[option] = fsu.resolve(projectDir, compilerOptions[option]); } }); /** * Support `rootDirs` * https://github.com/Microsoft/TypeScript-Handbook/blob/release-2.0/pages/Module%20Resolution.md#virtual-directories-with-rootdirs */ if (compilerOptions.rootDirs !== undefined && Array.isArray(compilerOptions.rootDirs)) { compilerOptions.rootDirs = compilerOptions.rootDirs.map(function (rd) { return fsu.resolve(projectDir, rd); }); } /** * Till `out` is removed. Support it by just copying it to `outFile` */ if (compilerOptions.out !== undefined) { compilerOptions.outFile = path.resolve(projectDir, compilerOptions.out); } /** * The default for moduleResolution as implemented by the compiler */ if (!jsonOptions.moduleResolution && compilerOptions.module !== ts.ModuleKind.CommonJS) { compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; } return compilerOptions; } /** * Compiler to Raw */ function tsToRawCompilerOptions(compilerOptions) { var jsonOptions = utils_1.extend({}, compilerOptions); /** * Convert enums to raw */ Object.keys(compilerOptions).forEach(function (key) { if (typescriptEnumMap[key] !== undefined && compilerOptions[key] !== undefined) { var value = compilerOptions[key]; var rawToTsMapForKey = typescriptEnumMap[key]; var reverseMap = utils_1.reverseKeysAndValues(rawToTsMapForKey); jsonOptions[key] = reverseMap[value]; } }); return jsonOptions; }