UNPKG

5.77 kBJavaScriptView Raw
1"use strict";
2
3// TODO: Use https://www.npmjs.com/package/pirates here?
4
5var Module = require("module"),
6 eslint = require("eslint");
7
8var moduleWrapper0 = Module.wrapper[0],
9 moduleWrapper1 = Module.wrapper[1],
10 originalExtensions = {},
11 linter = new eslint.Linter(),
12 eslintOptions = {
13 env: {
14 es6: true,
15 },
16 parserOptions: {
17 ecmaVersion: 6,
18 ecmaFeatures: {
19 globalReturn: true,
20 jsx: true,
21 experimentalObjectRestSpread: true
22 },
23 },
24 rules: {
25 "no-const-assign": 2
26 }
27 },
28 // The following regular expression is used to replace const declarations with let.
29 // This regex replacement is not 100% safe because transforming JavaScript requires an actual parser.
30 // However, parsing (e.g. via babel) comes with its own problems because now the parser needs to
31 // be aware of syntax extensions which might not be supported by the parser, but the underlying
32 // JavaScript engine. In fact, rewire used to have babel in place here but required an extra
33 // transform for the object spread operator (check out commit d9a81c0cdacf6995b24d205b4a2068adbd8b34ff
34 // or see https://github.com/jhnns/rewire/pull/128). It was also notable slower
35 // (see https://github.com/jhnns/rewire/issues/132).
36 // There is another issue: replacing const with let is not safe because of their different behavior.
37 // That's why we also have ESLint in place which tries to identify this error case.
38 // There is one edge case though: when a new syntax is used *and* a const re-assignment happens,
39 // rewire would compile happily in this situation but the actual code wouldn't work.
40 // However, since most projects have a seperate linting step which catches these const re-assignment
41 // errors anyway, it's probably still a reasonable trade-off.
42 // Test the regular expresssion at https://regex101.com/r/dvnZPv/2 and also check out testLib/constModule.js.
43 matchConst = /(^|\s|\}|;)const(\/\*|\s|{)/gm,
44 // Required for importing modules with shebang declarations, since NodeJS 12.16.0
45 shebang = /^#!.+/,
46 nodeRequire,
47 currentModule;
48
49function load(targetModule) {
50 nodeRequire = targetModule.require;
51 targetModule.require = requireProxy;
52 currentModule = targetModule;
53
54 registerExtensions();
55 targetModule.load(targetModule.id);
56
57 // This is only necessary if nothing has been required within the module
58 reset();
59}
60
61function reset() {
62 Module.wrapper[0] = moduleWrapper0;
63 Module.wrapper[1] = moduleWrapper1;
64}
65
66function inject(prelude, appendix) {
67 Module.wrapper[0] = moduleWrapper0 + prelude;
68 Module.wrapper[1] = appendix + moduleWrapper1;
69}
70
71/**
72 * Proxies the first require call in order to draw back all changes to the Module.wrapper.
73 * Thus our changes don't influence other modules
74 *
75 * @param {!String} path
76 */
77function requireProxy(path) {
78 reset();
79 currentModule.require = nodeRequire;
80 return nodeRequire.call(currentModule, path); // node's require only works when "this" points to the module
81}
82
83function registerExtensions() {
84 var originalJsExtension = require.extensions[".js"];
85 var originalTsExtension = require.extensions[".ts"];
86
87 if (originalJsExtension) {
88 originalExtensions.js = originalJsExtension;
89 }
90 if (originalTsExtension) {
91 originalExtensions.ts = originalTsExtension;
92 }
93 require.extensions[".js"] = jsExtension;
94 require.extensions[".ts"] = tsExtension;
95}
96
97function restoreExtensions() {
98 if ("js" in originalExtensions) {
99 require.extensions[".js"] = originalExtensions.js;
100 }
101 if ("ts" in originalExtensions) {
102 require.extensions[".ts"] = originalExtensions.ts;
103 }
104}
105
106function isNoConstAssignMessage(message) {
107 return message.ruleId === "no-const-assign";
108}
109
110function jsExtension(module, filename) {
111 var _compile = module._compile;
112
113 module._compile = function (content, filename) {
114 var noConstAssignMessage = linter.verify(content, eslintOptions).find(isNoConstAssignMessage);
115 var line;
116 var column;
117
118 if (noConstAssignMessage !== undefined) {
119 line = noConstAssignMessage.line;
120 column = noConstAssignMessage.column;
121 throw new TypeError(`Assignment to constant variable at ${ filename }:${ line }:${ column }`);
122 }
123
124 _compile.call(
125 module,
126 content
127 .replace(shebang, '') // Remove shebang declarations
128 .replace(matchConst, "$1let $2"), // replace const with let, while maintaining the column width
129 filename
130 );
131 };
132
133 restoreExtensions();
134 originalExtensions.js(module, filename);
135}
136
137function tsExtension(module, filename) {
138 var _compile = module._compile;
139
140 module._compile = function rewireCompile(content, filename) {
141 var noConstAssignMessage = linter.verify(content, eslintOptions).find(isNoConstAssignMessage);
142 var line;
143 var column;
144
145 if (noConstAssignMessage !== undefined) {
146 line = noConstAssignMessage.line;
147 column = noConstAssignMessage.column;
148 throw new TypeError(`Assignment to constant variable at ${ filename }:${ line }:${ column }`);
149 }
150 _compile.call(
151 this,
152 content
153 .replace(shebang, '') // Remove shebang declarations
154 .replace(matchConst, "$1let $2"), // replace const with let, while maintaining the column width
155 filename
156 );
157 };
158
159 restoreExtensions();
160 originalExtensions.ts(module, filename);
161}
162
163exports.load = load;
164exports.inject = inject;