From 9ceb6bc39397f7466d200cb0546c0d3a402b3a4f Mon Sep 17 00:00:00 2001 From: Justin Shih <36183898+Jshhhh@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:52:58 -0700 Subject: [PATCH] Render form utils and add forms to index file (#536) * fix: remove locale dependency on date formatting * feat: add utils file renderer Co-authored-by: Kevin Pranoto Co-authored-by: Justin Shih --- ...studio-ui-codegen-react-forms.test.ts.snap | 63 +- .../lib/amplify-ui-renderers/form.ts | 46 +- .../lib/forms/react-form-renderer.ts | 1 + packages/codegen-ui-react/lib/index.ts | 1 + .../react-index-studio-template-renderer.ts | 4 +- .../react-utils-studio-template-renderer.ts | 82 + .../lib/utils/forms/validation.ts | 1327 ++++++++++++++++- .../lib/types/form/form-metadata.ts | 11 +- .../lib/utils/form-component-metadata.ts | 12 + .../codegen-ui/lib/utils/string-formatter.ts | 22 +- .../cypress/e2e/generate-spec.cy.ts | 1 + .../lib/forms/basic-form-create.json | 44 + packages/test-generator/lib/forms/index.ts | 17 + .../lib/generators/BrowserTestGenerator.ts | 20 +- .../lib/generators/NodeTestGenerator.ts | 50 +- .../lib/generators/TestGenerator.ts | 75 +- 16 files changed, 1647 insertions(+), 129 deletions(-) create mode 100644 packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts create mode 100644 packages/test-generator/lib/forms/basic-form-create.json create mode 100644 packages/test-generator/lib/forms/index.ts diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index e8ef60837..04d40397b 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap @@ -7,13 +7,7 @@ import { getOverrideProps, useStateMutationAction, } from \\"@aws-amplify/ui-react/internal\\"; -import { - Button, - Flex, - Grid, - TextField, - useTypeCastFields, -} from \\"@aws-amplify/ui-react\\"; +import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; export default function customDataForm(props) { const { @@ -34,15 +28,10 @@ export default function customDataForm(props) { ); } try { - await DataStore.save( - new Post( - useTypeCastFields({ - fields: customDataFormFields, - modelName: Post.name, - schema, - }) - ) - ); + await DataStore.save(new Post(customDataFormFields)); + if (onSubmitComplete) { + onSubmitComplete({ saveSuccessful: true }); + } } catch (err) { if (onSubmitComplete) { onSubmitComplete({ @@ -140,13 +129,7 @@ import { } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { schema } from \\"../models/schema\\"; -import { - Button, - Flex, - Grid, - TextField, - useTypeCastFields, -} from \\"@aws-amplify/ui-react\\"; +import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; export default function myPostForm(props) { const { onSubmitBefore, onSubmitComplete, onCancel, overrides, ...rest } = @@ -165,15 +148,10 @@ export default function myPostForm(props) { setMyPostFormFields(onSubmitBefore({ fields: myPostFormFields })); } try { - await DataStore.save( - new Post( - useTypeCastFields({ - fields: myPostFormFields, - modelName: Post.name, - schema, - }) - ) - ); + await DataStore.save(new Post(myPostFormFields)); + if (onSubmitComplete) { + onSubmitComplete({ saveSuccessful: true }); + } } catch (err) { if (onSubmitComplete) { onSubmitComplete({ @@ -301,13 +279,7 @@ import { } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { schema } from \\"../models/schema\\"; -import { - Button, - Flex, - Grid, - TextField, - useTypeCastFields, -} from \\"@aws-amplify/ui-react\\"; +import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; export default function myPostForm(props) { const { id, onSubmitBefore, onSubmitComplete, onCancel, overrides, ...rest } = @@ -327,15 +299,10 @@ export default function myPostForm(props) { setMyPostFormFields(onSubmitBefore({ fields: myPostFormFields })); } try { - await DataStore.save( - new Post( - useTypeCastFields({ - fields: myPostFormFields, - modelName: Post.name, - schema, - }) - ) - ); + await DataStore.save(new Post(myPostFormFields)); + if (onSubmitComplete) { + onSubmitComplete({ saveSuccessful: true }); + } } catch (err) { if (onSubmitComplete) { onSubmitComplete({ diff --git a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts index 88f0dfb51..f55b9b5a8 100644 --- a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts +++ b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts @@ -48,7 +48,6 @@ export default class FormRenderer extends ReactComponentRenderer { await this.renderComponentToFilesystem(transpiledComponentText)(this.fileName)(outputPath); if (declaration) { diff --git a/packages/codegen-ui-react/lib/index.ts b/packages/codegen-ui-react/lib/index.ts index 17ef40979..c94d75618 100644 --- a/packages/codegen-ui-react/lib/index.ts +++ b/packages/codegen-ui-react/lib/index.ts @@ -24,4 +24,5 @@ export * from './amplify-ui-renderers/amplify-renderer'; export * from './amplify-ui-renderers/amplify-form-renderer'; export * from './primitive'; export * from './react-index-studio-template-renderer'; +export * from './react-utils-studio-template-renderer'; export * from './react-required-dependency-provider'; diff --git a/packages/codegen-ui-react/lib/react-index-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-index-studio-template-renderer.ts index 938f91459..52e281252 100644 --- a/packages/codegen-ui-react/lib/react-index-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-index-studio-template-renderer.ts @@ -15,14 +15,14 @@ */ import { EOL } from 'os'; import { EmitHint, ExportDeclaration, factory } from 'typescript'; -import { StudioTemplateRenderer, StudioTheme, StudioComponent } from '@aws-amplify/codegen-ui'; +import { StudioTemplateRenderer, StudioTheme, StudioComponent, StudioForm } from '@aws-amplify/codegen-ui'; import { ReactRenderConfig, scriptKindToFileExtensionNonReact } from './react-render-config'; import { ImportCollection } from './imports'; import { ReactOutputManager } from './react-output-manager'; import { transpile, buildPrinter, defaultRenderConfig } from './react-studio-template-renderer-helper'; import { RequiredKeys } from './utils/type-utils'; -type StudioSchema = StudioComponent | StudioTheme; +type StudioSchema = StudioComponent | StudioForm | StudioTheme; export class ReactIndexStudioTemplateRenderer extends StudioTemplateRenderer< string, diff --git a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts new file mode 100644 index 000000000..094c21dda --- /dev/null +++ b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts @@ -0,0 +1,82 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { EOL } from 'os'; +import ts, { EmitHint } from 'typescript'; +import { StudioTemplateRenderer } from '@aws-amplify/codegen-ui'; +import { ReactRenderConfig, scriptKindToFileExtensionNonReact } from './react-render-config'; +import { ImportCollection } from './imports'; +import { ReactOutputManager } from './react-output-manager'; +import { RequiredKeys } from './utils/type-utils'; +import { transpile, buildPrinter, defaultRenderConfig } from './react-studio-template-renderer-helper'; +import { generateValidationFunction } from './utils/forms/validation'; + +export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer< + string, + string[], + ReactOutputManager, + { + componentText: string; + renderComponentToFilesystem: (outputPath: string) => Promise; + } +> { + protected importCollection = new ImportCollection(); + + protected renderConfig: RequiredKeys; + + fileName: string; + + /* + * list of util functions to generate + */ + utils: string[]; + + constructor(utils: string[], renderConfig: ReactRenderConfig) { + super(utils, new ReactOutputManager(), renderConfig); + this.utils = utils; + this.renderConfig = { + ...defaultRenderConfig, + ...renderConfig, + renderTypeDeclarations: false, // Never render type declarations for index.js|ts file. + }; + this.fileName = `utils.${scriptKindToFileExtensionNonReact(this.renderConfig.script)}`; + } + + renderComponentInternal() { + const { printer, file } = buildPrinter(this.fileName, this.renderConfig); + const utilsStatements: (ts.TypeAliasDeclaration | ts.VariableStatement)[] = []; + + this.utils.forEach((util) => { + if (util === 'validation') { + utilsStatements.push(...generateValidationFunction()); + } + }); + + const { componentText } = transpile( + utilsStatements.map((util) => printer.printNode(EmitHint.Unspecified, util, file)).join(EOL), + this.renderConfig, + ); + + return { + componentText, + renderComponentToFilesystem: async (outputPath: string) => { + await this.renderComponentToFilesystem(componentText)(this.fileName)(outputPath); + }, + }; + } + + // no-op + validateSchema() {} +} diff --git a/packages/codegen-ui-react/lib/utils/forms/validation.ts b/packages/codegen-ui-react/lib/utils/forms/validation.ts index 1d7db9bc8..899b1132d 100644 --- a/packages/codegen-ui-react/lib/utils/forms/validation.ts +++ b/packages/codegen-ui-react/lib/utils/forms/validation.ts @@ -1,88 +1,89 @@ /* eslint-disable */ -import { - FieldValidationConfiguration, - ValidationResponse, - ValidationTypes, -} from '@aws-amplify/codegen-ui/lib/types/form/form-validation'; +import ts, { factory } from 'typescript'; -export const validateField = (value: any, validations: FieldValidationConfiguration[]): ValidationResponse => { +type ValidationResponse = { hasError: boolean; errorMessage?: string }; + +export const validateField = ( + value: any, + validations: { type: string; values?: any; validationMessage: string }[], +): ValidationResponse => { for (const validation of validations) { switch (validation.type) { - case ValidationTypes.REQUIRED: + case 'Required': return { hasError: value === undefined || value === '', errorMessage: validation.validationMessage || 'The value is required', }; - case ValidationTypes.START_WITH: + case 'StartWith': return { - hasError: !validation.values.some((el) => value.startsWith(el)), - errorMessage: validation.validationMessage || `The value must start with ${validation.values.join(', ')}`, + hasError: !validation.values?.some((el: any) => value.startsWith(el)), + errorMessage: validation.validationMessage || `The value must start with ${validation.values?.join(', ')}`, }; - case ValidationTypes.END_WITH: + case 'EndWith': return { - hasError: !validation.values.some((el) => value.endsWith(el)), - errorMessage: validation.validationMessage || `The value must end with ${validation.values.join(', ')}`, + hasError: !validation.values?.some((el: any) => value.endsWith(el)), + errorMessage: validation.validationMessage || `The value must end with ${validation.values?.join(', ')}`, }; - case ValidationTypes.CONTAINS: + case 'Contains': return { - hasError: !validation.values.some((el) => value.includes(el)), - errorMessage: validation.validationMessage || `The value must contain ${validation.values.join(', ')}`, + hasError: !validation.values?.some((el: any) => value.includes(el)), + errorMessage: validation.validationMessage || `The value must contain ${validation.values?.join(', ')}`, }; - case ValidationTypes.NOT_CONTAINS: + case 'NotContains': return { - hasError: !validation.values.every((el) => !value.includes(el)), - errorMessage: validation.validationMessage || `The value must not contain ${validation.values.join(', ')}`, + hasError: !validation.values?.every((el: any) => !value.includes(el)), + errorMessage: validation.validationMessage || `The value must not contain ${validation.values?.join(', ')}`, }; - case ValidationTypes.LESS_THAN_CHAR_LENGTH: + case 'LessThanChar': return { hasError: !(value.length < validation.values), errorMessage: validation.validationMessage || `The value must be shorter than ${validation.values}`, }; - case ValidationTypes.GREATER_THAN_CHAR_LENGTH: + case 'GreaterThanChar': return { hasError: !(value.length > validation.values), errorMessage: validation.validationMessage || `The value must be longer than ${validation.values}`, }; - case ValidationTypes.LESS_THAN_NUM: + case 'LessThanNum': return { hasError: !(value < validation.values), errorMessage: validation.validationMessage || `The value must be less than ${validation.values}`, }; - case ValidationTypes.GREATER_THAN_NUM: + case 'GreaterThanNum': return { hasError: !(value > validation.values), errorMessage: validation.validationMessage || `The value must be greater than ${validation.values}`, }; - case ValidationTypes.EQUAL_TO_NUM: + case 'EqualTo': if (Array.isArray(validation.values)) { return { - hasError: !validation.values.some((el) => el === value), + hasError: !validation.values?.some((el) => el === value), errorMessage: - validation.validationMessage || `The value must be equal to ${validation.values.join(' or ')}`, + validation.validationMessage || `The value must be equal to ${validation.values?.join(' or ')}`, }; } return { hasError: !(value === validation.values), errorMessage: validation.validationMessage || `The value must be equal to ${validation.values}`, }; - case ValidationTypes.BE_AFTER: + case 'BeAfter': return { hasError: !(new Date(value) > new Date(validation.values)), errorMessage: validation.validationMessage || `The value must be after ${validation.values}`, }; - case ValidationTypes.BE_BEFORE: + case 'BeBefore': return { hasError: !(new Date(value) < new Date(validation.values)), errorMessage: validation.validationMessage || `The value must be before ${validation.values}`, }; - case ValidationTypes.EMAIL: + case 'Email': const EMAIL_ADDRESS_REGEX = /^[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/; return { hasError: !EMAIL_ADDRESS_REGEX.test(value), errorMessage: validation.validationMessage || 'The value must be a valid email address', }; - case ValidationTypes.JSON: + case 'JSON': let isInvalidJSON = false; try { JSON.parse(value); @@ -93,7 +94,7 @@ export const validateField = (value: any, validations: FieldValidationConfigurat hasError: isInvalidJSON, errorMessage: validation.validationMessage || 'The value must be in a correct JSON format', }; - case ValidationTypes.IP_ADDRESS: + case 'IpAddress': const IPV_4 = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/; const IPV_6 = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/; @@ -101,7 +102,7 @@ export const validateField = (value: any, validations: FieldValidationConfigurat hasError: !(IPV_4.test(value) || IPV_6.test(value)), errorMessage: validation.validationMessage || 'The value must be an IPv4 or IPv6 address', }; - case ValidationTypes.URL: + case 'URL': let isInvalidUrl = false; try { new URL(value); @@ -119,3 +120,1265 @@ export const validateField = (value: any, validations: FieldValidationConfigurat } return { hasError: false }; }; + +export const generateValidationFunction = () => { + return [ + factory.createTypeAliasDeclaration( + undefined, + undefined, + factory.createIdentifier('ValidationResponse'), + undefined, + factory.createTypeLiteralNode([ + factory.createPropertySignature( + undefined, + factory.createIdentifier('hasError'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), + ), + factory.createPropertySignature( + undefined, + factory.createIdentifier('errorMessage'), + factory.createToken(ts.SyntaxKind.QuestionToken), + factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ), + ]), + ), + factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('validateField'), + undefined, + undefined, + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('value'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined, + ), + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('validations'), + undefined, + factory.createArrayTypeNode( + factory.createTypeLiteralNode([ + factory.createPropertySignature( + undefined, + factory.createIdentifier('type'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ), + factory.createPropertySignature( + undefined, + factory.createIdentifier('values'), + factory.createToken(ts.SyntaxKind.QuestionToken), + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + ), + factory.createPropertySignature( + undefined, + factory.createIdentifier('validationMessage'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ), + ]), + ), + undefined, + ), + ], + factory.createTypeReferenceNode(factory.createIdentifier('ValidationResponse'), undefined), + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createForOfStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('validation'), + undefined, + undefined, + undefined, + ), + ], + ts.NodeFlags.Const, + ), + factory.createIdentifier('validations'), + factory.createBlock( + [ + factory.createSwitchStatement( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('type'), + ), + factory.createCaseBlock([ + factory.createCaseClause(factory.createStringLiteral('Required'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createBinaryExpression( + factory.createBinaryExpression( + factory.createIdentifier('value'), + factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + factory.createIdentifier('undefined'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createBinaryExpression( + factory.createIdentifier('value'), + factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + factory.createStringLiteral(''), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createStringLiteral('The value is required'), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('StartWith'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('some'), + ), + undefined, + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('el'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined, + ), + ], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('startsWith'), + ), + undefined, + [factory.createIdentifier('el')], + ), + ), + ], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must start with ', + 'The value must start with ', + ), + [ + factory.createTemplateSpan( + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('join'), + ), + undefined, + undefined, + [factory.createStringLiteral(', ')], + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('EndWith'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('some'), + ), + undefined, + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('el'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined, + ), + ], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('endsWith'), + ), + undefined, + [factory.createIdentifier('el')], + ), + ), + ], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must end with ', + 'The value must end with ', + ), + [ + factory.createTemplateSpan( + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('join'), + ), + undefined, + undefined, + [factory.createStringLiteral(', ')], + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('Contains'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('some'), + ), + undefined, + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('el'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined, + ), + ], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('includes'), + ), + undefined, + [factory.createIdentifier('el')], + ), + ), + ], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must contain ', + 'The value must contain ', + ), + [ + factory.createTemplateSpan( + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('join'), + ), + undefined, + undefined, + [factory.createStringLiteral(', ')], + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('NotContains'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('every'), + ), + undefined, + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('el'), + undefined, + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined, + ), + ], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('includes'), + ), + undefined, + [factory.createIdentifier('el')], + ), + ), + ), + ], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must not contain ', + 'The value must not contain ', + ), + [ + factory.createTemplateSpan( + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('join'), + ), + undefined, + undefined, + [factory.createStringLiteral(', ')], + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('LessThanChar'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('length'), + ), + factory.createToken(ts.SyntaxKind.LessThanToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be shorter than ', + 'The value must be shorter than ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('GreaterThanChar'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('length'), + ), + factory.createToken(ts.SyntaxKind.GreaterThanToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be longer than ', + 'The value must be longer than ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('LessThanNum'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createIdentifier('value'), + factory.createToken(ts.SyntaxKind.LessThanToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be less than ', + 'The value must be less than ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('GreaterThanNum'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createIdentifier('value'), + factory.createToken(ts.SyntaxKind.GreaterThanToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be greater than ', + 'The value must be greater than ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('EqualTo'), [ + factory.createIfStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('Array'), + factory.createIdentifier('isArray'), + ), + undefined, + [ + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ], + ), + factory.createBlock( + [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('some'), + ), + undefined, + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('el'), + undefined, + undefined, + undefined, + ), + ], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createBinaryExpression( + factory.createIdentifier('el'), + factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + factory.createIdentifier('value'), + ), + ), + ], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be equal to ', + 'The value must be equal to ', + ), + [ + factory.createTemplateSpan( + factory.createCallChain( + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createToken(ts.SyntaxKind.QuestionDotToken), + factory.createIdentifier('join'), + ), + undefined, + undefined, + [factory.createStringLiteral(' or ')], + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ], + true, + ), + undefined, + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createIdentifier('value'), + factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be equal to ', + 'The value must be equal to ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('BeAfter'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createNewExpression(factory.createIdentifier('Date'), undefined, [ + factory.createIdentifier('value'), + ]), + factory.createToken(ts.SyntaxKind.GreaterThanToken), + factory.createNewExpression(factory.createIdentifier('Date'), undefined, [ + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ]), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be after ', + 'The value must be after ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('BeBefore'), [ + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createNewExpression(factory.createIdentifier('Date'), undefined, [ + factory.createIdentifier('value'), + ]), + factory.createToken(ts.SyntaxKind.LessThanToken), + factory.createNewExpression(factory.createIdentifier('Date'), undefined, [ + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + ]), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createTemplateExpression( + factory.createTemplateHead( + 'The value must be before ', + 'The value must be before ', + ), + [ + factory.createTemplateSpan( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('values'), + ), + factory.createTemplateTail('', ''), + ), + ], + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('Email'), [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('EMAIL_ADDRESS_REGEX'), + undefined, + undefined, + factory.createRegularExpressionLiteral( + "/^[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~](.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*.?[a-zA-Z0-9])*.[a-zA-Z](-?[a-zA-Z0-9])+$/", + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('EMAIL_ADDRESS_REGEX'), + factory.createIdentifier('test'), + ), + undefined, + [factory.createIdentifier('value')], + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createStringLiteral('The value must be a valid email address'), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('JSON'), [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('isInvalidJSON'), + undefined, + undefined, + factory.createFalse(), + ), + ], + ts.NodeFlags.Let, + ), + ), + factory.createTryStatement( + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('JSON'), + factory.createIdentifier('parse'), + ), + undefined, + [factory.createIdentifier('value')], + ), + ), + ], + true, + ), + factory.createCatchClause( + factory.createVariableDeclaration( + factory.createIdentifier('e'), + undefined, + undefined, + undefined, + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createIdentifier('isInvalidJSON'), + factory.createToken(ts.SyntaxKind.EqualsToken), + factory.createTrue(), + ), + ), + ], + true, + ), + ), + undefined, + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createIdentifier('isInvalidJSON'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createStringLiteral('The value must be in a correct JSON format'), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('IpAddress'), [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('IPV_4'), + undefined, + undefined, + factory.createRegularExpressionLiteral( + '/^(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}$/', + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('IPV_6'), + undefined, + undefined, + factory.createRegularExpressionLiteral( + '/^(?:(?:[a-fA-Fd]{1,4}:){7}(?:[a-fA-Fd]{1,4}|:)|(?:[a-fA-Fd]{1,4}:){6}(?:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|:[a-fA-Fd]{1,4}|:)|(?:[a-fA-Fd]{1,4}:){5}(?::(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,2}|:)|(?:[a-fA-Fd]{1,4}:){4}(?:(?::[a-fA-Fd]{1,4}){0,1}:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,3}|:)|(?:[a-fA-Fd]{1,4}:){3}(?:(?::[a-fA-Fd]{1,4}){0,2}:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,4}|:)|(?:[a-fA-Fd]{1,4}:){2}(?:(?::[a-fA-Fd]{1,4}){0,3}:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,5}|:)|(?:[a-fA-Fd]{1,4}:){1}(?:(?::[a-fA-Fd]{1,4}){0,4}:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-Fd]{1,4}){0,5}:(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)(?:\\.(?:25[0-5]|2[0-4]d|1dd|[1-9]d|d)){3}|(?::[a-fA-Fd]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/', + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + factory.createParenthesizedExpression( + factory.createBinaryExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('IPV_4'), + factory.createIdentifier('test'), + ), + undefined, + [factory.createIdentifier('value')], + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('IPV_6'), + factory.createIdentifier('test'), + ), + undefined, + [factory.createIdentifier('value')], + ), + ), + ), + ), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createStringLiteral('The value must be an IPv4 or IPv6 address'), + ), + ), + ], + true, + ), + ), + ]), + factory.createCaseClause(factory.createStringLiteral('URL'), [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('isInvalidUrl'), + undefined, + undefined, + factory.createFalse(), + ), + ], + ts.NodeFlags.Let, + ), + ), + factory.createTryStatement( + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createNewExpression(factory.createIdentifier('URL'), undefined, [ + factory.createIdentifier('value'), + ]), + ), + ], + true, + ), + factory.createCatchClause( + factory.createVariableDeclaration( + factory.createIdentifier('e'), + undefined, + undefined, + undefined, + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createIdentifier('isInvalidUrl'), + factory.createToken(ts.SyntaxKind.EqualsToken), + factory.createTrue(), + ), + ), + ], + true, + ), + ), + undefined, + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('hasError'), + factory.createIdentifier('isInvalidUrl'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('errorMessage'), + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('validation'), + factory.createIdentifier('validationMessage'), + ), + factory.createToken(ts.SyntaxKind.BarBarToken), + factory.createStringLiteral( + 'The value must be a valid URL that begins with a schema (i.e. http:// or mailto:)', + ), + ), + ), + ], + true, + ), + ), + ]), + factory.createDefaultClause([]), + ]), + ), + ], + true, + ), + ), + factory.createReturnStatement( + factory.createObjectLiteralExpression( + [factory.createPropertyAssignment(factory.createIdentifier('hasError'), factory.createFalse())], + false, + ), + ), + ], + true, + ), + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + ]; +}; diff --git a/packages/codegen-ui/lib/types/form/form-metadata.ts b/packages/codegen-ui/lib/types/form/form-metadata.ts index 85ba146e8..da7ead534 100644 --- a/packages/codegen-ui/lib/types/form/form-metadata.ts +++ b/packages/codegen-ui/lib/types/form/form-metadata.ts @@ -13,17 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -export type FormValidation = { - validationType: string; - validationRule: string; -}; +import { FieldValidationConfiguration } from './form-validation'; export type FormMetadata = { name: string; fieldState: string; onChangeFields: string[]; errorStateFields: string[]; - // indicates the validation function provided for that field - // ex. name field has a lengthValidation type where the rule is length > 5 - onValidationFields?: Record; + // indicates the validation rule for that field + // ex. name field has a string validation type where the rule is char length > 5 + onValidationFields?: { [field: string]: FieldValidationConfiguration[] }; }; diff --git a/packages/codegen-ui/lib/utils/form-component-metadata.ts b/packages/codegen-ui/lib/utils/form-component-metadata.ts index 0426b351d..0a041d33d 100644 --- a/packages/codegen-ui/lib/utils/form-component-metadata.ts +++ b/packages/codegen-ui/lib/utils/form-component-metadata.ts @@ -14,6 +14,7 @@ limitations under the License. */ import { FormDefinition, FormMetadata, StudioForm } from '../types'; +import { FieldValidationConfiguration } from '../types/form/form-validation'; export const getFormFieldStateName = (formName: string) => { return [formName.charAt(0).toLowerCase() + formName.slice(1), 'Fields'].join(''); @@ -29,6 +30,17 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition } return fields; }, []), + onValidationFields: Object.entries(form.fields).reduce<{ [field: string]: FieldValidationConfiguration[] }>( + (validationFields, [field, config]) => { + if ('validations' in config && config.validations?.length) { + const updatedFields = validationFields; + updatedFields[field] = config.validations; + return updatedFields; + } + return validationFields; + }, + {}, + ), errorStateFields: [], }; }; diff --git a/packages/codegen-ui/lib/utils/string-formatter.ts b/packages/codegen-ui/lib/utils/string-formatter.ts index a644011dd..7074fab94 100644 --- a/packages/codegen-ui/lib/utils/string-formatter.ts +++ b/packages/codegen-ui/lib/utils/string-formatter.ts @@ -18,6 +18,21 @@ import { DateFormat, DateTimeFormat, TimeFormat } from '../types'; const invalidDateStr = 'Invalid Date'; +const monthToShortMon: { [mon: string]: string } = { + '1': 'Jan', + '2': 'Feb', + '3': 'Mar', + '4': 'Apr', + '5': 'May', + '6': 'Jun', + '7': 'Jul', + '8': 'Aug', + '9': 'Sep', + '10': 'Oct', + '11': 'Nov', + '12': 'Dec', +}; + export function formatDate(date: string, format: DateFormat['dateFormat']): string { if (date === undefined || date === null) { return date; @@ -33,7 +48,10 @@ export function formatDate(date: string, format: DateFormat['dateFormat']): stri const year = splitDate[0]; const month = splitDate[1]; - const day = validDate.toLocaleString('en-us', { day: '2-digit' }); + const day = splitDate[2]; + + // Remove leading zeroes + const truncatedMonth = month.replace(/^0+/, ''); switch (format) { case 'locale': @@ -45,7 +63,7 @@ export function formatDate(date: string, format: DateFormat['dateFormat']): stri case 'MM/DD/YYYY': return `${month}/${day}/${year}`; case 'Mmm DD, YYYY': - return `${validDate.toLocaleString('en-us', { month: 'short' })} ${day}, ${year}`; + return `${monthToShortMon[truncatedMonth]} ${day}, ${year}`; default: return date; } diff --git a/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts b/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts index 6e086930e..3c7c97156 100644 --- a/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts +++ b/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts @@ -28,6 +28,7 @@ const EXPECTED_SUCCESSFUL_CASES = new Set([ 'BasicComponentImage', 'BasicComponentText', 'BasicComponentCustomRating', + 'BasicForm', 'ComponentWithDataBindingWithPredicate', 'ComponentWithDataBindingWithoutPredicate', 'ComponentWithSimplePropertyBinding', diff --git a/packages/test-generator/lib/forms/basic-form-create.json b/packages/test-generator/lib/forms/basic-form-create.json new file mode 100644 index 000000000..3b8a57853 --- /dev/null +++ b/packages/test-generator/lib/forms/basic-form-create.json @@ -0,0 +1,44 @@ +{ + "name": "BasicFormCreate", + "formActionType": "create", + "dataType": { + "dataSourceType": "Custom", + "dataTypeName": "Post" + }, + "fields": { + "name": { + "inputType": { + "required": true, + "type": "TextField", + "name": "name", + "defaultValue": "John Doe" + }, + "label": "name", + "validations": [ + { + "type": "Required" + } + ] + }, + "email": { + "inputType": { + "required": true, + "type": "TextField", + "name": "email", + "defaultValue": "johndoe@amplify.com" + }, + "label": "E-mail", + "validations": [ + { + "type": "Required" + }, + { + "type": "Email", + "validationMessage": "Email should be required" + } + ] + } + }, + "sectionalElements": {}, + "style": {} +} \ No newline at end of file diff --git a/packages/test-generator/lib/forms/index.ts b/packages/test-generator/lib/forms/index.ts new file mode 100644 index 000000000..d05edd67b --- /dev/null +++ b/packages/test-generator/lib/forms/index.ts @@ -0,0 +1,17 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +export { default as BasicForm } from './basic-form-create.json'; diff --git a/packages/test-generator/lib/generators/BrowserTestGenerator.ts b/packages/test-generator/lib/generators/BrowserTestGenerator.ts index 09bacde59..506766697 100644 --- a/packages/test-generator/lib/generators/BrowserTestGenerator.ts +++ b/packages/test-generator/lib/generators/BrowserTestGenerator.ts @@ -15,11 +15,13 @@ */ /* Test Generator to be used in the browser environment */ -import { StudioComponent, StudioTheme } from '@aws-amplify/codegen-ui'; +import { FormMetadata, StudioComponent, StudioForm, StudioTheme } from '@aws-amplify/codegen-ui'; import { AmplifyRenderer, ReactThemeStudioTemplateRenderer, ReactIndexStudioTemplateRenderer, + ReactUtilsStudioTemplateRenderer, + AmplifyFormRenderer, } from '@aws-amplify/codegen-ui-react'; import { TestGenerator } from './TestGenerator'; @@ -28,18 +30,32 @@ export class BrowserTestGenerator extends TestGenerator { writeThemeToDisk() {} // no-op + writeFormToDisk() { + return { formMetadata: {} as FormMetadata }; + } // no-op + writeIndexFileToDisk() {} // no-op + writeUtilsFileToDisk() {} // no-op + writeSnippetToDisk() {} // no-op - renderIndexFile(schemas: (StudioComponent | StudioTheme)[]) { + renderIndexFile(schemas: (StudioComponent | StudioForm | StudioTheme)[]) { return new ReactIndexStudioTemplateRenderer(schemas, this.renderConfig).renderComponent(); } + renderUtilsFile(utils: string[]) { + return new ReactUtilsStudioTemplateRenderer(utils, this.renderConfig).renderComponent(); + } + renderComponent(component: StudioComponent) { return new AmplifyRenderer(component, this.renderConfig).renderComponentOnly(); } + renderForm(form: StudioForm) { + return new AmplifyFormRenderer(form, undefined, this.renderConfig).renderComponentOnly(); + } + renderTheme(theme: StudioTheme) { return new ReactThemeStudioTemplateRenderer(theme, this.renderConfig).renderComponent(); } diff --git a/packages/test-generator/lib/generators/NodeTestGenerator.ts b/packages/test-generator/lib/generators/NodeTestGenerator.ts index 4730fb334..b044bad0b 100644 --- a/packages/test-generator/lib/generators/NodeTestGenerator.ts +++ b/packages/test-generator/lib/generators/NodeTestGenerator.ts @@ -22,11 +22,14 @@ import { StudioTemplateRendererFactory, StudioComponent, StudioTheme, + StudioForm, } from '@aws-amplify/codegen-ui'; import { AmplifyRenderer, ReactThemeStudioTemplateRenderer, ReactIndexStudioTemplateRenderer, + ReactUtilsStudioTemplateRenderer, + AmplifyFormRenderer, } from '@aws-amplify/codegen-ui-react'; import { TestGenerator, TestGeneratorParams } from './TestGenerator'; @@ -35,14 +38,22 @@ export class NodeTestGenerator extends TestGenerator { private readonly themeRendererFactory: any; + private readonly formRendererFactory: any; + private readonly indexRendererFactory: any; - private readonly rendererManager: any; + private readonly utilsRendererFactory: any; + + private readonly componentRendererManager: any; + + private readonly formRendererManager: any; private readonly themeRendererManager: any; private readonly indexRendererManager: any; + private readonly utilsRendererManager: any; + constructor(params: TestGeneratorParams) { super(params); this.componentRendererFactory = new StudioTemplateRendererFactory( @@ -51,16 +62,25 @@ export class NodeTestGenerator extends TestGenerator { this.themeRendererFactory = new StudioTemplateRendererFactory( (theme: StudioTheme) => new ReactThemeStudioTemplateRenderer(theme, this.renderConfig), ); + this.formRendererFactory = new StudioTemplateRendererFactory( + (form: StudioForm) => new AmplifyFormRenderer(form, undefined, this.renderConfig), + ); this.indexRendererFactory = new StudioTemplateRendererFactory( - (schemas: (StudioComponent | StudioTheme)[]) => new ReactIndexStudioTemplateRenderer(schemas, this.renderConfig), + (schemas: (StudioComponent | StudioForm | StudioTheme)[]) => + new ReactIndexStudioTemplateRenderer(schemas, this.renderConfig), + ); + this.utilsRendererFactory = new StudioTemplateRendererFactory( + (utils: string[]) => new ReactUtilsStudioTemplateRenderer(utils, this.renderConfig), ); - this.rendererManager = new StudioTemplateRendererManager(this.componentRendererFactory, this.outputConfig); + this.componentRendererManager = new StudioTemplateRendererManager(this.componentRendererFactory, this.outputConfig); + this.formRendererManager = new StudioTemplateRendererManager(this.formRendererFactory, this.outputConfig); this.themeRendererManager = new StudioTemplateRendererManager(this.themeRendererFactory, this.outputConfig); this.indexRendererManager = new StudioTemplateRendererManager(this.indexRendererFactory, this.outputConfig); + this.utilsRendererManager = new StudioTemplateRendererManager(this.utilsRendererFactory, this.outputConfig); } writeComponentToDisk(component: StudioComponent) { - this.rendererManager.renderSchemaToTemplate(component); + this.componentRendererManager.renderSchemaToTemplate(component); } renderComponent(component: StudioComponent) { @@ -68,6 +88,15 @@ export class NodeTestGenerator extends TestGenerator { return buildRenderer.renderComponentOnly(); } + writeFormToDisk(form: StudioForm) { + return this.formRendererManager.renderSchemaToTemplate(form); + } + + renderForm(form: StudioForm) { + const buildRenderer = this.formRendererFactory.buildRenderer(form); + return buildRenderer.renderFormOnly(); + } + writeSnippetToDisk(components: StudioComponent[]) { const { importsText, compText } = this.renderSnippet(components); fs.writeFileSync(path.join(this.outputConfig.outputPathDir, '..', 'SnippetTests.jsx'), importsText + compText); @@ -99,12 +128,21 @@ export class NodeTestGenerator extends TestGenerator { return buildRenderer.renderComponent(); } - writeIndexFileToDisk(schemas: (StudioComponent | StudioTheme)[]) { + writeIndexFileToDisk(schemas: (StudioComponent | StudioTheme | StudioForm)[]) { this.indexRendererManager.renderSchemaToTemplate(schemas); } - renderIndexFile(schemas: (StudioComponent | StudioTheme)[]) { + renderIndexFile(schemas: (StudioComponent | StudioTheme | StudioForm)[]) { const indexRenderer = this.indexRendererFactory.buildRenderer(schemas); return indexRenderer.renderComponent(); } + + writeUtilsFileToDisk(utils: string[]) { + this.utilsRendererManager.renderSchemaToTemplate(utils); + } + + renderUtilsFile(utils: string[]) { + const utilsRenderer = this.utilsRendererFactory.buildRenderer(utils); + return utilsRenderer.renderComponent(); + } } diff --git a/packages/test-generator/lib/generators/TestGenerator.ts b/packages/test-generator/lib/generators/TestGenerator.ts index b74d9a731..b225bf6e7 100644 --- a/packages/test-generator/lib/generators/TestGenerator.ts +++ b/packages/test-generator/lib/generators/TestGenerator.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { StudioComponent, StudioTheme } from '@aws-amplify/codegen-ui'; +import { FormMetadata, StudioComponent, StudioForm, StudioTheme } from '@aws-amplify/codegen-ui'; import { ModuleKind, ScriptTarget, @@ -24,6 +24,7 @@ import { import log from 'loglevel'; import * as ComponentSchemas from '../components'; import * as ThemeSchemas from '../themes'; +import * as FormSchemas from '../forms'; const DEFAULT_RENDER_CONFIG = { module: ModuleKind.CommonJS, @@ -40,7 +41,7 @@ log.setLevel('info'); export type TestCase = { name: string; schema: any; - testType: 'Component' | 'Theme' | 'Snippet'; + testType: 'Component' | 'Theme' | 'Form' | 'Snippet'; }; export type TestGeneratorParams = { @@ -66,6 +67,7 @@ export abstract class TestGenerator { generate = (testCases: TestCase[]) => { const renderErrors: { [key: string]: any } = {}; + const utilsFunctions = new Set(); const generateComponent = (testCase: TestCase) => { const { name, schema } = testCase; @@ -112,6 +114,33 @@ export abstract class TestGenerator { } }; + const generateForm = (testCase: TestCase) => { + const { name, schema } = testCase; + try { + if (this.params.writeToDisk) { + const res = this.writeFormToDisk(schema as StudioForm); + if (res.formMetadata?.onValidationFields && Object.keys(res.formMetadata.onValidationFields).length) { + utilsFunctions.add('validation'); + } + } + + if (this.params.writeToLogger) { + const { importsText, compText } = this.renderForm(schema as StudioForm); + log.info(`# ${name}`); + log.info('## Form Only Output'); + log.info('### formImports'); + log.info(this.decorateTypescriptWithMarkdown(importsText)); + log.info('### formText'); + log.info(this.decorateTypescriptWithMarkdown(compText)); + } + } catch (err) { + if (this.params.immediatelyThrowGenerateErrors) { + throw err; + } + renderErrors[name] = err; + } + }; + const generateIndexFile = (indexFileTestCases: TestCase[]) => { const schemas = indexFileTestCases.map((testCase) => testCase.schema); try { @@ -131,6 +160,24 @@ export abstract class TestGenerator { } }; + const generateUtilsFile = (utils: string[]) => { + try { + if (this.params.writeToDisk) { + this.writeUtilsFileToDisk(utils); + } + if (this.params.writeToLogger) { + const utilsFile = this.renderUtilsFile(utils); + log.info(`# utils`); + log.info(this.decorateTypescriptWithMarkdown(utilsFile.componentText)); + } + } catch (err) { + if (this.params.immediatelyThrowGenerateErrors) { + throw err; + } + renderErrors.index = err; + } + }; + const generateSnippet = (snippetTestCases: TestCase[]) => { const components = snippetTestCases.map((testCase) => testCase.schema); try { @@ -162,16 +209,23 @@ export abstract class TestGenerator { case 'Theme': generateTheme(testCase); break; + case 'Form': + generateForm(testCase); + break; case 'Snippet': generateSnippet([testCase]); break; default: - throw new Error('Expected either a `Component` or `Theme` test case type'); + throw new Error('Expected either a `Component`, `Theme`, `Form` test case type'); } }); generateIndexFile(testCases); + if (utilsFunctions.size) { + generateUtilsFile([...utilsFunctions]); + } + // only test with 4 components for performance generateSnippet(testCases.filter((testCase) => testCase.testType === 'Component').slice(0, 4)); @@ -193,13 +247,21 @@ export abstract class TestGenerator { abstract writeThemeToDisk(theme: StudioTheme): void; + abstract writeFormToDisk(form: StudioForm): { formMetadata: FormMetadata }; + abstract renderComponent(component: StudioComponent): { compText: string; importsText: string }; abstract renderTheme(theme: StudioTheme): { componentText: string }; - abstract writeIndexFileToDisk(schemas: (StudioComponent | StudioTheme)[]): void; + abstract renderForm(form: StudioForm): { compText: string; importsText: string }; - abstract renderIndexFile(schemas: (StudioComponent | StudioTheme)[]): { componentText: string }; + abstract writeIndexFileToDisk(schemas: (StudioComponent | StudioForm | StudioTheme)[]): void; + + abstract renderIndexFile(schemas: (StudioComponent | StudioForm | StudioTheme)[]): { componentText: string }; + + abstract writeUtilsFileToDisk(utils: string[]): void; + + abstract renderUtilsFile(utils: string[]): { componentText: string }; abstract writeSnippetToDisk(components: StudioComponent[]): void; @@ -214,6 +276,9 @@ export abstract class TestGenerator { ...Object.entries(ThemeSchemas).map(([name, schema]) => { return { name, schema, testType: 'Theme' } as TestCase; }), + ...Object.entries(FormSchemas).map(([name, schema]) => { + return { name, schema, testType: 'Form' } as TestCase; + }), ].filter((testCase) => !disabledSchemaSet.has(testCase.name)); } }