typedoc-theme-yaf
Version:
A fresh, opinionated and standalone front-end documentation application consuming Typedoc generated data.
549 lines • 25.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.YafSerialiser = void 0;
const typedoc_1 = require("typedoc");
const typedoc_2 = require("typedoc");
/**
* Serialises the standard TypeDoc data output into `typedoc-theme-yaf` frontend compatible data.
*/
class YafSerialiser {
/**
* An array of serialised data objects, each of which is the data for a page in the documentation frontend.
*/
dataObjectArray;
/**
*
* @param object The TypeDoc JSONOutput.ProjectReflection before it gets serialised.
* @param reflection The Typedoc ProjectReflection.
* @param context The `typedoc-theme-yaf` modified render context.
*/
constructor(object, reflection, context) {
const { parseDataObjectToArray, parseReflectionToYafDataObject } = YafSerialiser.parserFactory;
const { fixMutateSignatureLinks } = YafSerialiser.fixerFactory;
const dataObject = parseReflectionToYafDataObject(object, reflection, context, this);
this.dataObjectArray = parseDataObjectToArray(dataObject);
this.dataObjectArray.forEach((objectReflection) => fixMutateSignatureLinks(this.dataObjectArray, objectReflection));
}
/**
* A collection of static data serialiser methods
*
* @group Factories
*/
static serialiseFactory = {
/**
* Serialises a standard TypeDoc reflection into a `typedoc-theme-yaf` reflection
*
* @param object The standard TypeDoc JSONOutput object before it is serialised.
* @param reflection The unserialised TypeDoc `Reflection` object
* @param context
* @param rootReflection A standard TypeDoc `Reflection` for the top level of a page. It provides the context for child and signature reflections.
* @param hashPrefix Keeps track of the location hash as reflections recurse into children
* @returns
*/
serializeYafDataObject: (object, reflection, context, rootReflection, hashPrefix) => {
const { mutateComment, mutateReadme } = this.mutationFactory;
const { serialiseHierarchy, serialiseReflectionLocation, extendDefaultFlags, } = this.serialiseFactory;
const { fixObjectCallKind } = this.fixerFactory;
['is', 'location', 'text'].forEach((key) => !object[key] && (object[key] = {}));
const parentReflection = rootReflection || reflection.parent;
const { hash, query } = serialiseReflectionLocation(reflection, hashPrefix, parentReflection);
object.sources = reflection.sources;
object.parentId =
parentReflection && parentReflection.id
? parentReflection.id
: undefined;
object.is.declaration = reflection instanceof typedoc_2.DeclarationReflection;
object.location.query = query;
object.location.hash = hash;
if (reflection.version)
object.version = reflection.version;
object.flags = extendDefaultFlags(object);
if (reflection.typeHierarchy)
object.hierarchy = serialiseHierarchy(reflection.typeHierarchy);
mutateComment(object, context);
if (reflection.readme)
mutateReadme(object, reflection, context);
if (object.getSignature)
mutateComment(object.getSignature, context);
if (object.setSignature)
mutateComment(object.setSignature, context);
return fixObjectCallKind(object);
},
/**
* Formats data regarding the URL location of the reflection data.
* @param reflection
* @returns a data object which will extend the typedoc `JSONOutput` reflection
*
* @param reflection
* @param hashPrefix
* @param rootReflection
* @returns
*/
serialiseReflectionLocation: (reflection, hashPrefix, rootReflection) => {
if (!rootReflection)
return { hash: '', query: 'index' };
const isPage = this.utilities.isPage(reflection.kind);
const locationString = isPage
? reflection.getFriendlyFullName()
: `${rootReflection.getFriendlyFullName()}.${reflection.name}`;
const locationArray = locationString.split('.');
const hash = !isPage ? locationArray.pop() : '';
const kindId = hash ? rootReflection.kind : reflection.kind;
const kind = typedoc_2.ReflectionKind[kindId];
locationArray.splice(locationArray.length - 1, 0, kind);
const query = locationArray.join('.');
return {
hash: hashPrefix ? `${hashPrefix}.${hash}` : hash,
query,
};
},
/**
* Serialises the standard TypeDoc reflection hierarchy into a `typedoc-theme-yaf`
* compatible object.
*
* @param hierarchy
* @returns
*/
serialiseHierarchy: (hierarchy) => {
if (!hierarchy)
return undefined;
const { serialiseHierarchy } = this.serialiseFactory;
const parsedHierarchy = [];
hierarchy.types.forEach((type, i, l) => {
const hierarchyItem = {
name: type.toString(),
isTarget: hierarchy.isTarget,
linkId: typeof type['_target'] === 'number'
? String(type['_target'])
: undefined,
};
if (i === l.length - 1 && !!hierarchy.next)
hierarchyItem.children = serialiseHierarchy(hierarchy.next);
parsedHierarchy.push(hierarchyItem);
});
return parsedHierarchy;
},
/**
* Adds a new `isInherited` flag to the standard TypeDoc flags.
* @param reflectionObject
* @returns
*/
extendDefaultFlags: (reflectionObject) => {
const { flags } = reflectionObject;
if (reflectionObject.inheritedFrom &&
typedoc_2.ReflectionKind[reflectionObject.kind] !== 'Constructor') {
flags['isInherited'] = true;
}
if (reflectionObject.overwrites)
flags['isOverride'] = true;
return flags;
},
};
/**
* A collection of methods which perform data serialisations by means of data mutation for various good enough reasons.
*
* @group Factories
*/
static mutationFactory = {
/**
* Performs MarkDown parsing on a reflection readme.
*
* @param object
* @param reflection
* @param context
*/
mutateReadme: (object, reflection, context) => {
if (!object.text)
object.text = {};
if (reflection.readme) {
object.text.readme = context.markdown(reflection.readme);
}
},
/**
* Performs MarkDown parsing on a reflection comment.
*
* @param object
* @param context
*/
mutateComment: (object, context) => {
const { mutateComment } = this.mutationFactory;
const { hasVisibleTextComponent: hasVisibleComponent } = this.utilities;
if (!object.text)
object.text = {};
if (hasVisibleComponent(object) && !!object.comment) {
object.text.comment = context.markdown(mapComment(object.comment.summary));
object.comment.blockTags?.forEach((tag) => {
const tagString = tag.tag.charAt(1).toLocaleUpperCase() +
tag.tag.substring(2).toLocaleLowerCase();
object.text.comment += `<h5>${tagString}:</h5>`;
object.text.comment += context.markdown(mapComment(tag.content));
});
}
const parameters = object.parameters;
if (parameters)
parameters.forEach((parameter) => mutateComment(parameter, context));
function mapComment(contentArray) {
return contentArray.map((part) => {
if ('tag' in part && part.tag === '@link') {
return {
kind: 'text',
text: `[${part.text}](${part.target})`,
};
}
return part;
});
}
},
};
/**
* A collection of high high level methods to parse the standard TypeDoc data into the desired `typedoc-theme-yaf` outputs.
*
* @group Factories
*/
static parserFactory = {
/**
* Parses the standard TypeDoc data output into a `typedoc-theme-yaf` serialised object.
* @param object
* @param reflection
* @param context
* @param serialiser
* @param hashPrefix
* @param rootReflection
* @returns
*/
parseReflectionToYafDataObject: (object, reflection, context, serialiser, hashPrefix, rootReflection) => {
const { serializeYafDataObject } = this.serialiseFactory;
const { parseReflectionToYafDataObject } = this.parserFactory;
const { fixMutateDeclarationReflections, fixMutateReflectionGroups, fixDeclarationSignatures, } = this.fixerFactory;
object = serializeYafDataObject(object, reflection, context, rootReflection, hashPrefix);
if (object.type && 'declaration' in object.type) {
const parentKind = reflection.parent.kind;
const { Class, Interface, ClassOrInterface } = typedoc_2.ReflectionKind;
if (!rootReflection &&
[Class, Interface, ClassOrInterface].includes(parentKind)) {
rootReflection = reflection.parent;
}
fixMutateDeclarationReflections(object, reflection, serialiser, context, rootReflection);
fixMutateReflectionGroups(object);
}
if (reflection.signatures) {
object.signatures = fixDeclarationSignatures(object, reflection, serialiser, context, rootReflection);
}
if (object.groups) {
object.groups.forEach((group) => {
group.children = group.children?.filter((id) => !!object.children?.find((child) => child.id === id));
});
}
object.children?.forEach((objectChild) => {
const reflectionChild = reflection.children?.find((reflectionChild) => reflectionChild.id === objectChild.id);
parseReflectionToYafDataObject(objectChild, reflectionChild, context, serialiser, object.location.hash, rootReflection);
});
return object;
},
/**
* Flattens the extended JSONOutput project into an array of reflections.
*
* @param projectArray
* @param object
* @returns an array of extended JSONOutput reflections
*/
parseDataObjectToArray: (object, projectArray = []) => {
const { parseDataObjectToArray } = this.parserFactory;
const { isPage } = this.utilities;
const thisChildren = [];
const newPages = [];
object.children?.forEach((child) => {
isPage(child.kind)
? newPages.push(child)
: thisChildren.push(child);
});
object.children = thisChildren;
projectArray.push(object);
newPages.forEach((child) => {
parseDataObjectToArray(child, projectArray);
});
return projectArray;
},
};
/**
* Ladies and Gents, hang onto your purses and wallets caus' the fixer is gonna fix your
* standard typeDoc JSON into a super recursive frontend object!!
*
* @group Factories
*/
static fixerFactory = {
/**
* The standard TypeDoc JSON signature output gets named as "__type".
*
* This function serialises the signature into the YAF frontend format with the corrected name.
*
* @param object
* @param reflection
* @param serializer
* @param context
* @param rootReflection
* @returns
*/
fixDeclarationSignatures: (object, reflection, serializer, context, rootReflection) => {
const { signatures: objectSignatures } = object;
const { parseReflectionToYafDataObject } = this.parserFactory;
const { extendDefaultFlags } = this.serialiseFactory;
const { fixMutateSignatureReferences } = this.fixerFactory;
if (!objectSignatures)
return undefined;
const fixedSignatures = objectSignatures?.map((objectSignature) => {
objectSignature.name = object.name;
const reflectionSignature = reflection.signatures?.find((reflectionSignature) => reflectionSignature.id == objectSignature.id);
fixMutateSignatureReferences(objectSignature, reflectionSignature);
objectSignature.flags = extendDefaultFlags(objectSignature);
objectSignature = parseReflectionToYafDataObject(objectSignature, reflectionSignature, context, serializer, object.location.hash, rootReflection || reflection);
return objectSignature;
});
return fixedSignatures;
},
/**
* The TypeDoc serialiser cannot track the id's of reflections across workspace packages in
* a [monorepo](https://typedoc.org/guides/monorepo/) entryPoint strategy.
*
* The result is that documentation cross linkages for parameter types are lost.
*
* This method examines the type reflection and adds a reference to the file context, which identifies the package.\
* `typedoc-theme-yaf` can the forensically find the target id when it later parses the links.
*
* @param objectSignature
* @param reflectionSignature
*/
fixMutateSignatureReferences: (objectSignature, reflectionSignature) => {
reflectionSignature.parameters.forEach((reflectionParameter, i) => {
const objectParameter = objectSignature.parameters[i];
const { type: reflectionType } = reflectionParameter;
const { type: objectType } = objectParameter;
parseType(objectType, reflectionType);
});
if (reflectionSignature.type) {
parseType(objectSignature.type, reflectionSignature.type);
}
function parseType(objectType, reflectionType) {
if (reflectionType && 'types' in reflectionType) {
const reflectionTypes = reflectionType.types;
const objectTypes = objectType['types'];
return reflectionTypes.forEach((reflectionType, i) => {
const objectType = objectTypes[i];
addFilePrefix(reflectionType, objectType);
});
}
if (reflectionType && 'elementType' in reflectionType) {
reflectionType = reflectionType.elementType;
objectType = objectType['elementType'];
}
addFilePrefix(reflectionType, objectType);
}
function addFilePrefix(reflectionType, objectType) {
if (reflectionType &&
reflectionType instanceof typedoc_1.ReferenceType &&
!reflectionType.package) {
const filePrefix = !objectType['id'] && reflectionType['_target'].parent
? reflectionType['_target'].parent?.name
.replace(/"/g, '')
.trim()
: undefined;
objectType['filePrefix'] = filePrefix;
}
}
},
/**
* Finds missing parameter links and fills in the target id.
*
* @param projectArray
* @param objectReflection
* @returns
*/
fixMutateSignatureLinks: (projectArray, objectReflection) => {
const { fixMutateSignatureLinks } = this.fixerFactory;
objectReflection.signatures?.forEach((signature) => {
signature.parameters?.forEach((parameter) => fixTypeId(parameter.type));
fixTypeId(signature.type);
});
objectReflection.children = objectReflection.children?.map((child) => fixMutateSignatureLinks(projectArray, child));
if (objectReflection.type &&
'declaration' in objectReflection.type) {
objectReflection.type.declaration = fixMutateSignatureLinks(projectArray, objectReflection.type.declaration);
}
return objectReflection;
function filterReflections(reflection, name, filePrefix) {
const { sources } = reflection;
if (!sources)
return false;
const fileName = sources[0].fileName.split('.')[0];
const isFile = sources && filePrefix.endsWith(fileName);
const hasName = name === reflection.name;
return isFile && hasName;
}
function fixTypeId(type) {
if (type && 'types' in type) {
return type.types.forEach((type) => fixTypeId(type));
}
if (type && 'elementType' in type)
type = type.elementType;
if (!type ||
type.type !== 'reference' ||
!!type.id ||
!type['filePrefix']) {
return;
}
const filePrefix = String(type['filePrefix']);
delete type['filePrefix'];
const name = type.name;
const targetReflections = projectArray.filter((reflection) => filterReflections(reflection, name, filePrefix));
if (targetReflections.length > 1) {
console.warn(`[yaf] Parameter ${name} is ambiguously linked to reflection id's ${targetReflections
.map((r) => r.id)
.join(' and ')}. The link will be ignored`);
}
if (targetReflections.length === 1) {
type['id'] = targetReflections[0].id;
}
}
},
/**
* The default TypeDoc declaration Reflection gets a `Property` kind, regardless if it has a call signature.
*
* This function fixes the frontend data to be the correct kind.
*
* @param objectReflection
* @returns
*/
fixObjectCallKind: (objectReflection) => {
if (objectReflection.kind !== typedoc_2.ReflectionKind.Property)
return objectReflection;
const hasSignatures = objectReflection.type?.declaration?.signatures;
if (!hasSignatures)
return objectReflection;
const methodSignature = hasSignatures.length === 1 &&
hasSignatures[0].kind === typedoc_2.ReflectionKind.CallSignature
? hasSignatures[0]
: undefined;
if (methodSignature)
objectReflection.kind = typedoc_2.ReflectionKind.CallSignature;
return objectReflection;
},
/**
* The standard Typedoc data structure creates a container object `Reflection.type.declaration`
* to house reflection recursion.\
* This breaks the pattern which would otherwise be `Reflection.children[]`.
*
* This function bridges the break and passes down some properties from the parent to the frontend data structure.
*
* @param object The YafDataObject at the current point of recursion
* @param reflection The TypeDoc reflection corresponding to the `object`
* @param serialiser
* @param rootReflection The page parent Typedoc reflection (ie. the first ancestor on any given document page)
* @returns Void. Because there are a few different mappings to parse on the parent objects properties, this function
* mutates the object instead of returning a new value.
*/
fixMutateDeclarationReflections: (object, reflection, serialiser, context, rootReflection) => {
const { fixDeclarationSignatures } = YafSerialiser.fixerFactory;
const { parseReflectionToYafDataObject } = YafSerialiser.parserFactory;
const reflectionDeclaration = reflection.type && 'declaration' in reflection.type
? reflection.type.declaration
: undefined;
if (!reflectionDeclaration)
return;
if (!rootReflection) {
const parentKind = reflectionDeclaration.kind;
const { Class, Interface, ClassOrInterface } = typedoc_2.ReflectionKind;
if ([Class, Interface, ClassOrInterface].includes(parentKind)) {
rootReflection = reflection;
}
}
const objectDeclaration = object.type.declaration;
objectDeclaration.location = object.location;
objectDeclaration.name = reflection.name;
objectDeclaration.signatures = fixDeclarationSignatures(objectDeclaration, reflectionDeclaration, serialiser, context, rootReflection);
objectDeclaration.children = objectDeclaration.children?.map((objectChild, i) => {
return parseReflectionToYafDataObject(objectChild, reflectionDeclaration.children[i], context, serialiser, object.location.hash, rootReflection || reflection);
});
},
/**
* Typedoc has already created groupings in the JSON output, before {@link fixObjectCallKind} has done it's job.
*
* This function re-maps the groupings for this correction by moving method reflections from the `Properties` group into the `Methods` group.
*
* @param object
* @returns Void. The child property is mutated.
*/
fixMutateReflectionGroups: (object) => {
if (!object.type ||
!('declaration' in object.type) ||
!object.type.declaration.groups)
return;
const groups = object.type.declaration.groups;
const children = object.type.declaration.children;
const propertyGroup = groups.find((group) => group.title === 'Properties');
let methodGroup = groups.find((group) => group.title === 'Methods');
const getMethodGroup = () => {
if (methodGroup)
return methodGroup;
methodGroup = {
title: 'Methods',
children: [],
};
groups.push(methodGroup);
return methodGroup;
};
if (propertyGroup)
propertyGroup.children = propertyGroup.children?.filter((id) => {
const child = children?.find((child) => child.id === id);
if (child &&
child.kind === typedoc_2.ReflectionKind.CallSignature) {
methodGroup = getMethodGroup();
methodGroup.children?.push(id);
return false;
}
return true;
});
object.type.declaration.groups = groups.filter((group) => group.children?.length);
},
};
/**
* A collection of utility methods to report on data.
*
* @group Factories
*/
static utilities = {
/**
* Indicates if the reflection kind has its own documentation page.
* @param kind
* @returns
*/
isPage: (kind) => {
return this.hasOwnPage.indexOf(kind) > -1;
},
/**
* Indicates if the reflection displays any custom documentation text.
* @param object
* @returns
*/
hasVisibleTextComponent: (object) => {
return (object.comment &&
(object.comment.summary.some((item) => item.kind !== 'text' || item.text !== '') ||
(object.comment.blockTags &&
object.comment.blockTags.length > 0)));
},
};
/**
* A list of the kinds of reflections which will generate their own document page in the front-end.
*/
static hasOwnPage = [
typedoc_2.ReflectionKind.Class,
typedoc_2.ReflectionKind.Interface,
typedoc_2.ReflectionKind.Enum,
typedoc_2.ReflectionKind.Namespace,
typedoc_2.ReflectionKind.Module,
typedoc_2.ReflectionKind.TypeAlias,
typedoc_2.ReflectionKind.Function,
typedoc_2.ReflectionKind.Variable,
];
}
exports.YafSerialiser = YafSerialiser;
//# sourceMappingURL=YafSerialiser.js.map