mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
347 lines
12 KiB
JavaScript
347 lines
12 KiB
JavaScript
const { ApiItemKind } = require('@microsoft/api-extractor-model');
|
|
const utils = require('./utils');
|
|
const documentation = require('./documentation');
|
|
|
|
// TypeDefinition structure: { name, id, params?, path?, example? }
|
|
// TypeToken structure: { kind, text, canonicalReference? }
|
|
|
|
function resolveTypeDefinitions(apiPackage) {
|
|
const allMembers = getAllTypeMembers(apiPackage);
|
|
const typeDefinitions = allMembers.map((member) => createInitialTypeDef(member, apiPackage));
|
|
|
|
const resolvedTypeDefinitions = resolveObjectTypes(
|
|
resolveUnionTypes(typeDefinitions)
|
|
);
|
|
|
|
// Transform to final format expected by parser
|
|
return resolvedTypeDefinitions.map((type) => {
|
|
const result = {
|
|
id: type.id,
|
|
name: type.name,
|
|
properties: (type.params || []).map(({ description, type: paramType, name }) => ({
|
|
description, type: paramType, name
|
|
}))
|
|
};
|
|
|
|
if (type.path) result.path = type.path;
|
|
if (type.example) result.example = type.example;
|
|
if (type.detailedType && !type.params?.length && !type.example) {
|
|
result.detailedType = type.detailedType;
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
// Extract all type members from API package
|
|
function getAllTypeMembers(apiPackage) {
|
|
const typeKinds = [ApiItemKind.TypeAlias, ApiItemKind.Interface, ApiItemKind.Enum];
|
|
return apiPackage.entryPoints
|
|
.flatMap((entryPoint) => entryPoint.members)
|
|
.filter((member) => typeKinds.includes(member.kind));
|
|
}
|
|
|
|
// Create initial type definition
|
|
function createInitialTypeDef(member, apiPackage) {
|
|
const typeDef = {
|
|
name: member.name,
|
|
id: member.name,
|
|
params: [],
|
|
path: member.fileUrlPath || undefined
|
|
};
|
|
|
|
const processor = {
|
|
[ApiItemKind.Enum]: () => processEnumMember(member, typeDef),
|
|
[ApiItemKind.Interface]: () => processInterfaceMember(member, typeDef),
|
|
[ApiItemKind.TypeAlias]: () => processTypeAliasMember(member, typeDef, apiPackage)
|
|
}[member.kind];
|
|
|
|
processor?.();
|
|
return typeDef;
|
|
}
|
|
|
|
// Create member descriptor for enum/interface items
|
|
const createMemberDescriptor = (member, defaultType) => ({
|
|
name: member.name,
|
|
type: member.initializerExcerpt?.text || member.propertyTypeExcerpt?.text || defaultType,
|
|
description: documentation.getDocComment(member)
|
|
});
|
|
|
|
// Process enum members
|
|
function processEnumMember(member, typeDef) {
|
|
typeDef.params = member.members
|
|
.filter((enumMember) => enumMember.kind === ApiItemKind.EnumMember)
|
|
.map((enumMember) => createMemberDescriptor(enumMember, `"${enumMember.name}"`));
|
|
}
|
|
|
|
// Process interface members
|
|
function processInterfaceMember(member, typeDef) {
|
|
typeDef.params = member.members
|
|
.filter((prop) => prop.kind === ApiItemKind.PropertySignature)
|
|
.map((prop) => createMemberDescriptor(prop, 'any'));
|
|
}
|
|
|
|
// Process type alias members
|
|
function processTypeAliasMember(member, typeDef, apiPackage) {
|
|
const detailedType = extractDetailedTypeInfo(member);
|
|
if (!detailedType) return;
|
|
|
|
// Check if this is a callback type and extract full signature
|
|
const callbackSignature = extractCallbackSignature(detailedType, member);
|
|
if (callbackSignature) {
|
|
typeDef.example = callbackSignature;
|
|
typeDef.params = [];
|
|
return;
|
|
}
|
|
|
|
const literalValues = extractLiteralValues(member, apiPackage);
|
|
if (literalValues.length > 0) {
|
|
// For string literal unions, create an example instead of properties
|
|
const literalTypes = literalValues.map(v => v.type).join(' | ');
|
|
typeDef.example = literalTypes;
|
|
typeDef.params = [];
|
|
} else if (detailedType.isComplex) {
|
|
// Mark for later resolution
|
|
typeDef._needsResolution = { member, detailedType };
|
|
} else {
|
|
typeDef.detailedType = detailedType;
|
|
}
|
|
}
|
|
|
|
// Extract callback signature from type alias
|
|
function extractCallbackSignature(detailedType, member) {
|
|
const signature = detailedType.signature;
|
|
|
|
// Check if this is a function type by looking for function patterns
|
|
const isFunctionPattern = signature.includes('=>') ||
|
|
signature.includes(': ') ||
|
|
utils.isCallbackType(member.name) ||
|
|
member.name.includes('Callback');
|
|
|
|
if (!isFunctionPattern) {
|
|
return null;
|
|
}
|
|
|
|
// Extract function signature from the tokens
|
|
const functionSignature = extractFunctionFromTokens(detailedType.tokens);
|
|
if (functionSignature) {
|
|
return functionSignature;
|
|
}
|
|
|
|
// For known callback patterns, extract the full signature
|
|
if (signature.includes('=>')) {
|
|
return signature.trim();
|
|
}
|
|
|
|
// Fallback to generic callback for simple function types
|
|
return '() => {}';
|
|
}
|
|
|
|
// Extract function signature from tokens
|
|
function extractFunctionFromTokens(tokens) {
|
|
if (!tokens || tokens.length === 0) return null;
|
|
|
|
// Join all tokens to get the full signature, looking for function patterns
|
|
const fullText = tokens.map(token => token.text).join('');
|
|
|
|
// Clean up the signature to make it more readable
|
|
let cleanSignature = fullText
|
|
.replace(/^\s*\(\s*/, '(') // Clean leading spaces in params
|
|
.replace(/\s*\)\s*$/, ')') // Clean trailing spaces in params
|
|
.replace(/\s*=>\s*/g, ' => ') // Normalize arrow function spacing
|
|
.replace(/\s*:\s*/g, ': ') // Normalize type annotation spacing
|
|
.replace(/\s*,\s*/g, ', ') // Normalize comma spacing
|
|
.trim();
|
|
|
|
// If it looks like a function, return it
|
|
if (cleanSignature.includes('=>') || cleanSignature.includes(': ')) {
|
|
return cleanSignature;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Extract detailed type information and tokens
|
|
function extractDetailedTypeInfo(member) {
|
|
if (!member.excerptTokens) return null;
|
|
|
|
const tokens = member.excerptTokens.slice(
|
|
member.typeTokenRange?.startIndex || 1,
|
|
member.typeTokenRange?.endIndex || member.excerptTokens.length - 1
|
|
);
|
|
const signature = tokens.map((token) => token.text).join('').trim();
|
|
const isComplex = /[|&<]|typeof |keyof /.test(signature);
|
|
|
|
return { signature, tokens, isComplex };
|
|
}
|
|
|
|
// Extract literal values from type tokens
|
|
function extractLiteralValues(member, apiPackage) {
|
|
const typeInfo = extractDetailedTypeInfo(member);
|
|
if (!typeInfo) return [];
|
|
|
|
const literalValues = [];
|
|
typeInfo.tokens.forEach(token => {
|
|
literalValues.push(...extractStringLiterals(token.text));
|
|
|
|
if (token.kind === 'Reference' && token.canonicalReference) {
|
|
const referencedValues = extractFromReferencedType(token.canonicalReference, apiPackage);
|
|
literalValues.push(...referencedValues);
|
|
}
|
|
});
|
|
|
|
return literalValues;
|
|
}
|
|
|
|
// Extract string literals from text
|
|
function extractStringLiterals(text) {
|
|
const patterns = [/'([^']+)'/g, /"([^"]+)"/g];
|
|
const literals = [];
|
|
|
|
patterns.forEach(pattern => {
|
|
let match;
|
|
while ((match = pattern.exec(text)) !== null) {
|
|
const value = match[1];
|
|
literals.push(utils.createStringLiteral(value));
|
|
}
|
|
});
|
|
|
|
return literals;
|
|
}
|
|
|
|
// Extract values from referenced types
|
|
function extractFromReferencedType(canonicalReference, apiPackage) {
|
|
const referencedType = findTypeByCanonicalReference(apiPackage, canonicalReference);
|
|
if (!referencedType) return [];
|
|
|
|
const extractors = {
|
|
[ApiItemKind.Variable]: () => extractFromVariable(referencedType),
|
|
[ApiItemKind.TypeAlias]: () => extractLiteralValues(referencedType, apiPackage),
|
|
[ApiItemKind.Enum]: () => extractFromEnum(referencedType)
|
|
};
|
|
|
|
return extractors[referencedType.kind]?.() || [];
|
|
}
|
|
|
|
// Find type by canonical reference
|
|
function findTypeByCanonicalReference(apiPackage, canonicalReference) {
|
|
const typeName = canonicalReference.toString().split('!')[1];
|
|
if (!typeName) return null;
|
|
|
|
return apiPackage.entryPoints
|
|
.flatMap((entryPoint) => entryPoint.members)
|
|
.find((member) => member.name === typeName);
|
|
}
|
|
|
|
// Extract from variable (arrays)
|
|
function extractFromVariable(referencedType) {
|
|
const varContent = referencedType.excerptTokens?.[1]?.text || '';
|
|
const arrayMatch = varContent.match(/readonly\s*\[([\s\S]*?)\]/);
|
|
if (!arrayMatch) return [];
|
|
|
|
const stringMatches = arrayMatch[1].match(/"([^"]+)"/g) || [];
|
|
return stringMatches.map((match) => {
|
|
const value = match.replace(/"/g, '');
|
|
return utils.createStringLiteral(value);
|
|
});
|
|
}
|
|
|
|
// Extract from enum
|
|
function extractFromEnum(referencedType) {
|
|
return referencedType.members
|
|
.filter((enumMember) => enumMember.kind === ApiItemKind.EnumMember)
|
|
.map((enumMember) => ({
|
|
name: enumMember.name,
|
|
type: enumMember.initializerExcerpt?.text || `"${enumMember.name}"`,
|
|
description: documentation.getDocComment(enumMember) || `Enum value: ${enumMember.name}`
|
|
}));
|
|
}
|
|
|
|
function resolveUnionTypes(typeDefinitions) {
|
|
return typeDefinitions.map(typeDef => {
|
|
const resolution = typeDef._needsResolution;
|
|
if (!resolution) {
|
|
return typeDef; // Return unchanged
|
|
}
|
|
|
|
const gatheredValues = gatherValuesFromReferencedTypes(resolution.member, typeDefinitions);
|
|
|
|
// Create new object without mutation
|
|
const newTypeDef = { ...typeDef };
|
|
if (gatheredValues.length > 0) {
|
|
newTypeDef.params = gatheredValues;
|
|
} else {
|
|
newTypeDef.example = generateTypeExample(resolution.detailedType.signature);
|
|
newTypeDef.params = [];
|
|
}
|
|
|
|
// Remove internal properties from the new object
|
|
const { _needsResolution, detailedType, ...cleanTypeDef } = newTypeDef;
|
|
return cleanTypeDef;
|
|
});
|
|
}
|
|
|
|
// Gather values from already processed types
|
|
function gatherValuesFromReferencedTypes(member, typeDefinitions) {
|
|
const typeInfo = extractDetailedTypeInfo(member);
|
|
if (!typeInfo) return [];
|
|
|
|
const gatheredValues = [];
|
|
typeInfo.tokens.forEach(token => {
|
|
if (token.kind === 'Reference' && token.canonicalReference) {
|
|
const typeName = token.canonicalReference.toString().split('!')[1];
|
|
const foundType = typeDefinitions.find(t => t.name === typeName);
|
|
|
|
if (foundType?.params) {
|
|
const validParams = foundType.params.filter(param => param.name !== 'example');
|
|
gatheredValues.push(...validParams);
|
|
}
|
|
}
|
|
});
|
|
|
|
return gatheredValues;
|
|
}
|
|
|
|
// Generate example for complex types
|
|
function generateTypeExample(signature) {
|
|
if (/KnownEventName|KnownUnsafeEditableEvent/.test(signature)) {
|
|
return '"$pageview" | "$identify" | "custom_event" | string';
|
|
}
|
|
if (signature.includes('string &')) {
|
|
return 'string';
|
|
}
|
|
if (signature.includes('|')) {
|
|
const parts = signature.split('|').map(p => p.trim());
|
|
return parts.join(' | ');
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
// Resolve object types
|
|
function resolveObjectTypes(typeDefinitions) {
|
|
return typeDefinitions.map(typeDef => {
|
|
const detailedType = typeDef.detailedType;
|
|
if (!detailedType || typeDef.params?.length || typeDef.example) {
|
|
return typeDef;
|
|
}
|
|
|
|
const signature = detailedType.signature;
|
|
const newTypeDef = { ...typeDef };
|
|
|
|
if (utils.isSimpleObjectType(signature)) {
|
|
const properties = utils.parseObjectTypeSignature(signature);
|
|
newTypeDef.params = properties.length > 0 ? properties : [];
|
|
newTypeDef.example = properties.length === 0 ? signature : undefined;
|
|
} else if (detailedType.isComplex) {
|
|
newTypeDef.example = signature;
|
|
newTypeDef.params = [];
|
|
}
|
|
|
|
delete newTypeDef.detailedType;
|
|
return newTypeDef;
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
resolveTypeDefinitions,
|
|
}; |