2 var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
5 var __importStar = (this && this.__importStar) || function (mod) {
6 if (mod && mod.__esModule) return mod;
8 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
9 result["default"] = mod;
12 Object.defineProperty(exports, "__esModule", { value: true });
13 const chokidar_1 = __importDefault(require("chokidar"));
14 const path_1 = __importDefault(require("path"));
15 const ts = __importStar(require("typescript")); // leave this as * as ts so people using util package don't need syntheticDefaultImports
16 //------------------------------------------------------------------------------
17 // Environment calculation
18 //------------------------------------------------------------------------------
20 * Default compiler options for program generation from single root file
22 exports.defaultCompilerOptions = {
23 allowNonTsExtensions: true,
29 * Maps tsconfig paths to their corresponding file contents and resulting watches
31 const knownWatchProgramMap = new Map();
33 * Maps file paths to their set of corresponding watch callbacks
34 * There may be more than one per file if a file is shared between projects
36 const watchCallbackTrackingMap = new Map();
38 * Tracks the ts.sys.watchFile watchers that we've opened for config files.
39 * We store these so we can clean up our handles if required.
41 const configSystemFileWatcherTrackingSet = new Set();
43 * Tracks the ts.sys.watchDirectory watchers that we've opened for project folders.
44 * We store these so we can clean up our handles if required.
46 const directorySystemFileWatcherTrackingSet = new Set();
47 const parsedFilesSeen = new Set();
49 * Clear all of the parser caches.
50 * This should only be used in testing to ensure the parser is clean between tests.
52 function clearCaches() {
53 knownWatchProgramMap.clear();
54 watchCallbackTrackingMap.clear();
55 parsedFilesSeen.clear();
56 // stop tracking config files
57 configSystemFileWatcherTrackingSet.forEach(cb => cb.close());
58 configSystemFileWatcherTrackingSet.clear();
59 // stop tracking folders
60 directorySystemFileWatcherTrackingSet.forEach(cb => cb.close());
61 directorySystemFileWatcherTrackingSet.clear();
63 exports.clearCaches = clearCaches;
65 * Holds information about the file currently being linted
67 const currentLintOperationState = {
72 * Appropriately report issues found when reading a config file
73 * @param diagnostic The diagnostic raised when creating a program
75 function diagnosticReporter(diagnostic) {
76 throw new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine));
78 function getTsconfigPath(tsconfigPath, extra) {
79 return path_1.default.isAbsolute(tsconfigPath)
81 : path_1.default.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath);
84 * Watches a file or directory for changes
86 function watch(path, options, extra) {
87 // an escape hatch to disable the file watchers as they can take a bit to initialise in some cases
88 // this also supports an env variable so it's easy to switch on/off from the CLI
89 if (process.env.PARSER_NO_WATCH === 'true' || extra.noWatch === true) {
95 return chokidar_1.default.watch(path, Object.assign({ ignoreInitial: true, persistent: false, useFsEvents: false }, options));
98 * Calculate project environments using options provided by consumer and paths from config
99 * @param code The code being linted
100 * @param filePath The path of the file being parsed
101 * @param extra.tsconfigRootDir The root directory for relative tsconfig paths
102 * @param extra.projects Provided tsconfig paths
103 * @returns The programs corresponding to the supplied tsconfig paths
105 function calculateProjectParserOptions(code, filePath, extra) {
107 // preserve reference to code and file being linted
108 currentLintOperationState.code = code;
109 currentLintOperationState.filePath = filePath;
110 // Update file version if necessary
111 // TODO: only update when necessary, currently marks as changed on every lint
112 const watchCallbacks = watchCallbackTrackingMap.get(filePath);
113 if (parsedFilesSeen.has(filePath) &&
115 watchCallbacks.size > 0) {
116 watchCallbacks.forEach(cb => cb(filePath, ts.FileWatcherEventKind.Changed));
118 for (const rawTsconfigPath of extra.projects) {
119 const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra);
120 const existingWatch = knownWatchProgramMap.get(tsconfigPath);
121 if (typeof existingWatch !== 'undefined') {
122 // get new program (updated if necessary)
123 const updatedProgram = existingWatch.getProgram().getProgram();
124 updatedProgram.getTypeChecker(); // sets parent pointers in source files
125 results.push(updatedProgram);
128 // create compiler host
129 const watchCompilerHost = ts.createWatchCompilerHost(tsconfigPath, exports.defaultCompilerOptions, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, diagnosticReporter,
130 /*reportWatchStatus*/ () => { });
131 // ensure readFile reads the code being linted instead of the copy on disk
132 const oldReadFile = watchCompilerHost.readFile;
133 watchCompilerHost.readFile = (filePath, encoding) => path_1.default.normalize(filePath) ===
134 path_1.default.normalize(currentLintOperationState.filePath)
135 ? currentLintOperationState.code
136 : oldReadFile(filePath, encoding);
137 // ensure process reports error on failure instead of exiting process immediately
138 watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter;
139 // ensure process doesn't emit programs
140 watchCompilerHost.afterProgramCreate = (program) => {
141 // report error if there are any errors in the config file
142 const configFileDiagnostics = program
143 .getConfigFileParsingDiagnostics()
144 .filter(diag => diag.category === ts.DiagnosticCategory.Error &&
145 diag.code !== 18003);
146 if (configFileDiagnostics.length > 0) {
147 diagnosticReporter(configFileDiagnostics[0]);
150 // in watch mode, eslint will give us the latest file contents
151 // store the watch callback so we can trigger an update with eslint's content
152 watchCompilerHost.watchFile = (fileName, callback, interval) => {
153 // specifically (and separately) watch the tsconfig file
154 // this allows us to react to changes in the tsconfig's include/exclude options
156 if (fileName.includes(tsconfigPath)) {
157 watcher = watch(fileName, {
160 watcher.on('change', path => {
161 callback(path, ts.FileWatcherEventKind.Changed);
163 configSystemFileWatcherTrackingSet.add(watcher);
165 const normalizedFileName = path_1.default.normalize(fileName);
166 const watchers = (() => {
167 let watchers = watchCallbackTrackingMap.get(normalizedFileName);
169 watchers = new Set();
170 watchCallbackTrackingMap.set(normalizedFileName, watchers);
174 watchers.add(callback);
177 watchers.delete(callback);
180 configSystemFileWatcherTrackingSet.delete(watcher);
185 // when new files are added in watch mode, we need to tell typescript about those files
186 // if we don't then typescript will act like they don't exist.
187 watchCompilerHost.watchDirectory = (dirPath, callback, recursive) => {
188 const watcher = watch(dirPath, {
189 depth: recursive ? 0 : undefined,
192 watcher.on('add', path => {
195 directorySystemFileWatcherTrackingSet.add(watcher);
199 directorySystemFileWatcherTrackingSet.delete(watcher);
203 // allow files with custom extensions to be included in program (uses internal ts api)
204 const oldOnDirectoryStructureHostCreate = watchCompilerHost.onCachedDirectoryStructureHostCreate;
205 watchCompilerHost.onCachedDirectoryStructureHostCreate = (host) => {
206 const oldReadDirectory = host.readDirectory;
207 host.readDirectory = (path, extensions, exclude, include, depth) => oldReadDirectory(path, !extensions
209 : extensions.concat(extra.extraFileExtensions), exclude, include, depth);
210 oldOnDirectoryStructureHostCreate(host);
213 const programWatch = ts.createWatchProgram(watchCompilerHost);
214 const program = programWatch.getProgram().getProgram();
215 // cache watch program and return current program
216 knownWatchProgramMap.set(tsconfigPath, programWatch);
217 results.push(program);
219 parsedFilesSeen.add(filePath);
222 exports.calculateProjectParserOptions = calculateProjectParserOptions;
224 * Create program from single root file. Requires a single tsconfig to be specified.
225 * @param code The code being linted
226 * @param filePath The file being linted
227 * @param extra.tsconfigRootDir The root directory for relative tsconfig paths
228 * @param extra.projects Provided tsconfig paths
229 * @returns The program containing just the file being linted and associated library files
231 function createProgram(code, filePath, extra) {
232 if (!extra.projects || extra.projects.length !== 1) {
235 const tsconfigPath = getTsconfigPath(extra.projects[0], extra);
236 const commandLine = ts.getParsedCommandLineOfConfigFile(tsconfigPath, exports.defaultCompilerOptions, Object.assign(Object.assign({}, ts.sys), { onUnRecoverableConfigFileDiagnostic: () => { } }));
240 const compilerHost = ts.createCompilerHost(commandLine.options, true);
241 const oldReadFile = compilerHost.readFile;
242 compilerHost.readFile = (fileName) => path_1.default.normalize(fileName) === path_1.default.normalize(filePath)
244 : oldReadFile(fileName);
245 return ts.createProgram([filePath], commandLine.options, compilerHost);
247 exports.createProgram = createProgram;
248 //# sourceMappingURL=tsconfig-parser.js.map