typescript-estree
Version:
A parser that converts TypeScript source code into an ESTree compatible form
161 lines (160 loc) • 7.99 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const typescript_1 = __importDefault(require("typescript"));
//------------------------------------------------------------------------------
// Environment calculation
//------------------------------------------------------------------------------
/**
* Default compiler options for program generation from single root file
* @type {ts.CompilerOptions}
*/
const defaultCompilerOptions = {
allowNonTsExtensions: true,
allowJs: true
};
/**
* Maps tsconfig paths to their corresponding file contents and resulting watches
* @type {Map<string, ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram>>}
*/
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
* @type {Map<string, ts.FileWatcherCallback>}
*/
const watchCallbackTrackingMap = new Map();
/**
* Holds information about the file currently being linted
* @type {{code: string, filePath: string}}
*/
const currentLintOperationState = {
code: '',
filePath: ''
};
/**
* Appropriately report issues found when reading a config file
* @param {ts.Diagnostic} diagnostic The diagnostic raised when creating a program
* @returns {void}
*/
function diagnosticReporter(diagnostic) {
throw new Error(typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, typescript_1.default.sys.newLine));
}
const noopFileWatcher = { close: () => { } };
/**
* Calculate project environments using options provided by consumer and paths from config
* @param {string} code The code being linted
* @param {string} filePath The path of the file being parsed
* @param {string} extra.tsconfigRootDir The root directory for relative tsconfig paths
* @param {string[]} extra.project Provided tsconfig paths
* @returns {ts.Program[]} The programs corresponding to the supplied tsconfig paths
*/
function calculateProjectParserOptions(code, filePath, extra) {
const results = [];
const tsconfigRootDir = extra.tsconfigRootDir;
// 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 (typeof watchCallback !== 'undefined') {
watchCallback(filePath, typescript_1.default.FileWatcherEventKind.Changed);
}
for (let tsconfigPath of extra.projects) {
// if absolute paths aren't provided, make relative to tsconfigRootDir
if (!path_1.default.isAbsolute(tsconfigPath)) {
tsconfigPath = path_1.default.join(tsconfigRootDir, tsconfigPath);
}
const existingWatch = knownWatchProgramMap.get(tsconfigPath);
if (typeof existingWatch !== 'undefined') {
// get new program (updated if necessary)
results.push(existingWatch.getProgram().getProgram());
continue;
}
// create compiler host
const watchCompilerHost = typescript_1.default.createWatchCompilerHost(tsconfigPath,
/*optionsToExtend*/ { allowNonTsExtensions: true }, typescript_1.default.sys, typescript_1.default.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 === typescript_1.default.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 = typescript_1.default.createWatchProgram(watchCompilerHost);
const program = programWatch.getProgram().getProgram();
// cache watch program and return current program
knownWatchProgramMap.set(tsconfigPath, programWatch);
results.push(program);
}
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 {string} extra.tsconfigRootDir The root directory for relative tsconfig paths
* @param {string[]} extra.project Provided tsconfig paths
* @returns {ts.Program} 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;
}
let tsconfigPath = extra.projects[0];
// if absolute paths aren't provided, make relative to tsconfigRootDir
if (!path_1.default.isAbsolute(tsconfigPath)) {
tsconfigPath = path_1.default.join(extra.tsconfigRootDir, tsconfigPath);
}
const commandLine = typescript_1.default.getParsedCommandLineOfConfigFile(tsconfigPath, defaultCompilerOptions, Object.assign({}, typescript_1.default.sys, { onUnRecoverableConfigFileDiagnostic: () => { } }));
if (!commandLine) {
return undefined;
}
const compilerHost = typescript_1.default.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 typescript_1.default.createProgram([filePath], commandLine.options, compilerHost);
}
exports.createProgram = createProgram;
;