diff --git a/packages/react-lowcode/src/codegen/app.ts b/packages/react-lowcode/src/codegen/app.ts new file mode 100644 index 000000000..020b8e45e --- /dev/null +++ b/packages/react-lowcode/src/codegen/app.ts @@ -0,0 +1,69 @@ +import { AppGenerator } from './generation/generators/app-generator' +import { UiFramework, TableType, Formatter } from './definition/context-types' +import { CodeDir, CodeRW } from '../io' +import ts, { factory } from "typescript" +import { Project } from "ts-morph" +import { CodegenOptions } from './interfaces' +import TemplateResolver from './generation/generators/template/template-resolver' + +// generates CRUD React pages (master-detail, eg. orders list, order detail form) from typescript +export function generatePages(inputSourceCode: string, io: CodeRW & CodeDir, options?: CodegenOptions) { + const project = new Project({}) + const myClassFile = project.createSourceFile("src/types.ts", inputSourceCode) + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }) + + options?.names.map((typeName) => { + const typeAlias = myClassFile.getTypeAlias(typeName) + const props = typeAlias?.getType()?.getProperties() ?? [] + if (typeAlias) { + const entity = { + getName: () => typeName, + getType: () => typeAlias, + properties: props.map((prop) => ({ + getName: () => prop.getName(), + getType: () => prop.getTypeAtLocation(myClassFile), + getTypeText: () => prop.getDeclarations()[0].getText() + })) + } + + let context = {uiFramework: UiFramework.MaterialUI, formatter: Formatter.None, index: {tableType: TableType.BasicTable, height: "400px"}}; + + const generator = new AppGenerator(context, entity) + const page = generator.generateListComponent(/* TODO entity / type name should be input - not in context */) + + const filePath = `src/components/${typeName}.tsx` + const sourceFile = ts.createSourceFile( + filePath, + '', + ts.ScriptTarget.ESNext, + true, + ts.ScriptKind.TSX + ) + const pageSouceCode = printer.printList(ts.ListFormat.MultiLine, factory.createNodeArray([...page!.imports, page!.functionDeclaration]), sourceFile) + io.writeFile(filePath, pageSouceCode) + + //generate list wrapper + const indexWrapperTemplatePath = 'path-to-template'//TODO: put here real template path when template will be done + let template = '' + io.readFile(indexWrapperTemplatePath).then((source => {if(source) template = source;})) + + const templateResolver = new TemplateResolver(entity); + const listWrapper = templateResolver.generateListPage(template); + + if(listWrapper) { + const listWrapperFilePath = `src/components/${typeName}Page.tsx` + const sourceFileWrapperSourceFile = ts.createSourceFile( + listWrapperFilePath, + listWrapper, + ts.ScriptTarget.ESNext, + true, + ts.ScriptKind.TSX + ) + + // TODO:PC: Need print here? or only: io.writeFile(listWrapperFilePath, listWrapper) + const wrapperPageSourceCode = printer.printFile(sourceFileWrapperSourceFile); + io.writeFile(listWrapperFilePath, wrapperPageSourceCode) + } + } + }) +} \ No newline at end of file diff --git a/packages/react-lowcode/src/codegen/detail.ts b/packages/react-lowcode/src/codegen/detail.ts new file mode 100644 index 000000000..509e52b1e --- /dev/null +++ b/packages/react-lowcode/src/codegen/detail.ts @@ -0,0 +1,44 @@ +import { SourceLineCol } from "../ast"; +import { CodeRW } from "../io"; +import { isFormWidget } from "./ast/widgetDeclaration"; +import { + insertFormWidget, + getColumnSourcePosition as fGetColumnSourcePosition, + getFormWidgetProperties as fGetFormWidgetProperties, + setFormWidgetProperties as fSetFormWidgetProperties +} from './facade/facadeApi' +import { Property } from "./generation/entity"; +import { InsertOptions, WidgetProperties } from "./interfaces"; +import { getEntityProperty } from "./tests/helper"; + +export function isSelectedFormWidget(sourceCode:string, formPosition: SourceLineCol){ + return isFormWidget(sourceCode, formPosition) +} + +export async function getFormWidgetProperties(io: CodeRW, + sourceCode:SourceLineCol): Promise{ + return await fGetFormWidgetProperties(sourceCode, io); +} + +export async function setFormWidgetProperties(io: CodeRW, + sourceCode:SourceLineCol, + properties: WidgetProperties): Promise{ + + return await fSetFormWidgetProperties(sourceCode, io, properties); +} + +export async function addFormInput(typesSourceCode: string, + io: CodeRW, + sourceLine:SourceLineCol, + options: InsertOptions): Promise{ + + const property: Property = getEntityProperty(typesSourceCode, options.property, options.entityName)[0] + let generatedSource = undefined + + if(property){ + generatedSource = await insertFormWidget(sourceLine, + {entityField: property, index: options.index}, + io) + } + return generatedSource +} \ No newline at end of file diff --git a/packages/react-lowcode/src/codegen/index.ts b/packages/react-lowcode/src/codegen/index.ts index da20f337a..d85581019 100644 --- a/packages/react-lowcode/src/codegen/index.ts +++ b/packages/react-lowcode/src/codegen/index.ts @@ -1,203 +1,4 @@ -import { AppGenerator } from './generation/generators/app-generator' -import { UiFramework, TableType, Formatter } from './definition/context-types' -import { CodeDir, CodeRW } from '../io' - -import ts, { factory } from "typescript" -import { Project } from "ts-morph" -import { HookImport } from '../ast/hooks' -import { TagImport } from '../ast/tags' -import { - insertColumn, - insertFormWidget, - deleteColumn as fDeleteColumn, - getColumnSourcePosition as fGetColumnSourcePosition, - getFormWidgetProperties as fGetFormWidgetProperties, - setFormWidgetProperties as fSetFormWidgetProperties -} from './facade/facadeApi' -import { SourceLineCol } from '../ast' -import { Property } from './generation/entity' -import { getEntityProperty } from './tests/helper' -import { isDataTableWidget, isFormWidget } from './ast/widgetDeclaration' -import { CodegenOptions, ColumnSourcePositionOptions, ColumnSourcePositionResult, DeleteOptions, InsertOptions, WidgetProperties } from './interfaces' -import TemplateResolver from './generation/generators/template/template-resolver' - - -// generates CRUD React pages (master-detail, eg. orders list, order detail form) from typescript -export function generatePages(inputSourceCode: string, io: CodeRW & CodeDir, options?: CodegenOptions) { - const project = new Project({}) - const myClassFile = project.createSourceFile("src/types.ts", inputSourceCode) - const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }) - - options?.names.map((typeName) => { - const typeAlias = myClassFile.getTypeAlias(typeName) - const props = typeAlias?.getType()?.getProperties() ?? [] - if (typeAlias) { - const entity = { - getName: () => typeName, - getType: () => typeAlias, - properties: props.map((prop) => ({ - getName: () => prop.getName(), - getType: () => prop.getTypeAtLocation(myClassFile), - getTypeText: () => prop.getDeclarations()[0].getText() - })) - } - - let context = {uiFramework: UiFramework.MaterialUI, formatter: Formatter.None, index: {tableType: TableType.BasicTable, height: "400px"}}; - - const generator = new AppGenerator(context, entity) - const page = generator.generateListComponent(/* TODO entity / type name should be input - not in context */) - - const filePath = `src/components/${typeName}.tsx` - const sourceFile = ts.createSourceFile( - filePath, - '', - ts.ScriptTarget.ESNext, - true, - ts.ScriptKind.TSX - ) - const pageSouceCode = printer.printList(ts.ListFormat.MultiLine, factory.createNodeArray([...page!.imports, page!.functionDeclaration]), sourceFile) - io.writeFile(filePath, pageSouceCode) - - //generate list wrapper - const indexWrapperTemplatePath = 'path-to-template'//TODO: put here real template path when template will be done - let template = '' - io.readFile(indexWrapperTemplatePath).then((source => {if(source) template = source;})) - - const templateResolver = new TemplateResolver(entity); - const listWrapper = templateResolver.generateListPage(template); - - if(listWrapper) { - const listWrapperFilePath = `src/components/${typeName}Page.tsx` - const sourceFileWrapperSourceFile = ts.createSourceFile( - listWrapperFilePath, - listWrapper, - ts.ScriptTarget.ESNext, - true, - ts.ScriptKind.TSX - ) - - // TODO:PC: Need print here? or only: io.writeFile(listWrapperFilePath, listWrapper) - const wrapperPageSourceCode = printer.printFile(sourceFileWrapperSourceFile); - io.writeFile(listWrapperFilePath, wrapperPageSourceCode) - } - } - }) -} - -export function isSelectedDataTable(sourceCode:string, tablePosition: SourceLineCol){ - return isDataTableWidget(sourceCode, tablePosition) -} - -export function isSelectedFormWidget(sourceCode:string, formPosition: SourceLineCol){ - return isFormWidget(sourceCode, formPosition) -} - -export async function addColumn(typesSourceCode: string, - io: CodeRW, - sourceCode:SourceLineCol, - options: InsertOptions): Promise{ - - const property: Property = getEntityProperty(typesSourceCode, options.property, options.entity)[0] - let generatedSource = undefined - - if(property){ - generatedSource = await insertColumn(sourceCode, - {entityField: property, index: options.index}, - io) - } - - return generatedSource -} - -export async function deleteColumn(io: CodeRW, - sourceCode:SourceLineCol, - options: DeleteOptions): Promise { - - let generatedSource = await fDeleteColumn(sourceCode, options, io); - - return generatedSource -} - -export async function getFormWidgetProperties(io: CodeRW, - sourceCode:SourceLineCol): Promise{ - return await fGetFormWidgetProperties(sourceCode, io); -} - -export async function setFormWidgetProperties(io: CodeRW, - sourceCode:SourceLineCol, - properties: WidgetProperties): Promise{ - - return await fSetFormWidgetProperties(sourceCode, io, properties); -} - -export async function addFormInput(typesSourceCode: string, - io: CodeRW, - sourceLine:SourceLineCol, - options: InsertOptions): Promise{ - - const property: Property = getEntityProperty(typesSourceCode, options.property, options.entity)[0] - let generatedSource = undefined - - if(property){ - - generatedSource = await insertFormWidget(sourceLine, - {entityField: property, index: options.index}, - io) - } - - return generatedSource -} - -export async function getColumnSourcePosition(io: CodeRW, - sourceCode:SourceLineCol, - options: ColumnSourcePositionOptions): Promise { - - return await fGetColumnSourcePosition(sourceCode, options, io); -} - -interface ThemeCodegen { - providerTag(...children: ts.JsxChild[]): any -} - - interface IntlCodegen { - providerTag(...children: ts.JsxChild[]): any - } - -export interface AppGenerators { - newSourceFileContext(path: string): JsxFileContext - theme: ThemeCodegen, - intl: IntlCodegen, - //authorization: AuthorizationCodegen -} - -export class JsxFileContext { - - uniqueImports() { - return [] - } - - useHook(hook: HookImport, ...params: []) { - // TODO unique import - return null - } - - tag(tag: TagImport, ...children: ts.JsxChild[]) { - // TODO unique import - return null - } - - returnFragment(...children: ts.JsxChild[]): ts.Statement | null { - - if (children?.length == 1) { - // TODO handle one child - } - - factory.createReturnStatement(factory.createJsxFragment( - factory.createJsxOpeningFragment(), - children, - factory.createJsxJsxClosingFragment() - )) - - return null - } -} +export * from './interfaces' +export * from './list' +export * from './detail' +export * from './app' \ No newline at end of file diff --git a/packages/react-lowcode/src/codegen/interfaces.ts b/packages/react-lowcode/src/codegen/interfaces.ts index 699b16eb7..3a4c99730 100644 --- a/packages/react-lowcode/src/codegen/interfaces.ts +++ b/packages/react-lowcode/src/codegen/interfaces.ts @@ -1,4 +1,7 @@ +import ts, { factory } from "typescript"; import { SourceLineCol } from "../ast"; +import { HookImport } from "../ast/hooks"; +import { TagImport } from "../ast/tags"; import { UiFramework } from "./definition/context-types"; export interface CodegenOptions { @@ -9,7 +12,7 @@ export interface CodegenOptions { } export interface InsertOptions { - entity: string + entityName: string property: string index?: number } @@ -39,4 +42,51 @@ export interface WidgetProperty { export interface WidgetProperties { properties: WidgetProperty[] -} \ No newline at end of file +} + +interface ThemeCodegen { + providerTag(...children: ts.JsxChild[]): any +} + +interface IntlCodegen { + providerTag(...children: ts.JsxChild[]): any + } + +export interface AppGenerators { + newSourceFileContext(path: string): JsxFileContext + theme: ThemeCodegen, + intl: IntlCodegen, + //authorization: AuthorizationCodegen +} + +export class JsxFileContext { + + uniqueImports() { + return [] + } + + useHook(hook: HookImport, ...params: []) { + // TODO unique import + return null + } + + tag(tag: TagImport, ...children: ts.JsxChild[]) { + // TODO unique import + return null + } + + returnFragment(...children: ts.JsxChild[]): ts.Statement | null { + + if (children?.length == 1) { + // TODO handle one child + } + + factory.createReturnStatement(factory.createJsxFragment( + factory.createJsxOpeningFragment(), + children, + factory.createJsxJsxClosingFragment() + )) + + return null + } +} \ No newline at end of file diff --git a/packages/react-lowcode/src/codegen/list.ts b/packages/react-lowcode/src/codegen/list.ts new file mode 100644 index 000000000..9c890beba --- /dev/null +++ b/packages/react-lowcode/src/codegen/list.ts @@ -0,0 +1,48 @@ +import { SourceLineCol } from "../ast" +import { CodeRW } from "../io" +import { isDataTableWidget } from "./ast/widgetDeclaration" +import { ColumnSourcePositionOptions, ColumnSourcePositionResult, DeleteOptions, InsertOptions } from "./interfaces" +import { getEntityProperty } from "./tests/helper" +import { + insertColumn, + deleteColumn as fDeleteColumn, + getColumnSourcePosition as fGetColumnSourcePosition, +} from './facade/facadeApi' +import { Property } from "./generation/entity" + +export function isSelectedDataTable(sourceCode:string, tablePosition: SourceLineCol){ + return isDataTableWidget(sourceCode, tablePosition) +} + +export async function addColumn(typesSourceCode: string, + io: CodeRW, + sourceCode:SourceLineCol, + options: InsertOptions): Promise{ + + const property: Property = getEntityProperty(typesSourceCode, options.property, options.entityName)[0] + let generatedSource = undefined + + if(property){ + generatedSource = await insertColumn(sourceCode, + {entityField: property, index: options.index}, + io) + } + + return generatedSource +} + +export async function deleteColumn(io: CodeRW, + sourceCode:SourceLineCol, + options: DeleteOptions): Promise { + +let generatedSource = await fDeleteColumn(sourceCode, options, io); + +return generatedSource +} + +export async function getColumnSourcePosition(io: CodeRW, + sourceCode:SourceLineCol, + options: ColumnSourcePositionOptions): Promise { + +return await fGetColumnSourcePosition(sourceCode, options, io); +} \ No newline at end of file diff --git a/packages/react-lowcode/src/codegen/tests/api/api.test.ts b/packages/react-lowcode/src/codegen/tests/api/api.test.ts index 42f8ce2d1..fc243f7d4 100644 --- a/packages/react-lowcode/src/codegen/tests/api/api.test.ts +++ b/packages/react-lowcode/src/codegen/tests/api/api.test.ts @@ -2,10 +2,11 @@ import fs from 'fs' import path from 'path' import { ts } from 'ts-morph'; import { SyntaxKind } from 'typescript'; -import { addColumn, addFormInput, deleteColumn, getColumnSourcePosition, getFormWidgetProperties, isSelectedDataTable, isSelectedFormWidget, setFormWidgetProperties } from '../..'; import { findByCondition, SourceLineCol } from '../../../ast'; +import { addFormInput, getFormWidgetProperties, isSelectedFormWidget, setFormWidgetProperties } from '../../detail'; import MuiDetailGenerator from '../../generation/generators/detail/mui-detail-generator'; import { CodegenRw } from '../../io/codegenRw'; +import { addColumn, deleteColumn, getColumnSourcePosition, isSelectedDataTable } from '../../list'; import { createAst } from '../helper'; import { TestListHelper } from '../list/list-helper'; import { graphqlGenTs1 } from '../typeAlias.example'; @@ -58,7 +59,7 @@ describe(".api tests", () => { test(".add column (MUI DataTable)", async () => { const filePath = 'src/codegen/tests/list/files/is-datatable-test-file.txt'; const source : SourceLineCol = {lineNumber: 12, columnNumber:17, fileName:filePath}; - const result = await addColumn(graphqlGenTs1, new CodegenRw(), source, {property: 'testdate', entity: 'Customer'}); + const result = await addColumn(graphqlGenTs1, new CodegenRw(), source, {property: 'testdate', entityName: 'Customer'}); expect(result).not.toBe(undefined); @@ -83,7 +84,7 @@ describe(".api tests", () => { // TODO:PC: Expected result: // - added property "test2" to initialValues // - added TextField with id: test2 - addFormInput(graphqlGenTs1, new CodegenRw(), source, {property: 'test2', entity: 'Customer'}).then(generated => console.log(generated)); + addFormInput(graphqlGenTs1, new CodegenRw(), source, {property: 'test2', entityName: 'Customer'}).then(generated => console.log(generated)); }); test(".delete column (MUI DataTable)", async () => { diff --git a/packages/react-lowcode/src/codegen/tests/detail/detail.test.ts b/packages/react-lowcode/src/codegen/tests/detail/detail.test.ts index c29012407..783b247b6 100644 --- a/packages/react-lowcode/src/codegen/tests/detail/detail.test.ts +++ b/packages/react-lowcode/src/codegen/tests/detail/detail.test.ts @@ -1,5 +1,4 @@ // TODO https://github.com/vvakame/typescript-formatter/blob/master/lib/formatter.ts -import { Project, SourceFile } from "ts-morph" import ts, { factory } from "typescript" import { graphqlGenTs1 } from "../typeAlias.example" import { UiFramework, TableType, Formatter } from '../../definition/context-types' diff --git a/packages/react-lowcode/src/codegen/tests/list/list.test.ts b/packages/react-lowcode/src/codegen/tests/list/list.test.ts index bed4ca4a7..97f72eb5e 100644 --- a/packages/react-lowcode/src/codegen/tests/list/list.test.ts +++ b/packages/react-lowcode/src/codegen/tests/list/list.test.ts @@ -2,11 +2,9 @@ import ts, { factory } from "typescript" import { graphqlGenTs1 } from "../typeAlias.example" import { Formatter, TableType, UiFramework } from '../../definition/context-types' import { AppGenerator } from '../../generation/generators/app-generator' -import {generatePages} from '../../index' import { CodeDir, CodeRW } from "../../../io" import { sourceFileEntity, createAst, parseGraphqlTypes } from "../helper" -import path from 'path' -import fs from "fs" +import { generatePages } from "../../app" class testDemoWriter implements CodeRW, CodeDir { private _sourceCodeString: string = ''