blob: 96874e9c524cb4c76a03c6aee4e4f468a9248269 [file] [log] [blame]
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const ts = __importStar(require("typescript")); // leave this as * as ts so people using util package don't need syntheticDefaultImports
//------------------------------------------------------------------------------
// Environment calculation
//------------------------------------------------------------------------------
/**
* Default compiler options for program generation from single root file
*/
const defaultCompilerOptions = {
allowNonTsExtensions: true,
allowJs: true,
};
/**
* Maps tsconfig paths to their corresponding file contents and resulting watches
*/
const knownWatchProgramMap = new Map();
/**
* Maps file paths to their set of corresponding watch callbacks
* There may be more than one per file if a file is shared between projects
*/
const watchCallbackTrackingMap = new Map();
const parsedFilesSeen = new Set();
/**
* Holds information about the file currently being linted
*/
const currentLintOperationState = {
code: '',
filePath: '',
};
/**
* Appropriately report issues found when reading a config file
* @param diagnostic The diagnostic raised when creating a program
*/
function diagnosticReporter(diagnostic) {
throw new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine));
}
const noopFileWatcher = { close: () => { } };
function getTsconfigPath(tsconfigPath, extra) {
return path_1.default.isAbsolute(tsconfigPath)
? tsconfigPath
: path_1.default.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath);
}
/**
* Calculate project environments using options provided by consumer and paths from config
* @param code The code being linted
* @param filePath The path of the file being parsed
* @param extra.tsconfigRootDir The root directory for relative tsconfig paths
* @param extra.project Provided tsconfig paths
* @returns The programs corresponding to the supplied tsconfig paths
*/
function calculateProjectParserOptions(code, filePath, extra) {
const results = [];
// preserve reference to code and file being linted
currentLintOperationState.code = code;
currentLintOperationState.filePath = filePath;
// Update file version if necessary
// TODO: only update when necessary, currently marks as changed on every lint
const watchCallback = watchCallbackTrackingMap.get(filePath);
if (parsedFilesSeen.has(filePath) && typeof watchCallback !== 'undefined') {
watchCallback(filePath, ts.FileWatcherEventKind.Changed);
}
for (const rawTsconfigPath of extra.projects) {
const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra);
const existingWatch = knownWatchProgramMap.get(tsconfigPath);
if (typeof existingWatch !== 'undefined') {
// get new program (updated if necessary)
const updatedProgram = existingWatch.getProgram().getProgram();
updatedProgram.getTypeChecker(); // sets parent pointers in source files
results.push(updatedProgram);
continue;
}
// create compiler host
const watchCompilerHost = ts.createWatchCompilerHost(tsconfigPath,
/*optionsToExtend*/ { allowNonTsExtensions: true }, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, diagnosticReporter,
/*reportWatchStatus*/ () => { });
// ensure readFile reads the code being linted instead of the copy on disk
const oldReadFile = watchCompilerHost.readFile;
watchCompilerHost.readFile = (filePath, encoding) => path_1.default.normalize(filePath) ===
path_1.default.normalize(currentLintOperationState.filePath)
? currentLintOperationState.code
: oldReadFile(filePath, encoding);
// ensure process reports error on failure instead of exiting process immediately
watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter;
// ensure process doesn't emit programs
watchCompilerHost.afterProgramCreate = program => {
// report error if there are any errors in the config file
const configFileDiagnostics = program
.getConfigFileParsingDiagnostics()
.filter(diag => diag.category === ts.DiagnosticCategory.Error &&
diag.code !== 18003);
if (configFileDiagnostics.length > 0) {
diagnosticReporter(configFileDiagnostics[0]);
}
};
// register callbacks to trigger program updates without using fileWatchers
watchCompilerHost.watchFile = (fileName, callback) => {
const normalizedFileName = path_1.default.normalize(fileName);
watchCallbackTrackingMap.set(normalizedFileName, callback);
return {
close: () => {
watchCallbackTrackingMap.delete(normalizedFileName);
},
};
};
// ensure fileWatchers aren't created for directories
watchCompilerHost.watchDirectory = () => noopFileWatcher;
// allow files with custom extensions to be included in program (uses internal ts api)
const oldOnDirectoryStructureHostCreate = watchCompilerHost
.onCachedDirectoryStructureHostCreate;
watchCompilerHost.onCachedDirectoryStructureHostCreate = (host) => {
const oldReadDirectory = host.readDirectory;
host.readDirectory = (path, extensions, exclude, include, depth) => oldReadDirectory(path, !extensions
? undefined
: extensions.concat(extra.extraFileExtensions), exclude, include, depth);
oldOnDirectoryStructureHostCreate(host);
};
// create program
const programWatch = ts.createWatchProgram(watchCompilerHost);
const program = programWatch.getProgram().getProgram();
// cache watch program and return current program
knownWatchProgramMap.set(tsconfigPath, programWatch);
results.push(program);
}
parsedFilesSeen.add(filePath);
return results;
}
exports.calculateProjectParserOptions = calculateProjectParserOptions;
/**
* Create program from single root file. Requires a single tsconfig to be specified.
* @param code The code being linted
* @param filePath The file being linted
* @param extra.tsconfigRootDir The root directory for relative tsconfig paths
* @param extra.project Provided tsconfig paths
* @returns The program containing just the file being linted and associated library files
*/
function createProgram(code, filePath, extra) {
if (!extra.projects || extra.projects.length !== 1) {
return undefined;
}
const tsconfigPath = getTsconfigPath(extra.projects[0], extra);
const commandLine = ts.getParsedCommandLineOfConfigFile(tsconfigPath, defaultCompilerOptions, Object.assign({}, ts.sys, { onUnRecoverableConfigFileDiagnostic: () => { } }));
if (!commandLine) {
return undefined;
}
const compilerHost = ts.createCompilerHost(commandLine.options, true);
const oldReadFile = compilerHost.readFile;
compilerHost.readFile = (fileName) => path_1.default.normalize(fileName) === path_1.default.normalize(filePath)
? code
: oldReadFile(fileName);
return ts.createProgram([filePath], commandLine.options, compilerHost);
}
exports.createProgram = createProgram;
//# sourceMappingURL=tsconfig-parser.js.map