Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @typescript-eslint / typescript-estree / dist / create-program / createWatchProgram.js
1 "use strict";
2 var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3     if (k2 === undefined) k2 = k;
4     Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5 }) : (function(o, m, k, k2) {
6     if (k2 === undefined) k2 = k;
7     o[k2] = m[k];
8 }));
9 var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10     Object.defineProperty(o, "default", { enumerable: true, value: v });
11 }) : function(o, v) {
12     o["default"] = v;
13 });
14 var __importStar = (this && this.__importStar) || function (mod) {
15     if (mod && mod.__esModule) return mod;
16     var result = {};
17     if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18     __setModuleDefault(result, mod);
19     return result;
20 };
21 var __importDefault = (this && this.__importDefault) || function (mod) {
22     return (mod && mod.__esModule) ? mod : { "default": mod };
23 };
24 Object.defineProperty(exports, "__esModule", { value: true });
25 exports.getProgramsForProjects = exports.createWatchProgram = exports.clearCaches = void 0;
26 const debug_1 = __importDefault(require("debug"));
27 const fs_1 = __importDefault(require("fs"));
28 const semver_1 = __importDefault(require("semver"));
29 const ts = __importStar(require("typescript"));
30 const shared_1 = require("./shared");
31 const log = debug_1.default('typescript-eslint:typescript-estree:createWatchProgram');
32 /**
33  * Maps tsconfig paths to their corresponding file contents and resulting watches
34  */
35 const knownWatchProgramMap = new Map();
36 /**
37  * Maps file/folder paths to their set of corresponding watch callbacks
38  * There may be more than one per file/folder if a file/folder is shared between projects
39  */
40 const fileWatchCallbackTrackingMap = new Map();
41 const folderWatchCallbackTrackingMap = new Map();
42 /**
43  * Stores the list of known files for each program
44  */
45 const programFileListCache = new Map();
46 /**
47  * Caches the last modified time of the tsconfig files
48  */
49 const tsconfigLastModifiedTimestampCache = new Map();
50 const parsedFilesSeenHash = new Map();
51 /**
52  * Clear all of the parser caches.
53  * This should only be used in testing to ensure the parser is clean between tests.
54  */
55 function clearCaches() {
56     knownWatchProgramMap.clear();
57     fileWatchCallbackTrackingMap.clear();
58     folderWatchCallbackTrackingMap.clear();
59     parsedFilesSeenHash.clear();
60     programFileListCache.clear();
61     tsconfigLastModifiedTimestampCache.clear();
62 }
63 exports.clearCaches = clearCaches;
64 function saveWatchCallback(trackingMap) {
65     return (fileName, callback) => {
66         const normalizedFileName = shared_1.getCanonicalFileName(fileName);
67         const watchers = (() => {
68             let watchers = trackingMap.get(normalizedFileName);
69             if (!watchers) {
70                 watchers = new Set();
71                 trackingMap.set(normalizedFileName, watchers);
72             }
73             return watchers;
74         })();
75         watchers.add(callback);
76         return {
77             close: () => {
78                 watchers.delete(callback);
79             },
80         };
81     };
82 }
83 /**
84  * Holds information about the file currently being linted
85  */
86 const currentLintOperationState = {
87     code: '',
88     filePath: '',
89 };
90 /**
91  * Appropriately report issues found when reading a config file
92  * @param diagnostic The diagnostic raised when creating a program
93  */
94 function diagnosticReporter(diagnostic) {
95     throw new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine));
96 }
97 /**
98  * Hash content for compare content.
99  * @param content hashed contend
100  * @returns hashed result
101  */
102 function createHash(content) {
103     var _a;
104     // No ts.sys in browser environments.
105     if ((_a = ts.sys) === null || _a === void 0 ? void 0 : _a.createHash) {
106         return ts.sys.createHash(content);
107     }
108     return content;
109 }
110 /**
111  * Calculate project environments using options provided by consumer and paths from config
112  * @param code The code being linted
113  * @param filePathIn The path of the file being parsed
114  * @param extra.tsconfigRootDir The root directory for relative tsconfig paths
115  * @param extra.projects Provided tsconfig paths
116  * @returns The programs corresponding to the supplied tsconfig paths
117  */
118 function getProgramsForProjects(code, filePathIn, extra) {
119     const filePath = shared_1.getCanonicalFileName(filePathIn);
120     const results = [];
121     // preserve reference to code and file being linted
122     currentLintOperationState.code = code;
123     currentLintOperationState.filePath = filePath;
124     // Update file version if necessary
125     const fileWatchCallbacks = fileWatchCallbackTrackingMap.get(filePath);
126     const codeHash = createHash(code);
127     if (parsedFilesSeenHash.get(filePath) !== codeHash &&
128         fileWatchCallbacks &&
129         fileWatchCallbacks.size > 0) {
130         fileWatchCallbacks.forEach(cb => cb(filePath, ts.FileWatcherEventKind.Changed));
131     }
132     /*
133      * before we go into the process of attempting to find and update every program
134      * see if we know of a program that contains this file
135      */
136     for (const rawTsconfigPath of extra.projects) {
137         const tsconfigPath = shared_1.getTsconfigPath(rawTsconfigPath, extra);
138         const existingWatch = knownWatchProgramMap.get(tsconfigPath);
139         if (!existingWatch) {
140             continue;
141         }
142         let fileList = programFileListCache.get(tsconfigPath);
143         let updatedProgram = null;
144         if (!fileList) {
145             updatedProgram = existingWatch.getProgram().getProgram();
146             fileList = new Set(updatedProgram.getRootFileNames().map(f => shared_1.getCanonicalFileName(f)));
147             programFileListCache.set(tsconfigPath, fileList);
148         }
149         if (fileList.has(filePath)) {
150             log('Found existing program for file. %s', filePath);
151             updatedProgram = updatedProgram !== null && updatedProgram !== void 0 ? updatedProgram : existingWatch.getProgram().getProgram();
152             // sets parent pointers in source files
153             updatedProgram.getTypeChecker();
154             return [updatedProgram];
155         }
156     }
157     log('File did not belong to any existing programs, moving to create/update. %s', filePath);
158     /*
159      * We don't know of a program that contains the file, this means that either:
160      * - the required program hasn't been created yet, or
161      * - the file is new/renamed, and the program hasn't been updated.
162      */
163     for (const rawTsconfigPath of extra.projects) {
164         const tsconfigPath = shared_1.getTsconfigPath(rawTsconfigPath, extra);
165         const existingWatch = knownWatchProgramMap.get(tsconfigPath);
166         if (existingWatch) {
167             const updatedProgram = maybeInvalidateProgram(existingWatch, filePath, tsconfigPath);
168             if (!updatedProgram) {
169                 continue;
170             }
171             // sets parent pointers in source files
172             updatedProgram.getTypeChecker();
173             results.push(updatedProgram);
174             continue;
175         }
176         const programWatch = createWatchProgram(tsconfigPath, extra);
177         const program = programWatch.getProgram().getProgram();
178         // cache watch program and return current program
179         knownWatchProgramMap.set(tsconfigPath, programWatch);
180         // sets parent pointers in source files
181         program.getTypeChecker();
182         results.push(program);
183     }
184     return results;
185 }
186 exports.getProgramsForProjects = getProgramsForProjects;
187 const isRunningNoTimeoutFix = semver_1.default.satisfies(ts.version, '>=3.9.0-beta', {
188     includePrerelease: true,
189 });
190 function createWatchProgram(tsconfigPath, extra) {
191     log('Creating watch program for %s.', tsconfigPath);
192     // create compiler host
193     const watchCompilerHost = ts.createWatchCompilerHost(tsconfigPath, shared_1.createDefaultCompilerOptionsFromExtra(extra), ts.sys, ts.createAbstractBuilder, diagnosticReporter, 
194     /*reportWatchStatus*/ () => { });
195     // ensure readFile reads the code being linted instead of the copy on disk
196     const oldReadFile = watchCompilerHost.readFile;
197     watchCompilerHost.readFile = (filePathIn, encoding) => {
198         const filePath = shared_1.getCanonicalFileName(filePathIn);
199         const fileContent = filePath === currentLintOperationState.filePath
200             ? currentLintOperationState.code
201             : oldReadFile(filePath, encoding);
202         if (fileContent !== undefined) {
203             parsedFilesSeenHash.set(filePath, createHash(fileContent));
204         }
205         return fileContent;
206     };
207     // ensure process reports error on failure instead of exiting process immediately
208     watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter;
209     // ensure process doesn't emit programs
210     watchCompilerHost.afterProgramCreate = (program) => {
211         // report error if there are any errors in the config file
212         const configFileDiagnostics = program
213             .getConfigFileParsingDiagnostics()
214             .filter(diag => diag.category === ts.DiagnosticCategory.Error && diag.code !== 18003);
215         if (configFileDiagnostics.length > 0) {
216             diagnosticReporter(configFileDiagnostics[0]);
217         }
218     };
219     /*
220      * From the CLI, the file watchers won't matter, as the files will be parsed once and then forgotten.
221      * When running from an IDE, these watchers will let us tell typescript about changes.
222      *
223      * ESLint IDE plugins will send us unfinished file content as the user types (before it's saved to disk).
224      * We use the file watchers to tell typescript about this latest file content.
225      *
226      * When files are created (or renamed), we won't know about them because we have no filesystem watchers attached.
227      * We use the folder watchers to tell typescript it needs to go and find new files in the project folders.
228      */
229     watchCompilerHost.watchFile = saveWatchCallback(fileWatchCallbackTrackingMap);
230     watchCompilerHost.watchDirectory = saveWatchCallback(folderWatchCallbackTrackingMap);
231     // allow files with custom extensions to be included in program (uses internal ts api)
232     const oldOnDirectoryStructureHostCreate = watchCompilerHost.onCachedDirectoryStructureHostCreate;
233     watchCompilerHost.onCachedDirectoryStructureHostCreate = (host) => {
234         const oldReadDirectory = host.readDirectory;
235         host.readDirectory = (path, extensions, exclude, include, depth) => oldReadDirectory(path, !extensions ? undefined : extensions.concat(extra.extraFileExtensions), exclude, include, depth);
236         oldOnDirectoryStructureHostCreate(host);
237     };
238     // This works only on 3.9
239     watchCompilerHost.extraFileExtensions = extra.extraFileExtensions.map(extension => ({
240         extension,
241         isMixedContent: true,
242         scriptKind: ts.ScriptKind.Deferred,
243     }));
244     watchCompilerHost.trace = log;
245     // Since we don't want to asynchronously update program we want to disable timeout methods
246     // So any changes in the program will be delayed and updated when getProgram is called on watch
247     let callback;
248     if (isRunningNoTimeoutFix) {
249         watchCompilerHost.setTimeout = undefined;
250         watchCompilerHost.clearTimeout = undefined;
251     }
252     else {
253         log('Running without timeout fix');
254         // But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined
255         // instead save it and call before getProgram is called
256         watchCompilerHost.setTimeout = (cb, _ms, ...args) => {
257             callback = cb.bind(/*this*/ undefined, ...args);
258             return callback;
259         };
260         watchCompilerHost.clearTimeout = () => {
261             callback = undefined;
262         };
263     }
264     const watch = ts.createWatchProgram(watchCompilerHost);
265     if (!isRunningNoTimeoutFix) {
266         const originalGetProgram = watch.getProgram;
267         watch.getProgram = () => {
268             if (callback) {
269                 callback();
270             }
271             callback = undefined;
272             return originalGetProgram.call(watch);
273         };
274     }
275     return watch;
276 }
277 exports.createWatchProgram = createWatchProgram;
278 function hasTSConfigChanged(tsconfigPath) {
279     const stat = fs_1.default.statSync(tsconfigPath);
280     const lastModifiedAt = stat.mtimeMs;
281     const cachedLastModifiedAt = tsconfigLastModifiedTimestampCache.get(tsconfigPath);
282     tsconfigLastModifiedTimestampCache.set(tsconfigPath, lastModifiedAt);
283     if (cachedLastModifiedAt === undefined) {
284         return false;
285     }
286     return Math.abs(cachedLastModifiedAt - lastModifiedAt) > Number.EPSILON;
287 }
288 function maybeInvalidateProgram(existingWatch, filePath, tsconfigPath) {
289     /*
290      * By calling watchProgram.getProgram(), it will trigger a resync of the program based on
291      * whatever new file content we've given it from our input.
292      */
293     let updatedProgram = existingWatch.getProgram().getProgram();
294     // In case this change causes problems in larger real world codebases
295     // Provide an escape hatch so people don't _have_ to revert to an older version
296     if (process.env.TSESTREE_NO_INVALIDATION === 'true') {
297         return updatedProgram;
298     }
299     if (hasTSConfigChanged(tsconfigPath)) {
300         /*
301          * If the stat of the tsconfig has changed, that could mean the include/exclude/files lists has changed
302          * We need to make sure typescript knows this so it can update appropriately
303          */
304         log('tsconfig has changed - triggering program update. %s', tsconfigPath);
305         fileWatchCallbackTrackingMap
306             .get(tsconfigPath)
307             .forEach(cb => cb(tsconfigPath, ts.FileWatcherEventKind.Changed));
308         // tsconfig change means that the file list more than likely changed, so clear the cache
309         programFileListCache.delete(tsconfigPath);
310     }
311     let sourceFile = updatedProgram.getSourceFile(filePath);
312     if (sourceFile) {
313         return updatedProgram;
314     }
315     /*
316      * Missing source file means our program's folder structure might be out of date.
317      * So we need to tell typescript it needs to update the correct folder.
318      */
319     log('File was not found in program - triggering folder update. %s', filePath);
320     // Find the correct directory callback by climbing the folder tree
321     const currentDir = shared_1.canonicalDirname(filePath);
322     let current = null;
323     let next = currentDir;
324     let hasCallback = false;
325     while (current !== next) {
326         current = next;
327         const folderWatchCallbacks = folderWatchCallbackTrackingMap.get(current);
328         if (folderWatchCallbacks) {
329             folderWatchCallbacks.forEach(cb => {
330                 if (currentDir !== current) {
331                     cb(currentDir, ts.FileWatcherEventKind.Changed);
332                 }
333                 cb(current, ts.FileWatcherEventKind.Changed);
334             });
335             hasCallback = true;
336         }
337         next = shared_1.canonicalDirname(current);
338     }
339     if (!hasCallback) {
340         /*
341          * No callback means the paths don't matchup - so no point returning any program
342          * this will signal to the caller to skip this program
343          */
344         log('No callback found for file, not part of this program. %s', filePath);
345         return null;
346     }
347     // directory update means that the file list more than likely changed, so clear the cache
348     programFileListCache.delete(tsconfigPath);
349     // force the immediate resync
350     updatedProgram = existingWatch.getProgram().getProgram();
351     sourceFile = updatedProgram.getSourceFile(filePath);
352     if (sourceFile) {
353         return updatedProgram;
354     }
355     /*
356      * At this point we're in one of two states:
357      * - The file isn't supposed to be in this program due to exclusions
358      * - The file is new, and was renamed from an old, included filename
359      *
360      * For the latter case, we need to tell typescript that the old filename is now deleted
361      */
362     log('File was still not found in program after directory update - checking file deletions. %s', filePath);
363     const rootFilenames = updatedProgram.getRootFileNames();
364     // use find because we only need to "delete" one file to cause typescript to do a full resync
365     const deletedFile = rootFilenames.find(file => !fs_1.default.existsSync(file));
366     if (!deletedFile) {
367         // There are no deleted files, so it must be the former case of the file not belonging to this program
368         return null;
369     }
370     const fileWatchCallbacks = fileWatchCallbackTrackingMap.get(shared_1.getCanonicalFileName(deletedFile));
371     if (!fileWatchCallbacks) {
372         // shouldn't happen, but just in case
373         log('Could not find watch callbacks for root file. %s', deletedFile);
374         return updatedProgram;
375     }
376     log('Marking file as deleted. %s', deletedFile);
377     fileWatchCallbacks.forEach(cb => cb(deletedFile, ts.FileWatcherEventKind.Deleted));
378     // deleted files means that the file list _has_ changed, so clear the cache
379     programFileListCache.delete(tsconfigPath);
380     updatedProgram = existingWatch.getProgram().getProgram();
381     sourceFile = updatedProgram.getSourceFile(filePath);
382     if (sourceFile) {
383         return updatedProgram;
384     }
385     log('File was still not found in program after deletion check, assuming it is not part of this program. %s', filePath);
386     return null;
387 }
388 //# sourceMappingURL=createWatchProgram.js.map