uui-build/ts/tasks/docsGen/converters/converterForProps.ts (176 lines of code) (raw):
import { Node, Symbol, SyntaxKind, Type } from 'ts-morph';
import { IConverterContext, TConvertable, TTypePropsConverted } from '../types/types';
import { SymbolUtils } from './converterUtils/symbolUtils';
import { NodeUtils } from './converterUtils/nodeUtils';
import { TypeUtils } from './converterUtils/typeUtils';
import { TTypeProp, TTypeRef } from '../types/sharedTypes';
import { getTypeRefFromTypeSummary } from './converterUtils/converterUtils';
import { ConvertableUtils } from './converterUtils/convertableUtils';
function isPropsSupported(node: Node) {
const type = node.getType();
const isExternalType = NodeUtils.isExternalNode(node);
return TypeUtils.isPropsSupportedByType({ type, isExternalType });
}
export function convertTypeProps(nodeOrSymbol: TConvertable, context: IConverterContext): TTypePropsConverted | undefined {
const typeNode = ConvertableUtils.getNode(nodeOrSymbol);
if (!isPropsSupported(typeNode)) {
return;
}
const type = NodeUtils.getTypeFromNode(typeNode);
const idGen = new SimpleIdGen();
if (type.isUnion()) {
const unionTypes = type.getUnionTypes();
const props = extractPropsFromNonUnionTypeArr(unionTypes, typeNode, context, idGen);
if (props) {
return {
props,
propsFromUnion: true,
};
}
} else {
const props = extractPropsFromNonUnionType({ parentNode: typeNode, type, context, idGen });
if (props) {
return {
props,
propsFromUnion: false,
};
}
}
}
function extractPropsFromNonUnionTypeArr(typeArr: Type[], parentNode: Node, context: IConverterContext, idGen: SimpleIdGen) {
const allSupportProps = typeArr.every((singleType) => {
const typeNode = TypeUtils.getNodeFromType(singleType);
/*
* If node isn't available for this type, then we treat it as internal.
* I hope it's OK - in the worst case, the end user will see a bunch of props from the external type.
*/
const isExternalType = typeNode ? NodeUtils.isExternalNode(typeNode) : false;
return TypeUtils.isPropsSupportedByType({ type: singleType, isExternalType });
});
if (allSupportProps) {
const allPropsSets = typeArr.reduce<PropsSet[]>((acc, singleType) => {
const utProps = extractPropsFromNonUnionType({ parentNode, type: singleType, context, idGen });
if (utProps) {
acc.push(PropsSet.fromArray(utProps));
}
return acc;
}, []);
return PropsSet.concat(allPropsSets);
}
}
function extractPropsFromNonUnionType(params: { parentNode: Node, type: Type, context: IConverterContext, idGen: SimpleIdGen }): TTypeProp[] | undefined {
const { parentNode, type, context, idGen } = params;
const propsOnly = type.getProperties();
const indexSigns = TypeUtils.getIndexSignature(type);
const props = indexSigns.concat(propsOnly);
if (props.length > 0) {
return props.reduce<TTypeProp[]>((acc, propertySymbol) => {
const mapped = mapSingleMember({ parentNode, propertySymbol, context, idGen });
if (mapped) {
acc.push(mapped);
}
return acc;
}, []);
}
}
function mapSingleMember(params: { parentNode?: Node, propertySymbol: Symbol, context: IConverterContext, idGen: SimpleIdGen }): TTypeProp | undefined {
const { parentNode, propertySymbol, context, idGen } = params;
let prop: TTypeProp | undefined = undefined;
const propertyNode = SymbolUtils.getNodeFromSymbol(propertySymbol);
const nKind = propertyNode.getKind();
const isSupported = [
SyntaxKind.PropertySignature,
SyntaxKind.MethodSignature,
SyntaxKind.GetAccessor,
SyntaxKind.SetAccessor,
SyntaxKind.MethodDeclaration,
SyntaxKind.PropertyDeclaration,
SyntaxKind.IndexSignature,
].indexOf(nKind) !== -1;
if (isSupported) {
const tv = NodeUtils.getPropertySymbolTypeValue(propertySymbol, context);
if (!tv) {
return;
}
const comment = NodeUtils.getCommentFromNode(propertyNode);
let fromRef;
const propParent = NodeUtils.getPropertyNodeParent(propertyNode, parentNode);
if (propParent) {
const fromSummary = context.convertTypeSummary({ convertable: propParent });
fromRef = getTypeRefFromTypeSummary(fromSummary);
}
const name = NodeUtils.getPropertyNodeName(propertyNode);
const required = NodeUtils.isPropertyNodeRequired(propertyNode) && !propertySymbol.isOptional();
const uid = idGen.getNextId(name);
prop = {
// "uid" property is needed because we may have unions where there are props with same name but different type
uid,
name,
comment,
typeValue: tv,
typeValueRef: getPropertyTypeValueRef({ propertySymbol, context }),
editor: context.convertPropEditor({ convertable: propertySymbol }),
from: fromRef,
required,
};
} else {
console.error(`New SyntaxKind was found: ${nKind}. Please add it to the list and check that it's processed correctly.`);
}
return prop;
}
function getPropertyTypeValueRef(params: { propertySymbol: Symbol, context: IConverterContext }): TTypeRef | undefined {
const { propertySymbol, context } = params;
const valueDeclNode = propertySymbol.getValueDeclaration();
if (valueDeclNode) {
const aliasSymbol = valueDeclNode.getType().getAliasSymbol();
if (aliasSymbol) {
// I.e. the property refers some type which is declared in another place (I.e. it's not inline type declaration).
const valueTypeSummary = context.convertTypeSummary({ convertable: aliasSymbol });
return `${valueTypeSummary.module}:${valueTypeSummary.typeName.name}`;
}
}
}
class PropsSet {
private _propsMap = new Map<string, TTypeProp>();
add(p: TTypeProp) {
const id = PropsSet.buildId(p);
if (!this._propsMap.has(id)) {
// we want to keep the first unique type rather than the last one,
// because "uid" in unions are less likely contain index in such case - as a result the output JSON is cleaner.
this._propsMap.set(id, p);
}
}
addAll(pa: TTypeProp[]) {
pa.forEach((p) => {
this.add(p);
});
}
has(p: TTypeProp): boolean {
const id = PropsSet.buildId(p);
return this._propsMap.has(id);
}
toArray(): TTypeProp[] {
return [...this._propsMap.values()];
}
static concat(psa: PropsSet[]): TTypeProp[] {
const tempPs = psa.reduce<PropsSet>((acc, ps) => {
acc.addAll(ps.toArray());
return acc;
}, new PropsSet());
return tempPs.toArray();
}
static fromArray(pa: TTypeProp[]): PropsSet {
const ps = new PropsSet();
pa.forEach((p) => {
ps.add(p);
});
return ps;
}
static buildId(p: TTypeProp) {
// we don't use uniqueId property because we need to eliminate duplicates which come from same type (it's possible in union types)
return `${p.from}:${p.name}:${p.typeValue.raw.replace(/[\n\s]/g, '')}`;
}
}
class SimpleIdGen {
private _usedIds = new Map<string, number>();
getNextId = (name: string) => {
const prevIndex = this._usedIds.get(name);
if (prevIndex === undefined) {
this._usedIds.set(name, 1);
return name;
}
this._usedIds.set(name, prevIndex + 1);
return `${name}_${prevIndex + 1}`;
};
}