apisurf
Version:
Analyze API surface changes between npm package versions to catch breaking changes
110 lines (109 loc) • 4.04 kB
JavaScript
/**
* Parse type alias definition from source lines
*/
export function parseTypeDefinition(lines, startIndex, name) {
const properties = [];
let braceCount = 0;
let inTypeDef = false;
let isObjectType = false;
let signature = '';
// Get the full line with the type definition
const fullLine = lines[startIndex];
// Check if this is a function type alias
const functionTypeMatch = fullLine.match(/export\s+type\s+\w+.*?=\s*(?:<[^>]+>\s*)?\(/);
if (functionTypeMatch) {
// Extract the full function signature
let fullSignature = fullLine.trim();
// Handle multi-line function types
if (!fullSignature.includes(';')) {
for (let i = startIndex + 1; i < lines.length; i++) {
const nextLine = lines[i].trim();
fullSignature += ' ' + nextLine;
if (nextLine.includes(';')) {
break;
}
}
}
// Keep the signature as-is, including semicolon
fullSignature = fullSignature.trim();
return {
name,
kind: 'type',
signature: fullSignature
};
}
// Check for mapped types or other non-object types first
if (fullLine.includes('[') && fullLine.includes(' in ')) {
// This is a mapped type
let fullSig = fullLine.trim();
if (!fullSig.includes(';')) {
for (let j = startIndex + 1; j < lines.length; j++) {
fullSig += ' ' + lines[j].trim();
if (lines[j].includes(';')) {
break;
}
}
}
return {
name,
kind: 'type',
extendedProperties: undefined,
signature: fullSig
};
}
for (let i = startIndex; i < lines.length; i++) {
const line = lines[i].trim();
// Check if this is an object type definition
if (i === startIndex && line.includes('= {') && !line.includes('[')) {
isObjectType = true;
}
// Count braces
const openBraces = (line.match(/{/g) || []).length;
const closeBraces = (line.match(/}/g) || []).length;
if (openBraces > 0 && isObjectType) {
braceCount += openBraces;
inTypeDef = true;
}
// Only parse properties at the top level (braceCount === 1)
if (inTypeDef && braceCount === 1 && !line.includes('{') && !line.includes('}')) {
// Match properties like: propertyName: type; or propertyName?: type;
// This includes arrow function properties like: status?: () => string;
const propMatch = line.match(/^\s*(\w+)(\?)?:\s*/);
if (propMatch && !line.includes('[')) {
properties.push({
name: propMatch[1],
required: !propMatch[2]
});
}
// Match method signatures like: methodName(): void;
else if (line.match(/^\s*(\w+)(\?)?\s*\(/)) {
const methodMatch = line.match(/^\s*(\w+)(\?)?\s*\(/);
if (methodMatch) {
properties.push({
name: methodMatch[1],
required: !methodMatch[2]
});
}
}
}
if (closeBraces > 0 && isObjectType) {
braceCount -= closeBraces;
if (braceCount === 0) {
break;
}
}
// For non-object types, capture the full type definition
if (!isObjectType) {
signature = fullLine.trim();
break;
}
}
return {
name,
kind: 'type',
extendedProperties: isObjectType ? properties : undefined,
signature: isObjectType
? `export type ${name} = { ${properties.map(p => `${p.name}${p.required ? '' : '?'}: any`).join('; ')} }`
: signature || `export type ${name} = any`
};
}