-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement basic document generation feature
- Loading branch information
Showing
9 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { TypeStorage } from "@root/utils/type-storage"; | ||
import { ClassData } from "@utils/types"; | ||
|
||
type DocumentTypeOptions = ClassData["userData"]; | ||
|
||
export function DocType(options: DocumentTypeOptions): ClassDecorator { | ||
return target => { | ||
TypeStorage.instance.collectClassData({ | ||
classType: target, | ||
userData: { | ||
...options, | ||
}, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { DecoratorType, FieldData } from "@utils/types"; | ||
import { TypeStorage } from "@root/utils/type-storage"; | ||
|
||
type DocumentFieldOptions = FieldData["userData"]; | ||
|
||
export function DocField(fieldData: DocumentFieldOptions): DecoratorType { | ||
return <T>(target: object, propertyKey: string | symbol, descriptor?: TypedPropertyDescriptor<T>) => { | ||
if (typeof propertyKey === "symbol") { | ||
throw new Error("Symbol keys are not supported yet!"); | ||
} | ||
|
||
const isMethod = Boolean(descriptor && descriptor.value); | ||
if (isMethod) { | ||
throw new Error("Field decorator can only be used on properties!"); | ||
} | ||
|
||
const type = Reflect.getMetadata("design:type", target, propertyKey); | ||
if (!type || (type !== Number && type !== String && type !== Boolean)) { | ||
throw new Error("Field decorator can only be used on properties of type Number, String or Boolean!"); | ||
} | ||
|
||
TypeStorage.instance.collectFieldData({ | ||
type, | ||
fieldName: propertyKey, | ||
classType: target.constructor, | ||
userData: { | ||
...fieldData, | ||
description: fieldData.description || `field '${propertyKey}'.`, | ||
nullable: fieldData.nullable ?? false, | ||
}, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./class"; | ||
export * from "./field"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ClassType } from "@utils/types"; | ||
import { TypeStorage } from "@root/utils/type-storage"; | ||
|
||
import { generateTableFromField, getHeaderGenerator } from "@generators/utils"; | ||
|
||
interface DocumentOptions { | ||
initialHeaderLevel?: number; // = 1 | ||
} | ||
|
||
export function generateDocsForClass(classType: ClassType, options?: DocumentOptions) { | ||
const { initialHeaderLevel = 1 } = options || {}; | ||
const header = getHeaderGenerator(initialHeaderLevel); | ||
|
||
const targetClass = TypeStorage.instance.classes.find(classData => classData.classType === classType); | ||
if (!targetClass) { | ||
throw new Error(`Class '${classType.name}' is not registered!`); | ||
} | ||
|
||
const contents: string[] = []; | ||
contents.push(`${header(targetClass.userData.name, 1)} (${targetClass.className})\n`); | ||
contents.push(`${targetClass.userData.description}\n`); | ||
contents.push(`${header("Fields", 2)}\n`); | ||
|
||
for (const field of targetClass.fields) { | ||
contents.push(`${header(`\`${field.fieldName}\``, 3)}\n`); | ||
contents.push(generateTableFromField(field)); | ||
contents.push("\n\n"); | ||
} | ||
|
||
return contents.join("\n"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./class"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import markdownTable from "markdown-table"; | ||
|
||
import { FieldData } from "@utils/types"; | ||
|
||
export function getHeaderGenerator(initialLevel = 1) { | ||
return function header(text: string, level: number) { | ||
return `${"#".repeat(level + (initialLevel - 1))} ${text}`; | ||
}; | ||
} | ||
|
||
export function generateTableFromField(field: FieldData) { | ||
const rows: string[][] = []; | ||
|
||
rows.push(["Name", "Description"]); | ||
rows.push(["Type", field.type.name]); | ||
rows.push(["Nullable", field.userData.nullable ? "✔️ Yes" : "❌ No"]); | ||
|
||
if (field.userData.description) { | ||
rows.push(["Description", field.userData.description]); | ||
} | ||
|
||
return markdownTable(rows); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import type * as _ from "reflect-metadata"; | ||
|
||
export * from "./decorators"; | ||
export * from "./generators"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { ClassData, ClassType, FieldData } from "@utils/types"; | ||
|
||
interface CollectFieldDataOptions extends FieldData { | ||
classType: ClassType; | ||
} | ||
|
||
interface CollectClassDataOptions extends Omit<ClassData, "className" | "fields"> { | ||
classType: ClassType; | ||
} | ||
|
||
export class TypeStorage { | ||
public static readonly instance = new TypeStorage(); | ||
|
||
private constructor(private readonly classMap = new Map<ClassType, ClassData>()) {} | ||
|
||
public get classes() { | ||
return [...this.classMap.values()]; | ||
} | ||
|
||
public collectFieldData(fieldData: CollectFieldDataOptions) { | ||
const classData = this.getClassData(fieldData.classType); | ||
|
||
classData.fields.push({ | ||
type: fieldData.type, | ||
fieldName: fieldData.fieldName, | ||
userData: { | ||
...fieldData.userData, | ||
}, | ||
}); | ||
} | ||
public collectClassData({ classType, userData }: CollectClassDataOptions) { | ||
const classData = this.getClassData(classType); | ||
classData.className = classType.name; | ||
classData.classType = classType; | ||
classData.userData = { | ||
...userData, | ||
}; | ||
} | ||
|
||
private getClassData(classType: ClassType) { | ||
if (!this.classMap.has(classType)) { | ||
const classData: ClassData = { | ||
classType, | ||
className: classType.name, | ||
fields: [], | ||
userData: { | ||
name: classType.name, | ||
description: `type '${classType.name}'.`, | ||
}, | ||
}; | ||
this.classMap.set(classType, classData); | ||
|
||
return classData; | ||
} | ||
|
||
const classData = this.classMap.get(classType); | ||
if (!classData) { | ||
throw new Error("Class data is undefined!"); | ||
} | ||
|
||
return classData; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
export type DecoratorType = PropertyDecorator & MethodDecorator; | ||
export type ClassType = Function; | ||
|
||
export interface FieldData { | ||
type: typeof String | typeof Number | typeof Boolean; | ||
fieldName: string; | ||
|
||
// user defined | ||
userData: { | ||
description?: string; | ||
nullable?: boolean; | ||
defaultValue?: any; | ||
}; | ||
} | ||
export interface ClassData { | ||
className: string; | ||
classType: ClassType; | ||
fields: FieldData[]; | ||
|
||
// user defined | ||
userData: { | ||
name: string; | ||
description: string; | ||
}; | ||
} |