11const Promise = require ( "nodegit-promise" ) ;
22const args = require ( "./utils/args" ) ;
3+ const cloneFunction = require ( "./utils/cloneFunction" ) ;
4+ const objectAssign = require ( "object-assign" ) ;
35
46// Unfortunately this list is not exhaustive, so if you find that a method does
57// not use a "standard"-ish name, you'll have to extend this list.
@@ -12,22 +14,26 @@ var callbacks = ["cb", "callback", "callback_", "done"];
1214 * @param {* } exports - Should be a function or an object, identity other.
1315 * @param {Function } test - Optional function to identify async methods.
1416 * @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.
1519 * @returns {* } exports - Identity.
1620 */
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 ) {
2023 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+ }
2725
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 ; } ) ;
2829 if ( cacheResult . length ) {
2930 return cacheResult [ 0 ] . wrapped ;
3031 }
32+ } else {
33+ // Return early if this object has already been processed.
34+ if ( cached . indexOf ( exports ) > - 1 ) {
35+ return exports ;
36+ }
3137 }
3238
3339 // Record this object in the cache, if it is not a function.
@@ -41,94 +47,121 @@ function processExports(exports, test, cached, parentKeyName) {
4147 }
4248
4349 var name = exports . name + "#" ;
50+ var target ;
4451
4552 // If a function, simply return it wrapped.
4653 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 ;
4973
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.
5275 cached . push ( {
5376 original : exports ,
5477 wrapped : wrapped
5578 } ) ;
5679
5780 // Find properties added to functions.
5881 for ( var keyName in exports ) {
59- exports [ keyName ] = processExports ( exports [ keyName ] , test , cached , name ) ;
82+ target [ keyName ] = processExports ( exports [ keyName ] , test , cached , name , noMutate ) ;
6083 }
6184
6285 // Find methods on the prototype, if there are any.
6386 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 ) ;
6589 }
6690
67- // Attach the augmented prototype.
68- wrapped . prototype = exports . prototype ;
69-
7091 // 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+ }
7297
7398 return wrapped ;
7499 }
75100
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 ) {
77113 // Convert to values.
78- return [ keyName , exports [ keyName ] ] ;
114+ return [ keyName , target [ keyName ] ] ;
79115 } ) . filter ( function ( keyVal ) {
80116 var keyName = keyVal [ 0 ] ;
81117 var value = keyVal [ 1 ] ;
82118
83119 // If an object is encountered, recursively traverse.
84120 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" ) {
89123 // If a filter function exists, use this to determine if the function
90124 // is asynchronous.
91125 if ( test ) {
92126 // Pass the function itself, its keyName, and the parent keyName.
93127 return test ( value , keyName , parentKeyName ) ;
94128 }
95129
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 ;
101131 }
102132 } ) . forEach ( function ( keyVal ) {
103133 var keyName = keyVal [ 0 ] ;
104134 var func = keyVal [ 1 ] ;
105135
106136 // Wrap this function and reassign.
107- exports [ keyName ] = processExports ( func , test , cached , parentKeyName ) ;
137+ target [ keyName ] = processExports ( func , test , cached , parentKeyName , noMutate ) ;
108138 } ) ;
109139
110- return exports ;
140+ return target ;
111141}
112142
113143/**
114144 * Public API for Promisify. Will resolve modules names using `require`.
115145 *
116146 * @param {* } name - Can be a module name, object, or function.
117147 * @param {Function } test - Optional function to identify async methods.
148+ * @param {Boolean } noMutate - Optional set to true to avoid mutating the target.
118149 * @returns {* } exports - The resolved value from require or passed in value.
119150 */
120- module . exports = function ( name , test ) {
151+ module . exports = function ( name , test , noMutate ) {
121152 var exports = name ;
122153
123154 // If the name argument is a String, will need to resovle using the built in
124155 // Node require function.
125156 if ( typeof name === "string" ) {
126157 exports = require ( name ) ;
158+ // Unless explicitly overridden, don't mutate when requiring modules.
159+ noMutate = ! ( noMutate === false ) ;
127160 }
128161
129162 // Iterate over all properties and find asynchronous functions to convert to
130163 // promises.
131- return processExports ( exports , test , [ ] ) ;
164+ return processExports ( exports , test , [ ] , undefined , noMutate ) ;
132165} ;
133166
134167// Export callbacks to the module.
0 commit comments