packages/jsii-reflect/lib/tree.ts (337 lines of code) (raw):
import { Stability } from '@jsii/spec';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import chalk = require('chalk');
import { AsciiTree } from 'oo-ascii-tree';
import { Assembly } from './assembly';
import { ClassType } from './class';
import { Dependency } from './dependency';
import { Documentable } from './docs';
import { EnumType } from './enum';
import { Initializer } from './initializer';
import { InterfaceType } from './interface';
import { Method } from './method';
import { OptionalValue } from './optional-value';
import { Parameter } from './parameter';
import { Property } from './property';
import { Submodule } from './submodule';
import { TypeSystem } from './type-system';
export interface TypeSystemTreeOptions {
/**
* Show all entity types (supersedes other options)
* @default false
*/
showAll?: boolean;
/**
* Show type members (methods, properties)
* @default false
*/
members?: boolean;
/**
* Show dependencies
* @default false
*/
dependencies?: boolean;
/**
* Show inheritance information (base classes, interfaces)
* @default false
*/
inheritance?: boolean;
/**
* Show types
* @default false
*/
types?: boolean;
/**
* Show method signatures.
* @default false
*/
signatures?: boolean;
/**
* Output with ANSI colors
* @default true
*/
colors?: boolean;
/**
* Show stabilities
*
* @default false
*/
stabilities?: boolean;
}
/**
* Visualizes a `TypeSystem` as an ASCII tree.
*/
export class TypeSystemTree extends AsciiTree {
public constructor(typesys: TypeSystem, options: TypeSystemTreeOptions = {}) {
super();
if (options.showAll) {
options.dependencies = true;
options.inheritance = true;
options.members = true;
options.signatures = true;
options.types = true;
}
const shouldUseColors = options.colors ?? true;
withColors(shouldUseColors, () => {
if (typesys.assemblies.length > 0) {
const assemblies = new TitleNode('assemblies');
assemblies.add(
...typesys.assemblies.map((a) => new AssemblyNode(a, options)),
);
this.add(assemblies);
}
});
}
}
class AssemblyNode extends AsciiTree {
public constructor(assembly: Assembly, options: TypeSystemTreeOptions) {
super(chalk.green(assembly.name));
if (options.dependencies && assembly.dependencies.length > 0) {
const deps = new TitleNode('dependencies');
this.add(deps);
deps.add(
...assembly.dependencies.map((d) => new DependencyNode(d, options)),
);
}
const submodules = assembly.submodules;
if (submodules.length > 0) {
const title = new TitleNode('submodules');
this.add(title);
title.add(...submodules.map((s) => new SubmoduleNode(s, options)));
}
if (options.types) {
const types = new TitleNode('types');
this.add(types);
types.add(...assembly.classes.map((c) => new ClassNode(c, options)));
types.add(
...assembly.interfaces.map((c) => new InterfaceNode(c, options)),
);
types.add(...assembly.enums.map((c) => new EnumNode(c, options)));
}
}
}
class SubmoduleNode extends AsciiTree {
public constructor(submodule: Submodule, options: TypeSystemTreeOptions) {
super(chalk.green(submodule.name));
const submodules = submodule.submodules;
if (submodules.length > 0) {
const title = new TitleNode('submodules');
this.add(title);
title.add(...submodules.map((s) => new SubmoduleNode(s, options)));
}
if (options.types) {
const types = new TitleNode('types');
this.add(types);
types.add(...submodule.classes.map((c) => new ClassNode(c, options)));
types.add(
...submodule.interfaces.map((i) => new InterfaceNode(i, options)),
);
types.add(...submodule.enums.map((e) => new EnumNode(e, options)));
}
}
}
class MethodNode extends AsciiTree {
public constructor(method: Method, options: TypeSystemTreeOptions) {
const args = method.parameters.map((p) => p.name).join(',');
super(
`${maybeStatic(method)}${method.name}(${args}) ${chalk.gray(
'method',
)}${describeStability(method, options)}`,
);
if (options.signatures) {
if (method.abstract) {
this.add(new FlagNode('abstract'));
}
if (method.protected) {
this.add(new FlagNode('protected'));
}
if (method.static) {
this.add(new FlagNode('static'));
}
if (method.variadic) {
this.add(new FlagNode('variadic'));
}
if (method.parameters.length > 0) {
const params = new TitleNode('parameters');
this.add(params);
params.add(
...method.parameters.map((p) => new ParameterNode(p, options)),
);
}
this.add(
new OptionalValueNode('returns', method.returns, {
asPromise: method.async,
}),
);
}
}
}
class InitializerNode extends AsciiTree {
public constructor(initializer: Initializer, options: TypeSystemTreeOptions) {
const args = initializer.parameters.map((p) => p.name).join(',');
super(
`${initializer.name}(${args}) ${chalk.gray(
'initializer',
)}${describeStability(initializer, options)}`,
);
if (options.signatures) {
if (initializer.protected) {
this.add(new FlagNode('protected'));
}
if (initializer.variadic) {
this.add(new FlagNode('variadic'));
}
if (initializer.parameters.length > 0) {
const params = new TitleNode('parameters');
this.add(params);
params.add(
...initializer.parameters.map((p) => new ParameterNode(p, options)),
);
}
}
}
}
class ParameterNode extends AsciiTree {
public constructor(param: Parameter, _options: TypeSystemTreeOptions) {
super(param.name);
this.add(new OptionalValueNode('type', param));
if (param.variadic) {
this.add(new FlagNode('variadic'));
}
}
}
class PropertyNode extends AsciiTree {
public constructor(property: Property, options: TypeSystemTreeOptions) {
super(
`${maybeStatic(property)}${property.name} ${chalk.gray(
'property',
)}${describeStability(property, options)}`,
);
if (options.signatures) {
if (property.abstract) {
this.add(new FlagNode('abstract'));
}
if (property.const) {
this.add(new FlagNode('const'));
}
if (property.immutable) {
this.add(new FlagNode('immutable'));
}
if (property.protected) {
this.add(new FlagNode('protected'));
}
if (property.static) {
this.add(new FlagNode('static'));
}
this.add(new OptionalValueNode('type', property));
}
}
}
class OptionalValueNode extends AsciiTree {
public constructor(
name: string,
optionalValue: OptionalValue,
{ asPromise } = { asPromise: false },
) {
let type = OptionalValue.describe(optionalValue);
if (asPromise) {
type = `Promise<${type}>`;
}
super(`${chalk.underline(name)}: ${type}`);
}
}
class ClassNode extends AsciiTree {
public constructor(type: ClassType, options: TypeSystemTreeOptions) {
super(
`${chalk.gray('class')} ${chalk.cyan(type.name)}${describeStability(
type,
options,
)}`,
);
if (options.inheritance && type.base) {
this.add(new KeyValueNode('base', type.base.name));
}
if (options.inheritance && type.interfaces.length > 0) {
this.add(
new KeyValueNode(
'interfaces',
type.interfaces.map((i) => i.name).join(','),
),
);
}
if (options.members) {
const members = new TitleNode('members');
this.add(members);
if (type.initializer) {
members.add(new InitializerNode(type.initializer, options));
}
members.add(...type.ownMethods.map((m) => new MethodNode(m, options)));
members.add(
...type.ownProperties.map((p) => new PropertyNode(p, options)),
);
}
}
}
class InterfaceNode extends AsciiTree {
public constructor(type: InterfaceType, options: TypeSystemTreeOptions) {
super(
`${chalk.gray('interface')} ${chalk.cyan(type.name)}${describeStability(
type,
options,
)}`,
);
if (options.inheritance && type.interfaces.length > 0) {
const interfaces = new TitleNode('interfaces');
this.add(interfaces);
interfaces.add(...type.interfaces.map((i) => new TextNode(i.name)));
}
if (options.members) {
const members = new TitleNode('members');
members.add(...type.ownMethods.map((m) => new MethodNode(m, options)));
members.add(
...type.ownProperties.map((p) => new PropertyNode(p, options)),
);
this.add(members);
}
}
}
class EnumNode extends AsciiTree {
public constructor(enumType: EnumType, options: TypeSystemTreeOptions) {
super(
`${chalk.gray('enum')} ${chalk.cyan(enumType.name)}${describeStability(
enumType,
options,
)}`,
);
if (options.members) {
enumType.members.forEach((mem) => {
this.add(new AsciiTree(mem.name + describeStability(mem, options)));
});
}
}
}
class DependencyNode extends AsciiTree {
public constructor(dep: Dependency, _options: TypeSystemTreeOptions) {
super(dep.assembly.name);
}
}
class TitleNode extends AsciiTree {
public constructor(name: string, children: AsciiTree[] = []) {
super(chalk.underline(name), ...children);
}
}
class KeyValueNode extends AsciiTree {
public constructor(key: string, value: any) {
super(`${chalk.underline(key)}: ${value}`);
}
}
class TextNode extends AsciiTree {}
class FlagNode extends AsciiTree {
public constructor(flag: string) {
super(chalk.italic(flag));
}
}
/**
* Invokes `block` with colors enabled/disabled and reverts to old value afterwards.
*/
function withColors(enabled: boolean, block: () => void) {
const oldLevel = chalk.level;
try {
if (!enabled) {
chalk.level = 0; // No colors at all
}
block();
} finally {
chalk.level = oldLevel;
}
}
function describeStability(
thing: Documentable,
options: TypeSystemTreeOptions,
) {
if (!options.stabilities || thing.docs.stability == null) {
return '';
}
switch (thing.docs.stability) {
case Stability.Stable:
return ` (${chalk.green('stable')})`;
case Stability.External:
return ` (${chalk.green('external')})`;
case Stability.Experimental:
return ` (${chalk.yellow('experimental')})`;
case Stability.Deprecated:
return ` (${chalk.red('deprecated')})`;
default:
return '';
}
}
function maybeStatic(mem: Property | Method) {
let isStatic;
if (mem instanceof Property) {
isStatic = !!mem.static;
}
if (mem instanceof Method) {
isStatic = !!mem.static;
}
return isStatic ? `${chalk.grey('static')} ` : '';
}