diff --git a/Composer/packages/client/src/pages/design/createDialogModal.tsx b/Composer/packages/client/src/pages/design/createDialogModal.tsx index 7a5a3d0ef6..3e469ac141 100644 --- a/Composer/packages/client/src/pages/design/createDialogModal.tsx +++ b/Composer/packages/client/src/pages/design/createDialogModal.tsx @@ -8,10 +8,10 @@ import { Stack, StackItem } from 'office-ui-fabric-react/lib/Stack'; import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { useRecoilValue } from 'recoil'; import { RecognizerSchema, useRecognizerConfig, useShellApi } from '@bfc/extension-client'; -import { DialogFactory, SDKKinds } from '@bfc/shared'; +import { DialogFactory, SDKKinds, DialogUtils } from '@bfc/shared'; import { DialogWrapper, DialogTypes } from '@bfc/ui-shared'; -import { DialogCreationCopy, nameRegex } from '../../constants'; +import { DialogCreationCopy } from '../../constants'; import { StorageFolder } from '../../recoilModel/types'; import { FieldConfig, useForm } from '../../hooks/useForm'; import { actionsSeedState, schemasState, validateDialogSelectorFamily } from '../../recoilModel'; @@ -46,9 +46,12 @@ export const CreateDialogModal: React.FC = (props) => { name: { required: true, validate: (value) => { - if (!nameRegex.test(value)) { - return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); + try { + DialogUtils.validateDialogName(value); + } catch (error) { + return error.message; } + if (dialogs.some((dialog) => dialog.id.toLowerCase() === value.toLowerCase())) { return formatMessage('Duplicate dialog name'); } diff --git a/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts b/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts new file mode 100644 index 0000000000..8edcbec0a2 --- /dev/null +++ b/Composer/packages/lib/shared/__tests__/dialogUtils/validateDialogName.test.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { validateDialogName } from '../../src/dialogUtils/validateDialogName'; + +const error = new Error( + "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." +); +const emptyError = new Error('The file name can not be empty'); + +describe('check dialog name', () => { + it('don not support special characters', () => { + expect(() => validateDialogName('*a')).toThrowError(error); + expect(() => validateDialogName('c*a')).toThrowError(error); + expect(() => validateDialogName('c a')).toThrowError(error); + expect(() => validateDialogName('')).toThrowError(emptyError); + }); + + it('don not support number at the beginning.', () => { + expect(() => validateDialogName('1a')).toThrowError(error); + expect(() => validateDialogName('a1')).not.toThrowError(); + }); +}); diff --git a/Composer/packages/lib/shared/src/dialogUtils/index.ts b/Composer/packages/lib/shared/src/dialogUtils/index.ts index e5fc5c94fd..7801a7923a 100644 --- a/Composer/packages/lib/shared/src/dialogUtils/index.ts +++ b/Composer/packages/lib/shared/src/dialogUtils/index.ts @@ -2,3 +2,4 @@ // Licensed under the MIT License. export * from './jsonTracker'; +export * from './validateDialogName'; diff --git a/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts b/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts new file mode 100644 index 0000000000..cf6d2fad1c --- /dev/null +++ b/Composer/packages/lib/shared/src/dialogUtils/validateDialogName.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import formatMessage from 'format-message'; + +export const validateDialogName = (name: string) => { + const nameRegex = /^[a-zA-Z][a-zA-Z0-9-_]*$/; + + if (!name) { + throw new Error(formatMessage('The file name can not be empty')); + } + + if (!nameRegex.test(name)) { + throw new Error( + formatMessage( + "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." + ) + ); + } +}; diff --git a/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts b/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts index e3da0f8a70..b3f5ceb57a 100644 --- a/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts +++ b/Composer/packages/server/src/models/bot/__tests__/botProject.test.ts @@ -358,40 +358,45 @@ describe('dialog schema operations', () => { }); describe('should validate the file name when create a new one', () => { + const error = new Error( + "Spaces and special characters are not allowed. Use letters, numbers, -, or _ and don't use number at the beginning." + ); + const emptyError = new Error('The file name can not be empty'); + it('validate the empty dialog name', () => { expect(() => { proj.validateFileName('.dialog'); - }).toThrowError('The file name can not be empty'); + }).toThrowError(emptyError); }); it('validate the illegal dialog name', async () => { expect(() => { proj.validateFileName('a.b.dialog'); - }).toThrowError('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); + }).toThrowError(error); }); it('validate the empty lu file name', () => { expect(() => { proj.validateFileName('.en-us.lu'); - }).toThrowError('The file name can not be empty'); + }).toThrowError(emptyError); }); it('validate the illegal lu file name', async () => { expect(() => { proj.validateFileName('a.b.en-us.lu'); - }).toThrowError('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); + }).toThrowError(error); }); it('validate the empty lg file name', () => { expect(() => { proj.validateFileName('.en-us.lg'); - }).toThrowError('The file name can not be empty'); + }).toThrowError(emptyError); }); it('validate the illegal lu file name', async () => { expect(() => { proj.validateFileName('a.b.en-us.lg'); - }).toThrowError('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); + }).toThrowError(error); }); }); diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index 1feecbdd89..2052967dff 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -6,7 +6,16 @@ import fs from 'fs'; import axios from 'axios'; import { autofixReferInDialog } from '@bfc/indexers'; -import { getNewDesigner, FileInfo, Diagnostic, IBotProject, DialogSetting, FileExtensions, Skill } from '@bfc/shared'; +import { + getNewDesigner, + FileInfo, + Diagnostic, + IBotProject, + DialogSetting, + FileExtensions, + Skill, + DialogUtils, +} from '@bfc/shared'; import merge from 'lodash/merge'; import { UserIdentity, ExtensionContext } from '@bfc/extension'; import { FeedbackType, generate } from '@microsoft/bf-generate-library'; @@ -409,7 +418,6 @@ export class BotProject implements IBotProject { }; public validateFileName = (name: string) => { - const nameRegex = /^[a-zA-Z0-9-_]+$/; const { fileId, fileType } = parseFileName(name, ''); let fileName = fileId; @@ -417,13 +425,7 @@ export class BotProject implements IBotProject { fileName = Path.basename(name, fileType); } - if (!fileName) { - throw new Error('The file name can not be empty'); - } - - if (!nameRegex.test(fileName)) { - throw new Error('Spaces and special characters are not allowed. Use letters, numbers, -, or _.'); - } + DialogUtils.validateDialogName(fileName); }; public createFile = async (name: string, content = '') => {