Skip to content

Commit 44b4073

Browse files
committed
feat: implement minimal space option for function bodies with 40-line solution addressing complexity concerns (#195)
1 parent 246fcc8 commit 44b4073

File tree

1 file changed

+79
-247
lines changed

1 file changed

+79
-247
lines changed

index.js

Lines changed: 79 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -139,276 +139,108 @@ module.exports = function serialize(obj, options) {
139139
}
140140

141141
function serializeFunc(fn, options) {
142-
var serializedFn = fn.toString();
143-
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
144-
throw new TypeError('Serializing native function: ' + fn.name);
145-
}
146-
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;
142+
var serializedFn = fn.toString();
143+
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
144+
throw new TypeError('Serializing native function: ' + fn.name);
152145
}
153146

154-
// arrow functions, example: arg1 => arg1+5
155-
if(IS_ARROW_FUNCTION.test(serializedFn)) {
156-
return serializedFn;
157-
}
147+
// If no space option, use original behavior
148+
if (!options || !options.space) {
149+
// pure functions, example: {key: function() {}}
150+
if(IS_PURE_FUNCTION.test(serializedFn)) {
151+
return serializedFn;
152+
}
158153

159-
var argsStartsAt = serializedFn.indexOf('(');
160-
var def = serializedFn.substr(0, argsStartsAt)
161-
.trim()
162-
.split(' ')
163-
.filter(function(val) { return val.length > 0 });
154+
// arrow functions, example: arg1 => arg1+5
155+
if(IS_ARROW_FUNCTION.test(serializedFn)) {
156+
return serializedFn;
157+
}
164158

165-
var nonReservedSymbols = def.filter(function(val) {
166-
return RESERVED_SYMBOLS.indexOf(val) === -1
167-
});
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+
}
168175

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);
176+
// arrow functions
177+
return serializedFn;
174178
}
175179

176-
// arrow functions
177-
return serializedFn;
178-
}
179-
180-
// Format function body with space option
181-
return formatFunctionWithSpace(serializedFn, options.space);
180+
// Format function with space option - much simpler approach
181+
return formatFunctionWithSpace(serializedFn, options.space);
182182
}
183183

184184
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
193-
}
194-
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
226-
}
227-
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) + '}';
245-
}
246-
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(if|for|while)\s*\(/g, '$1 (')
296-
.replace(/\)\s*\{/g, ') {')
297-
.replace(/\belse\s*\{/g, 'else {')
298-
.replace(/\breturn\s+([^\s])/g, 'return $1')
299-
// Restore arrow functions
300-
.replace(/___ARROW___/g, ' => ');
301-
302-
// Parse and format the statements with proper line breaks and nesting
303-
return formatCodeWithNesting(formatted, baseIndent, indentUnit);
304-
}
305-
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];
185+
// Determine indent string
186+
var indent = typeof space === 'number' ? ' '.repeat(space) : (space || ' ');
187+
var functionIndent = indent.repeat(2); // Functions are at depth 2 (inside object)
317188

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-
}
189+
// Find function body bounds - need to find the { that's after the parameter list
190+
var parenDepth = 0;
191+
var bodyStart = -1;
326192

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-
}
193+
for (var i = 0; i < serializedFn.length; i++) {
194+
var char = serializedFn[i];
195+
if (char === '(') {
196+
parenDepth++;
197+
} else if (char === ')') {
198+
parenDepth--;
199+
} else if (char === '{' && parenDepth === 0) {
200+
// This is a brace outside of parentheses, likely the function body
201+
bodyStart = i;
202+
break;
360203
}
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-
}
371204
}
372205

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;
206+
var bodyEnd = serializedFn.lastIndexOf('}');
386207

387-
// Adjust depth for closing braces
388-
if (line === '}' || line.startsWith('}')) {
389-
currentDepth--;
208+
if (bodyStart === -1 || bodyEnd === -1 || bodyStart >= bodyEnd) {
209+
return serializedFn; // No function body found
390210
}
391211

392-
// Apply indentation
393-
result += indentUnit.repeat(currentDepth) + line;
212+
var signature = serializedFn.substring(0, bodyStart).trim();
213+
var body = serializedFn.substring(bodyStart + 1, bodyEnd).trim();
394214

395-
// Add newline except for last line
396-
if (k < lines.length - 1) {
397-
result += '\n';
215+
// Clean up signature spacing for arrow functions
216+
if (signature.includes('=>')) {
217+
signature = signature.replace(/\s*=>\s*/, ' => ');
398218
}
399219

400-
// Adjust depth for opening braces
401-
if (line.endsWith('{')) {
402-
currentDepth++;
220+
// Handle empty body
221+
if (!body) {
222+
return signature + ' {\n' + functionIndent + '\n' + indent + '}';
403223
}
404224

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;
225+
// Minimal formatting: split by semicolons and add basic spacing
226+
var statements = body.split(';').filter(function(s) { return s.trim(); });
227+
var formattedStatements = statements.map(function(stmt) {
228+
var trimmed = stmt.trim();
229+
230+
// Basic operator spacing (minimal set to avoid complexity)
231+
trimmed = trimmed
232+
.replace(/===(?!=)/g, ' === ')
233+
.replace(/!==(?!=)/g, ' !== ')
234+
.replace(/([^=])=([^=])/g, '$1 = $2')
235+
.replace(/\|\|/g, ' || ')
236+
.replace(/&&/g, ' && ')
237+
.replace(/,(?!\s)/g, ', ')
238+
.replace(/\s+/g, ' ');
239+
240+
return functionIndent + trimmed + (trimmed ? ';' : '');
241+
});
242+
243+
return signature + ' {\n' + formattedStatements.join('\n') + '\n' + indent + '}';
412244
} // Check if the parameter is function
413245
if (options.ignoreFunction && typeof obj === "function") {
414246
obj = undefined;

0 commit comments

Comments
 (0)