@@ -138,44 +138,278 @@ module.exports = function serialize(obj, options) {
138138 return value ;
139139 }
140140
141- function serializeFunc ( fn ) {
141+ function serializeFunc ( fn , options ) {
142142 var serializedFn = fn . toString ( ) ;
143143 if ( IS_NATIVE_CODE_REGEXP . test ( serializedFn ) ) {
144144 throw new TypeError ( 'Serializing native function: ' + fn . name ) ;
145145 }
146146
147- // pure functions, example: {key: function() {}}
148- if ( IS_PURE_FUNCTION . test ( serializedFn ) ) {
149- return serializedFn ;
147+ // If no space option, return original behavior
148+ if ( ! options || ! options . space ) {
149+ // pure functions, example: {key: function() {}}
150+ if ( IS_PURE_FUNCTION . test ( serializedFn ) ) {
151+ return serializedFn ;
152+ }
153+
154+ // arrow functions, example: arg1 => arg1+5
155+ if ( IS_ARROW_FUNCTION . test ( serializedFn ) ) {
156+ return serializedFn ;
157+ }
158+
159+ var argsStartsAt = serializedFn . indexOf ( '(' ) ;
160+ var def = serializedFn . substr ( 0 , argsStartsAt )
161+ . trim ( )
162+ . split ( ' ' )
163+ . filter ( function ( val ) { return val . length > 0 } ) ;
164+
165+ var nonReservedSymbols = def . filter ( function ( val ) {
166+ return RESERVED_SYMBOLS . indexOf ( val ) === - 1
167+ } ) ;
168+
169+ // enhanced literal objects, example: {key() {}}
170+ if ( nonReservedSymbols . length > 0 ) {
171+ return ( def . indexOf ( 'async' ) > - 1 ? 'async ' : '' ) + 'function'
172+ + ( def . join ( '' ) . indexOf ( '*' ) > - 1 ? '*' : '' )
173+ + serializedFn . substr ( argsStartsAt ) ;
174+ }
175+
176+ // arrow functions
177+ return serializedFn ;
178+ }
179+
180+ // Format function body with space option
181+ return formatFunctionWithSpace ( serializedFn , options . space ) ;
182+ }
183+
184+ function formatFunctionWithSpace ( serializedFn , space ) {
185+ // Determine indentation unit
186+ var indentUnit ;
187+ if ( typeof space === 'number' ) {
188+ indentUnit = ' ' . repeat ( space ) ;
189+ } else if ( typeof space === 'string' ) {
190+ indentUnit = space ;
191+ } else {
192+ return serializedFn ; // fallback to original
150193 }
151194
152- // arrow functions, example: arg1 => arg1+5
153- if ( IS_ARROW_FUNCTION . test ( serializedFn ) ) {
154- return serializedFn ;
195+ // Find the function body opening brace (not parameter destructuring braces)
196+ var bodyStartBraceIndex = - 1 ;
197+ var parenDepth = 0 ;
198+ var braceDepth = 0 ;
199+
200+ for ( var i = 0 ; i < serializedFn . length ; i ++ ) {
201+ var char = serializedFn [ i ] ;
202+ if ( char === '(' ) {
203+ parenDepth ++ ;
204+ } else if ( char === ')' ) {
205+ parenDepth -- ;
206+ // After closing the parameter list, the next { is the function body
207+ if ( parenDepth === 0 ) {
208+ for ( var j = i + 1 ; j < serializedFn . length ; j ++ ) {
209+ if ( serializedFn [ j ] === '{' ) {
210+ bodyStartBraceIndex = j ;
211+ break ;
212+ } else if ( serializedFn [ j ] !== ' ' && serializedFn [ j ] !== '=' && serializedFn [ j ] !== '>' ) {
213+ // Non-space/arrow character before brace, not a function body brace
214+ break ;
215+ }
216+ }
217+ break ;
218+ }
219+ }
220+ }
221+
222+ var closeBraceIndex = serializedFn . lastIndexOf ( '}' ) ;
223+
224+ if ( bodyStartBraceIndex === - 1 || closeBraceIndex === - 1 || bodyStartBraceIndex >= closeBraceIndex ) {
225+ return serializedFn ; // No function body braces found, return original
155226 }
156227
157- var argsStartsAt = serializedFn . indexOf ( '(' ) ;
158- var def = serializedFn . substr ( 0 , argsStartsAt )
159- . trim ( )
160- . split ( ' ' )
161- . filter ( function ( val ) { return val . length > 0 } ) ;
162-
163- var nonReservedSymbols = def . filter ( function ( val ) {
164- return RESERVED_SYMBOLS . indexOf ( val ) === - 1
165- } ) ;
166-
167- // enhanced literal objects, example: {key() {}}
168- if ( nonReservedSymbols . length > 0 ) {
169- return ( def . indexOf ( 'async' ) > - 1 ? 'async ' : '' ) + 'function'
170- + ( def . join ( '' ) . indexOf ( '*' ) > - 1 ? '*' : '' )
171- + serializedFn . substr ( argsStartsAt ) ;
228+ var signature = serializedFn . substring ( 0 , bodyStartBraceIndex ) . trim ( ) ;
229+ var body = serializedFn . substring ( bodyStartBraceIndex + 1 , closeBraceIndex ) . trim ( ) ;
230+
231+ // Clean up signature: ensure proper spacing
232+ // For arrow functions, add space around =>
233+ if ( signature . includes ( '=>' ) ) {
234+ signature = signature . replace ( / \s * = > \s * / , ' => ' ) ;
235+ }
236+
237+ // Ensure space before opening brace
238+ if ( ! signature . endsWith ( ' ' ) ) {
239+ signature += ' ' ;
240+ }
241+
242+ // If body is empty, format minimally
243+ if ( ! body ) {
244+ return signature + '{\n' + indentUnit . repeat ( 2 ) + '}' ;
172245 }
173246
174- // arrow functions
175- return serializedFn ;
247+ // Format the function body with proper indentation and spacing
248+ var formattedBody = formatSimpleFunctionBody ( body , indentUnit ) ;
249+
250+ // Ensure we don't double-add closing braces
251+ var lines = formattedBody . split ( '\n' ) ;
252+ var lastNonEmptyIndex = lines . length - 1 ;
253+ while ( lastNonEmptyIndex >= 0 && ! lines [ lastNonEmptyIndex ] . trim ( ) ) {
254+ lastNonEmptyIndex -- ;
255+ }
256+
257+ if ( lastNonEmptyIndex >= 0 && lines [ lastNonEmptyIndex ] . trim ( ) === '}' ) {
258+ // Remove the last closing brace line
259+ lines . splice ( lastNonEmptyIndex , 1 ) ;
260+ formattedBody = lines . join ( '\n' ) ;
261+ }
262+
263+ return signature + '{\n' + formattedBody + '\n' + indentUnit + '}' ;
264+ }
265+
266+ function formatSimpleFunctionBody ( body , indentUnit ) {
267+ // Enhanced function body formatter that handles nested structures
268+ var baseIndent = indentUnit . repeat ( 2 ) ; // Functions are already inside objects, so depth 2
269+
270+ // First, add spaces around operators and keywords, being careful about arrow functions
271+ var formatted = body
272+ // Protect arrow functions from being split
273+ . replace ( / = > / g, '___ARROW___' )
274+ // Clean up multiple spaces first
275+ . replace ( / \s + / g, ' ' )
276+ // Add spaces around operators (but not === or !==)
277+ . replace ( / ( [ ^ = ! < > ] ) \s * = \s * ( [ ^ = ] ) / g, '$1 = $2' )
278+ . replace ( / ( [ ^ = ] ) \s * = = = \s * ( [ ^ = ] ) / g, '$1 === $2' )
279+ . replace ( / ( [ ^ ! ] ) \s * ! = = \s * ( [ ^ = ] ) / g, '$1 !== $2' )
280+ . replace ( / ( [ ^ | ] ) \s * \| \| \s * ( [ ^ | ] ) / g, '$1 || $2' )
281+ . replace ( / ( [ ^ & ] ) \s * & & \s * ( [ ^ & ] ) / g, '$1 && $2' )
282+ // Add spaces around arithmetic operators
283+ . replace ( / ( [ ^ \s * ] ) \s * \* \s * ( [ ^ \s * ] ) / g, '$1 * $2' )
284+ . replace ( / ( [ ^ \s + ] ) \s * \+ \s * ( [ ^ \s + ] ) / g, '$1 + $2' )
285+ . replace ( / ( [ ^ \s - ] ) \s * - \s * ( [ ^ \s - ] ) / g, '$1 - $2' )
286+ . replace ( / ( [ ^ \s / ] ) \s * \/ \s * ( [ ^ \s / ] ) / g, '$1 / $2' )
287+ // Add spaces around comparison operators
288+ . replace ( / ( [ ^ \s > ] ) \s * > \s * ( [ ^ \s > = ] ) / g, '$1 > $2' )
289+ . replace ( / ( [ ^ \s < ] ) \s * < \s * ( [ ^ \s < = ] ) / g, '$1 < $2' )
290+ . replace ( / \s * > = \s * (? ! [ > ] ) / g, ' >= ' )
291+ . replace ( / \s * < = \s * (? ! [ < ] ) / g, ' <= ' )
292+ // Add spaces after commas
293+ . replace ( / , (? ! \s ) / g, ', ' )
294+ // Add space after control keywords and before braces
295+ . replace ( / \b ( i f | f o r | w h i l e ) \s * \( / g, '$1 (' )
296+ . replace ( / \) \s * \{ / g, ') {' )
297+ . replace ( / \b e l s e \s * \{ / g, 'else {' )
298+ . replace ( / \b r e t u r n \s + ( [ ^ \s ] ) / g, 'return $1' )
299+ // Restore arrow functions
300+ . replace ( / _ _ _ A R R O W _ _ _ / g, ' => ' ) ;
301+
302+ // Parse and format the statements with proper line breaks and nesting
303+ return formatCodeWithNesting ( formatted , baseIndent , indentUnit ) ;
176304 }
177305
178- // Check if the parameter is function
306+ function formatCodeWithNesting ( code , baseIndent , indentUnit ) {
307+ var result = '' ;
308+ var lines = [ ] ;
309+ var current = '' ;
310+ var braceDepth = 0 ;
311+ var inString = false ;
312+ var stringChar = '' ;
313+
314+ // First pass: break into logical lines, handling } else { pattern
315+ for ( var i = 0 ; i < code . length ; i ++ ) {
316+ var char = code [ i ] ;
317+
318+ // Handle strings
319+ if ( ! inString && ( char === '"' || char === "'" || char === '`' ) ) {
320+ inString = true ;
321+ stringChar = char ;
322+ } else if ( inString && char === stringChar && code [ i - 1 ] !== '\\' ) {
323+ inString = false ;
324+ stringChar = '' ;
325+ }
326+
327+ if ( ! inString ) {
328+ if ( char === '{' ) {
329+ current += char ;
330+ lines . push ( current . trim ( ) ) ;
331+ current = '' ;
332+ braceDepth ++ ;
333+ continue ;
334+ } else if ( char === '}' ) {
335+ if ( current . trim ( ) ) {
336+ lines . push ( current . trim ( ) ) ;
337+ }
338+ braceDepth -- ;
339+
340+ // Check for } else { pattern
341+ var nextNonWhitespace = '' ;
342+ var j = i + 1 ;
343+ while ( j < code . length && / \s / . test ( code [ j ] ) ) {
344+ j ++ ;
345+ }
346+ if ( j < code . length - 4 && code . substring ( j , j + 4 ) === 'else' ) {
347+ // Skip to after 'else'
348+ j += 4 ;
349+ while ( j < code . length && / \s / . test ( code [ j ] ) ) {
350+ j ++ ;
351+ }
352+ if ( j < code . length && code [ j ] === '{' ) {
353+ // This is } else {
354+ lines . push ( '} else {' ) ;
355+ i = j ; // Skip to the {
356+ braceDepth ++ ;
357+ current = '' ;
358+ continue ;
359+ }
360+ }
361+
362+ lines . push ( '}' ) ;
363+ current = '' ;
364+ continue ;
365+ } else if ( char === ';' ) {
366+ current += char ;
367+ lines . push ( current . trim ( ) ) ;
368+ current = '' ;
369+ continue ;
370+ }
371+ }
372+
373+ current += char ;
374+ }
375+
376+ // Add any remaining content
377+ if ( current . trim ( ) ) {
378+ lines . push ( current . trim ( ) ) ;
379+ }
380+
381+ // Second pass: apply proper indentation
382+ var currentDepth = 2 ; // Start at depth 2 for function bodies (object has 1, function has 2)
383+ for ( var k = 0 ; k < lines . length ; k ++ ) {
384+ var line = lines [ k ] . trim ( ) ;
385+ if ( ! line ) continue ;
386+
387+ // Adjust depth for closing braces
388+ if ( line === '}' || line . startsWith ( '}' ) ) {
389+ currentDepth -- ;
390+ }
391+
392+ // Apply indentation
393+ result += indentUnit . repeat ( currentDepth ) + line ;
394+
395+ // Add newline except for last line
396+ if ( k < lines . length - 1 ) {
397+ result += '\n' ;
398+ }
399+
400+ // Adjust depth for opening braces
401+ if ( line . endsWith ( '{' ) ) {
402+ currentDepth ++ ;
403+ }
404+
405+ // Add semicolon if missing (except for braces)
406+ if ( ! line . endsWith ( ';' ) && ! line . endsWith ( '{' ) && line !== '}' && ! line . startsWith ( '}' ) ) {
407+ result = result . replace ( / ( [ ^ ; } ] ) $ / , '$1;' ) ;
408+ }
409+ }
410+
411+ return result ;
412+ } // Check if the parameter is function
179413 if ( options . ignoreFunction && typeof obj === "function" ) {
180414 obj = undefined ;
181415 }
@@ -261,6 +495,6 @@ module.exports = function serialize(obj, options) {
261495
262496 var fn = functions [ valueIndex ] ;
263497
264- return serializeFunc ( fn ) ;
498+ return serializeFunc ( fn , options ) ;
265499 } ) ;
266500}
0 commit comments