mirror of
https://github.com/appium/appium.git
synced 2026-05-13 06:18:42 -05:00
chore(typedoc-plugin-appium): a load of cleanup
This commit is contained in:
Generated
+1
@@ -23760,6 +23760,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.7",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.2.0",
|
||||
"typedoc-plugin-markdown": "3.13.6"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* A thing that creates {@linkcode typedoc!DeclarationReflection} instances from parsed
|
||||
* command & execute method data.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {Context, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
@@ -5,117 +11,110 @@ import {
|
||||
CommandInfo,
|
||||
CommandReflection,
|
||||
CommandsReflection,
|
||||
ExecCommandData,
|
||||
ExecMethodData,
|
||||
ModuleCommands,
|
||||
ParentReflection,
|
||||
Route,
|
||||
} from '../model';
|
||||
|
||||
export class CommandTreeBuilder {
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
constructor(log: AppiumPluginLogger) {
|
||||
this.#log = log.createChildLogger('builder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates `DeclarationReflection` based on the `ModuleCommands` object & adds them to the project.
|
||||
* @param ctx TypeDoc Context
|
||||
* @param commands Data from the converter
|
||||
*/
|
||||
public createReflections(ctx: Context, commands: ModuleCommands): void {
|
||||
const {project} = ctx;
|
||||
const modules = project.getChildrenByKind(ReflectionKind.Module);
|
||||
|
||||
// the project itself may have commands, as well as any modules within the project
|
||||
const parents = [...modules, project].filter((parent) => commands.get(parent)?.hasCommands);
|
||||
if (parents.length) {
|
||||
for (const parent of parents) {
|
||||
this.#createCommandsReflection(ctx, parent, commands.get(parent)!);
|
||||
}
|
||||
} else {
|
||||
this.#log.warn('No Appium commands found in the entire project');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a child {@linkcode CommandReflection} to this reflection
|
||||
* @param data Command reference
|
||||
* @param route Route
|
||||
* @param parent Commands reflection
|
||||
*/
|
||||
#createCommandReflection(
|
||||
ctx: Context,
|
||||
data: CommandData | ExecCommandData,
|
||||
parent: CommandsReflection,
|
||||
route?: Route
|
||||
): void {
|
||||
const commandRefl = new CommandReflection(data, parent, route);
|
||||
/**
|
||||
* During "normal" usage of TypeDoc, one would call `createDeclarationReflection()`. But
|
||||
* since we've subclassed `DeclarationReflection`, we cannot call it directly. It doesn't
|
||||
* seem to do anything useful besides instantiation then delegating to `postReflectionCreation()`;
|
||||
* so we just need to call it directly.
|
||||
*
|
||||
* Finally, we call `finalizeDeclarationReflection()` which I think just fires some events for other
|
||||
* plugins to potentially use.
|
||||
*
|
||||
* And yes, the `undefined`s are apparently needed.
|
||||
*/
|
||||
ctx.postReflectionCreation(commandRefl, undefined, undefined);
|
||||
ctx.finalizeDeclarationReflection(commandRefl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode CommandsReflection}
|
||||
* @param ctx - Current context
|
||||
* @param parent - Parent module (or project)
|
||||
* @param commandInfo - Command information for `module`
|
||||
*/
|
||||
#createCommandsReflection(
|
||||
ctx: Context,
|
||||
parent: ParentReflection,
|
||||
commandInfo: CommandInfo
|
||||
): CommandsReflection {
|
||||
// TODO: module.name may not be right here
|
||||
const commandsRefl = new CommandsReflection(parent.name, ctx.project, commandInfo);
|
||||
/**
|
||||
* See note in `#createCommandReflection` above about this call
|
||||
*/
|
||||
ctx.postReflectionCreation(commandsRefl, undefined, undefined);
|
||||
|
||||
const parentCtx = ctx.withScope(commandsRefl);
|
||||
const {routeMap: routeMap, execCommandDataSet: execCommandsData} = commandInfo;
|
||||
|
||||
// sort routes in alphabetical order
|
||||
const sortedRouteMap = new Map([...routeMap.entries()].sort());
|
||||
for (const [route, commandMap] of sortedRouteMap) {
|
||||
for (const data of commandMap.values()) {
|
||||
this.#createCommandReflection(parentCtx, data, commandsRefl, route);
|
||||
}
|
||||
}
|
||||
|
||||
// sort execute commands in alphabetical order
|
||||
const sortedExecCommandsData = new Set([...execCommandsData].sort());
|
||||
for (const data of sortedExecCommandsData) {
|
||||
this.#createCommandReflection(parentCtx, data, commandsRefl);
|
||||
}
|
||||
|
||||
ctx.finalizeDeclarationReflection(commandsRefl);
|
||||
return commandsRefl;
|
||||
}
|
||||
/**
|
||||
* Creates and adds a child {@linkcode CommandReflection} to this reflection
|
||||
*
|
||||
* During "normal" usage of TypeDoc, one would call
|
||||
* `createDeclarationReflection()`. But since we've subclassed
|
||||
* `DeclarationReflection`, we cannot call it directly. It doesn't seem to do
|
||||
* anything useful besides instantiation then delegating to
|
||||
* `postReflectionCreation()`; so we just need to call it directly.
|
||||
*
|
||||
* Finally, we call `finalizeDeclarationReflection()` which I think just fires
|
||||
* some events for other plugins to potentially use.
|
||||
* @param log Logger
|
||||
* @param data Command reference
|
||||
* @param route Route
|
||||
* @param parent Commands reflection
|
||||
* @internal
|
||||
*/
|
||||
function createCommandReflection(
|
||||
log: AppiumPluginLogger,
|
||||
ctx: Context,
|
||||
data: CommandData | ExecMethodData,
|
||||
parent: CommandsReflection,
|
||||
route?: Route
|
||||
): void {
|
||||
const commandRefl = new CommandReflection(data, parent, route);
|
||||
// yes, the `undefined`s are needed
|
||||
ctx.postReflectionCreation(commandRefl, undefined, undefined);
|
||||
ctx.finalizeDeclarationReflection(commandRefl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to instantiate a {@linkcode CommandTreeBuilder} and create relfections
|
||||
* Create a new {@linkcode CommandsReflection} and all {@linkcode CommandReflection} children within it.
|
||||
* @param log - Logger
|
||||
* @param ctx - Current context
|
||||
* @param parent - Parent module (or project)
|
||||
* @param commandInfo - Command information for `module`
|
||||
* @internal
|
||||
*/
|
||||
function createCommandsReflection(
|
||||
log: AppiumPluginLogger,
|
||||
ctx: Context,
|
||||
parent: ParentReflection,
|
||||
commandInfo: CommandInfo
|
||||
): CommandsReflection {
|
||||
// TODO: parent.name may not be right here
|
||||
const commandsRefl = new CommandsReflection(parent.name, ctx.project, commandInfo);
|
||||
/**
|
||||
* See note in `#createCommandReflection` above about this call
|
||||
*/
|
||||
ctx.postReflectionCreation(commandsRefl, undefined, undefined);
|
||||
|
||||
const parentCtx = ctx.withScope(commandsRefl);
|
||||
const {routeMap: routeMap, execMethodDataSet: execCommandsData} = commandInfo;
|
||||
|
||||
// sort routes in alphabetical order
|
||||
const sortedRouteMap = new Map([...routeMap.entries()].sort());
|
||||
for (const [route, commandMap] of sortedRouteMap) {
|
||||
for (const data of commandMap.values()) {
|
||||
createCommandReflection(log, parentCtx, data, commandsRefl, route);
|
||||
}
|
||||
}
|
||||
|
||||
// sort execute commands in alphabetical order
|
||||
const sortedExecCommandsData = new Set([...execCommandsData].sort());
|
||||
for (const data of sortedExecCommandsData) {
|
||||
createCommandReflection(log, parentCtx, data, commandsRefl);
|
||||
}
|
||||
|
||||
ctx.finalizeDeclarationReflection(commandsRefl);
|
||||
return commandsRefl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates custom {@linkcode typedoc!DeclarationReflection}s from parsed command & execute method data.
|
||||
*
|
||||
* These instances are added to the {@linkcode Context} object itself; this mutates TypeDoc's internal state. Nothing is returned.
|
||||
* @param ctx TypeDoc Context
|
||||
* @param log Plugin logger
|
||||
* @param commandInfo Command info from converter
|
||||
* @param parentLog Plugin logger
|
||||
* @param commandInfo Command info from converter; a map of parent reflections to parsed data
|
||||
*/
|
||||
export function createReflections(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
parentLog: AppiumPluginLogger,
|
||||
commandInfo: ModuleCommands
|
||||
): void {
|
||||
new CommandTreeBuilder(log).createReflections(ctx, commandInfo);
|
||||
const log = parentLog.createChildLogger('builder');
|
||||
const {project} = ctx;
|
||||
|
||||
// note that this could be an empty array
|
||||
const modules = project.getChildrenByKind(ReflectionKind.Module);
|
||||
|
||||
// the project itself may have commands, as well as any modules within the project
|
||||
const parents = [...modules, project].filter((parent) => commandInfo.get(parent)?.hasData);
|
||||
if (!parents.length) {
|
||||
log.warn('No Appium commands found in the entire project');
|
||||
// TODO: maybe we should abort processing gracefully here? or throw?
|
||||
}
|
||||
for (const parent of parents) {
|
||||
createCommandsReflection(log, ctx, parent, commandInfo.get(parent)!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/**
|
||||
* Converts code parsed by TypeDoc into a data structure describing the commands and execute methods, which will later be used to create new {@linkcode DeclarationReflection} instances in the TypeDoc context.
|
||||
*
|
||||
* The logic in this module is highly dependent on Appium's extension API, and is further dependent on specific usages of TS types. Anything that will be parsed successfully by this module must use a `const` type alias in TS parlance. For example:
|
||||
*
|
||||
* ```ts
|
||||
* const METHOD_MAP = {
|
||||
* '/status': {
|
||||
* GET: {command: 'getStatus'}
|
||||
* },
|
||||
* // ...
|
||||
* } as const; // <-- required
|
||||
* ```
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {Context, DeclarationReflection, LiteralType, ReflectionKind} from 'typedoc';
|
||||
import {
|
||||
@@ -14,7 +30,7 @@ import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
CommandInfo,
|
||||
CommandMap,
|
||||
ExecCommandDataSet,
|
||||
ExecMethodDataSet,
|
||||
ModuleCommands,
|
||||
ParentReflection,
|
||||
RouteMap,
|
||||
@@ -29,6 +45,7 @@ import {
|
||||
* Name of the static `newMethodMap` property in a Driver
|
||||
*/
|
||||
export const NAME_NEW_METHOD_MAP = 'newMethodMap';
|
||||
|
||||
/**
|
||||
* Name of the static `executeMethodMap` property in a Driver
|
||||
*/
|
||||
@@ -70,7 +87,14 @@ export const NAME_BUILTIN_COMMAND_MODULE = '@appium/base-driver';
|
||||
* Converts declarations to information about Appium commands
|
||||
*/
|
||||
export class CommandConverter {
|
||||
/**
|
||||
* The project context of TypeDoc
|
||||
*/
|
||||
#ctx: Context;
|
||||
|
||||
/**
|
||||
* Custom logger
|
||||
*/
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
@@ -108,7 +132,7 @@ export class CommandConverter {
|
||||
for (const mod of modules) {
|
||||
this.#log.verbose('Converting module %s', mod.name);
|
||||
const cmdInfo = this.#convertModuleClasses(mod);
|
||||
if (cmdInfo.hasCommands) {
|
||||
if (cmdInfo.hasData) {
|
||||
projectCommands.set(mod, this.#convertModuleClasses(mod));
|
||||
}
|
||||
this.#log.info('Converted module %s', mod.name);
|
||||
@@ -162,13 +186,13 @@ export class CommandConverter {
|
||||
* @param refl A class which may contain an `executeMethodMap` static property
|
||||
* @returns List of "execute commands", if any
|
||||
*/
|
||||
#convertExecuteMethodMap(refl: DeclarationReflectionWithReflectedType): ExecCommandDataSet {
|
||||
#convertExecuteMethodMap(refl: DeclarationReflectionWithReflectedType): ExecMethodDataSet {
|
||||
const executeMethodMap = findChildByNameAndGuard(
|
||||
refl,
|
||||
NAME_EXECUTE_METHOD_MAP,
|
||||
isExecMethodDefReflection
|
||||
);
|
||||
const commandRefs: ExecCommandDataSet = new Set();
|
||||
const commandRefs: ExecMethodDataSet = new Set();
|
||||
if (!executeMethodMap) {
|
||||
// no execute commands in this class
|
||||
return commandRefs;
|
||||
@@ -315,7 +339,7 @@ export class CommandConverter {
|
||||
*/
|
||||
#convertModuleClasses(parent: ParentReflection) {
|
||||
let routes: RouteMap = new Map();
|
||||
let executeCommands: ExecCommandDataSet = new Set();
|
||||
let executeMethods: ExecMethodDataSet = new Set();
|
||||
|
||||
const classReflections = parent
|
||||
.getChildrenByKind(ReflectionKind.Class)
|
||||
@@ -333,12 +357,12 @@ export class CommandConverter {
|
||||
|
||||
const executeMethodMap = this.#convertExecuteMethodMap(classRefl);
|
||||
if (executeMethodMap.size) {
|
||||
executeCommands = new Set([...executeCommands, ...executeMethodMap]);
|
||||
executeMethods = new Set([...executeMethods, ...executeMethodMap]);
|
||||
}
|
||||
this.#log.verbose('Converted class %s', classRefl.name);
|
||||
}
|
||||
|
||||
return new CommandInfo(routes, executeCommands);
|
||||
return new CommandInfo(routes, executeMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {Merge} from 'type-fest';
|
||||
import {
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
@@ -10,40 +11,72 @@ import {
|
||||
import {AllowedHttpMethod} from '../model';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE, NAME_METHOD_MAP, NAME_NEW_METHOD_MAP} from './converter';
|
||||
|
||||
export type MethodMapDeclarationReflection = DeclarationReflectionWithReflectedType & {
|
||||
name: typeof NAME_METHOD_MAP | typeof NAME_NEW_METHOD_MAP;
|
||||
};
|
||||
/**
|
||||
* Type corresponding to a reflection of a {@linkcode @appium/types!MethodMap}
|
||||
*/
|
||||
export type MethodMapDeclarationReflection = Merge<
|
||||
DeclarationReflectionWithReflectedType,
|
||||
{name: typeof NAME_METHOD_MAP | typeof NAME_NEW_METHOD_MAP}
|
||||
>;
|
||||
|
||||
export type BaseDriverDeclarationReflection = DeclarationReflection & {
|
||||
name: typeof NAME_BUILTIN_COMMAND_MODULE;
|
||||
};
|
||||
/**
|
||||
* Type corresponding to a reflection of {@linkcode @appium/base-driver!}
|
||||
*/
|
||||
export type BaseDriverDeclarationReflection = Merge<
|
||||
DeclarationReflection,
|
||||
{
|
||||
name: typeof NAME_BUILTIN_COMMAND_MODULE;
|
||||
kind: ReflectionKind.Module;
|
||||
}
|
||||
>;
|
||||
|
||||
export type WithType<T extends SomeType> = {type: T};
|
||||
/**
|
||||
* Utility to narrow a declaration reflection to a specific `SomeType`
|
||||
*/
|
||||
type WithSomeType<
|
||||
T extends SomeType,
|
||||
R extends DeclarationReflection = DeclarationReflection
|
||||
> = Merge<R, {type: T}>;
|
||||
|
||||
export type MethodDefParamsDeclarationReflection = DeclarationReflection &
|
||||
WithType<
|
||||
WithReadonlyOperator & {
|
||||
target: TupleType & {
|
||||
elements: LiteralType[];
|
||||
};
|
||||
}
|
||||
>;
|
||||
/**
|
||||
* Utility; a TupleType with literal elements
|
||||
*/
|
||||
type TupleTypeWithLiteralElements = Merge<TupleType, {elements: LiteralType[]}>;
|
||||
|
||||
export type WithReadonlyOperator = TypeOperatorType & {operator: 'readonly'};
|
||||
/**
|
||||
* Type for the parameters of a command definition or execute method definition.
|
||||
*
|
||||
* Node that merging `TypeOperatorType` won't work because it will no longer satisfy `SomeType`, because `SomeType` is a finite collection.
|
||||
*/
|
||||
export type MethodDefParamsDeclarationReflection = WithSomeType<
|
||||
TypeOperatorType & {operator: 'readonly'; target: TupleTypeWithLiteralElements}
|
||||
>;
|
||||
|
||||
export type RoutePropDeclarationReflection = DeclarationReflectionWithReflectedType & {
|
||||
kind: ReflectionKind.Property;
|
||||
};
|
||||
/**
|
||||
* Narrows a declaration reflection to one having a reflection type and a property kind. Generic
|
||||
*/
|
||||
export type PropDeclarationReflection = Merge<
|
||||
DeclarationReflectionWithReflectedType,
|
||||
{kind: ReflectionKind.Property}
|
||||
>;
|
||||
|
||||
export type HTTPMethodDeclarationReflection = DeclarationReflectionWithReflectedType & {
|
||||
kind: ReflectionKind.Property;
|
||||
originalName: AllowedHttpMethod;
|
||||
};
|
||||
/**
|
||||
* A type corresponding to the HTTP method of a route, which is a property off of the object with the route name in a `MethodMap`
|
||||
*/
|
||||
export type HTTPMethodDeclarationReflection = Merge<
|
||||
PropDeclarationReflection,
|
||||
{originalName: AllowedHttpMethod}
|
||||
>;
|
||||
|
||||
export type DeclarationReflectionWithReflectedType = DeclarationReflection &
|
||||
WithType<ReflectionType>;
|
||||
/**
|
||||
* A declaration reflection having a reflection type. Generic
|
||||
*/
|
||||
export type DeclarationReflectionWithReflectedType = WithSomeType<ReflectionType>;
|
||||
|
||||
export type CommandPropDeclarationReflection = DeclarationReflection & WithType<LiteralType>;
|
||||
/**
|
||||
* Type corresponding to the value of the `command` property within a `MethodDef`, which must be a type literal.
|
||||
*/
|
||||
export type CommandPropDeclarationReflection = WithSomeType<LiteralType>;
|
||||
|
||||
/**
|
||||
* A generic type guard
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
/**
|
||||
* A bunch of type guards. Because here is a place to put all of them.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
DeclarationReflection,
|
||||
ReflectionType,
|
||||
TypeOperatorType,
|
||||
TupleType,
|
||||
LiteralType,
|
||||
IntrinsicType,
|
||||
ReflectionKind,
|
||||
Reflection,
|
||||
} from 'typedoc';
|
||||
@@ -21,32 +25,70 @@ import {
|
||||
HTTPMethodDeclarationReflection,
|
||||
MethodDefParamsDeclarationReflection,
|
||||
MethodMapDeclarationReflection,
|
||||
RoutePropDeclarationReflection,
|
||||
PropDeclarationReflection,
|
||||
} from './converter/types';
|
||||
import {AllowedHttpMethod} from './model';
|
||||
import {AllowedHttpMethod, ExecMethodData} from './model';
|
||||
|
||||
/**
|
||||
* Set of HTTP methods allowed by WebDriver; see {@linkcode AllowedHttpMethod}
|
||||
*/
|
||||
const ALLOWED_HTTP_METHODS: Readonly<Set<AllowedHttpMethod>> = new Set([
|
||||
'GET',
|
||||
'POST',
|
||||
'DELETE',
|
||||
] as const);
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode DeclarationReflection}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isDeclarationReflection(value: any): value is DeclarationReflection {
|
||||
return value instanceof DeclarationReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode ReflectionType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isReflectionType(value: any): value is ReflectionType {
|
||||
return value instanceof ReflectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode TypeOperatorType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isTypeOperatorType(value: any): value is TypeOperatorType {
|
||||
return value instanceof TypeOperatorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode LiteralType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isLiteralType(value: any): value is LiteralType {
|
||||
return value instanceof LiteralType;
|
||||
}
|
||||
export function isIntrinsicType(value: any): value is IntrinsicType {
|
||||
return value instanceof IntrinsicType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode TupleType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isTupleType(value: any): value is TupleType {
|
||||
return value instanceof TupleType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode DeclarationReflectionWithReflectedType} corresponding to
|
||||
* the `executeMethodMap` static property of an extension class.
|
||||
* @param value any
|
||||
*/
|
||||
export function isExecMethodDefReflection(
|
||||
value: any
|
||||
): value is DeclarationReflectionWithReflectedType {
|
||||
): value is DeclarationReflectionWithReflectedType & {
|
||||
name: typeof NAME_EXECUTE_METHOD_MAP;
|
||||
flags: {isStatic: true};
|
||||
} {
|
||||
return (
|
||||
isReflectionWithReflectedType(value) &&
|
||||
value.name === NAME_EXECUTE_METHOD_MAP &&
|
||||
@@ -54,6 +96,10 @@ export function isExecMethodDefReflection(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode MethodDefParamsDeclarationReflection} corresponding to a list of required or optional parameters within a command or execute method definition.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isParamsArray(value: any): value is MethodDefParamsDeclarationReflection {
|
||||
return (
|
||||
isDeclarationReflection(value) &&
|
||||
@@ -63,12 +109,18 @@ export function isParamsArray(value: any): value is MethodDefParamsDeclarationRe
|
||||
);
|
||||
}
|
||||
|
||||
export function isRoutePropDeclarationReflection(
|
||||
value: any
|
||||
): value is RoutePropDeclarationReflection {
|
||||
/**
|
||||
* Type guard for a {@linkcode PropDeclarationReflection} corresponding to some property of a constant object.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isRoutePropDeclarationReflection(value: any): value is PropDeclarationReflection {
|
||||
return isReflectionWithReflectedType(value) && isPropertyKind(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode BaseDriverDeclarationReflection} corresponding to the `@appium/base-driver` module (_not_ the class).
|
||||
* @param value any value
|
||||
*/
|
||||
export function isBaseDriverDeclarationReflection(
|
||||
value: any
|
||||
): value is BaseDriverDeclarationReflection {
|
||||
@@ -79,10 +131,21 @@ export function isBaseDriverDeclarationReflection(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a property of an object (a {@linkcode Reflection} having kind {@linkcode ReflectionKind.Property}).
|
||||
* @param value any value
|
||||
* @returns
|
||||
*/
|
||||
export function isPropertyKind(value: any) {
|
||||
return value instanceof Reflection && value.kindOf(ReflectionKind.Property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode MethodMapDeclarationReflection} corresponding to the `newMethodMap` static property of an extension class _or_ the `METHOD_MAP` export within `@appium/base-driver`.
|
||||
*
|
||||
* Note that the type does not care about the `isStatic` flag, but this guard does.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isMethodMapDeclarationReflection(
|
||||
value: any
|
||||
): value is MethodMapDeclarationReflection {
|
||||
@@ -92,13 +155,18 @@ export function isMethodMapDeclarationReflection(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode DeclarationReflectionWithReflectedType} a declaration reflection having a reflection type.
|
||||
*
|
||||
* I don't know what that means, exactly, but there it is.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isReflectionWithReflectedType(
|
||||
value: any
|
||||
): value is DeclarationReflectionWithReflectedType {
|
||||
return isDeclarationReflection(value) && isReflectionType(value.type);
|
||||
}
|
||||
|
||||
const ALLOWED_HTTP_METHODS = Object.freeze(new Set(['GET', 'POST', 'DELETE']));
|
||||
export function isHTTPMethodDeclarationReflection(
|
||||
value: any
|
||||
): value is HTTPMethodDeclarationReflection {
|
||||
@@ -109,12 +177,29 @@ export function isHTTPMethodDeclarationReflection(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for an {@linkcode AllowedHttpMethod}
|
||||
*
|
||||
* @param value any value
|
||||
*/
|
||||
export function isAllowedHTTPMethod(value: any): value is AllowedHttpMethod {
|
||||
return ALLOWED_HTTP_METHODS.has(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode CommandPropDeclarationReflection} corresponding to the `command` property of a {@linkcode @appium/types!MethodDef} object contained within a {@linkcode @appium/types!MethodMap}.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isCommandPropDeclarationReflection(
|
||||
value: any
|
||||
): value is CommandPropDeclarationReflection {
|
||||
return isDeclarationReflection(value) && isLiteralType(value.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode ExecMethodData} derived from a {@linkcode @appium/types!ExecuteMethodMap} object.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isExecMethodData(value: any): value is ExecMethodData {
|
||||
return value && typeof value === 'object' && value.script;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,17 @@
|
||||
import {format} from 'node:util';
|
||||
import {Logger, LogLevel} from 'typedoc';
|
||||
|
||||
const LogMethods = {
|
||||
[LogLevel.Error]: 'error',
|
||||
[LogLevel.Warn]: 'warn',
|
||||
[LogLevel.Info]: 'info',
|
||||
[LogLevel.Verbose]: 'verbose',
|
||||
} as const;
|
||||
/**
|
||||
* Mapping of TypeDoc {@linkcode LogLevel}s to method names.
|
||||
*/
|
||||
const LogMethods: Readonly<
|
||||
Map<LogLevel, keyof Pick<Logger, 'error' | 'warn' | 'info' | 'verbose'>>
|
||||
> = new Map([
|
||||
[LogLevel.Error, 'error'],
|
||||
[LogLevel.Warn, 'warn'],
|
||||
[LogLevel.Info, 'info'],
|
||||
[LogLevel.Verbose, 'verbose'],
|
||||
]);
|
||||
|
||||
export class AppiumPluginLogger extends Logger {
|
||||
/**
|
||||
@@ -142,10 +147,13 @@ export class AppiumPluginLogger extends Logger {
|
||||
if (this.#logThroughParent) {
|
||||
this.#logThroughParent(level, ns, message, ...args);
|
||||
} else {
|
||||
const parentMethod = LogMethods[level];
|
||||
const parentMethod = LogMethods.get(level)!;
|
||||
this.#parent[parentMethod](this.#formatMessage(ns, message, ...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ParentLogger = (level: LogLevel, message: string, ...args: any[]) => void;
|
||||
/**
|
||||
* Used internally by {@link AppiumPluginLogger.createChildLogger} to pass log messages to the parent.
|
||||
*/
|
||||
export type ParentLogger = (level: LogLevel, message: string, ...args: any[]) => void;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import {ExecCommandDataSet, RouteMap} from './types';
|
||||
import {ExecMethodDataSet, RouteMap} from './types';
|
||||
|
||||
/**
|
||||
* Data structure describing routes and commands for a particular module (or project)
|
||||
* Data structure describing routes and commands for a particular module (or project),
|
||||
* including execute methods (if any)
|
||||
*/
|
||||
export class CommandInfo {
|
||||
constructor(
|
||||
public readonly routeMap: RouteMap,
|
||||
public readonly execCommandDataSet: ExecCommandDataSet = new Set()
|
||||
public readonly execMethodDataSet: ExecMethodDataSet = new Set()
|
||||
) {}
|
||||
|
||||
/**
|
||||
* `true` if this instance has some actual data
|
||||
* Returns `true` if this instance has some actual data
|
||||
*/
|
||||
public get hasCommands() {
|
||||
return Boolean(this.execCommandDataSet.size + this.routeMap.size);
|
||||
public get hasData() {
|
||||
return Boolean(this.execMethodDataSet.size + this.routeMap.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,117 @@
|
||||
import {Comment} from 'typedoc';
|
||||
import {CommandData, ExecCommandData, ParentReflection, Route} from '../types';
|
||||
import {Comment, DeclarationReflection} from 'typedoc';
|
||||
import {isExecMethodData} from '../../guards';
|
||||
import {AllowedHttpMethod, CommandData, ExecMethodData, Route} from '../types';
|
||||
import {CommandsReflection} from './commands';
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
import {AppiumPluginReflection} from './plugin';
|
||||
|
||||
/**
|
||||
* The route will be this
|
||||
* Execute Methods all have the same route.
|
||||
*/
|
||||
export const NAME_EXECUTE_ROUTE = '/session/:sessionId/execute';
|
||||
|
||||
/**
|
||||
* Execute methods all have the same HTTP method.
|
||||
*/
|
||||
export const HTTP_METHOD_EXECUTE = 'POST';
|
||||
|
||||
export class CommandReflection extends AppiumPluginReflection {
|
||||
/**
|
||||
* A reflection containing data about a single command or execute method.
|
||||
*
|
||||
* Methods may be invoked directly by Handlebars templates.
|
||||
*/
|
||||
export class CommandReflection extends DeclarationReflection {
|
||||
/**
|
||||
* HTTP Method of the command or execute method
|
||||
*/
|
||||
public readonly httpMethod: string;
|
||||
|
||||
/**
|
||||
* Optional parameters, if any
|
||||
*/
|
||||
public readonly optionalParams: string[];
|
||||
|
||||
/**
|
||||
* Required parameters, if any
|
||||
*/
|
||||
public readonly requiredParams: string[];
|
||||
|
||||
/**
|
||||
* Route name
|
||||
*/
|
||||
public readonly route: Route;
|
||||
|
||||
/**
|
||||
* Script name, if any. Only used if kind is `EXECUTE_METHOD`
|
||||
*/
|
||||
public readonly script?: string;
|
||||
|
||||
/**
|
||||
* Comment, if any.
|
||||
*/
|
||||
public readonly comment?: Comment;
|
||||
|
||||
/**
|
||||
* Sets props depending on type of `data`
|
||||
* @param data Command or execute method data
|
||||
* @param parent Always a {@linkcode CommandsReflection}
|
||||
* @param route Route, if not an execute method
|
||||
*/
|
||||
constructor(
|
||||
readonly commandRef: CommandData | ExecCommandData,
|
||||
readonly data: CommandData | ExecMethodData,
|
||||
parent: CommandsReflection,
|
||||
route: Route = NAME_EXECUTE_ROUTE
|
||||
route?: Route
|
||||
) {
|
||||
let name: string;
|
||||
let kind: AppiumPluginReflectionKind;
|
||||
let script: string | undefined;
|
||||
let httpMethod: AllowedHttpMethod;
|
||||
|
||||
if (CommandReflection.isExecCommandData(commandRef)) {
|
||||
name = commandRef.script;
|
||||
kind = AppiumPluginReflectionKind.EXECUTE_COMMAND;
|
||||
// common data
|
||||
const {requiredParams, optionalParams, comment} = data;
|
||||
|
||||
// kind-specific data
|
||||
if (isExecMethodData(data)) {
|
||||
script = name = data.script;
|
||||
kind = AppiumPluginReflectionKind.EXECUTE_METHOD;
|
||||
route = NAME_EXECUTE_ROUTE;
|
||||
httpMethod = HTTP_METHOD_EXECUTE;
|
||||
} else {
|
||||
if (!route) {
|
||||
throw new TypeError('"route" arg is required for a non-execute-method command');
|
||||
}
|
||||
name = route;
|
||||
kind = AppiumPluginReflectionKind.COMMAND;
|
||||
httpMethod = data.httpMethod;
|
||||
}
|
||||
|
||||
super(name, kind as any, parent);
|
||||
|
||||
this.route = route;
|
||||
this.httpMethod = 'httpMethod' in commandRef ? commandRef.httpMethod : HTTP_METHOD_EXECUTE;
|
||||
this.requiredParams = commandRef.requiredParams ?? [];
|
||||
this.optionalParams = commandRef.optionalParams ?? [];
|
||||
this.script = CommandReflection.isExecCommandData(commandRef) ? commandRef.script : undefined;
|
||||
this.comment = commandRef.comment;
|
||||
this.httpMethod = httpMethod;
|
||||
this.requiredParams = requiredParams ?? [];
|
||||
this.optionalParams = optionalParams ?? [];
|
||||
this.script = script;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command has required parameters
|
||||
*/
|
||||
public get hasRequiredParams(): boolean {
|
||||
return Boolean(this.requiredParams.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command has optional parameters
|
||||
*/
|
||||
public get hasOptionalParams(): boolean {
|
||||
return Boolean(this.optionalParams.length);
|
||||
}
|
||||
|
||||
public get isExecuteCommand(): boolean {
|
||||
return Boolean(this.script && this.route === NAME_EXECUTE_ROUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for execute command refs
|
||||
* @param ref Command reference
|
||||
* @returns `true` if it's an execute command
|
||||
* If `true`, this command contains data about an execute method
|
||||
*/
|
||||
public static isExecCommandData(ref: CommandData | ExecCommandData): ref is ExecCommandData {
|
||||
return 'script' in ref;
|
||||
public get isExecuteMethod(): boolean {
|
||||
return this.kindOf(AppiumPluginReflectionKind.EXECUTE_METHOD as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import {DeclarationReflection} from 'typedoc';
|
||||
import {CommandInfo} from '../command-info';
|
||||
import {ExecCommandDataSet, ParentReflection, RouteMap} from '../types';
|
||||
|
||||
import {ExecMethodDataSet, ParentReflection, RouteMap} from '../types';
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
import {AppiumPluginReflection} from './plugin';
|
||||
|
||||
/**
|
||||
* A Reflection representing a set of commands within a module or project
|
||||
* A reflection containing data about commands and/or execute methods.
|
||||
*
|
||||
* Methods may be invoked directly by Handlebars templates.
|
||||
*/
|
||||
export class CommandsReflection extends AppiumPluginReflection {
|
||||
export class CommandsReflection extends DeclarationReflection {
|
||||
/**
|
||||
* A set of objects
|
||||
* Info about execute methods
|
||||
*/
|
||||
public readonly execMethodDataSet: ExecMethodDataSet;
|
||||
/**
|
||||
* Info about routes/commands
|
||||
*/
|
||||
public readonly execCommandDataSet: ExecCommandDataSet;
|
||||
public readonly routeMap: RouteMap;
|
||||
|
||||
constructor(name: string, parent: ParentReflection, commands: CommandInfo) {
|
||||
constructor(name: string, parent: ParentReflection, {routeMap, execMethodDataSet}: CommandInfo) {
|
||||
super(name, AppiumPluginReflectionKind.COMMANDS as any, parent);
|
||||
this.parent = parent;
|
||||
this.routeMap = commands.routeMap;
|
||||
this.execCommandDataSet = commands.execCommandDataSet;
|
||||
this.routeMap = routeMap;
|
||||
this.execMethodDataSet = execMethodDataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,8 +30,8 @@ export class CommandsReflection extends AppiumPluginReflection {
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasExecuteCommands(): boolean {
|
||||
return Boolean(this.execCommandDataSet.size);
|
||||
public get hasExecuteMethod(): boolean {
|
||||
return Boolean(this.execMethodCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +39,21 @@ export class CommandsReflection extends AppiumPluginReflection {
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasRoutes(): boolean {
|
||||
return Boolean(this.routeMap.size);
|
||||
public get hasRoute(): boolean {
|
||||
return Boolean(this.routeCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of routes ("commands") in this in this data
|
||||
*/
|
||||
public get routeCount(): number {
|
||||
return this.routeMap.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of execute methods in this data
|
||||
*/
|
||||
public get execMethodCount(): number {
|
||||
return this.execMethodDataSet.size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './command';
|
||||
export * from './commands';
|
||||
export * from './kind';
|
||||
export * from './plugin';
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
/**
|
||||
* Declares new "kinds" within TypeDoc.
|
||||
*
|
||||
* A "kind" is a way for TypeDoc to understand how to document something. Mostly, these have a 1:1 relationship with some sort of TypeScript concept. This is unsuitable for our purposes, since there's no notion of a "command" or "execute method" in TypeScript. To that end, we must create new ones.
|
||||
*
|
||||
* Note that _creating new `ReflectionKind`s is a hack_ and is not supported by TypeDoc. This is the reason you will see `as any` wherever a {@linkcode AppiumPluginReflectionKind} is used.
|
||||
*/
|
||||
|
||||
import {addReflectionKind} from './utils';
|
||||
|
||||
/**
|
||||
* Namespace for our reflection kinds
|
||||
* Namespace for our reflection kinds.
|
||||
*
|
||||
* The only reason we use a namespace is to avoid conflicts with TypeDoc or other plugins monkeying around in the "kind" system.
|
||||
*/
|
||||
export const NS = 'appium';
|
||||
|
||||
@@ -11,6 +21,6 @@ export const NS = 'appium';
|
||||
export enum AppiumPluginReflectionKind {
|
||||
COMMANDS = addReflectionKind(NS, 'Commands'),
|
||||
COMMAND = addReflectionKind(NS, 'Command'),
|
||||
EXECUTE_COMMAND = addReflectionKind(NS, 'ExecuteCommand'),
|
||||
ANY = addReflectionKind(NS, 'Any', COMMAND | EXECUTE_COMMAND | COMMANDS),
|
||||
EXECUTE_METHOD = addReflectionKind(NS, 'ExecuteMethod'),
|
||||
ANY = addReflectionKind(NS, 'Any', COMMAND | EXECUTE_METHOD | COMMANDS),
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import {DeclarationReflection} from 'typedoc';
|
||||
|
||||
/**
|
||||
* This exists just to allow `instanceof` checks
|
||||
*/
|
||||
export abstract class AppiumPluginReflection extends DeclarationReflection {}
|
||||
@@ -4,6 +4,7 @@
|
||||
* Copyright (c) 2022 KnodesCommunity
|
||||
* Licensed MIT
|
||||
* @see https://github.com/KnodesCommunity/typedoc-plugins/blob/05717565fae14357b1c4be8122f3156e1d46d332/LICENSE
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {ReflectionKind} from 'typedoc';
|
||||
|
||||
@@ -32,7 +32,7 @@ export type CommandMap = Map<Command, CommandData>;
|
||||
export type ModuleCommands = Map<ParentReflection, CommandInfo>;
|
||||
|
||||
/**
|
||||
* Common fields for a {@linkcode CommandData} or {@linkcode ExecCommandData}
|
||||
* Common fields for a {@linkcode CommandData} or {@linkcode ExecMethodData}
|
||||
*/
|
||||
export interface BaseCommandData {
|
||||
/**
|
||||
@@ -76,7 +76,7 @@ export interface CommandData extends BaseCommandData {
|
||||
*
|
||||
* All of these share the same `execute` route, so it is omitted from this interface.
|
||||
*/
|
||||
export interface ExecCommandData extends BaseCommandData {
|
||||
export interface ExecMethodData extends BaseCommandData {
|
||||
script: string;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,6 @@ export type ParentReflection = DeclarationReflection | ProjectReflection;
|
||||
export type RouteMap = Map<Route, CommandMap>;
|
||||
|
||||
/**
|
||||
* A set of {@linkcode ExecCommandData} objects
|
||||
* A set of {@linkcode ExecMethodData} objects
|
||||
*/
|
||||
export type ExecCommandDataSet = Set<ExecCommandData>;
|
||||
export type ExecMethodDataSet = Set<ExecMethodData>;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './theme';
|
||||
@@ -1,61 +0,0 @@
|
||||
import {ContainerReflection, PageEvent, Renderer} from 'typedoc';
|
||||
import {MarkdownTheme} from 'typedoc-plugin-markdown';
|
||||
import {AppiumPluginLogger} from '../../logger';
|
||||
import {AppiumPluginReflectionKind} from '../../model';
|
||||
import {compileTemplate, registerHelpers, Template} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the theme; used at definition time
|
||||
*/
|
||||
export const THEME_NAME = 'appium';
|
||||
|
||||
/**
|
||||
* Factory for `AppiumTheme` class; needs custom logger otherwise inaccessible
|
||||
* @param log - Custom logger
|
||||
* @returns `AppiumTheme` class
|
||||
*/
|
||||
export function getTheme(log: AppiumPluginLogger): new (renderer: Renderer) => MarkdownTheme {
|
||||
return class AppiumTheme extends MarkdownTheme {
|
||||
#log = log.createChildLogger('theme');
|
||||
|
||||
#commandsTemplateRenderer: TemplateRenderer;
|
||||
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
|
||||
this.#commandsTemplateRenderer = this.#getTemplate(Template.Commands);
|
||||
|
||||
// the intent is to have mkdocs render breadcrumbs
|
||||
this.hideBreadcrumbs = true;
|
||||
|
||||
// this ensures we can overwrite MarkdownTheme's Handlebars helpers
|
||||
registerHelpers();
|
||||
}
|
||||
|
||||
public override get mappings() {
|
||||
return [
|
||||
{
|
||||
kind: [AppiumPluginReflectionKind.COMMANDS as any],
|
||||
isLeaf: true,
|
||||
directory: 'commands',
|
||||
template: this.#commandsTemplateRenderer,
|
||||
},
|
||||
...super.mappings,
|
||||
];
|
||||
}
|
||||
|
||||
#getTemplate(template: Template): TemplateRenderer {
|
||||
const render = compileTemplate(template);
|
||||
return (pageEvent: PageEvent<ContainerReflection>) => {
|
||||
this.#log.verbose('Rendering template for model %s', pageEvent.model.name);
|
||||
return render(pageEvent, {
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowProtoPropertiesByDefault: true,
|
||||
data: {theme: this},
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type TemplateRenderer = (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './types';
|
||||
export * from './appium';
|
||||
@@ -1,14 +0,0 @@
|
||||
import {RenderTemplate, RendererEvent, Theme} from 'typedoc';
|
||||
import {CommandReflection} from '../../model';
|
||||
|
||||
export type RenderCommandLinkProps = {page: CommandReflection; label?: string};
|
||||
export interface IAppiumPluginThemeMethods {
|
||||
renderPageLink: RenderTemplate<RenderCommandLinkProps>;
|
||||
}
|
||||
export interface IAppiumPluginTheme extends Theme {
|
||||
appiumPlugin(event: RendererEvent): IAppiumPluginThemeMethods;
|
||||
}
|
||||
|
||||
export function isAppiumPluginTheme(theme: Theme): theme is IAppiumPluginTheme {
|
||||
return 'appiumPlugin' in theme;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import Handlebars from 'handlebars';
|
||||
import {ContainerReflection, PageEvent, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginReflectionKind} from '../../model';
|
||||
|
||||
const RESOURCES_PATH = path.join(__dirname, '..', '..', '..', 'resources');
|
||||
const TEMPLATE_PATH = path.join(RESOURCES_PATH, 'templates');
|
||||
const PARTIALS_PATH = path.join(RESOURCES_PATH, 'partials');
|
||||
|
||||
const Partials = {
|
||||
command: 'command.hbs',
|
||||
executeCommand: 'execute-command.hbs',
|
||||
} as const;
|
||||
|
||||
function registerPartials() {
|
||||
for (const [name, filename] of Object.entries(Partials)) {
|
||||
Handlebars.registerPartial(name, fs.readFileSync(path.join(PARTIALS_PATH, filename), 'utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
registerPartials();
|
||||
|
||||
export enum Template {
|
||||
Commands = 'commands.hbs',
|
||||
}
|
||||
|
||||
export const compileTemplate = _.memoize((template: Template) => {
|
||||
const templatePath = path.join(TEMPLATE_PATH, template);
|
||||
return Handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
||||
});
|
||||
|
||||
export function registerHelpers() {
|
||||
Handlebars.registerHelper('reflectionPath', function (this: PageEvent<ContainerReflection>) {
|
||||
if (this.model) {
|
||||
if (this.model.kind && this.model.kind !== ReflectionKind.Module) {
|
||||
if (this.model.kind === (AppiumPluginReflectionKind.COMMANDS as any)) {
|
||||
return `${this.model.name} Commands`;
|
||||
}
|
||||
const title: string[] = [];
|
||||
if (this.model.parent && this.model.parent.parent) {
|
||||
if (this.model.parent.parent.parent) {
|
||||
title.push(
|
||||
`[${this.model.parent.parent.name}](${Handlebars.helpers.relativeURL(
|
||||
this.model?.parent?.parent.url
|
||||
)})`
|
||||
);
|
||||
}
|
||||
title.push(
|
||||
`[${this.model.parent.name}](${Handlebars.helpers.relativeURL(this.model.parent.url)})`
|
||||
);
|
||||
}
|
||||
title.push(this.model.name);
|
||||
return title.length > 1 ? `${title.join('.')}` : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Application, Context, Converter} from 'typedoc';
|
||||
import {convertCommands, createReflections} from './converter';
|
||||
import {AppiumPluginLogger} from './logger';
|
||||
import {getTheme, THEME_NAME} from './output';
|
||||
import {AppiumTheme, THEME_NAME} from './theme';
|
||||
|
||||
/**
|
||||
* Loads the Appium TypeDoc plugin
|
||||
@@ -11,7 +11,7 @@ export function load(app: Application) {
|
||||
const log = new AppiumPluginLogger(app.logger, 'appium');
|
||||
|
||||
// register our custom theme. the user still has to choose it
|
||||
app.renderer.defineTheme(THEME_NAME, getTheme(log));
|
||||
app.renderer.defineTheme(THEME_NAME, AppiumTheme);
|
||||
|
||||
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (ctx: Context) => {
|
||||
// we don't want to do this work if we're not using the custom theme!
|
||||
@@ -22,6 +22,10 @@ export function load(app: Application) {
|
||||
// this creates new custom reflections from the data we gathered and registers them
|
||||
// with TypeDoc
|
||||
createReflections(ctx, log, projectCommands);
|
||||
} else {
|
||||
log.warn('Not using the Appium theme; skipping command reflection creation');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export * from './theme';
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Custom Handlebars helpers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Handlebars from 'handlebars';
|
||||
import {PageEvent, ContainerReflection, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginReflectionKind} from '../model';
|
||||
import plural from 'pluralize';
|
||||
|
||||
/**
|
||||
* Overwrites {@linkcode typedoc-plugin-markdown!MarkdownTheme}'s `reflectionPath` helper to handle {@linkcode AppiumPluginReflectionKind} reflection kinds
|
||||
* @param this Page event
|
||||
* @returns Reflection path, if any
|
||||
*/
|
||||
function reflectionPath(this: PageEvent<ContainerReflection>) {
|
||||
if (this.model) {
|
||||
if (this.model.kind && this.model.kind !== ReflectionKind.Module) {
|
||||
if (this.model.kind === (AppiumPluginReflectionKind.COMMANDS as any)) {
|
||||
return `${this.model.name} Commands`;
|
||||
}
|
||||
const title: string[] = [];
|
||||
if (this.model.parent && this.model.parent.parent) {
|
||||
if (this.model.parent.parent.parent) {
|
||||
title.push(
|
||||
`[${this.model.parent.parent.name}](${Handlebars.helpers.relativeURL(
|
||||
this.model?.parent?.parent.url
|
||||
)})`
|
||||
);
|
||||
}
|
||||
title.push(
|
||||
`[${this.model.parent.name}](${Handlebars.helpers.relativeURL(this.model.parent.url)})`
|
||||
);
|
||||
}
|
||||
title.push(this.model.name);
|
||||
return title.length > 1 ? `${title.join('.')}` : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to "pluralize" a string.
|
||||
* @param value String to pluralize
|
||||
* @param count Number of items to consider
|
||||
* @param inclusive Whether to show the count in the output
|
||||
* @returns The pluralized string (if necessary)
|
||||
*/
|
||||
function pluralize(value: string, count: number, inclusive: boolean = false) {
|
||||
const safeValue = Handlebars.escapeExpression(value);
|
||||
// XXX: Handlebars seems to be passing in a truthy value here, even if the arg is unused in the template! Make double-sure it's a boolean.
|
||||
inclusive = inclusive === true;
|
||||
const pluralValue = plural(safeValue, count, inclusive);
|
||||
return new Handlebars.SafeString(pluralValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all custom helpers with Handlebars
|
||||
*/
|
||||
export function registerHelpers() {
|
||||
Handlebars.registerHelper('reflectionPath', reflectionPath);
|
||||
Handlebars.registerHelper('pluralize', pluralize);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import {ContainerReflection, PageEvent, ReflectionKind, Renderer} from 'typedoc';
|
||||
import {MarkdownTheme} from 'typedoc-plugin-markdown';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {AppiumPluginReflectionKind, NS} from '../model';
|
||||
import {registerHelpers} from './helpers';
|
||||
import {compileTemplate, AppiumThemeTemplate} from './template';
|
||||
|
||||
/**
|
||||
* Name of the theme; used at definition time
|
||||
*/
|
||||
export const THEME_NAME = 'appium';
|
||||
|
||||
export class AppiumTheme extends MarkdownTheme {
|
||||
/**
|
||||
* A template renderer for `CommandReflection`s
|
||||
*/
|
||||
#commandsTemplateRenderer: TemplateRenderer;
|
||||
|
||||
/**
|
||||
* Custom logger. This is not the same as the one created by the plugin loader.
|
||||
*/
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* Creates template renderers and registers all {@linkcode Handlebars} helpers.
|
||||
* @param renderer - TypeDoc renderer
|
||||
*
|
||||
* @todo Make `hideBreadcrumbs` configurable
|
||||
*/
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
|
||||
// ideally, this would be a child of the logger created by the `load()` function,
|
||||
// but I don't know how to get at it.
|
||||
this.#log = new AppiumPluginLogger(renderer.owner.logger, `${NS}:theme`);
|
||||
|
||||
this.#commandsTemplateRenderer = this.#createTemplateRenderer(AppiumThemeTemplate.Commands);
|
||||
|
||||
// the intent is to have mkdocs render breadcrumbs
|
||||
this.hideBreadcrumbs = true;
|
||||
|
||||
// this ensures we can overwrite MarkdownTheme's Handlebars helpers
|
||||
registerHelpers();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is essentially a lookup of {@linkcode ReflectionKind}s to templates. It also controls in which directory the output files live.
|
||||
*
|
||||
* If `isLeaf` is `false`, the model gets its own document.
|
||||
*/
|
||||
public override get mappings(): TemplateMapping[] {
|
||||
return [
|
||||
{
|
||||
kind: [AppiumPluginReflectionKind.COMMANDS as any],
|
||||
isLeaf: false,
|
||||
directory: 'commands',
|
||||
template: this.#commandsTemplateRenderer,
|
||||
},
|
||||
...super.mappings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a {@linkcode AppiumThemeTemplate} return a function which will render the template
|
||||
* given some data.
|
||||
* @param template Template to render
|
||||
* @returns Rendering function
|
||||
*/
|
||||
#createTemplateRenderer(template: AppiumThemeTemplate): TemplateRenderer {
|
||||
const render = compileTemplate(template);
|
||||
return (pageEvent: PageEvent<ContainerReflection>) => {
|
||||
this.#log.verbose('Rendering template for model %s', pageEvent.model.name);
|
||||
return render(pageEvent, {
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowProtoPropertiesByDefault: true,
|
||||
data: {theme: this},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which accepts {@linkcode PageEvent} as its model and returns the final markdown.
|
||||
*/
|
||||
export type TemplateRenderer = (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
|
||||
/**
|
||||
* A mapping of {@linkcode ReflectionKind} to a template and other metadata.
|
||||
*
|
||||
* Defined by {@linkcode MarkdownTheme}.
|
||||
* @public
|
||||
*/
|
||||
export type TemplateMapping = {
|
||||
kind: ReflectionKind[];
|
||||
isLeaf: boolean;
|
||||
directory: string;
|
||||
template: (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Handlebars template & partial helpers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Handlebars from 'handlebars';
|
||||
import _ from 'lodash';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
/**
|
||||
* Path to resources directory, containing all templates and partials.
|
||||
*/
|
||||
const RESOURCES_PATH = path.join(__dirname, '..', '..', 'resources');
|
||||
|
||||
/**
|
||||
* Path to templates directory within {@linkcode RESOURCES_PATH}
|
||||
*/
|
||||
const TEMPLATE_PATH = path.join(RESOURCES_PATH, 'templates');
|
||||
|
||||
/**
|
||||
* Path to partials directory within {@linkcode RESOURCES_PATH}
|
||||
*/
|
||||
const PARTIALS_PATH = path.join(RESOURCES_PATH, 'partials');
|
||||
|
||||
/**
|
||||
* Enum of all available partials
|
||||
*/
|
||||
enum AppiumThemePartial {
|
||||
command = 'command.hbs',
|
||||
executeMethod = 'execute-command.hbs',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of all available templates
|
||||
*/
|
||||
export enum AppiumThemeTemplate {
|
||||
/**
|
||||
* Template to render a list of commands
|
||||
*/
|
||||
Commands = 'commands.hbs',
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all partials found in {@linkcode PARTIALS_PATH} with {@linkcode Handlebars}.
|
||||
*
|
||||
* This is executed immediately upon loading this module.
|
||||
*/
|
||||
function registerPartials() {
|
||||
for (const [name, filename] of Object.entries(AppiumThemePartial)) {
|
||||
console.log('registerPartials:', name, filename);
|
||||
Handlebars.registerPartial(name, fs.readFileSync(path.join(PARTIALS_PATH, filename), 'utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
registerPartials();
|
||||
|
||||
/**
|
||||
* Compiles a {@linkcode AppiumThemeTemplate}.
|
||||
*/
|
||||
export const compileTemplate = _.memoize((template: AppiumThemeTemplate) => {
|
||||
const templatePath = path.join(TEMPLATE_PATH, template);
|
||||
return Handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
||||
});
|
||||
@@ -54,6 +54,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.7",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.2.0",
|
||||
"typedoc-plugin-markdown": "3.13.6"
|
||||
}
|
||||
|
||||
@@ -18,20 +18,20 @@
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#if hasRoutes}}
|
||||
## Routes
|
||||
{{#if hasRoute}}
|
||||
## {{pluralize 'Route' routeCount}}
|
||||
{{#each children}}
|
||||
{{#unless isExecuteCommand}}
|
||||
{{#unless isExecuteMethod}}
|
||||
{{> command}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasExecuteCommands}}
|
||||
## Execute Scripts
|
||||
{{#if hasExecuteMethod}}
|
||||
## Execute {{pluralize 'Method' execMethodCount}}
|
||||
{{#each children}}
|
||||
{{#if isExecuteCommand}}
|
||||
{{> executeCommand}}
|
||||
{{#if isExecuteMethod}}
|
||||
{{> executeMethod}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"./packages/fake-driver",
|
||||
"./packages/typedoc-plugin-appium"
|
||||
],
|
||||
"excludeInternal": true,
|
||||
"includeVersion": false,
|
||||
"name": "Appium",
|
||||
"out": "typedoc-docs",
|
||||
|
||||
Reference in New Issue
Block a user