69f053667046ca2a4f6e3ce66d5cd307ebc3d1bd
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / v8-compile-cache / v8-compile-cache.js
1 'use strict';
2
3 const Module = require('module');
4 const crypto = require('crypto');
5 const fs = require('fs');
6 const path = require('path');
7 const vm = require('vm');
8 const os = require('os');
9
10 const hasOwnProperty = Object.prototype.hasOwnProperty;
11
12 //------------------------------------------------------------------------------
13 // FileSystemBlobStore
14 //------------------------------------------------------------------------------
15
16 class FileSystemBlobStore {
17   constructor(directory, prefix) {
18     const name = prefix ? slashEscape(prefix + '.') : '';
19     this._blobFilename = path.join(directory, name + 'BLOB');
20     this._mapFilename = path.join(directory, name + 'MAP');
21     this._lockFilename = path.join(directory, name + 'LOCK');
22     this._directory = directory;
23     this._load();
24   }
25
26   has(key, invalidationKey) {
27     if (hasOwnProperty.call(this._memoryBlobs, key)) {
28       return this._invalidationKeys[key] === invalidationKey;
29     } else if (hasOwnProperty.call(this._storedMap, key)) {
30       return this._storedMap[key][0] === invalidationKey;
31     }
32     return false;
33   }
34
35   get(key, invalidationKey) {
36     if (hasOwnProperty.call(this._memoryBlobs, key)) {
37       if (this._invalidationKeys[key] === invalidationKey) {
38         return this._memoryBlobs[key];
39       }
40     } else if (hasOwnProperty.call(this._storedMap, key)) {
41       const mapping = this._storedMap[key];
42       if (mapping[0] === invalidationKey) {
43         return this._storedBlob.slice(mapping[1], mapping[2]);
44       }
45     }
46   }
47
48   set(key, invalidationKey, buffer) {
49     this._invalidationKeys[key] = invalidationKey;
50     this._memoryBlobs[key] = buffer;
51     this._dirty = true;
52   }
53
54   delete(key) {
55     if (hasOwnProperty.call(this._memoryBlobs, key)) {
56       this._dirty = true;
57       delete this._memoryBlobs[key];
58     }
59     if (hasOwnProperty.call(this._invalidationKeys, key)) {
60       this._dirty = true;
61       delete this._invalidationKeys[key];
62     }
63     if (hasOwnProperty.call(this._storedMap, key)) {
64       this._dirty = true;
65       delete this._storedMap[key];
66     }
67   }
68
69   isDirty() {
70     return this._dirty;
71   }
72
73   save() {
74     const dump = this._getDump();
75     const blobToStore = Buffer.concat(dump[0]);
76     const mapToStore = JSON.stringify(dump[1]);
77
78     try {
79       mkdirpSync(this._directory);
80       fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'});
81     } catch (error) {
82       // Swallow the exception if we fail to acquire the lock.
83       return false;
84     }
85
86     try {
87       fs.writeFileSync(this._blobFilename, blobToStore);
88       fs.writeFileSync(this._mapFilename, mapToStore);
89     } catch (error) {
90       throw error;
91     } finally {
92       fs.unlinkSync(this._lockFilename);
93     }
94
95     return true;
96   }
97
98   _load() {
99     try {
100       this._storedBlob = fs.readFileSync(this._blobFilename);
101       this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename));
102     } catch (e) {
103       this._storedBlob = Buffer.alloc(0);
104       this._storedMap = {};
105     }
106     this._dirty = false;
107     this._memoryBlobs = {};
108     this._invalidationKeys = {};
109   }
110
111   _getDump() {
112     const buffers = [];
113     const newMap = {};
114     let offset = 0;
115
116     function push(key, invalidationKey, buffer) {
117       buffers.push(buffer);
118       newMap[key] = [invalidationKey, offset, offset + buffer.length];
119       offset += buffer.length;
120     }
121
122     for (const key of Object.keys(this._memoryBlobs)) {
123       const buffer = this._memoryBlobs[key];
124       const invalidationKey = this._invalidationKeys[key];
125       push(key, invalidationKey, buffer);
126     }
127
128     for (const key of Object.keys(this._storedMap)) {
129       if (hasOwnProperty.call(newMap, key)) continue;
130       const mapping = this._storedMap[key];
131       const buffer = this._storedBlob.slice(mapping[1], mapping[2]);
132       push(key, mapping[0], buffer);
133     }
134
135     return [buffers, newMap];
136   }
137 }
138
139 //------------------------------------------------------------------------------
140 // NativeCompileCache
141 //------------------------------------------------------------------------------
142
143 class NativeCompileCache {
144   constructor() {
145     this._cacheStore = null;
146     this._previousModuleCompile = null;
147   }
148
149   setCacheStore(cacheStore) {
150     this._cacheStore = cacheStore;
151   }
152
153   install() {
154     const self = this;
155     const hasRequireResolvePaths = typeof require.resolve.paths === 'function';
156     this._previousModuleCompile = Module.prototype._compile;
157     Module.prototype._compile = function(content, filename) {
158       const mod = this;
159
160       function require(id) {
161         return mod.require(id);
162       }
163
164       // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L28
165       function resolve(request, options) {
166         return Module._resolveFilename(request, mod, false, options);
167       }
168       require.resolve = resolve;
169
170       // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L37
171       // resolve.resolve.paths was added in v8.9.0
172       if (hasRequireResolvePaths) {
173         resolve.paths = function paths(request) {
174           return Module._resolveLookupPaths(request, mod, true);
175         };
176       }
177
178       require.main = process.mainModule;
179
180       // Enable support to add extra extension types
181       require.extensions = Module._extensions;
182       require.cache = Module._cache;
183
184       const dirname = path.dirname(filename);
185
186       const compiledWrapper = self._moduleCompile(filename, content);
187
188       // We skip the debugger setup because by the time we run, node has already
189       // done that itself.
190
191       // `Buffer` is included for Electron.
192       // See https://github.com/zertosh/v8-compile-cache/pull/10#issuecomment-518042543
193       const args = [mod.exports, require, mod, filename, dirname, process, global, Buffer];
194       return compiledWrapper.apply(mod.exports, args);
195     };
196   }
197
198   uninstall() {
199     Module.prototype._compile = this._previousModuleCompile;
200   }
201
202   _moduleCompile(filename, content) {
203     // https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511
204
205     // Remove shebang
206     var contLen = content.length;
207     if (contLen >= 2) {
208       if (content.charCodeAt(0) === 35/*#*/ &&
209           content.charCodeAt(1) === 33/*!*/) {
210         if (contLen === 2) {
211           // Exact match
212           content = '';
213         } else {
214           // Find end of shebang line and slice it off
215           var i = 2;
216           for (; i < contLen; ++i) {
217             var code = content.charCodeAt(i);
218             if (code === 10/*\n*/ || code === 13/*\r*/) break;
219           }
220           if (i === contLen) {
221             content = '';
222           } else {
223             // Note that this actually includes the newline character(s) in the
224             // new output. This duplicates the behavior of the regular
225             // expression that was previously used to replace the shebang line
226             content = content.slice(i);
227           }
228         }
229       }
230     }
231
232     // create wrapper function
233     var wrapper = Module.wrap(content);
234
235     var invalidationKey = crypto
236       .createHash('sha1')
237       .update(content, 'utf8')
238       .digest('hex');
239
240     var buffer = this._cacheStore.get(filename, invalidationKey);
241
242     var script = new vm.Script(wrapper, {
243       filename: filename,
244       lineOffset: 0,
245       displayErrors: true,
246       cachedData: buffer,
247       produceCachedData: true,
248     });
249
250     if (script.cachedDataProduced) {
251       this._cacheStore.set(filename, invalidationKey, script.cachedData);
252     } else if (script.cachedDataRejected) {
253       this._cacheStore.delete(filename);
254     }
255
256     var compiledWrapper = script.runInThisContext({
257       filename: filename,
258       lineOffset: 0,
259       columnOffset: 0,
260       displayErrors: true,
261     });
262
263     return compiledWrapper;
264   }
265 }
266
267 //------------------------------------------------------------------------------
268 // utilities
269 //
270 // https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98
271 // https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js
272 //------------------------------------------------------------------------------
273
274 function mkdirpSync(p_) {
275   _mkdirpSync(path.resolve(p_), 0o777);
276 }
277
278 function _mkdirpSync(p, mode) {
279   try {
280     fs.mkdirSync(p, mode);
281   } catch (err0) {
282     if (err0.code === 'ENOENT') {
283       _mkdirpSync(path.dirname(p));
284       _mkdirpSync(p);
285     } else {
286       try {
287         const stat = fs.statSync(p);
288         if (!stat.isDirectory()) { throw err0; }
289       } catch (err1) {
290         throw err0;
291       }
292     }
293   }
294 }
295
296 function slashEscape(str) {
297   const ESCAPE_LOOKUP = {
298     '\\': 'zB',
299     ':': 'zC',
300     '/': 'zS',
301     '\x00': 'z0',
302     'z': 'zZ',
303   };
304   return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));
305 }
306
307 function supportsCachedData() {
308   const script = new vm.Script('""', {produceCachedData: true});
309   // chakracore, as of v1.7.1.0, returns `false`.
310   return script.cachedDataProduced === true;
311 }
312
313 function getCacheDir() {
314   // Avoid cache ownership issues on POSIX systems.
315   const dirname = typeof process.getuid === 'function'
316     ? 'v8-compile-cache-' + process.getuid()
317     : 'v8-compile-cache';
318   const version = typeof process.versions.v8 === 'string'
319     ? process.versions.v8
320     : typeof process.versions.chakracore === 'string'
321       ? 'chakracore-' + process.versions.chakracore
322       : 'node-' + process.version;
323   const cacheDir = path.join(os.tmpdir(), dirname, version);
324   return cacheDir;
325 }
326
327 function getParentName() {
328   // `module.parent.filename` is undefined or null when:
329   //    * node -e 'require("v8-compile-cache")'
330   //    * node -r 'v8-compile-cache'
331   //    * Or, requiring from the REPL.
332   const parentName = module.parent && typeof module.parent.filename === 'string'
333     ? module.parent.filename
334     : process.cwd();
335   return parentName;
336 }
337
338 //------------------------------------------------------------------------------
339 // main
340 //------------------------------------------------------------------------------
341
342 if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
343   const cacheDir = getCacheDir();
344   const prefix = getParentName();
345   const blobStore = new FileSystemBlobStore(cacheDir, prefix);
346
347   const nativeCompileCache = new NativeCompileCache();
348   nativeCompileCache.setCacheStore(blobStore);
349   nativeCompileCache.install();
350
351   process.once('exit', code => {
352     if (blobStore.isDirty()) {
353       blobStore.save();
354     }
355     nativeCompileCache.uninstall();
356   });
357 }
358
359 module.exports.__TEST__ = {
360   FileSystemBlobStore,
361   NativeCompileCache,
362   mkdirpSync,
363   slashEscape,
364   supportsCachedData,
365   getCacheDir,
366   getParentName,
367 };