uui-build/ts/tasks/docsGen/converters/converterUtils/nodeUtils.ts (151 lines of code) (raw):
import { EmitHint, Node, Symbol, ts } from 'ts-morph';
import { resolveModuleName, isExternalFile, makeRelativeToUuiRoot } from '../../utils/fileUtils';
// eslint-disable-next-line import/no-cycle
import { SymbolUtils } from './symbolUtils';
import { TypeUtils } from './typeUtils';
import { TComment, TTypeSummary, TTypeValue } from '../../types/sharedTypes';
import { IConverterContext } from '../../types/types';
import { TsDocUtils } from '../../tsdoc/tsDocUtils';
export class NodeUtils {
static getRelativeSource(typeNode: Node) {
const src = typeNode.getSourceFile();
const fullPath = src.getFilePath();
return makeRelativeToUuiRoot(fullPath);
}
static unWrapTypeNodeFromUtility(typeNode: Node): Node | undefined {
const typeRef = typeNode.getChildren().find(Node.isTypeReference);
const name = typeRef?.getTypeName().getText() || '';
// TODO: tests, Record is missed.
if (['Omit', 'Pick', 'Partial', 'Awaited', 'Required', 'Readonly'].indexOf(name) !== -1) {
const t = typeRef?.getTypeArguments();
return t?.[0];
}
}
static printNode(node: Node): string[] {
const printer = ts.createPrinter();
function removeLeadingExportKw(s: string) {
return s.replace(/^(export\s+)(type|interface|enum)(.*)$/g, '$2$3');
}
return printer.printNode(EmitHint.Unspecified, node.compilerNode, node.getSourceFile().compilerNode)
.split('\n')
.map(removeLeadingExportKw);
}
static getCommentFromNode(prop: Node): TComment | undefined {
const ranges = prop.getLeadingCommentRanges();
if (ranges.length > 0) {
const closestDoc = ranges[ranges.length - 1].getText().trim();
return TsDocUtils.parseComment(closestDoc);
}
}
static getPropertyNodeParent(propertyNode: Node, containerNode?: Node): Node | undefined {
const anc = propertyNode.getAncestors().filter((a) => {
return a !== containerNode;
});
const ancFiltered = anc.filter((a) => {
return (Node.isTypeAliasDeclaration(a) || Node.isInterfaceDeclaration(a) || Node.isClassDeclaration(a));
});
if (ancFiltered.length > 0) {
return ancFiltered[0];
}
}
static getTypeSummary(typeNode: Node): TTypeSummary {
const fileName = typeNode.getSourceFile().compilerNode.fileName;
const module = resolveModuleName(fileName);
const typeName = SymbolUtils.getTypeName(typeNode.getSymbol());
const src = NodeUtils.getRelativeSource(typeNode);
const comment = NodeUtils.getCommentFromNode(typeNode);
return {
module,
typeName,
src,
comment,
exported: false, // on this level, we don't know whether it's exported or not, so this value may be changed later.
};
}
static isInternalTypeNodeWrappedInUtility(typeNode: Node): boolean {
const typeNodeUnwrapped = NodeUtils.unWrapTypeNodeFromUtility(typeNode);
if (typeNodeUnwrapped) {
return !NodeUtils.isExternalNode(typeNodeUnwrapped);
}
return false;
}
static getTypeFromNode(typeNode: Node) {
const symbol = typeNode.getSymbol();
if (symbol) {
const symbolDecls = symbol.getDeclarations();
return symbolDecls[0].getType();
}
return typeNode.getType();
}
static isExternalNode(typeNode: Node): boolean {
const type = NodeUtils.getTypeFromNode(typeNode);
const symbol = TypeUtils.getSymbolFromType(type);
if (!symbol) {
return false;
}
if (NodeUtils.isInternalTypeNodeWrappedInUtility(typeNode)) {
return false;
}
return (symbol.getDeclarations() || []).some((d) => {
const filePath = d.getSourceFile().compilerNode.fileName;
return isExternalFile(filePath);
});
}
static getTypeValueFromNode(params: { typeNode: Node, print: boolean }): TTypeValue {
const {
typeNode,
print,
} = params;
const type = typeNode.getType();
const result: TTypeValue = {
raw: TypeUtils.getCompilerTypeText(type),
};
if (print) {
result.print = NodeUtils.printNode(typeNode);
}
return result;
}
static getPropertySymbolTypeValue(propertySymbol: Symbol, context: IConverterContext): TTypeValue {
const node = SymbolUtils.getNodeFromSymbol(propertySymbol);
const name = NodeUtils.getPropertyNodeName(node);
if (Node.isGetAccessorDeclaration(node)) {
const returnType = node.getStructure().returnType;
return {
raw: `${name}(): ${returnType}`,
};
} else if (Node.isSetAccessorDeclaration(node)) {
const structureParams = node.getStructure().parameters?.[0];
if (structureParams) {
return {
raw: `${name}(${structureParams.name}: ${structureParams.type})`,
};
}
} else if (Node.isIndexSignatureDeclaration(node)) {
return {
raw: TypeUtils.getCompilerTypeText(node.getReturnType()),
};
}
const conv = context.convertToTypeValue({ convertable: propertySymbol, isProperty: true });
return conv;
}
static getPropertyNodeName(propertyNode: Node): string {
if (Node.isPropertyNamed(propertyNode)) {
const name = propertyNode.getName();
if (Node.isGetAccessorDeclaration(propertyNode)) {
return `get ${name}`;
} else if (Node.isSetAccessorDeclaration(propertyNode)) {
return `set ${name}`;
}
return name;
} else if (Node.isIndexSignatureDeclaration(propertyNode)) {
const kName = propertyNode.getKeyName();
const kType = propertyNode.getKeyType();
const kTypeText = TypeUtils.getCompilerTypeText(kType);
return `[${kName}: ${kTypeText}]`;
}
return '';
}
static isPropertyNodeRequired(propertyNode: Node): boolean {
const hasQuestionToken = Node.isQuestionTokenable(propertyNode) ? propertyNode.hasQuestionToken() : false;
const typeNode = Node.isTypeAliasDeclaration(propertyNode) ? propertyNode.getTypeNode() : propertyNode;
return !(NodeUtils.getTypeFromNode(typeNode as Node).isNullable() || hasQuestionToken);
}
}