Skip to content

Commit

Permalink
feat: add Typescript generator (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZACHSTRIVES committed Mar 11, 2024
1 parent feac580 commit f1decb3
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 87 deletions.
2 changes: 1 addition & 1 deletion scripts/addNewFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const createFormatter = (formatterName: string, fileExtension: string) => {
const path = `./src/core/formatters/${formatterName}`;

// add formatter enum option
enumUtils('./src/constants/enums.ts', 'ExportFormat', formatterName.toUpperCase(), formatterName.toLowerCase());
enumUtils('./src/constants/enums.ts', 'ExportFormat', formatterName.toUpperCase(), formatterName);

// create directory
fs.mkdirSync(path, {recursive: true});
Expand Down
21 changes: 17 additions & 4 deletions src/components/Utils/src/OptionsInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,23 @@ export interface OptionsInputProps {
value: string;
onChange: (value: any) => void;
style?: React.CSSProperties;
required?: boolean; // Add this line for the new required prop
}

export const OptionsInput: React.FunctionComponent<OptionsInputProps> = ({...props}) => {
const {label, infoTooltip, errorMessage, value, style, suffix, onChange} = props;
const {label, infoTooltip, errorMessage, value, style, suffix, onChange, required} = props;

// Add a new useState to manage the validation error message
const [validationError, setValidationError] = React.useState<string | undefined>();

// Add effect to validate value when it changes or when required status changes
React.useEffect(() => {
if (required && isNullOrWhiteSpace(value)) {
setValidationError('This field is required.'); // Set default required error message or use props.errorMessage
} else {
setValidationError(undefined); // Clear error message when input is valid
}
}, [value, required]);

return (
<div className="generatorConfig_column">
Expand All @@ -24,15 +37,15 @@ export const OptionsInput: React.FunctionComponent<OptionsInputProps> = ({...pro
{infoTooltip}
</InfoTooltip>}
</div>
<ErrorTooltip message={errorMessage}>
<ErrorTooltip message={validationError || errorMessage}>
<Input
onChange={(value) => onChange(value)}
value={value}
style={style}
suffix={suffix}
validateStatus={!isNullOrWhiteSpace(errorMessage) ? 'error' : 'default'}
validateStatus={!isNullOrWhiteSpace(validationError || errorMessage) ? 'error' : 'default'}
/>
</ErrorTooltip>
</div>
)
}
}
5 changes: 3 additions & 2 deletions src/constants/enums.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

// export format
export enum ExportFormatCategory {
FILE_TYPES = "fileTypes",
FILE_TYPES = "file_types",
DATABASES = "databases",
PROGRAMMING_LANGUAGES = "programmingLanguages",
PROGRAMMING_LANGUAGES = "programming_languages",
}

export enum ExportFormat {
TYPESCRIPT = "Typescript",
CSHARP = "C#",
SQL = "SQL",
CSV = "CSV",
Expand Down
26 changes: 11 additions & 15 deletions src/core/formatters/CSharp/CSharp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {OptionsInput, OptionsSelect, SelectOption} from "@/components/Utils";
import {FormattedMessage} from "@/locale";
import {OptionsSwitch} from "@/components/Utils/src/OptionsSwitch";
import {ValueType} from "@/constants/enums";
import {GenerateResult} from "@/types/generator";
import {hasValue} from "@/utils/typeUtils";
import {formatValueForCSharp} from "@/core/formatters/CSharp/CSharpFormatterUtils";

// -------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -66,13 +64,13 @@ export const format = (request: FormatRequest): string => {
fieldType = `long${field.emptyRate !== 0 ? "?" : ""}`;
break;
case ValueType.INT_LIST:
fieldType = 'List<int>'
fieldType = 'List<int>';
break;
case ValueType.STRING_LIST:
fieldType = 'List<string>'
fieldType = 'List<string>';
break;
case ValueType.ONE_BIT:
fieldType = `int${field.emptyRate !== 0 ? "?" : ""}`
fieldType = `int${field.emptyRate !== 0 ? "?" : ""}`;
break;
// Add more cases as necessary
}
Expand Down Expand Up @@ -110,20 +108,16 @@ export const format = (request: FormatRequest): string => {

// Populate collection with values
values.forEach((value, index) => {
let fieldAssignments = sortedFieldIds.map(id => {
return ` ${fields[id].fieldName} = ${formatValueForCSharp(value[id], fields[id].valueType)}`;
}).join(',\n'); // Ensure each field assignment is on a new line

if (config.collectionType === CSharpCollectionType.ARRAY) {
// Array value assignment
csharpCode += `${config.collectionName}[${index}] = new ${itemType} { `;
csharpCode += `${config.collectionName}[${index}] = new ${itemType} {\n${fieldAssignments}\n };\n`;
} else {
// Other collections value addition
csharpCode += `${config.collectionName}.Add(new ${itemType} { `;
}

csharpCode += sortedFieldIds.map(id => `${fields[id].fieldName} = ${formatValueForCSharp(value[id], fields[id].valueType)}`).join(', ');

if (config.collectionType === CSharpCollectionType.ARRAY) {
csharpCode += ' };\n';
} else {
csharpCode += ' });\n';
csharpCode += `${config.collectionName}.Add(new ${itemType}\n{\n${fieldAssignments}\n});\n`;
}
});

Expand Down Expand Up @@ -156,6 +150,7 @@ export const CSharpConfigComponent: React.FC<FormatterConfigComponentInterface>
/>

<OptionsInput
required
label={<FormattedMessage id="export.configurator.csharp.collectionName"/>}
value={config.collectionName}
onChange={(v) => handleValueChange("collectionName", v)}
Expand All @@ -170,6 +165,7 @@ export const CSharpConfigComponent: React.FC<FormatterConfigComponentInterface>

{
config.dtoClass && <OptionsInput
required
label={<FormattedMessage id="export.configurator.csharp.dtoClassName"/>}
value={config.dtoClassName}
onChange={(v) => handleValueChange("dtoClassName", v)}
Expand Down
27 changes: 5 additions & 22 deletions src/core/formatters/JavaScript/Javascript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {FormatRequest, FormatterConfigComponentInterface} from "@/types/formatte
import {FormattedMessage, useIntl} from "@/locale";
import {OptionsInput, OptionsSelect, SelectOption} from "@/components/Utils";
import {isNullOrWhiteSpace} from "@/utils/stringUtils";
import {toJsonListStringWithoutQuotes} from "@/utils/formatterUtils";

// -------------------------------------------------------------------------------------------------------------
// types
Expand Down Expand Up @@ -38,30 +39,12 @@ export const format = (request: FormatRequest): string => {
return '';
}

const jsonData = values.map(item => {
const row: Record<string, string | null> = {};
for (const column of sortedFieldIds) {
const field = fields[column];
const {isDraft, fieldName} = field;
const itemValue = item[column];
let {value} = itemValue;

if (!isDraft && value !== null) {
if (typeof value === 'bigint') {
value = value.toString();
}
row[fieldName] = value;
}
}
return row;
});

let output = JSON.stringify(jsonData, null, 3);
let output = toJsonListStringWithoutQuotes(fields, sortedFieldIds, values);

return formatOutput(formatType, module, declarationKeyword, variableName, output);
};

function formatOutput(formatType, module, declarationKeyword, variableName, output) {
function formatOutput(formatType: JavascriptFormatterFormat, module: JavascriptModuleType, declarationKeyword: JavascriptDeclarationKeyword, variableName: string, output: string) {
switch (formatType) {
case JavascriptFormatterFormat.VARIABLE:
return formatVariableOutput(declarationKeyword, variableName, output);
Expand All @@ -72,14 +55,14 @@ function formatOutput(formatType, module, declarationKeyword, variableName, outp
}
}

function formatVariableOutput(declarationKeyword, variableName, output) {
function formatVariableOutput(declarationKeyword: JavascriptDeclarationKeyword, variableName: string, output: string) {
if (isNullOrWhiteSpace(variableName)) {
return '';
}
return `${declarationKeyword} ${variableName} = ${output};`;
}

function formatExportOutput(module, output) {
function formatExportOutput(module: JavascriptModuleType, output: string) {
switch (module) {
case JavascriptModuleType.ES6:
return `export default ${output};`;
Expand Down
131 changes: 131 additions & 0 deletions src/core/formatters/Typescript/Typescript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from "react";
import {FormatRequest, FormatterConfigComponentInterface} from "@/types/formatter";
import {OptionsInput} from "@/components/Utils";
import {OptionsRadio, RadioOption} from "@/components/Utils/src/OptionsRadio";
import {FormattedMessage} from "@/locale";
import {toJsonListStringWithoutQuotes} from "@/utils/formatterUtils";
import {ValueType} from "@/constants/enums";

// -------------------------------------------------------------------------------------------------------------
// types

export enum TypescriptDeclaration {
INTERFACE = "interface",
TYPE = "type"
}

export type TypescriptFormatterConfig = {
declaration: TypescriptDeclaration,
declarationName: string,
variableName: string
}

// -------------------------------------------------------------------------------------------------------------
// default options

export const defaultTypescriptFormatterConfig: TypescriptFormatterConfig = {
declaration: TypescriptDeclaration.TYPE,
declarationName: "MyRecord",
variableName: "myData"
}

// -------------------------------------------------------------------------------------------------------------
// format method

export const format = (request: FormatRequest): string => {
const {fields, values, sortedFieldIds, config} = request;
const {declaration, declarationName, variableName} = config as TypescriptFormatterConfig;

if (sortedFieldIds.length === 0 || values.length === 0) {
return '';
}

let output = '';

// type & interface
output += `export ${declaration} ${declarationName}${declaration === TypescriptDeclaration.TYPE ? " = " : ""} {\n`;
sortedFieldIds.forEach(id => {
const field = fields[id];
let fieldType = 'string'; // Default field type
switch (field.valueType) {
case ValueType.BIGINT:
case ValueType.DOUBLE:
case ValueType.ONE_BIT:
case ValueType.INT:
fieldType = `number`;
break;
case ValueType.BOOLEAN:
fieldType = 'boolean';
break;
case ValueType.INT_LIST:
fieldType = 'number[]'
break;
case ValueType.STRING_LIST:
fieldType = 'string[]'
break;
}
output+= ` ${field.fieldName}${field.emptyRate !== 0 ? "?" : ""}: ${fieldType};\n`;
});
output+="}; \n\n"

// values
output += `export const ${variableName}: ${declarationName}[] = ${toJsonListStringWithoutQuotes(fields, sortedFieldIds, values)};`;

return output;
}

// -------------------------------------------------------------------------------------------------------------
// config component

export const TypescriptConfigComponent: React.FC<FormatterConfigComponentInterface> = ({...props}) => {
const {config, onConfigChange} = props as {
config: TypescriptFormatterConfig
onConfigChange: typeof props.onConfigChange
}

// action
const handleValueChange = (field: string, value: any) => {
onConfigChange({...config, [field]: value})
}

// TODO: implement your own configs component here
return (
<div>
<OptionsRadio
label={<FormattedMessage id={"export.configurator.typescript.declarationType"}/>}
radioOptions={typescriptDeclarationTypeRadioOptions}
type={"button"}
value={config.declaration}
onChange={(v) => handleValueChange("declaration", v)}
style={{"width": "160px"}}
/>

<OptionsInput
label={<FormattedMessage
id={`export.configurator.typescript.declarationType.${config.declaration}.name`}/>}
value={config.declarationName}
onChange={(v) => handleValueChange("declarationName", v)}
style={{"width": "155px"}}
/>

<OptionsInput
label={<FormattedMessage id={"export.configurator.typescript.variableName"}/>}
value={config.variableName}
onChange={(v) => handleValueChange("variableName", v)}
style={{"width": "160px"}}
/>

</div>
);
}

const typescriptDeclarationTypeRadioOptions: RadioOption[] = [
{
label: "type",
value: TypescriptDeclaration.TYPE
},
{
label: "interface",
value: TypescriptDeclaration.INTERFACE
}
]
13 changes: 13 additions & 0 deletions src/core/formatters/Typescript/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Formatter} from "@/types/formatter";
import {ExportFormat, ExportFormatCategory} from "@/constants/enums";
import {TypescriptConfigComponent, format, defaultTypescriptFormatterConfig} from "./Typescript";


export const TypescriptFormatter: Formatter = {
type: ExportFormat.TYPESCRIPT,
category: ExportFormatCategory.PROGRAMMING_LANGUAGES,
format: format,
fileExtension: 'ts',
configComponent: TypescriptConfigComponent,
defaultConfig: defaultTypescriptFormatterConfig,
}
2 changes: 2 additions & 0 deletions src/core/formatters/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {TypescriptFormatter} from "@/core/formatters/Typescript";
import {CSharpFormatter} from "@/core/formatters/CSharp";
import {SqlFormatter} from "@/core/formatters/Sql";
import {CsvFormatter} from "@/core/formatters/Csv";
Expand All @@ -8,6 +9,7 @@ import {XmlFormatter} from "@/core/formatters/Xml";


export const formatters = {
[ExportFormat.TYPESCRIPT]: TypescriptFormatter,
[ExportFormat.CSHARP]: CSharpFormatter,
[ExportFormat.SQL]: SqlFormatter,
[ExportFormat.CSV]: CsvFormatter,
Expand Down
Loading

0 comments on commit f1decb3

Please sign in to comment.