alm
Version:
The best IDE for TypeScript
361 lines (360 loc) • 14.2 kB
JavaScript
"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;
}