type-to-string
Version:
Get a string representation of a TypeScript type.
247 lines (241 loc) • 8.93 kB
JavaScript
var ts = require('typescript');
var tsApiUtils = require('ts-api-utils');
/**
* Merge the given type and type arguments string representations.
*/
function getTypeWithTypeArgumentsString(name, typeArguments) {
if (typeArguments === undefined) {
return name;
}
if (typeArguments.includes(null)) {
return null;
}
return `${name}<${typeArguments.join(", ")}>`;
}
/**
* Get string representations of the given entity name.
*/
function entityNameToString(
// eslint-disable-next-line functional/prefer-immutable-types
entityName) {
return ts.isIdentifier(entityName)
? identifierToString(entityName)
: qualifiedNameToString(entityName);
}
/**
* Get string representations of the given identifier.
*/
function identifierToString(
// eslint-disable-next-line functional/prefer-immutable-types
identifier) {
return identifier.escapedText;
}
/**
* Get string representations of the given qualified name.
*/
// eslint-disable-next-line functional/prefer-immutable-types
function qualifiedNameToString(qualifiedName) {
return `${entityNameToString(qualifiedName.left)}.${identifierToString(qualifiedName.right)}`;
}
/**
* Get the highest set bit in the given value.
*/
function bitwiseMax(value) {
let m_shifts = 0;
// eslint-disable-next-line functional/no-loop-statements, no-cond-assign, no-param-reassign
while ((value >>= 1) !== 0) {
// eslint-disable-next-line functional/no-expression-statements
m_shifts++;
}
return 1 << m_shifts;
}
const typeFormatFlagsMaxValue = bitwiseMax(Object.values(ts.TypeFormatFlags).reduce((carry, current) => typeof current === "number" ? carry | current : carry, 0));
/**
* The same as {@link TypeFormatFlags} but with a few extra options added on.
*/
const TypeNodeFormatFlags = {
...ts.TypeFormatFlags,
OmitTypeLiterals: typeFormatFlagsMaxValue << 1,
};
/**
* Get the alias name of the given {@link TypeNode}.
*/
function getTypeNodeAliasAsString(
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, withArguments = false) {
if (ts.isTypeAliasDeclaration(typeNode.parent)) {
const name = identifierToString(typeNode.parent.name);
if (!withArguments) {
return name;
}
const typeArguments = typeNode.parent.typeParameters?.map((type) => identifierToString(type.name));
return getTypeWithTypeArgumentsString(name, typeArguments);
}
return null;
}
/**
* Get the name of the given {@link TypeReferenceNode}.
*
* If the given {@link Node} is not a {@link TypeReferenceNode}, `null` will be returned.
*/
function getTypeReferenceNodeAsString(program, node, withArguments = false) {
if (ts.isTypeReferenceNode(node)) {
const name = entityNameToString(node.typeName);
if (!withArguments) {
return name;
}
const checker = program.getTypeChecker();
const type = checker.getTypeAtLocation(node.typeName);
type.aliasSymbol ?? type.getSymbol();
const typeArgumentsNodes = type.aliasTypeArguments ??
(type.typeParameters);
const typeArguments = typeArgumentsNodes?.map((parameter) => parameter.getSymbol()?.getName() ?? null);
return getTypeWithTypeArgumentsString(name, typeArguments);
}
if (ts.isArrayTypeNode(node)) {
return withArguments ? "Array<T>" : "Array";
}
if (ts.isTypeOperatorNode(node) &&
node.operator === ts.SyntaxKind.ReadonlyKeyword &&
ts.isArrayTypeNode(node.type)) {
return withArguments ? "ReadonlyArray<T>" : "ReadonlyArray";
}
return null;
}
/**
* Get the {@link TypeNode} as written.
*/
function typeNodeAsWritten(
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, keepAllWhiteSpace = false) {
if (keepAllWhiteSpace) {
return typeNode.getFullText();
}
return typeNode.getText();
}
/**
* Get string representations of the given {@link TypeNode}.
*
* @throws When the given type node is anonymous.
*/
function typeNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags = ts.TypeFormatFlags.None) {
return getTypeNodeString(program, typeNode, flags);
}
function getTypeNodeString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags, readonly = false) {
if (ts.isTypeOperatorNode(typeNode)) {
return getTypeNodeString(program, typeNode.type, flags, typeNode.operator === ts.SyntaxKind.ReadonlyKeyword);
}
if (ts.isTypeReferenceNode(typeNode)) {
return typeReferenceNodeToString(program, typeNode, flags);
}
if (ts.isArrayTypeNode(typeNode)) {
return arrayTypeNodeToString(program, typeNode, flags, readonly);
}
if (ts.isTupleTypeNode(typeNode)) {
return tupleTypeNodeToString(program, typeNode, flags, readonly);
}
if (ts.isTypeLiteralNode(typeNode)) {
return typeLiteralNodeToString(program, typeNode, flags);
}
if (ts.isTypeQueryNode(typeNode)) {
return typeQueryNodeToString(program, typeNode, flags);
}
const checker = program.getTypeChecker();
const type = checker.getTypeAtLocation(typeNode);
if (tsApiUtils.isIntrinsicType(type)) {
return type.intrinsicName;
}
// TODO: implement
return null;
}
function typeReferenceNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags) {
const name = entityNameToString(typeNode.typeName);
const typeArguments = typeNode.typeArguments?.map((type) => getTypeNodeString(program, type, flags));
return getTypeWithTypeArgumentsString(name, typeArguments);
}
function typeLiteralNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags) {
if ((flags & TypeNodeFormatFlags.OmitTypeLiterals) !== 0) {
return "{}";
}
// TODO: implement
return "{}";
}
function arrayTypeNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags, readonly) {
const typeArgument = getTypeNodeString(program, typeNode.elementType, flags);
if ((flags & TypeNodeFormatFlags.WriteArrayAsGenericType) !== 0) {
const name = readonly ? "ReadonlyArray" : "Array";
return getTypeWithTypeArgumentsString(name, [typeArgument]);
}
return `${readonly ? "readonly " : ""}${typeArgument}[]`;
}
function tupleTypeNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags, readonly) {
const typeArguments = typeNode.elements.map((type) => getTypeNodeString(program, ts.isNamedTupleMember(type) ? type.type : type, flags));
return `${readonly ? "readonly " : ""}[${typeArguments.join(", ")}]`;
}
function typeQueryNodeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
typeNode, flags) {
const checker = program.getTypeChecker();
const type = checker.getTypeAtLocation(typeNode.exprName);
if (tsApiUtils.isIntrinsicErrorType(type)) {
return null;
}
return typeToString(program, type, typeNode.exprName, flags);
}
/**
* Get string representations of the given {@link Type}.
*/
function typeToString(program,
// eslint-disable-next-line functional/prefer-immutable-types
type, enclosingDeclaration = undefined, flags = ts.TypeFormatFlags.None) {
const checker = program.getTypeChecker();
return checker.typeToString(type, enclosingDeclaration, flags);
}
/**
* Get the alias name of the given {@link Type}.
*/
function getTypeAliasAsString(
// eslint-disable-next-line functional/prefer-immutable-types
type, withArguments = false) {
const t = "target" in type ? type.target : type;
const name = t.aliasSymbol?.getName() ?? null;
if (!withArguments || name === null) {
return name;
}
const typeArguments = t.aliasTypeArguments?.map((argument) => argument.getSymbol()?.getName() ?? null);
return getTypeWithTypeArgumentsString(name, typeArguments);
}
/**
* Get the name of the type reference on the given {@link Type}.
*
* If the given {@link Type} is not a type reference, `null` will be returned.
*/
function getTypeReferenceAsString(program,
// eslint-disable-next-line functional/prefer-immutable-types
type, withArguments = false) {
if (!("node" in type)) {
return null;
}
return getTypeReferenceNodeAsString(program, type.node, withArguments);
}
exports.TypeNodeFormatFlags = TypeNodeFormatFlags;
exports.getTypeAliasAsString = getTypeAliasAsString;
exports.getTypeNodeAliasAsString = getTypeNodeAliasAsString;
exports.getTypeReferenceAsString = getTypeReferenceAsString;
exports.getTypeReferenceNodeAsString = getTypeReferenceNodeAsString;
exports.typeNodeAsWritten = typeNodeAsWritten;
exports.typeNodeToString = typeNodeToString;
exports.typeToString = typeToString;
;