UNPKG

15 kBJavaScriptView Raw
1/*Copyright (c) 2015, Robert Binna <r.binna@synedra.com>
2
3 Permission to use, copy, modify, and/or distribute this software for any
4 purpose with or without fee is hereby granted, provided that the above
5 copyright notice and this permission notice appear in all copies.
6
7 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.*/
14
15var Transformer = require("babel-core").Transformer;
16var t = require("babel-core").types;
17
18var isES6Module;
19var hasES6Export;
20var hasES6DefaultExport;
21var hasCommonJSExport;
22
23module.exports = function(pluginArguments) {
24 var Plugin = pluginArguments.Plugin;
25 var t = pluginArguments.types;
26 return new Plugin("rewire", {
27 visitor: {
28 Program: (function () {
29 function getUniversalGetterID() {
30 return t.identifier('__GetDependency__');
31 }
32
33 function getUniversalSetterID() {
34 return t.identifier('__Rewire__');
35 }
36
37 function getUniversalResetterID() {
38 return t.identifier('__ResetDependency__');
39 }
40
41 function getAPIObjectID() {
42 return t.identifier('__RewireAPI__');
43 }
44
45 return {
46 enter: function (node) {
47 var universalGetter = t.functionDeclaration(
48 noRewire(getUniversalGetterID()),
49 [t.identifier("name")],
50 t.blockStatement([
51 t.returnStatement(t.callExpression(t.memberExpression(t.identifier("__$Getters__"), t.identifier("name"), true), []))
52 ])
53 );
54
55 var universalSetter = t.functionDeclaration(
56 noRewire(getUniversalSetterID()),
57 [t.identifier("name"), t.identifier("value")],
58 t.blockStatement([
59 t.expressionStatement(t.callExpression(t.memberExpression(t.identifier("__$Setters__"), t.identifier("name"), true), [t.identifier("value")]))
60 ])
61 );
62
63 var universalResetter = t.functionDeclaration(
64 noRewire( getUniversalResetterID()),
65 [t.identifier("name")],
66 t.blockStatement([
67 t.expressionStatement(t.callExpression(t.memberExpression(t.identifier("__$Resetters__"), t.identifier("name"), true), []))
68 ])
69 );
70
71 var rewireAPIObject = t.variableDeclaration('let', [
72 t.variableDeclarator(noRewire(getAPIObjectID()), t.objectExpression([
73 t.property('init', t.literal(universalGetter.id.name), universalGetter.id),
74 t.property('init', t.literal('__get__'), universalGetter.id),
75 t.property('init', t.literal(universalSetter.id.name), universalSetter.id),
76 t.property('init', t.literal('__set__'), universalSetter.id),
77 t.property('init', t.literal(universalResetter.id.name), universalResetter.id)
78 ]))
79 ]);
80
81 isES6Module = false;
82 hasES6DefaultExport = false;
83 hasES6Export = false;
84 hasCommonJSExport = false;
85 var gettersArrayDeclaration = t.variableDeclaration('let', [t.variableDeclarator(noRewire(t.identifier("__$Getters__")), t.arrayExpression([]))]);
86 var settersArrayDeclaration = t.variableDeclaration('let', [t.variableDeclarator(noRewire(t.identifier("__$Setters__")), t.arrayExpression([]))]);
87 var resettersArrayDeclaration = t.variableDeclaration('let', [t.variableDeclarator(noRewire(t.identifier("__$Resetters__")), t.arrayExpression([]))]);
88
89 node.body.unshift(gettersArrayDeclaration, settersArrayDeclaration, resettersArrayDeclaration, universalGetter,
90 universalSetter, universalResetter, rewireAPIObject);
91
92 return node;
93 },
94 exit: function (node, parent, scope, file) {
95
96 var exports;
97
98 var functionReplacementVariables = [];
99 var remainingBodyElements = [];
100
101 node.body.forEach(function(bodyElement) {
102 if(bodyElement.type == 'VariableDeclaration' && bodyElement.declarations.length === 1 &&
103 !!bodyElement.declarations[0].id && bodyElement.declarations[0].id.functionIdentifier === true) {
104 functionReplacementVariables.push(bodyElement);
105 } else {
106 remainingBodyElements.push(bodyElement);
107 }
108 });
109
110 if (isES6Module && (!hasCommonJSExport || hasES6Export)) {
111 exports = [
112 t.exportNamedDeclaration(null, [
113 t.exportSpecifier(getUniversalGetterID(), getUniversalGetterID()),
114 t.exportSpecifier(getUniversalGetterID(), t.identifier('__get__')),
115 t.exportSpecifier(getUniversalSetterID(), getUniversalSetterID()),
116 t.exportSpecifier(getUniversalSetterID(), t.identifier('__set__')),
117 t.exportSpecifier(getUniversalResetterID(), getUniversalResetterID()),
118 t.exportSpecifier(getAPIObjectID(), getAPIObjectID())
119 ])
120 ];
121
122 if(!hasES6DefaultExport) {
123 exports.push(t.exportDefaultDeclaration(getAPIObjectID()));
124 }
125 }
126 else if(!isES6Module || (!hasES6Export && hasCommonJSExport)) {
127 var moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'), false);
128
129 nonEnumerableExports = [
130 addNonEnumerableProperty(t, moduleExports, '__Rewire__', getUniversalSetterID()),
131 addNonEnumerableProperty(t, moduleExports, '__set__', getUniversalSetterID()),
132 addNonEnumerableProperty(t, moduleExports, '__ResetDependency__', getUniversalResetterID()),
133 addNonEnumerableProperty(t, moduleExports, '__GetDependency__', getUniversalGetterID()),
134 addNonEnumerableProperty(t, moduleExports, '__get__', getUniversalGetterID()),
135 addNonEnumerableProperty(t, moduleExports, '__RewireAPI__', getAPIObjectID())
136 ];
137
138 exports = [ t.ifStatement(
139 t.logicalExpression('||',
140 t.binaryExpression('===', t.unaryExpression('typeof', moduleExports, true), t.literal('object')),
141 t.binaryExpression('===', t.unaryExpression('typeof', moduleExports, true), t.literal('function'))
142 ),
143 t.blockStatement(nonEnumerableExports)
144 )];
145 }
146 node.body = functionReplacementVariables.concat(remainingBodyElements).concat(exports);
147 return node;
148 }
149 }
150 })(),
151
152 ExpressionStatement: function(node, parent, scope) {
153 if(parent.sourceType === 'module' && !!node.expression && node.expression.type === 'AssignmentExpression') {
154 var assignmentExpression = node.expression;
155
156 if(!!assignmentExpression.left.object && assignmentExpression.left.object.name === 'module' && !!assignmentExpression.left.property && assignmentExpression.left.property.name === 'exports') {
157 hasCommonJSExport = true;
158 }
159 }
160 return node;
161
162 },
163
164 VariableDeclaration: function (node, parent, scope) {
165 var variableDeclarations = [];
166 var accessors = [];
167
168 if(parent.type === 'ExportNamedDeclaration' || parent.sourceType === 'module') {
169 node.declarations.forEach(function (declaration) {
170 if (!declaration.id.__noRewire && declaration.init && !!declaration.id.name) {
171 var variableName = noRewire(declaration.id).name;
172 var existingBinding = scope.bindings[variableName];
173 var bindingType = node.kind === 'var' ? 'var' : 'let';
174
175 if (!!existingBinding) {
176 if (existingBinding.kind === 'var') {
177 bindingType = 'var';
178 } else {
179 existingBinding.kind = 'let';
180 }
181 }
182 node.kind = bindingType;
183
184 var originalVar = noRewire(scope.generateUidIdentifier(variableName));
185
186 variableDeclarations.push(t.variableDeclaration(bindingType, [t.variableDeclarator(originalVar, t.identifier(variableName))]));
187
188 accessors.push.apply(accessors, accessorsFor(variableName, originalVar));
189 }
190 });
191 }
192
193 if(parent.type === 'ExportNamedDeclaration') {
194 var exportDeclarations = node.declarations.map(function(variableDeclaration, index) {
195 var exportId = variableDeclarations[index].declarations[0].id;
196 return t.exportNamedDeclaration(null, [t.exportSpecifier(exportId, variableDeclaration.id)]);
197 });
198 this.parentPath.replaceWithMultiple([ node].concat(variableDeclarations).concat(accessors).concat(exportDeclarations));
199 } else {
200 return variableDeclarations.length == 0 ? node : [node].concat(variableDeclarations).concat(accessors);
201 }
202 },
203
204 /**
205 * Functions are replaced by a temporary function declaration and an assignment to a variable with the same name as the original function
206 * The actual rewireing functionality is added by The Handler for VariableDeclarations which creates a temporary variable and accessors for setting resetting the function.
207 */
208 FunctionDeclaration: function(declaration, parent, scope) {
209 if((parent.type !== 'ExportNamedDeclaration' && parent.sourceType !== 'module') || declaration.id.__noRewire || !declaration.id.name || declaration.id.name.length == 0) {
210 return declaration;
211 }
212
213 var replacedFunctionDeclarationIdentifier = noRewire(scope.generateUidIdentifier(declaration.id.name + 'Orig'));
214 var originalFunctionIdentifier = declaration.id;
215 originalFunctionIdentifier.functionIdentifier = true;
216 var replacedFunctionDeclaration = t.functionDeclaration(replacedFunctionDeclarationIdentifier, declaration.params, declaration.body, declaration.generator, declaration.expression);
217
218 var newFuntionDeclaration = [replacedFunctionDeclaration, t.variableDeclaration('var', [t.variableDeclarator(originalFunctionIdentifier, replacedFunctionDeclarationIdentifier)])];
219
220 if(parent.type === 'ExportNamedDeclaration') {
221 var exportSpecifier = [t.exportNamedDeclaration(null, [ t.exportSpecifier(replacedFunctionDeclarationIdentifier, originalFunctionIdentifier) ] )];
222 this.parentPath.replaceWithMultiple(newFuntionDeclaration.concat(exportSpecifier));
223 } else {
224 return newFuntionDeclaration;
225 }
226 },
227
228 ImportDeclaration: function (node, parent, scope, file) {
229 isES6Module = true;
230 var variableDeclarations = [];
231 var accessors = [];
232
233 node.specifiers.forEach(function (specifier) {
234 var importedSpecifierName = (specifier.imported && specifier.imported.name) || null;
235 var localVariable = specifier.local;
236 var localVariableName = localVariable.name;
237
238 var actualImport = scope.generateUidIdentifier(localVariableName + "Temp");
239 scope.bindings[localVariableName].constant = false;
240 scope.bindings[localVariableName].kind = 'let';
241
242 //scope.rename(localVariableName, actualImport.name);
243
244 if (importedSpecifierName === localVariableName) {
245 specifier.imported = t.identifier(importedSpecifierName);
246 }
247 specifier.local = actualImport;
248
249 variableDeclarations.push(t.variableDeclaration('let', [t.variableDeclarator(noRewire(t.identifier(localVariableName)), actualImport)]));
250
251 accessors.push.apply(accessors, accessorsFor(localVariableName, actualImport));
252 });
253
254 return [node].concat(variableDeclarations).concat(accessors);
255 },
256
257 'ExportNamedDeclaration|ExportAllDeclaration': {
258 enter: function (node) {
259 isES6Module = true;
260 return node;
261 },
262 exit: function(node) {
263 return node;
264 }
265 },
266
267 ExportDefaultDeclaration: function (node, parent, scope) {
268 if(!!node.rewired) {
269 return node;
270 }
271 hasES6DefaultExport = true;
272 hasES6Export = true;
273 isES6Module = true;
274 var originalExport = node.declaration;
275 if (!!node.declaration.id) {
276 this.insertBefore(node.declaration);
277 originalExport = node.declaration.id;
278 }
279
280 var bindingType = node.declaration.type === 'FunctionDeclaration' || node.declaration.kind === 'var' ? 'var' : 'let';
281
282 var defaultExportVariableId = scope.generateUidIdentifier("defaultExport");
283
284 var defaultExportVariableDeclaration = t.variableDeclaration(bindingType, [t.variableDeclarator(noRewire(defaultExportVariableId), originalExport)]);
285
286 t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty'), [ defaultExportVariableId, '__Rewire__', t.objectExpression([
287 t.property('init', t.literal('value'), t.identifier('__Rewire__'))
288 ])]));
289
290 var addAdditionalProperties = t.ifStatement(
291 t.logicalExpression('||',
292 t.binaryExpression('===', t.unaryExpression('typeof', defaultExportVariableId, true), t.literal('object')),
293 t.binaryExpression('===', t.unaryExpression('typeof', defaultExportVariableId, true), t.literal('function'))
294 ),
295 t.blockStatement([
296 addNonEnumerableProperty(t, defaultExportVariableId, '__Rewire__', t.identifier('__Rewire__')),
297 addNonEnumerableProperty(t, defaultExportVariableId, '__set__', t.identifier('__Rewire__')),
298 addNonEnumerableProperty(t, defaultExportVariableId, '__ResetDependency__', t.identifier('__ResetDependency__')),
299 addNonEnumerableProperty(t, defaultExportVariableId, '__GetDependency__', t.identifier('__GetDependency__')),
300 addNonEnumerableProperty(t, defaultExportVariableId, '__get__', t.identifier('__GetDependency__')),
301 addNonEnumerableProperty(t, defaultExportVariableId, '__RewireAPI__', t.identifier('__RewireAPI__'))
302 ])
303 );
304
305 var defaultExport = t.exportDefaultDeclaration(defaultExportVariableId);
306
307 defaultExport.rewired = true;
308
309 return [defaultExportVariableDeclaration, addAdditionalProperties, defaultExport];
310 }
311 }
312 });
313}
314
315function addNonEnumerableProperty(t, objectIdentifier, propertyName, valueIdentifier) {
316 return t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty')), [ objectIdentifier, t.literal(propertyName), t.objectExpression([
317 t.property('init', t.literal('value'), valueIdentifier),
318 t.property('init', t.literal('enumberable'), t.literal(false))
319 ])]));
320}
321
322function accessorsFor(variableName, originalVar) {
323 var accessor = function(array, variableName, operation) {
324 return t.expressionStatement(t.assignmentExpression("=", t.memberExpression(array, t.literal(variableName), true), operation));
325 };
326
327 var getter = noRewire(t.functionDeclaration(
328 getter,
329 [],
330 t.blockStatement([
331 t.returnStatement(t.identifier(variableName))
332 ])
333 ));
334
335 var setter = t.functionDeclaration(
336 null,
337 [t.identifier("value")],
338 t.blockStatement([
339 t.expressionStatement(t.assignmentExpression("=", t.identifier(variableName), t.identifier("value")))
340 ])
341 );
342
343 var resetter = t.functionDeclaration(
344 null,
345 [],
346 t.blockStatement([
347 t.expressionStatement(t.assignmentExpression("=", t.identifier(variableName), originalVar))
348 ])
349 );
350
351 return [
352 accessor(t.identifier("__$Getters__"), variableName, getter),
353 accessor(t.identifier("__$Setters__"), variableName, setter),
354 accessor(t.identifier("__$Resetters__"), variableName, resetter)
355 ];
356}
357
358function noRewire(identifier) {
359 identifier.__noRewire = true;
360 return identifier;
361}