From 420217fdac47ff1d4a8ddf0f889039798e2af7d7 Mon Sep 17 00:00:00 2001 From: gitstart-twenty Date: Fri, 18 Oct 2024 17:10:00 +0000 Subject: [PATCH] update rules and show ESLint warnings --- .../rules/folder-structure-rule.ts | 27 +- .../rules/folderStructureConfig.ts | 592 ++---------------- 2 files changed, 79 insertions(+), 540 deletions(-) diff --git a/tools/eslint-rules/rules/folder-structure-rule.ts b/tools/eslint-rules/rules/folder-structure-rule.ts index ba5f395bb86d..21395d3c4aa5 100644 --- a/tools/eslint-rules/rules/folder-structure-rule.ts +++ b/tools/eslint-rules/rules/folder-structure-rule.ts @@ -51,12 +51,27 @@ const validateString = (input: string, validator: NameValidationType) => { return false; } - const ext = fileName.substring(extIndex + 1) as ExtensionsType; - if (!extensionArray.includes(ext)) { + const extractedExt = fileName.substring(extIndex + 1) as ExtensionsType; + const foundExt = extensionArray.find((ext) => ext === extractedExt); + if (foundExt === undefined) { return false; } fileName = fileName.substring(0, extIndex); + + if (foundExt.endsWith('tsx')) { + if (foundExt.endsWith('test.tsx') && foundPrefix === 'use') { + return CASES['StrictPascalCase'].test(fileName); + } + return CASES['StrictPascalCase'].test(foundPrefix + fileName); + } + + if (foundExt.endsWith('ts')) { + return ( + CASES['StrictCamelCase'].test(foundPrefix + fileName) || + CASES['kebab-case'].test(foundPrefix + fileName) + ); + } } // Check for suffix @@ -117,6 +132,12 @@ const checkFolderStructure = ( if (ruleId) { const rule = RULES[ruleId]; + const folderName = currentPath.split('/').pop(); + + if (rule.reservedFolders && rule.reservedFolders.includes(folderName)) { + return false; + } + return checkFolderStructure(currentPath, { ...rule, name: name ? undefined : rule.name, @@ -196,7 +217,7 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({ const rootFolder = path.resolve( __dirname, - '../../../packages/twenty-front', + '../../../packages/twenty-front/src', ); checkFolderStructure(rootFolder, configs); diff --git a/tools/eslint-rules/rules/folderStructureConfig.ts b/tools/eslint-rules/rules/folderStructureConfig.ts index 97333b272180..80f014fe2f1a 100644 --- a/tools/eslint-rules/rules/folderStructureConfig.ts +++ b/tools/eslint-rules/rules/folderStructureConfig.ts @@ -1,10 +1,7 @@ export const CASES: Record = { 'kebab-case': /^[a-z]+(-[a-z]+)*$/, - 'kebab-case-with-numbers': /^[a-z0-9]+(-[a-z0-9]+)*$/, - camelCase: /^[a-z][a-zA-Z]*(V\d+)?(_[a-z]+)?$/, - snake_case: /^[a-z]+(_[a-z]+)*$/, - snake_case_with_numbers: /^[a-z0-9]+(_[a-z0-9]+)*$/, - StrictPascalCase: /^[A-Z][a-zA-Z0-9]*(V[0-9]+)?([A-Z][a-zA-Z0-9]*)*$/, + StrictCamelCase: /^[a-z]+((\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?$/, + StrictPascalCase: /^[A-Z](([a-z0-9]+[A-Z]?)*)$/, }; export type ExtensionsType = @@ -25,13 +22,7 @@ export type ExtensionsType = | 'd.ts' | 'factory.ts' | 'util.test.ts' - | 'docs.mdx' - | 'css' - | 'js' - | 'html' - | 'json' - | 'sh' - | 'config.js'; + | 'docs.mdx'; export type NameType = keyof typeof CASES | string; interface ValidationObject { @@ -41,6 +32,8 @@ interface ValidationObject { extension?: ExtensionsType[] | ExtensionsType; } +const SUB_FOLDER_NAME_CONSTRAINT = 'sub-folder-is-not-allowed'; + export type NameValidationType = ValidationObject | NameType; export const stringifyConfig = (configs: FolderRule[]) => { @@ -87,16 +80,11 @@ export const stringifyConfig = (configs: FolderRule[]) => { }; export const RULES: Record = { - ComponentsGroup: { - children: [{ name: { namePattern: 'StrictPascalCase', extension: 'tsx' } }], - }, - StorybookFolder: { name: '__stories__', children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase'], extension: [ 'ts', 'tsx', @@ -106,18 +94,7 @@ export const RULES: Record = { ], }, }, - // TODO: edge cases for subfolders of __stories__ that are also stories folders - /* - - /src/pages/settings/developers/__stories__/api-keys - - /src/pages/settings/developers/__stories__/webhooks - - /src/pages/settings/data-model/__stories__/SettingsObjectNewField - - */ - // should we enforce StrictPascalCase? - { name: 'kebab-case', ruleId: 'StorybookFolder' }, - { name: 'StrictPascalCase', ruleId: 'StorybookFolder' }, - { name: 'perf', ruleId: 'StorybookFolder' }, - { ruleId: 'StorybookFolder' }, + { name: SUB_FOLDER_NAME_CONSTRAINT, children: [] }, ], }, ComponentFolderWithStories: { @@ -126,9 +103,7 @@ export const RULES: Record = { { ruleId: 'StorybookFolder' }, { name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], - // TODO: SVG extensions is a edge case for list-view-grip.svg - extension: ['tsx', 'ts', 'svg'], + extension: ['tsx', 'ts'], }, }, { @@ -146,24 +121,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], - // TODO: test.tsx extensions is a edge case, because the files that end with test.tsx, infact do not use tsx - extension: [ - 'test.ts', - 'utils.test.ts', - 'util.test.ts', - 'test.tsx', - ], - }, - }, - ], - }, - { - name: '__test__', - children: [ - { - name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], extension: ['test.ts', 'utils.test.ts', 'util.test.ts'], }, }, @@ -171,9 +128,7 @@ export const RULES: Record = { }, { name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], - // TODO: getFiledButtonIcon.tsx, assertWorkflowWithCurrentVersionIsDefined.tsx extensions is a edge case, because the files that end .tsx, infact do not use tsx - extension: ['ts', 'tsx', 'utils.ts', 'util.ts'], + extension: ['ts', 'utils.ts', 'util.ts'], }, }, { name: 'kebab-case', ruleId: 'UtilsFolder' }, @@ -184,7 +139,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], extension: ['ts', 'tsx', 'interface.ts', 'd.ts'], }, }, @@ -196,8 +150,7 @@ export const RULES: Record = { children: [ { name: { - namePattern: 'StrictPascalCase', - extension: ['ts', 'tsx'], + extension: ['ts'], prefix: 'use', }, }, @@ -205,23 +158,8 @@ export const RULES: Record = { { name: '__tests__', children: [ - // TODO: edge case, this should start with use - { name: 'isMobile.test.tsx' }, { name: { - namePattern: 'StrictPascalCase', - extension: ['ts', 'tsx', 'test.ts', 'test.tsx', 'util.test.ts'], - prefix: 'use', - }, - }, - ], - }, - { - name: '__test__', - children: [ - { - name: { - namePattern: 'StrictPascalCase', extension: ['ts', 'tsx', 'test.ts', 'test.tsx', 'util.test.ts'], prefix: 'use', }, @@ -234,16 +172,16 @@ export const RULES: Record = { { name: 'kebab-case', children: [ - { name: { namePattern: 'camelCase', extension: ['ts', 'tsx'] } }, + { + name: { extension: ['ts', 'tsx'] }, + }, ], }, ], }, ConstantsFolder: { name: 'constants', - children: [ - { name: { namePattern: 'StrictPascalCase', extension: ['ts'] } }, - ], + children: [{ name: { extension: ['ts'] } }], }, ServicesFolder: { name: 'services', @@ -253,7 +191,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase'], extension: ['test.ts', 'factory.test.ts'], }, }, @@ -261,7 +198,6 @@ export const RULES: Record = { }, { name: { - namePattern: ['StrictPascalCase', 'camelCase'], extension: ['ts', 'factory.test.ts', 'factory.ts'], }, }, @@ -270,13 +206,12 @@ export const RULES: Record = { StatesFolder: { name: 'states', children: [ - { name: { namePattern: 'camelCase', extension: 'ts' } }, + { name: { extension: 'ts' } }, { name: 'kebab-case', children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase'], extension: 'ts', }, }, @@ -289,7 +224,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['StrictPascalCase', 'snake_case', 'kebab-case'], extension: ['png', 'svg'], }, }, @@ -302,11 +236,11 @@ export const RULES: Record = { name: 'kebab-case', children: [ { - name: { namePattern: 'StrictPascalCase', extension: ['ts', 'tsx'] }, + name: { extension: ['ts', 'tsx'] }, }, ], }, - { name: { namePattern: 'StrictPascalCase', extension: ['ts', 'tsx'] } }, + { name: { extension: ['ts', 'tsx'] } }, ], }, MocksFolder: { @@ -314,7 +248,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase'], extension: ['ts', 'tsx'], }, }, @@ -322,39 +255,45 @@ export const RULES: Record = { }, ThemesFolder: { name: 'theme', - children: [ - { name: { namePattern: ['StrictPascalCase'], extension: ['ts', 'tsx'] } }, - ], - }, - TestFolder: { - name: 'tests', - children: [ - { - name: { namePattern: ['camelCase'], extension: ['ts', 'tsx'] }, - }, - ], + children: [{ name: { extension: ['ts', 'tsx'] } }], }, TestsFolder: { name: '__tests__', children: [ { name: { - namePattern: ['StrictPascalCase', 'camelCase', 'kebab-case'], - extension: ['test.ts', 'utils.test.ts', 'test.tsx'], + extension: ['test.ts', 'utils.test.ts'], }, }, ], }, EnumsFolder: { name: 'enums', - children: [ - { name: { namePattern: 'StrictPascalCase', extension: 'enum.ts' } }, - ], + children: [{ name: { extension: 'enum.ts' } }], }, ModulesFolder: { name: 'kebab-case', + reservedFolders: [ + 'components', + 'hooks', + 'constants', + 'types', + 'utils', + 'states', + 'assets', + 'scope', + 'services', + 'mocks', + 'themes', + '__tests__', + 'enums', + 'context', + 'graphql', + 'queries', + '__stories__', + '__mocks__', + ], children: [ - { ruleId: 'TimelineActivitiesFolder' }, { ruleId: 'ComponentFolderWithStories' }, { ruleId: 'HooksFolder' }, { ruleId: 'ConstantsFolder' }, @@ -367,12 +306,11 @@ export const RULES: Record = { { ruleId: 'MocksFolder' }, { ruleId: 'ThemesFolder' }, { ruleId: 'TestsFolder' }, - { ruleId: 'TestFolder' }, { ruleId: 'StorybookFolder' }, { ruleId: 'EnumsFolder' }, { name: 'context', - children: [{ name: 'StrictPascalCase' }], + children: [{ name: { extension: 'ts' } }], }, { name: 'graphql', @@ -382,7 +320,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['camelCase', 'StrictPascalCase'], extension: 'ts', }, }, @@ -391,7 +328,6 @@ export const RULES: Record = { children: [ { name: { - namePattern: ['camelCase', 'StrictPascalCase'], extension: 'ts', }, }, @@ -399,7 +335,7 @@ export const RULES: Record = { }, ], }, - { name: { namePattern: 'camelCase', extension: 'ts' } }, + { name: { extension: 'ts' } }, { ruleId: 'TypesFolder' }, { ruleId: 'UtilsFolder' }, ], @@ -407,85 +343,22 @@ export const RULES: Record = { { name: 'queries', children: [ - { name: { namePattern: 'camelCase', extension: 'ts' } }, + { name: { extension: 'ts' } }, { name: 'kebab-case', - children: [{ name: { namePattern: 'camelCase', extension: 'ts' } }], + children: [{ name: { extension: 'ts' } }], }, { ruleId: 'TestsFolder' }, ], }, { name: { - namePattern: ['camelCase', 'StrictPascalCase', 'StrictPascalCase'], - extension: ['ts', 'tsx', 'test.tsx'], - }, - }, - { - ruleId: 'ModulesFolder', - }, - ], - }, - TimelineActivitiesFolder: { - name: { - namePattern: 'timelineActivities', - }, - children: [ - { ruleId: 'StorybookFolder' }, - { ruleId: 'UtilsFolder' }, - { - name: { - namePattern: 'StrictPascalCase', - extension: ['ts', 'tsx'], - }, - }, - { - name: { - namePattern: 'camelCase', extension: ['ts', 'tsx'], }, }, - { ruleId: 'HooksFolder' }, - { - name: 'kebab-case', - ruleId: 'TimelineActivitiesFolder', - }, - ], - }, - ImagesFolder: { - children: [ - { - name: { - namePattern: [ - 'StrictPascalCase', - 'snake_case_with_numbers', - 'kebab-case-with-numbers', - ], - extension: ['png', 'svg'], - }, - }, { - name: { - prefix: [ - 'Square150x150Logo.', - 'Square44x44Logo.altform-lightunplated_targetsize-', - 'Square44x44Logo.altform-unplated_targetsize-', - 'Square44x44Logo.', - 'StoreLogo.', - 'Wide310x150Logo.', - 'SmallTile.', - 'SplashScreen.', - 'LargeTile.', - ], - namePattern: [ - 'StrictPascalCase', - 'snake_case_with_numbers', - 'kebab-case-with-numbers', - ], - extension: ['png', 'svg'], - }, + ruleId: 'ModulesFolder', }, - { ruleId: 'ImagesFolder' }, ], }, }; @@ -494,385 +367,30 @@ export type FolderRule = { name?: NameValidationType; children?: FolderRule[]; ruleId?: keyof typeof RULES; + reservedFolders?: string[]; }; export const configs: FolderRule = { - name: 'twenty-front', + name: 'src', children: [ + // src/modules { - name: 'src', - children: [ - // src/__stories__/App.stories.tsx - { - name: '__stories__', - children: [ - { name: { namePattern: 'AppRouter', extension: 'stories.tsx' } }, - ], - }, - // src/config/index.ts - { - name: 'config', - children: [{ name: { namePattern: 'index', extension: 'ts' } }], - }, - // src/generated/graphql.tsx - { - name: 'generated', - children: [{ name: { namePattern: 'graphql', extension: 'tsx' } }], - }, - // src/generated-metadata - { - name: 'generated-metadata', - children: [ - { - name: { - namePattern: ['gql', 'graphql', 'index'], - extension: 'ts', - }, - }, - ], - }, - // src/hooks - { - ruleId: 'HooksFolder', - }, - // src/loading/ - { - name: 'loading', - children: [ - // src/loading/components - { - name: 'components', - children: [ - { - name: { - namePattern: 'StrictPascalCase', - suffix: 'Loader', - extension: 'tsx', - }, - }, - // src/loading/__stories__ - { - name: '__stories__', - // TODO: should we also include the Loader Suffix here? we have PrefetchLoading.stories in the folder - children: [ - { - name: { - namePattern: 'StrictPascalCase', - extension: 'stories.tsx', - }, - }, - ], - }, - ], - }, - { - name: 'hooks', - ruleId: 'HooksFolder', - }, - ], - }, - // src/modules - { - name: 'modules', + name: 'modules', - ruleId: 'ModulesFolder', - }, - // src/pages - { - name: 'pages', - children: [ - { - name: 'kebab-case', - ruleId: 'ComponentFolderWithStories', - }, - { - name: 'settings', - children: [ - { - name: 'kebab-case', - ruleId: 'ComponentFolderWithStories', - }, - { ruleId: 'StorybookFolder' }, - // TODO: this is an edge case for ComponentFolderWithStories - // This is the only folder that breaks the recursive rule for page/settings - { - name: 'data-model', - children: [ - { ruleId: 'UtilsFolder' }, - { ruleId: 'TypesFolder' }, - { ruleId: 'HooksFolder' }, - { ruleId: 'ConstantsFolder' }, - { ruleId: 'StorybookFolder' }, - { - name: 'SettingsObjectNewField', - ruleId: 'ComponentsGroup', - }, - { ruleId: 'ComponentsGroup' }, - ], - }, - // TODO: Edge case because SettingsCRMMigration is a folder and does not pass on StrictPascalCase - // This folder should be able to be passed on ComponentFolderWithStories rule - { - name: 'crm-migration', - children: [ - { - name: { - namePattern: 'SettingsCRMMigration', - extension: 'tsx', - }, - }, - ], - }, - { ruleId: 'ComponentsGroup' }, - ], - }, - ], - }, - // src/testing - { - name: 'testing', - children: [ - { - ruleId: 'ConstantsFolder', - }, - { - name: 'decorators', - children: [ - { - name: { - namePattern: 'StrictPascalCase', - suffix: 'Decorator', - extension: 'tsx', - }, - }, - { - name: { - namePattern: 'camelCase', - suffix: 'Decorator', - extension: 'tsx', - }, - }, - ], - }, - { - ruleId: 'HooksFolder', - }, - { - name: 'jest', - children: [ - { - name: { - namePattern: 'StrictPascalCase', - extension: 'tsx', - }, - }, - { - name: { - namePattern: 'camelCase', - extension: ['tsx', 'ts'], - }, - }, - ], - }, - { - name: 'mock-data', - children: [ - { - name: 'generated', - children: [ - { - name: { - namePattern: 'kebab-case', - prefix: 'mock-', - extension: 'ts', - }, - }, - ], - }, - { - name: { - namePattern: 'kebab-case', - extension: 'ts', - }, - }, - { - name: { - namePattern: 'camelCase', - extension: 'ts', - }, - }, - { - name: { - namePattern: 'camelCase', - prefix: 'mock-', - extension: 'ts', - }, - }, - ], - }, - { - name: 'profiling', - children: [ - { - name: 'components', - children: [ - { - name: { - namePattern: 'StrictPascalCase', - extension: 'tsx', - }, - }, - ], - }, - { ruleId: 'StatesFolder' }, - { ruleId: 'ConstantsFolder' }, - { ruleId: 'TypesFolder' }, - { ruleId: 'UtilsFolder' }, - ], - }, - { - name: { - namePattern: 'StrictPascalCase', - extension: 'tsx', - }, - }, - { - name: { - namePattern: 'camelCase', - extension: ['tsx', 'ts'], - }, - }, - ], - }, - // src/types - { - ruleId: 'TypesFolder', - }, - // src/utils - { ruleId: 'UtilsFolder' }, - // TODO: is it fine the way we are handling this? - { - name: { - namePattern: 'kebab-case', - extension: 'd.ts', - }, - }, - { - name: { - namePattern: 'index', - extension: 'css', - }, - }, - { - name: { - namePattern: 'index', - extension: 'tsx', - }, - }, - { - name: { - namePattern: 'App', - extension: 'tsx', - }, - }, - { - name: { - namePattern: 'SettingsRoutes', - extension: 'tsx', - }, - }, - ], - }, - { - name: { - namePattern: '__mocks__', - }, - children: [ - { - name: { - namePattern: 'hex-rgb', - extension: 'js', - }, - }, - { - name: { - namePattern: 'camelCase', - extension: 'js', - }, - }, - ], - }, - { - name: '.storybook', - children: [ - { - name: { - namePattern: 'kebab-case', - extension: ['js', 'ts', 'html', 'tsx', 'config.js'], - }, - }, - ], + ruleId: 'ModulesFolder', }, + // src/pages { - name: 'public', + name: 'pages', children: [ { - name: 'icons', + name: 'kebab-case', children: [ - { - name: { - namePattern: [ - 'kebab-case', - 'StrictPascalCase', - // TODO: Edge case for windows11 folder - 'kebab-case-with-numbers', - ], - }, - ruleId: 'ImagesFolder', - }, - { - ruleId: 'ImagesFolder', - }, + { ruleId: 'StorybookFolder' }, + { name: { extension: 'tsx' } }, ], }, - { - name: 'images', - children: [ - { - ruleId: 'ImagesFolder', - }, - ], - }, - { - name: { - namePattern: 'env-config', - extension: 'js', - }, - }, - { - name: { - namePattern: 'manifest', - extension: 'json', - }, - }, - { - name: { - namePattern: 'mockServiceWorker', - extension: 'js', - }, - }, - ], - }, - { - name: 'scripts', - children: [ - { - name: { - namePattern: 'kebab-case', - extension: 'sh', - }, - }, ], }, - { name: '*' }, + { name: '*', children: [] }, ], };