diff --git a/.gitignore b/.gitignore index 178e383f..e266322b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .vitepress/dist docs/api components +typedoc-theme diff --git a/package.json b/package.json index 3713a7a2..bca72d26 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "serve": "vitepress serve docs", "dev": "vitepress dev docs", "build": "vitepress build docs", - "typedoc": "cd profectus && vue-typedoc --logLevel Verbose --plugin typedoc-plugin-markdown --hideBreadcrumbs --hideInPageTOC --entryDocument index.md --plugin typedoc-plugin-rename-defaults --plugin typedoc-plugin-mdn-links", - "docgen": "vue-docgen -c docgen.config.js && node copyComponents.js" + "generate": "tsc --project ./profectus-theme && cd profectus && vue-typedoc --logLevel Verbose --plugin ../typedoc-theme/index.js --theme profectus --plugin typedoc-plugin-markdown --plugin typedoc-plugin-rename-defaults --plugin typedoc-plugin-mdn-links && cd .. && vue-docgen -c docgen.config.js && node copyComponents.js" }, "repository": "git+https://github.com/profectus-engine/profectus-docs.git", "author": "thepaperpilot", diff --git a/profectus-theme/index.ts b/profectus-theme/index.ts new file mode 100644 index 00000000..61bdc7ae --- /dev/null +++ b/profectus-theme/index.ts @@ -0,0 +1,7 @@ +import { Application } from 'typedoc'; + +import { ProfectusTheme } from './theme'; + +export function load(app: Application) { + app.renderer.defineTheme('profectus', ProfectusTheme); +} diff --git a/profectus-theme/theme.ts b/profectus-theme/theme.ts new file mode 100644 index 00000000..9e3a1612 --- /dev/null +++ b/profectus-theme/theme.ts @@ -0,0 +1,47 @@ +import * as fs from 'fs'; + +import { + ContainerReflection, + PageEvent, + Renderer, + DeclarationReflection, + RendererEvent +} from 'typedoc'; +import { MarkdownTheme } from 'typedoc-plugin-markdown'; +import registerTypeHelper from './type'; + +export class ProfectusTheme extends MarkdownTheme { + constructor(renderer: Renderer) { + super(renderer); + + console.log("!?!?!") + this.entryDocument = 'index.md'; + this.hideBreadcrumbs = true; + this.hideInPageTOC = true; + + registerTypeHelper(); + } + + + getReflectionTemplate() { + const templ = super.getReflectionTemplate(); + return (pageEvent) => { + if (pageEvent.url === "index.md") { + return "# Profectus API"; + } + return templ(pageEvent); + } + } + + getRelativeUrl(url: string) { + const relativeUrl = super + .getRelativeUrl(url) + .replace(/(.*).md/, '$1') + .replace(/ /g, '-'); + return relativeUrl.startsWith('..') ? relativeUrl : './' + relativeUrl; + } + + toUrl(mapping: any, reflection: DeclarationReflection) { + return `${mapping.directory}/${reflection.getFullName()}.md`; + } +} diff --git a/profectus-theme/tsconfig.json b/profectus-theme/tsconfig.json new file mode 100644 index 00000000..e01790ce --- /dev/null +++ b/profectus-theme/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "../typedoc-theme", + "noErrorTruncation": true, + "target": "esnext", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "skipLibCheck": true, + "sourceMap": true, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/profectus-theme/type.ts b/profectus-theme/type.ts new file mode 100644 index 00000000..835a448e --- /dev/null +++ b/profectus-theme/type.ts @@ -0,0 +1,329 @@ +import * as Handlebars from 'handlebars'; +import { + ArrayType, + ConditionalType, + DeclarationReflection, + IndexedAccessType, + InferredType, + IntersectionType, + IntrinsicType, + LiteralType, + MappedType, + PredicateType, + QueryType, + ReferenceType, + ReflectionType, + SignatureReflection, + TupleType, + TypeOperatorType, + UnionType, + UnknownType, +} from 'typedoc'; + +type Collapse = 'object' | 'function' | 'all' | 'none'; + +export default function () { + Handlebars.registerHelper( + 'type', + function ( + this: + | ArrayType + | IntersectionType + | IntrinsicType + | ReferenceType + | TupleType + | UnionType + | TypeOperatorType + | QueryType + | PredicateType + | ReferenceType + | ConditionalType + | IndexedAccessType + | UnknownType + | InferredType + | LiteralType + | MappedType, + + collapse: Collapse = 'none', + emphasis = true, + ) { + if (this instanceof ReferenceType) { + return getReferenceType(this, emphasis); + } + + if (this instanceof ArrayType && this.elementType) { + return getArrayType(this, emphasis); + } + + if (this instanceof UnionType && this.types) { + return getUnionType(this, emphasis); + } + + if (this instanceof IntersectionType && this.types) { + return getIntersectionType(this); + } + + if (this instanceof TupleType && this.elements) { + return getTupleType(this); + } + + if (this instanceof IntrinsicType && this.name) { + return getIntrinsicType(this, emphasis); + } + + if (this instanceof ReflectionType) { + return getReflectionType(this, collapse); + } + + if (this instanceof DeclarationReflection) { + return getReflectionType(this, collapse); + } + + if (this instanceof TypeOperatorType) { + return getTypeOperatorType(this); + } + + if (this instanceof QueryType) { + return getQueryType(this); + } + + if (this instanceof ConditionalType) { + return getConditionalType(this); + } + + if (this instanceof IndexedAccessType) { + return getIndexAccessType(this); + } + + if (this instanceof UnknownType) { + return getUnknownType(this); + } + + if (this instanceof InferredType) { + return getInferredType(this); + } + + if (this instanceof LiteralType) { + return getLiteralType(this); + } + + if (this instanceof MappedType) { + return getMappedType(this); + } + + if (this instanceof PredicateType) { + return getPredicateType(this); + } + + return this ? escapeChars(this.toString()) : ''; + }, + ); +} + +function getPredicateType(model: PredicateType) { + return `${model.asserts ? "asserts " : ""}\`${model.name}\` is ${Handlebars.helpers.type.call(model.targetType)}`; +} + +function getMappedType(model: MappedType) { + return `\{ ${model.readonlyModifier === "+" ? "readonly" : model.readonlyModifier === "-" ? "-readonly" : ""}[\`${model.parameter}\` in ${Handlebars.helpers.type.call(model.parameterType)}]${model.optionalModifier === "+" ? "?" : model.optionalModifier === "-" ? "-?" : ""}: ${Handlebars.helpers.type.call(model.templateType)} \}`; +} + +function getLiteralType(model: LiteralType) { + if (typeof model.value === 'bigint') { + return `\`${model.value}n\``; + } + return `\`\`${JSON.stringify(model.value)}\`\``; +} + +export function getReflectionType( + model: DeclarationReflection | ReflectionType, + collapse: Collapse, +) { + const root = model instanceof ReflectionType ? model.declaration : model; + if (root.signatures) { + return collapse === 'function' || collapse === 'all' + ? `\`fn\`` + : getFunctionType(root.signatures); + } + return collapse === 'object' || collapse === 'all' + ? `\`Object\`` + : getDeclarationType(root); +} + +function getDeclarationType(model: DeclarationReflection) { + if (model.indexSignature || model.children) { + let indexSignature = ''; + const declarationIndexSignature = model.indexSignature; + if (declarationIndexSignature) { + const key = declarationIndexSignature.parameters + ? declarationIndexSignature.parameters.map( + (param) => `\`[${param.name}: ${param.type}]\``, + ) + : ''; + const obj = Handlebars.helpers.type.call(declarationIndexSignature.type); + indexSignature = `${key}: ${obj}; `; + } + const types = + model.children && + model.children.map((obj) => { + return `\`${obj.name}${obj.flags.isOptional ? '?' : '' + }\`: ${Handlebars.helpers.type.call( + obj.signatures || obj.children ? obj : obj.type, + )} ${obj.defaultValue && obj.defaultValue !== '...' + ? `= ${escapeChars(obj.defaultValue)}` + : '' + }`; + }); + return `{ ${indexSignature ? indexSignature : ''}${types ? types.join('; ') : '' + } }${model.defaultValue && model.defaultValue !== '...' + ? `= ${escapeChars(model.defaultValue)}` + : '' + }`; + } + return '{}'; +} + +export function getFunctionType(modelSignatures: SignatureReflection[]) { + const functions = modelSignatures.map((fn) => { + const typeParams = fn.typeParameters + ? `<${fn.typeParameters + .map((typeParameter) => typeParameter.name) + .join(', ')}\\>` + : []; + const params = fn.parameters + ? fn.parameters.map((param) => { + return `${param.flags.isRest ? '...' : ''}\`${param.name}${param.flags.isOptional ? '?' : '' + }\`: ${Handlebars.helpers.type.call( + param.type ? param.type : param, + )}`; + }) + : []; + const returns = Handlebars.helpers.type.call(fn.type); + return typeParams + `(${params.join(', ')}) => ${returns}`; + }); + return functions.join(''); +} + +function getReferenceType(model: ReferenceType, emphasis) { + const externalUrl = Handlebars.helpers.attemptExternalResolution(model); + if (model.reflection || (model.name && model.typeArguments)) { + const reflection: string[] = []; + + if (model.reflection?.url) { + reflection.push( + `[${`\`${model.reflection.name}\``}](${Handlebars.helpers.relativeURL( + model.reflection.url, + )})`, + ); + } else { + reflection.push( + externalUrl + ? `[${`\`${model.name}\``}]( ${externalUrl} )` + : `\`${model.name}\``, + ); + } + if (model.typeArguments && model.typeArguments.length > 0) { + reflection.push( + `<${model.typeArguments + .map((typeArgument) => Handlebars.helpers.type.call(typeArgument)) + .join(', ')}\\>`, + ); + } + return reflection.join(''); + } + return emphasis + ? externalUrl + ? `[${`\`${model.name}\``}]( ${externalUrl} )` + : `\`${model.name}\`` + : escapeChars(model.name); +} + +function getArrayType(model: ArrayType, emphasis: boolean) { + const arrayType = Handlebars.helpers.type.call( + model.elementType, + 'none', + emphasis, + ); + return model.elementType.type === 'union' + ? `(${arrayType})[]` + : `${arrayType}[]`; +} + +function getUnionType(model: UnionType, emphasis: boolean) { + return model.types + .map((unionType) => + Handlebars.helpers.type.call(unionType, 'none', emphasis), + ) + .join(` \\| `); +} + +function getIntersectionType(model: IntersectionType) { + return model.types + .map((intersectionType) => Handlebars.helpers.type.call(intersectionType)) + .join(' & '); +} + +function getTupleType(model: TupleType) { + return `[${model.elements + .map((element) => Handlebars.helpers.type.call(element)) + .join(', ')}]`; +} + +function getIntrinsicType(model: IntrinsicType, emphasis: boolean) { + return emphasis ? `\`${model.name}\`` : escapeChars(model.name); +} + +function getTypeOperatorType(model: TypeOperatorType) { + return `${model.operator} ${Handlebars.helpers.type.call(model.target)}`; +} + +function getQueryType(model: QueryType) { + return `typeof ${Handlebars.helpers.type.call(model.queryType)}`; +} + +function getInferredType(model: InferredType) { + return `infer ${escapeChars(model.name)}`; +} + +function getUnknownType(model: UnknownType) { + return escapeChars(model.name); +} + +function getConditionalType(model: ConditionalType) { + const md: string[] = []; + if (model.checkType) { + md.push(Handlebars.helpers.type.call(model.checkType)); + } + md.push('extends'); + if (model.extendsType) { + md.push(Handlebars.helpers.type.call(model.extendsType)); + } + md.push('?'); + if (model.trueType) { + md.push(Handlebars.helpers.type.call(model.trueType)); + } + md.push(':'); + if (model.falseType) { + md.push(Handlebars.helpers.type.call(model.falseType)); + } + return md.join(' '); +} + +function getIndexAccessType(model: IndexedAccessType) { + const md: string[] = []; + if (model.objectType) { + md.push(Handlebars.helpers.type.call(model.objectType)); + } + if (model.indexType) { + md.push(`[${Handlebars.helpers.type.call(model.indexType)}]`); + } + return md.join(''); +} + +function escapeChars(str: string) { + return str + .replace(/>/g, '\\>') + .replace(/_/g, '\\_') + .replace(/`/g, '\\`') + .replace(/\|/g, '\\|'); +}