1
1
const Promise = require ( "nodegit-promise" ) ;
2
2
const args = require ( "./utils/args" ) ;
3
+ const cloneFunction = require ( "./utils/cloneFunction" ) ;
4
+ const objectAssign = require ( "object-assign" ) ;
3
5
4
6
// Unfortunately this list is not exhaustive, so if you find that a method does
5
7
// not use a "standard"-ish name, you'll have to extend this list.
@@ -12,22 +14,26 @@ var callbacks = ["cb", "callback", "callback_", "done"];
12
14
* @param {* } exports - Should be a function or an object, identity other.
13
15
* @param {Function } test - Optional function to identify async methods.
14
16
* @param {String } parentKeyName - Tracks the keyName in a digestable format.
17
+ * @param {Boolean } noMutate - if set to true then all reference properties are
18
+ * cloned to avoid mutating the original object.
15
19
* @returns {* } exports - Identity.
16
20
*/
17
- function processExports ( exports , test , cached , parentKeyName ) {
18
- // Return early if this object has already been processed.
19
- if ( cached . indexOf ( exports ) > - 1 ) {
21
+ function processExports ( exports , test , cached , parentKeyName , noMutate ) {
22
+ if ( ! exports ) {
20
23
return exports ;
21
- } else if ( typeof exports === "function" ) {
22
- // For functions, cache the original and wrapped version, else non-wrapped
23
- // functions end up being given back when encountered multiple times.
24
- var cacheResult = cached . filter ( function ( c ) {
25
- return c . original === exports ;
26
- } ) ;
24
+ }
27
25
26
+ if ( noMutate || typeof exports === "function" ) {
27
+ // When not mutating we have to cache the original and the wrapped clone.
28
+ var cacheResult = cached . filter ( function ( c ) { return c . original === exports ; } ) ;
28
29
if ( cacheResult . length ) {
29
30
return cacheResult [ 0 ] . wrapped ;
30
31
}
32
+ } else {
33
+ // Return early if this object has already been processed.
34
+ if ( cached . indexOf ( exports ) > - 1 ) {
35
+ return exports ;
36
+ }
31
37
}
32
38
33
39
// Record this object in the cache, if it is not a function.
@@ -41,94 +47,121 @@ function processExports(exports, test, cached, parentKeyName) {
41
47
}
42
48
43
49
var name = exports . name + "#" ;
50
+ var target ;
44
51
45
52
// If a function, simply return it wrapped.
46
53
if ( typeof exports === "function" ) {
47
- // Assign the new function in place.
48
- var wrapped = Promise . denodeify ( exports ) ;
54
+ var wrapped = exports ;
55
+ var isAsyncFunction = false ;
56
+
57
+ // Check the callback either passes the test function, or accepts a callback.
58
+ if ( ( test && test ( exports , exports . name , parentKeyName ) )
59
+ // If the callback name exists as the last argument, consider it an
60
+ // asynchronous function. Brittle? Fragile? Effective.
61
+ || ( callbacks . indexOf ( args ( exports ) . slice ( - 1 ) [ 0 ] ) > - 1 ) ) {
62
+ // Assign the new function in place.
63
+ wrapped = Promise . denodeify ( exports ) ;
64
+
65
+ isAsyncFunction = true ;
66
+ } else if ( noMutate ) {
67
+ // If not mutating, then we need to clone the function, even though it isn't async.
68
+ wrapped = cloneFunction ( exports ) ;
69
+ }
70
+
71
+ // Set which object we'll mutate based upon the noMutate flag.
72
+ target = noMutate ? wrapped : exports ;
49
73
50
- // Push the wrapped function onto the cache before processing properties,
51
- // else a cyclical function property causes a stack overflow.
74
+ // Here we can push our cloned/wrapped function and original onto cache.
52
75
cached . push ( {
53
76
original : exports ,
54
77
wrapped : wrapped
55
78
} ) ;
56
79
57
80
// Find properties added to functions.
58
81
for ( var keyName in exports ) {
59
- exports [ keyName ] = processExports ( exports [ keyName ] , test , cached , name ) ;
82
+ target [ keyName ] = processExports ( exports [ keyName ] , test , cached , name , noMutate ) ;
60
83
}
61
84
62
85
// Find methods on the prototype, if there are any.
63
86
if ( Object . keys ( exports . prototype ) . length ) {
64
- processExports ( exports . prototype , test , cached , name ) ;
87
+ // Attach the augmented prototype.
88
+ wrapped . prototype = processExports ( exports . prototype , test , cached , name , noMutate ) ;
65
89
}
66
90
67
- // Attach the augmented prototype.
68
- wrapped . prototype = exports . prototype ;
69
-
70
91
// Ensure attached properties to the previous function are accessible.
71
- wrapped . __proto__ = exports ;
92
+ // Only do this if it's an async (wrapped) function, else we're setting
93
+ // __proto__ to itself, which isn't allowed.
94
+ if ( isAsyncFunction ) {
95
+ wrapped . __proto__ = exports ;
96
+ }
72
97
73
98
return wrapped ;
74
99
}
75
100
76
- Object . keys ( exports ) . map ( function ( keyName ) {
101
+ // Make a shallow clone if we're not mutating and set it as the target, else just use exports
102
+ target = noMutate ? objectAssign ( { } , exports ) : exports ;
103
+
104
+ // We have our shallow cloned object, so put it (and the original) in the cache
105
+ if ( noMutate ) {
106
+ cached . push ( {
107
+ original : exports ,
108
+ wrapped : target
109
+ } ) ;
110
+ }
111
+
112
+ Object . keys ( target ) . map ( function ( keyName ) {
77
113
// Convert to values.
78
- return [ keyName , exports [ keyName ] ] ;
114
+ return [ keyName , target [ keyName ] ] ;
79
115
} ) . filter ( function ( keyVal ) {
80
116
var keyName = keyVal [ 0 ] ;
81
117
var value = keyVal [ 1 ] ;
82
118
83
119
// If an object is encountered, recursively traverse.
84
120
if ( typeof value === "object" ) {
85
- processExports ( exports , test , cached , keyName + "." ) ;
86
- }
87
- // Filter to functions with callbacks only.
88
- else if ( typeof value === "function" ) {
121
+ processExports ( value , test , cached , keyName + "." , noMutate ) ;
122
+ } else if ( typeof value === "function" ) {
89
123
// If a filter function exists, use this to determine if the function
90
124
// is asynchronous.
91
125
if ( test ) {
92
126
// Pass the function itself, its keyName, and the parent keyName.
93
127
return test ( value , keyName , parentKeyName ) ;
94
128
}
95
129
96
- // If the callback name exists as the last argument, consider it an
97
- // asynchronous function. Brittle? Fragile? Effective.
98
- if ( callbacks . indexOf ( args ( value ) . slice ( - 1 ) [ 0 ] ) > - 1 ) {
99
- return true ;
100
- }
130
+ return true ;
101
131
}
102
132
} ) . forEach ( function ( keyVal ) {
103
133
var keyName = keyVal [ 0 ] ;
104
134
var func = keyVal [ 1 ] ;
105
135
106
136
// Wrap this function and reassign.
107
- exports [ keyName ] = processExports ( func , test , cached , parentKeyName ) ;
137
+ target [ keyName ] = processExports ( func , test , cached , parentKeyName , noMutate ) ;
108
138
} ) ;
109
139
110
- return exports ;
140
+ return target ;
111
141
}
112
142
113
143
/**
114
144
* Public API for Promisify. Will resolve modules names using `require`.
115
145
*
116
146
* @param {* } name - Can be a module name, object, or function.
117
147
* @param {Function } test - Optional function to identify async methods.
148
+ * @param {Boolean } noMutate - Optional set to true to avoid mutating the target.
118
149
* @returns {* } exports - The resolved value from require or passed in value.
119
150
*/
120
- module . exports = function ( name , test ) {
151
+ module . exports = function ( name , test , noMutate ) {
121
152
var exports = name ;
122
153
123
154
// If the name argument is a String, will need to resovle using the built in
124
155
// Node require function.
125
156
if ( typeof name === "string" ) {
126
157
exports = require ( name ) ;
158
+ // Unless explicitly overridden, don't mutate when requiring modules.
159
+ noMutate = ! ( noMutate === false ) ;
127
160
}
128
161
129
162
// Iterate over all properties and find asynchronous functions to convert to
130
163
// promises.
131
- return processExports ( exports , test , [ ] ) ;
164
+ return processExports ( exports , test , [ ] , undefined , noMutate ) ;
132
165
} ;
133
166
134
167
// Export callbacks to the module.
0 commit comments