diff --git a/declarations/request/mod.ts b/declarations/request/mod.ts index 9ae047f4..f89be3b1 100644 --- a/declarations/request/mod.ts +++ b/declarations/request/mod.ts @@ -1,4 +1,4 @@ -import { Project, ts, log } from "../../deps.ts"; +import { Project, log } from "../../deps.ts"; import { ensureDir } from "../../deps.ts"; import { jsonObToTsType, @@ -6,45 +6,16 @@ import { constructFVSchema, } from "./utils/mod.ts"; import { rgb24 } from "https://deno.land/std@0.96.0/fmt/colors.ts"; +import { denoResolutionHost } from "../utils/mod.ts"; export const getRequestDeclarations = async (dirPath?: string) => { - log.info("Generating of declarations is started"); + log.info("Generating of declarations of request is started"); const project = new Project({ - resolutionHost: (moduleResolutionHost, getCompilerOptions) => { - return { - resolveModuleNames: (moduleNames, containingFile) => { - const compilerOptions = getCompilerOptions(); - const resolvedModules: ts.ResolvedModule[] = []; - - for (const moduleName of moduleNames.map(removeTsExtension)) { - const result = ts.resolveModuleName( - moduleName, - containingFile, - compilerOptions, - moduleResolutionHost - ); - resolvedModules.push(result.resolvedModule!); - } - - return resolvedModules; - }, - }; - - function removeTsExtension(moduleName: string) { - if ( - moduleName.slice(-3).toLowerCase() === ".ts" && - !moduleName.startsWith("http") - ) { - return moduleName.slice(0, -3); - } - return moduleName; - } - }, + resolutionHost: denoResolutionHost, }); const __dirname = dirPath || Deno.cwd(); - await ensureDir("declarations"); await ensureDir("declarations/request"); project.addSourceFilesAtPaths(`${__dirname}/**/*.ts`); @@ -88,13 +59,13 @@ export const getRequestDeclarations = async (dirPath?: string) => { newSourceFile.formatText({ indentSize: 1 }); //save new interface await newSourceFile.save(); - log.info(`creating of declaration files was successful + log.info(`creating of declaration files for request was successful ${rgb24( ` ------------------------------------------------------------- - | Fastest validator schema: file:///${__dirname}/declarations/request/fastestValidatorSchema.json - | Json schema: file:///${__dirname}/declarations/request/schema.json - | Ts interface: file:///${__dirname}/declarations/request/schema.ts + | Fastest validator schema: file://${__dirname}/declarations/request/fastestValidatorSchema.json + | Json schema: file://${__dirname}/declarations/request/schema.json + | Ts interface: file://${__dirname}/declarations/request/schema.ts ------------------------------------------------------------- `, 0xd257ff diff --git a/declarations/schema/mod.ts b/declarations/schema/mod.ts new file mode 100644 index 00000000..1103d361 --- /dev/null +++ b/declarations/schema/mod.ts @@ -0,0 +1,62 @@ +import { Project, log } from "../../deps.ts"; +import { ensureDir } from "../../deps.ts"; +import { rgb24 } from "https://deno.land/std@0.96.0/fmt/colors.ts"; +import { denoResolutionHost } from "../utils/mod.ts"; +import { addFunQLInterfaceToSourceFile } from "./utils/addInterfaceToSrcFile.ts"; + +export const getSchemaDeclarations = async (dirPath?: string) => { + log.info("Generating of declarations of schema is started"); + const project = new Project({ + resolutionHost: denoResolutionHost, + }); + + const __dirname = dirPath || Deno.cwd(); + await ensureDir("declarations/schema"); + project.addSourceFilesAtPaths(`${__dirname}/**/*.ts`); + //handle differentiate between schema and schemas + const dir = + project.getDirectory(`${__dirname}/schema`) || + project.getDirectory(`${__dirname}/schemas`); + + const createdSourceFile = project.createSourceFile( + `${__dirname}/declarations/schema/schema.ts`, + undefined, + { + overwrite: true, + } + ); + + const sourceFiles = dir?.getSourceFiles(); + + //get all of interfaces + sourceFiles?.map((sourceFile) => { + const selectedInterfaces = sourceFile + .getInterfaces() + .filter( + (inter) => + inter.getName().startsWith("Pu") || + inter.getName().startsWith("Em") || + inter.getName().startsWith("In") || + inter.getName().startsWith("OutRel") || + inter.getName().startsWith("I") + ); + + selectedInterfaces.map((inter) => + addFunQLInterfaceToSourceFile(inter, createdSourceFile) + ); + }); + + //console.log(newSourceFile.getText()); + await createdSourceFile.save(); + + log.info(`creating of declaration files for schema was successful + ${rgb24( + ` + ------------------------------------------------------------- + | schema: file://${__dirname}/declarations/schema/schema.ts + ------------------------------------------------------------- + `, + 0xadfc03 + )} + `); +}; diff --git a/declarations/schema/utils/addEnumToSrcFile.ts b/declarations/schema/utils/addEnumToSrcFile.ts new file mode 100644 index 00000000..6e4230ba --- /dev/null +++ b/declarations/schema/utils/addEnumToSrcFile.ts @@ -0,0 +1,23 @@ +import { EnumDeclaration, SourceFile } from "../../../deps.ts"; +import { changeNameAndItsRefs } from "./ts-morph/mod.ts"; + +/** + * @function + * add an enum to specified sourcefile and also rename it to be unique + * @param myInterface the interface that we want to add it to sourcefile + * @param createdSourceFile reference of created sourcefile that we want to add the interface to it + */ +export function addFunQLEnumToSourceFile( + myEnum: EnumDeclaration, + createdSourceFile: SourceFile +) { + //checks enum name is duplicate or not + if (myEnum.getName().startsWith("FunQl")) { + return; + } else { + //change name of interface first + changeNameAndItsRefs(myEnum); + } + //even enum is not exported we export it + createdSourceFile.addEnum({ ...myEnum.getStructure(), isExported: true }); +} diff --git a/declarations/schema/utils/addInterfaceToSrcFile.ts b/declarations/schema/utils/addInterfaceToSrcFile.ts new file mode 100644 index 00000000..753503e8 --- /dev/null +++ b/declarations/schema/utils/addInterfaceToSrcFile.ts @@ -0,0 +1,51 @@ +import { SourceFile, InterfaceDeclaration } from "../../../deps.ts"; +import { isInternalType, handlePropType } from "./mod.ts"; +import { + changeNameAndItsRefs, + findAllPropsOfInterface, +} from "./ts-morph/mod.ts"; + +/** + * @function + * add an interface to specified sourcefile with all dependencies and also rename it to be unique + * @param myInterface the interface that we want to add it to sourcefile + * @param createdSourceFile reference of created sourcefile that we want to add the interface to it + */ +export function addFunQLInterfaceToSourceFile( + myInterface: InterfaceDeclaration, + createdSourceFile: SourceFile +) { + //checks interface name is duplicate or not and also is Date or not + if ( + //ignore date type + isInternalType(myInterface.getName()) || + //when interface was inserted to source file + myInterface.getName().startsWith("FunQl") + ) { + return; + } else { + //change name of interface first + changeNameAndItsRefs(myInterface); + } + + //create new interface with new name + const createdInterface = createdSourceFile.addInterface({ + name: myInterface.getName(), + isExported: true, + }); + + //finds all props that include in body of interface or specified with inheritance + const foundedProps = findAllPropsOfInterface(myInterface); + + for (const prop of foundedProps) { + //handle when type of prop is Bson.ObjectId + //map it to string type + if (prop.getText().match(/Bson.ObjectI[dD]/)) { + prop.setType("string"); + } + //construct deps of interface in prop + handlePropType(prop, createdSourceFile); + //add prop to created interface + createdInterface.addProperty(prop.getStructure()); + } +} diff --git a/declarations/schema/utils/handlePropType.ts b/declarations/schema/utils/handlePropType.ts new file mode 100644 index 00000000..39eeeff3 --- /dev/null +++ b/declarations/schema/utils/handlePropType.ts @@ -0,0 +1,32 @@ +import { PropertySignature, SourceFile, SyntaxKind } from "../../../deps.ts"; +import { getInterfaceFromType, getEnumFromType } from "./ts-morph/mod.ts"; +import { addFunQLInterfaceToSourceFile } from "./addInterfaceToSrcFile.ts"; +import { addFunQLEnumToSourceFile } from "./mod.ts"; + +/** + * @function + * find and add associated declaration to this type to source file + * @param type + * @param createdSourceFile + */ +export function handlePropType( + prop: PropertySignature, + createdSourceFile: SourceFile +) { + const typeReferences = prop.getDescendantsOfKind(SyntaxKind.TypeReference); + typeReferences.map((reference) => { + //get type of references + const typeOfReference = reference.getType(); + + //if type is interface we should find interface and process it again + if (typeOfReference.isInterface()) { + const foundedInterface = getInterfaceFromType(typeOfReference); + addFunQLInterfaceToSourceFile(foundedInterface, createdSourceFile); + } + //if type is enum we should find enum and add it to source file + if (typeOfReference.isEnum()) { + const foundedEnum = getEnumFromType(typeOfReference); + addFunQLEnumToSourceFile(foundedEnum, createdSourceFile); + } + }); +} diff --git a/declarations/schema/utils/isTypeInternal.ts b/declarations/schema/utils/isTypeInternal.ts new file mode 100644 index 00000000..ced8ba8f --- /dev/null +++ b/declarations/schema/utils/isTypeInternal.ts @@ -0,0 +1,8 @@ +/** + * specifies whether this type is belongs to internal js type or not + * @param typeName name of type + * @todo should add new name of types + */ +export function isInternalType(typeName: string) { + return typeName === "Date"; +} diff --git a/declarations/schema/utils/mod.ts b/declarations/schema/utils/mod.ts new file mode 100644 index 00000000..82da0ef5 --- /dev/null +++ b/declarations/schema/utils/mod.ts @@ -0,0 +1,4 @@ +export * from "./addEnumToSrcFile.ts"; +export * from "./addEnumToSrcFile.ts"; +export * from "./handlePropType.ts"; +export * from "./isTypeInternal.ts"; diff --git a/declarations/schema/utils/ts-morph/changeNameAndRef.ts b/declarations/schema/utils/ts-morph/changeNameAndRef.ts new file mode 100644 index 00000000..37c73e55 --- /dev/null +++ b/declarations/schema/utils/ts-morph/changeNameAndRef.ts @@ -0,0 +1,25 @@ +import { + InterfaceDeclaration, + EnumDeclaration, + TypeAliasDeclaration, +} from "../../../../deps.ts"; + +/** + * @function + * change name of a node and its references + * @param node the node that we want to rename it and own references + */ +export function changeNameAndItsRefs( + node: InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration +) { + //we use split for remove some postfixes for example authority.embedded.ts + const fileName = node + .getSourceFile() + .getBaseNameWithoutExtension() + .split(".")[0]; + //generate new name + const newName = `FunQl_${node.getName()}_${fileName}`; + + //rename node and its refs + node.rename(newName, { renameInStrings: true, usePrefixAndSuffixText: true }); +} diff --git a/declarations/schema/utils/ts-morph/findInterfaceProps.ts b/declarations/schema/utils/ts-morph/findInterfaceProps.ts new file mode 100644 index 00000000..b8295a5c --- /dev/null +++ b/declarations/schema/utils/ts-morph/findInterfaceProps.ts @@ -0,0 +1,16 @@ +import { InterfaceDeclaration, PropertySignature } from "../../../../deps.ts"; + +/** + * @function + * finds all props of an interface includes extends and .... + */ +export function findAllPropsOfInterface( + myInterface: InterfaceDeclaration +): PropertySignature[] { + return myInterface + .getSymbolOrThrow() + .getDeclarations()[0] + .getType() + .getProperties() + .map((prop) => prop.getDeclarations()[0]); +} diff --git a/declarations/schema/utils/ts-morph/getEnumFromType.ts b/declarations/schema/utils/ts-morph/getEnumFromType.ts new file mode 100644 index 00000000..e2f3ed78 --- /dev/null +++ b/declarations/schema/utils/ts-morph/getEnumFromType.ts @@ -0,0 +1,5 @@ +import { Type, EnumDeclaration } from "../../../../deps.ts"; + +export function getEnumFromType(type: Type) { + return type.getSymbolOrThrow().getDeclarations()[0]; +} diff --git a/declarations/schema/utils/ts-morph/getInterfaceFromType.ts b/declarations/schema/utils/ts-morph/getInterfaceFromType.ts new file mode 100644 index 00000000..2b1a822c --- /dev/null +++ b/declarations/schema/utils/ts-morph/getInterfaceFromType.ts @@ -0,0 +1,5 @@ +import { InterfaceDeclaration, Type } from "../../../../deps.ts"; + +export function getInterfaceFromType(type: Type) { + return type.getSymbolOrThrow().getDeclarations()[0]; +} diff --git a/declarations/schema/utils/ts-morph/mod.ts b/declarations/schema/utils/ts-morph/mod.ts new file mode 100644 index 00000000..93d2eae3 --- /dev/null +++ b/declarations/schema/utils/ts-morph/mod.ts @@ -0,0 +1,4 @@ +export * from "./changeNameAndRef.ts"; +export * from "./findInterfaceProps.ts"; +export * from "./getEnumFromType.ts"; +export * from "./getInterfaceFromType.ts"; diff --git a/declarations/utils/denoResolutionHost.ts b/declarations/utils/denoResolutionHost.ts new file mode 100644 index 00000000..c5084d4e --- /dev/null +++ b/declarations/utils/denoResolutionHost.ts @@ -0,0 +1,35 @@ +import { ResolutionHostFactory, ts } from "../../deps.ts"; + +export const denoResolutionHost: ResolutionHostFactory = ( + moduleResolutionHost, + getCompilerOptions +) => { + return { + resolveModuleNames: (moduleNames, containingFile) => { + const compilerOptions = getCompilerOptions(); + const resolvedModules: ts.ResolvedModule[] = []; + + for (const moduleName of moduleNames.map(removeTsExtension)) { + const result = ts.resolveModuleName( + moduleName, + containingFile, + compilerOptions, + moduleResolutionHost + ); + resolvedModules.push(result.resolvedModule!); + } + + return resolvedModules; + }, + }; + + function removeTsExtension(moduleName: string) { + if ( + moduleName.slice(-3).toLowerCase() === ".ts" && + !moduleName.startsWith("http") + ) { + return moduleName.slice(0, -3); + } + return moduleName; + } +}; diff --git a/declarations/utils/mod.ts b/declarations/utils/mod.ts new file mode 100644 index 00000000..a7a55b8f --- /dev/null +++ b/declarations/utils/mod.ts @@ -0,0 +1 @@ +export * from "./denoResolutionHost.ts"; diff --git a/funql.ts b/funql.ts index f31c85ac..769e8f1b 100644 --- a/funql.ts +++ b/funql.ts @@ -5,6 +5,7 @@ import { getRequestDeclarations } from "./declarations/request/mod.ts"; import "./config/mod.ts"; import { upgrade } from "./cli/mod.ts"; import { log } from "./deps.ts"; +import { getSchemaDeclarations } from "./declarations/schema/mod.ts"; export interface CommandArgs { init?: boolean | string; @@ -23,5 +24,6 @@ const createProject = async (init: string | boolean) => { args.init && (await createProject(args.init)); args.declaration && (await getRequestDeclarations()); +args.declaration && (await getSchemaDeclarations()); args.upgrade && (await upgrade(args.upgrade));