apisurf
Version:
Analyze API surface changes between npm package versions to catch breaking changes
146 lines (145 loc) • 4.62 kB
JavaScript
/**
* Parse function parameters from a parameter string, handling nested types correctly
*/
export function parseParameters(paramString) {
if (!paramString || paramString.trim() === '') {
return [];
}
const parameters = [];
let current = '';
let parenDepth = 0;
let bracketDepth = 0;
let braceDepth = 0;
let angleDepth = 0;
let inString = false;
let stringChar = '';
for (let i = 0; i < paramString.length; i++) {
const char = paramString[i];
const prevChar = i > 0 ? paramString[i - 1] : '';
// Handle string literals
if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
if (!inString) {
inString = true;
stringChar = char;
}
else if (char === stringChar) {
inString = false;
}
}
// Skip string content for bracket counting
if (!inString) {
// Track depth for different bracket types separately
switch (char) {
case '(':
parenDepth++;
break;
case ')':
parenDepth--;
break;
case '[':
bracketDepth++;
break;
case ']':
bracketDepth--;
break;
case '{':
braceDepth++;
break;
case '}':
braceDepth--;
break;
case '<':
angleDepth++;
break;
case '>':
// Only decrease if not part of => and angle depth is positive
if (angleDepth > 0 && (i === paramString.length - 1 || paramString[i - 1] !== '=')) {
angleDepth--;
}
break;
}
// Split on comma only when all depths are 0
if (char === ',' && parenDepth === 0 && bracketDepth === 0 && braceDepth === 0 && angleDepth === 0) {
parameters.push(current.trim());
current = '';
continue;
}
}
current += char;
}
// Don't forget the last parameter
if (current.trim()) {
parameters.push(current.trim());
}
return parameters;
}
/**
* Check if a parameter is optional (has ? or default value)
*/
export function isOptionalParameter(param) {
// Check for optional marker (?)
if (param.includes('?:'))
return true;
// Check for default value (=)
// But make sure it's not part of an arrow function (=>)
const equalIndex = param.indexOf('=');
if (equalIndex > 0) {
const afterEqual = param.substring(equalIndex + 1);
// If the next character is >, it's an arrow function, not a default value
if (!afterEqual.trimStart().startsWith('>')) {
return true;
}
}
return false;
}
/**
* Extract parameter name from a parameter string
*/
export function getParameterName(param) {
// Handle destructured parameters
if (param.startsWith('{') || param.startsWith('[')) {
return param; // Return the whole destructured pattern
}
// Regular parameters: name: type
const colonIndex = param.indexOf(':');
if (colonIndex > 0) {
return param.substring(0, colonIndex).replace('?', '').trim();
}
// Parameters without type annotation
return param.trim();
}
/**
* Extract parameter type from a parameter string
*/
export function getParameterType(param) {
const colonIndex = param.indexOf(':');
if (colonIndex === -1)
return 'any';
let type = param.substring(colonIndex + 1).trim();
// Remove default value if present, but not arrow functions
let i = 0;
let parenDepth = 0;
let defaultStart = -1;
while (i < type.length) {
if (type[i] === '(')
parenDepth++;
else if (type[i] === ')')
parenDepth--;
else if (type[i] === '=' && parenDepth === 0) {
// Check if it's an arrow function
if (i + 1 < type.length && type[i + 1] === '>') {
i++; // Skip the arrow
}
else {
// It's a default value
defaultStart = i;
break;
}
}
i++;
}
if (defaultStart > 0) {
type = type.substring(0, defaultStart).trim();
}
return type || 'any';
}