mirror of
https://github.com/twentyhq/twenty.git
synced 2025-01-03 09:42:01 +03:00
Feat/sidecar components (#1578)
* Added a new eslint plugin in TypeScript for Effect components * Fixed edge cases * Fixed lint * Fix eslint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
09db29c91a
commit
84a27b148f
@ -5,7 +5,7 @@ module.exports = {
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'unused-imports', 'simple-import-sort', 'twenty'],
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'unused-imports', 'simple-import-sort', 'twenty', 'twenty-ts'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
@ -50,6 +50,7 @@ module.exports = {
|
||||
'twenty/no-hardcoded-colors': 'error',
|
||||
'twenty/styled-components-prefixed-with-styled': 'error',
|
||||
'twenty/matching-state-variable': 'error',
|
||||
'twenty-ts/effect-components': 'error',
|
||||
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-unused-vars": "off",
|
||||
|
@ -69,13 +69,15 @@
|
||||
"test": "craco test",
|
||||
"coverage": "craco test --coverage .",
|
||||
"lint": "eslint src --max-warnings=0",
|
||||
"lint:setup": "cd ../packages/eslint-plugin-twenty-ts/ && yarn && yarn build && cd ../../front/ && yarn upgrade eslint-plugin-twenty-ts",
|
||||
"storybook:dev": "storybook dev -p 6006 -s ../public",
|
||||
"storybook:test": "test-storybook",
|
||||
"storybook:test-slow": "test-storybook --maxWorkers=3",
|
||||
"storybook:build": "storybook build -s public",
|
||||
"storybook:coverage": "test-storybook --coverage --maxWorkers=3 && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
|
||||
"graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js",
|
||||
"chromatic": "dotenv cross-var npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
|
||||
"chromatic": "dotenv cross-var npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN",
|
||||
"install": "yarn lint:setup"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@ -147,6 +149,7 @@
|
||||
"@types/scroll-into-view": "^1.16.0",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/utils": "^6.7.0",
|
||||
"babel-plugin-named-exports-order": "^0.0.2",
|
||||
"chromatic": "^6.18.0",
|
||||
"concurrently": "^8.0.1",
|
||||
@ -164,6 +167,7 @@
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-storybook": "^0.6.12",
|
||||
"eslint-plugin-twenty": "file:../packages/eslint-plugin-twenty",
|
||||
"eslint-plugin-twenty-ts": "file:../packages/eslint-plugin-twenty-ts",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"http-server": "^14.1.1",
|
||||
"mock-apollo-client": "^1.2.1",
|
||||
|
@ -7,10 +7,9 @@ import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
||||
import { SignInUp } from '~/pages/auth/SignInUp';
|
||||
import { Verify } from '~/pages/auth/Verify';
|
||||
import { VerifyEffect } from '~/pages/auth/VerifyEffect';
|
||||
import { Companies } from '~/pages/companies/Companies';
|
||||
import { CompanyShow } from '~/pages/companies/CompanyShow';
|
||||
import { Impersonate } from '~/pages/impersonate/Impersonate';
|
||||
import { Opportunities } from '~/pages/opportunities/Opportunities';
|
||||
import { People } from '~/pages/people/People';
|
||||
import { PersonShow } from '~/pages/people/PersonShow';
|
||||
@ -19,8 +18,10 @@ import { SettingsProfile } from '~/pages/settings/SettingsProfile';
|
||||
import { SettingsWorkspace } from '~/pages/settings/SettingsWorkspace';
|
||||
import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMembers';
|
||||
import { Tasks } from '~/pages/tasks/Tasks';
|
||||
import { AppInternalHooks } from '~/sync-hooks/AppInternalHooks';
|
||||
|
||||
import { CommandMenuEffect } from './effect-components/CommandMenuEffect';
|
||||
import { GotoHotkeysEffect } from './effect-components/GotoHotkeysEffect';
|
||||
import { ImpersonateEffect } from './pages/impersonate/ImpersonateEffect';
|
||||
import { NotFound } from './pages/not-found/NotFound';
|
||||
import { getPageTitleFromPath } from './utils/title-utils';
|
||||
|
||||
@ -34,10 +35,11 @@ export function App() {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={pageTitle} />
|
||||
<AppInternalHooks />
|
||||
<GotoHotkeysEffect />
|
||||
<CommandMenuEffect />
|
||||
<DefaultLayout>
|
||||
<Routes>
|
||||
<Route path={AppPath.Verify} element={<Verify />} />
|
||||
<Route path={AppPath.Verify} element={<VerifyEffect />} />
|
||||
<Route path={AppPath.SignIn} element={<SignInUp />} />
|
||||
<Route path={AppPath.SignUp} element={<SignInUp />} />
|
||||
<Route path={AppPath.Invite} element={<SignInUp />} />
|
||||
@ -49,7 +51,7 @@ export function App() {
|
||||
<Route path={AppPath.CompaniesPage} element={<Companies />} />
|
||||
<Route path={AppPath.CompanyShowPage} element={<CompanyShow />} />
|
||||
<Route path={AppPath.TasksPage} element={<Tasks />} />
|
||||
<Route path={AppPath.Impersonate} element={<Impersonate />} />
|
||||
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
|
||||
|
||||
<Route path={AppPath.OpportunitiesPage} element={<Opportunities />} />
|
||||
<Route
|
||||
|
@ -4,7 +4,7 @@ import { useSetRecoilState } from 'recoil';
|
||||
import { commandMenuCommands } from '@/command-menu/constants/commandMenuCommands';
|
||||
import { commandMenuCommandsState } from '@/command-menu/states/commandMenuCommandsState';
|
||||
|
||||
export function CommandMenuHook() {
|
||||
export function CommandMenuEffect() {
|
||||
const setCommands = useSetRecoilState(commandMenuCommandsState);
|
||||
|
||||
const commands = commandMenuCommands;
|
@ -1,6 +1,6 @@
|
||||
import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys';
|
||||
|
||||
export function GotoHotkeysHooks() {
|
||||
export function GotoHotkeysEffect() {
|
||||
useGoToHotkeys('p', '/people');
|
||||
useGoToHotkeys('c', '/companies');
|
||||
useGoToHotkeys('o', '/opportunities');
|
@ -6,7 +6,7 @@ import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
||||
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
||||
import { RecoilDebugObserver } from '@/debug/components/RecoilDebugObserver';
|
||||
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
|
||||
import { DialogProvider } from '@/ui/dialog/components/DialogProvider';
|
||||
import { SnackBarProvider } from '@/ui/snack-bar/components/SnackBarProvider';
|
||||
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
||||
@ -15,7 +15,7 @@ import { UserProvider } from '@/users/components/UserProvider';
|
||||
|
||||
import '@emotion/react';
|
||||
|
||||
import { PageChangeEffect } from './sync-hooks/PageChangeEffect';
|
||||
import { PageChangeEffect } from './effect-components/PageChangeEffect';
|
||||
import { App } from './App';
|
||||
|
||||
import './index.css';
|
||||
@ -27,7 +27,7 @@ const root = ReactDOM.createRoot(
|
||||
|
||||
root.render(
|
||||
<RecoilRoot>
|
||||
<RecoilDebugObserver />
|
||||
<RecoilDebugObserverEffect />
|
||||
<BrowserRouter>
|
||||
<ApolloProvider>
|
||||
<HelmetProvider>
|
||||
|
@ -6,7 +6,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { CompanyBoard } from '../board/components/CompanyBoard';
|
||||
import { HooksCompanyBoard } from '../components/HooksCompanyBoard';
|
||||
import { HooksCompanyBoardEffect } from '../components/HooksCompanyBoardEffect';
|
||||
import { CompanyBoardRecoilScopeContext } from '../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
|
||||
|
||||
const meta: Meta<typeof CompanyBoard> = {
|
||||
@ -15,7 +15,7 @@ const meta: Meta<typeof CompanyBoard> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilScope SpecificContext={CompanyBoardRecoilScopeContext}>
|
||||
<HooksCompanyBoard />
|
||||
<HooksCompanyBoardEffect />
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
|
@ -14,7 +14,7 @@ import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/Componen
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress';
|
||||
|
||||
import { HooksCompanyBoard } from '../components/HooksCompanyBoard';
|
||||
import { HooksCompanyBoardEffect } from '../components/HooksCompanyBoardEffect';
|
||||
import { CompanyBoardRecoilScopeContext } from '../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
|
||||
|
||||
const meta: Meta<typeof CompanyBoardCard> = {
|
||||
@ -33,7 +33,7 @@ const meta: Meta<typeof CompanyBoardCard> = {
|
||||
|
||||
return (
|
||||
<>
|
||||
<HooksCompanyBoard />
|
||||
<HooksCompanyBoardEffect />
|
||||
<RecoilScope SpecificContext={BoardColumnRecoilScopeContext}>
|
||||
<BoardCardIdContext.Provider
|
||||
value={mockedPipelineProgressData[1].id}
|
||||
|
@ -9,7 +9,7 @@ import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
|
||||
import { useBoardViews } from '@/views/hooks/useBoardViews';
|
||||
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
||||
|
||||
import { HooksCompanyBoard } from '../../components/HooksCompanyBoard';
|
||||
import { HooksCompanyBoardEffect } from '../../components/HooksCompanyBoardEffect';
|
||||
import { CompanyBoardRecoilScopeContext } from '../../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
|
||||
|
||||
type CompanyBoardProps = Pick<
|
||||
@ -31,7 +31,7 @@ export const CompanyBoard = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<HooksCompanyBoard />
|
||||
<HooksCompanyBoardEffect />
|
||||
<ViewBarContext.Provider
|
||||
value={{
|
||||
defaultViewName: 'All Opportunities',
|
||||
|
@ -24,7 +24,7 @@ import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
||||
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
|
||||
import { CompanyBoardRecoilScopeContext } from '../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
|
||||
|
||||
export function HooksCompanyBoard() {
|
||||
export function HooksCompanyBoardEffect() {
|
||||
const [, setAvailableFilters] = useRecoilScopedState(
|
||||
availableFiltersScopedState,
|
||||
CompanyBoardRecoilScopeContext,
|
@ -9,7 +9,7 @@ import { companiesAvailableColumnDefinitions } from '../../constants/companiesAv
|
||||
|
||||
import { mockedCompaniesData } from './companies-mock-data';
|
||||
|
||||
export function CompanyTableMockData() {
|
||||
export function CompanyTableMockDataEffect() {
|
||||
const [, setTableColumns] = useRecoilScopedState(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
@ -2,12 +2,12 @@ import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
|
||||
import { useUpdateOneCompanyMutation } from '~/generated/graphql';
|
||||
|
||||
import { CompanyTableMockData } from './CompanyTableMockData';
|
||||
import { CompanyTableMockDataEffect } from './CompanyTableMockDataEffect';
|
||||
|
||||
export function CompanyTableMockMode() {
|
||||
return (
|
||||
<>
|
||||
<CompanyTableMockData />
|
||||
<CompanyTableMockDataEffect />
|
||||
<ViewBarContext.Provider value={{ defaultViewName: 'All Companies' }}>
|
||||
<EntityTable updateEntityMutation={[useUpdateOneCompanyMutation()]} />
|
||||
</ViewBarContext.Provider>
|
||||
|
@ -14,7 +14,7 @@ const formatTitle = (stateName: string) => {
|
||||
return [parts.join(' '), ...headerCss];
|
||||
};
|
||||
|
||||
export function RecoilDebugObserver() {
|
||||
export function RecoilDebugObserverEffect() {
|
||||
const snapshot = useRecoilSnapshot();
|
||||
|
||||
const isDebugMode = useRecoilValue(isDebugModeState);
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
|
||||
import { useSetPeopleEntityTable } from '../hooks/useSetPeopleEntityTable';
|
||||
|
||||
export function PeopleEntityTableData({
|
||||
export function PeopleEntityTableDataEffect({
|
||||
orderBy = [
|
||||
{
|
||||
createdAt: SortOrder.Desc,
|
@ -5,7 +5,7 @@ type OwnProps = {
|
||||
onAddButtonClick?: () => void;
|
||||
};
|
||||
|
||||
export function PageHotkeys({ onAddButtonClick }: OwnProps) {
|
||||
export function PageHotkeysEffect({ onAddButtonClick }: OwnProps) {
|
||||
useScopedHotkeys('c', () => onAddButtonClick?.(), TableHotkeyScope.Table, [
|
||||
onAddButtonClick,
|
||||
]);
|
@ -7,7 +7,7 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { AppPath } from '../../modules/types/AppPath';
|
||||
import { isNonEmptyString } from '../../utils/isNonEmptyString';
|
||||
|
||||
export function Verify() {
|
||||
export function VerifyEffect() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const loginToken = searchParams.get('loginToken');
|
||||
|
@ -12,7 +12,7 @@ import { PageAddButton } from '@/ui/layout/components/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/components/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/components/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/components/PageHeader';
|
||||
import { PageHotkeys } from '@/ui/layout/components/PageHotkeys';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/components/PageHotkeysEffect';
|
||||
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
|
||||
import { EntityTableContextMenu } from '@/ui/table/context-menu/components/EntityTableContextMenu';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
@ -59,7 +59,7 @@ export function Companies() {
|
||||
<PageContainer>
|
||||
<PageHeader title="Companies" Icon={IconBuildingSkyscraper}>
|
||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||
<PageHotkeys onAddButtonClick={handleAddButtonClick} />
|
||||
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
|
||||
<PageAddButton onClick={handleAddButtonClick} />
|
||||
</RecoilScope>
|
||||
</PageHeader>
|
||||
|
@ -10,7 +10,7 @@ import { useImpersonateMutation } from '~/generated/graphql';
|
||||
import { AppPath } from '../../modules/types/AppPath';
|
||||
import { isNonEmptyString } from '../../utils/isNonEmptyString';
|
||||
|
||||
export function Impersonate() {
|
||||
export function ImpersonateEffect() {
|
||||
const navigate = useNavigate();
|
||||
const { userId } = useParams();
|
||||
|
@ -10,7 +10,7 @@ import { PageAddButton } from '@/ui/layout/components/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/components/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/components/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/components/PageHeader';
|
||||
import { PageHotkeys } from '@/ui/layout/components/PageHotkeys';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/components/PageHotkeysEffect';
|
||||
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
|
||||
import { EntityTableContextMenu } from '@/ui/table/context-menu/components/EntityTableContextMenu';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
@ -55,7 +55,7 @@ export function People() {
|
||||
<PageContainer>
|
||||
<PageHeader title="People" Icon={IconUser}>
|
||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||
<PageHotkeys onAddButtonClick={handleAddButtonClick} />
|
||||
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
|
||||
<PageAddButton onClick={handleAddButtonClick} />
|
||||
</RecoilScope>
|
||||
</PageHeader>
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { CommandMenuHook } from './CommandMenuHook';
|
||||
import { GotoHotkeysHooks } from './GotoHotkeysHooks';
|
||||
|
||||
export function AppInternalHooks() {
|
||||
return (
|
||||
<>
|
||||
<GotoHotkeysHooks />
|
||||
<CommandMenuHook />
|
||||
</>
|
||||
);
|
||||
}
|
@ -3,7 +3,7 @@ import { useEffect } from 'react';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
|
||||
export function InitializeHotkeyStorybookHook() {
|
||||
export function InitializeHotkeyStorybookHookEffect() {
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -2,13 +2,13 @@ import { ApolloProvider } from '@apollo/client';
|
||||
import { Decorator } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { InitializeHotkeyStorybookHook } from '../InitializeHotkeyStorybookHook';
|
||||
import { InitializeHotkeyStorybookHookEffect } from '../InitializeHotkeyStorybookHook';
|
||||
import { mockedClient } from '../mockedClient';
|
||||
|
||||
export const RootDecorator: Decorator = (Story) => (
|
||||
<RecoilRoot>
|
||||
<ApolloProvider client={mockedClient}>
|
||||
<InitializeHotkeyStorybookHook />
|
||||
<InitializeHotkeyStorybookHookEffect />
|
||||
<Story />
|
||||
</ApolloProvider>
|
||||
</RecoilRoot>
|
||||
|
@ -1851,7 +1851,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
|
||||
integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0":
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||
@ -5462,7 +5462,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
|
||||
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
version "7.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||
@ -5673,6 +5673,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
|
||||
integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
|
||||
|
||||
"@types/semver@^7.5.0":
|
||||
version "7.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564"
|
||||
integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==
|
||||
|
||||
"@types/send@*":
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301"
|
||||
@ -5827,6 +5832,14 @@
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@6.7.0":
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz#6b3c22187976e2bf5ed0dc0d9095f1f2cbd1d106"
|
||||
integrity sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
"@typescript-eslint/visitor-keys" "6.7.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
|
||||
@ -5842,6 +5855,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
||||
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
||||
|
||||
"@typescript-eslint/types@6.7.0":
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.0.tgz#8de8ba9cafadc38e89003fe303e219c9250089ae"
|
||||
integrity sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
||||
@ -5855,6 +5873,19 @@
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@6.7.0":
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz#20ce2801733bd46f02cc0f141f5b63fbbf2afb63"
|
||||
integrity sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
"@typescript-eslint/visitor-keys" "6.7.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.45.0", "@typescript-eslint/utils@^5.58.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
||||
@ -5869,6 +5900,19 @@
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
|
||||
"@typescript-eslint/utils@^6.7.0":
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.0.tgz#61b6f1f1b82ad529abfcee074d21764e880886fb"
|
||||
integrity sha512-MfCq3cM0vh2slSikQYqK2Gq52gvOhe57vD2RM3V4gQRZYX4rDPnKLu5p6cm89+LJiGlwEXU8hkYxhqqEC/V3qA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@types/json-schema" "^7.0.12"
|
||||
"@types/semver" "^7.5.0"
|
||||
"@typescript-eslint/scope-manager" "6.7.0"
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
"@typescript-eslint/typescript-estree" "6.7.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
||||
@ -5877,6 +5921,14 @@
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@6.7.0":
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz#34140ac76dfb6316d17012e4469acf3366ad3f44"
|
||||
integrity sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.7.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
|
||||
version "1.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
|
||||
@ -9772,6 +9824,9 @@ eslint-plugin-testing-library@^5.0.1:
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "^5.58.0"
|
||||
|
||||
"eslint-plugin-twenty-ts@file:../packages/eslint-plugin-twenty-ts":
|
||||
version "1.0.1"
|
||||
|
||||
"eslint-plugin-twenty@file:../packages/eslint-plugin-twenty":
|
||||
version "0.0.2"
|
||||
dependencies:
|
||||
@ -17078,7 +17133,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3:
|
||||
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
@ -18211,6 +18266,11 @@ tryer@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||
|
||||
ts-api-utils@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
|
||||
integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==
|
||||
|
||||
ts-dedent@^2.0.0, ts-dedent@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
|
1
packages/eslint-plugin-twenty-ts/.gitignore
vendored
Normal file
1
packages/eslint-plugin-twenty-ts/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
5
packages/eslint-plugin-twenty-ts/index.ts
Normal file
5
packages/eslint-plugin-twenty-ts/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
"effect-components": require("./src/rules/effect-components"),
|
||||
},
|
||||
};
|
7
packages/eslint-plugin-twenty-ts/jest.config.js
Normal file
7
packages/eslint-plugin-twenty-ts/jest.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
||||
"moduleDirectories": ["node_modules"]
|
||||
};
|
35
packages/eslint-plugin-twenty-ts/package.json
Normal file
35
packages/eslint-plugin-twenty-ts/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "eslint-plugin-twenty-ts",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "rimraf ./dist && tsc --outDir ./dist"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
"@typescript-eslint/rule-tester": "^6.7.0",
|
||||
"@typescript-eslint/utils": "^6.7.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-standard-with-typescript": "^39.0.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^28.1.3",
|
||||
"prettier": "^3.0.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
114
packages/eslint-plugin-twenty-ts/src/rules/effect-components.ts
Normal file
114
packages/eslint-plugin-twenty-ts/src/rules/effect-components.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { TSESTree, ESLintUtils } from "@typescript-eslint/utils";
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`);
|
||||
|
||||
function checkIsPascalCase(input: string): boolean {
|
||||
const pascalCaseRegex = /^(?:\p{Uppercase_Letter}\p{Letter}*)+$/u;
|
||||
|
||||
return pascalCaseRegex.test(input);
|
||||
}
|
||||
|
||||
const effectComponentsRule = createRule({
|
||||
create(context) {
|
||||
const checkThatNodeIsEffectComponent = (node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression) => {
|
||||
const isPascalCase = checkIsPascalCase(node.id?.name ?? "");
|
||||
|
||||
if(!isPascalCase) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isReturningFragmentOrNull = (
|
||||
// Direct return of JSX fragment, e.g., () => <></>
|
||||
(node.body.type === 'JSXFragment' && node.body.children.length === 0) ||
|
||||
// Direct return of null, e.g., () => null
|
||||
(node.body.type === 'Literal' && node.body.value === null) ||
|
||||
// Return JSX fragment or null from block
|
||||
(node.body.type === 'BlockStatement' &&
|
||||
node.body.body.some(statement =>
|
||||
statement.type === 'ReturnStatement' &&
|
||||
(
|
||||
// Empty JSX fragment return, e.g., return <></>;
|
||||
(statement.argument?.type === 'JSXFragment' && statement.argument.children.length === 0) ||
|
||||
// Empty React.Fragment return, e.g., return <React.Fragment></React.Fragment>;
|
||||
(statement.argument?.type === 'JSXElement' &&
|
||||
statement.argument.openingElement.name.type === 'JSXIdentifier' &&
|
||||
statement.argument.openingElement.name.name === 'React.Fragment' &&
|
||||
statement.argument.children.length === 0) ||
|
||||
// Literal null return, e.g., return null;
|
||||
(statement.argument?.type === 'Literal' && statement.argument.value === null)
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
const hasEffectSuffix = node.id?.name.endsWith("Effect");
|
||||
|
||||
const hasEffectSuffixButIsNotEffectComponent = hasEffectSuffix && !isReturningFragmentOrNull
|
||||
const isEffectComponentButDoesNotHaveEffectSuffix = !hasEffectSuffix && isReturningFragmentOrNull;
|
||||
|
||||
if(isEffectComponentButDoesNotHaveEffectSuffix) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: "effectSuffix",
|
||||
data: {
|
||||
componentName: node.id?.name,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (node.id) {
|
||||
return fixer.replaceText(
|
||||
node.id,
|
||||
node.id?.name + "Effect",
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
} else if(hasEffectSuffixButIsNotEffectComponent) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: "noEffectSuffix",
|
||||
data: {
|
||||
componentName: node.id?.name,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (node.id) {
|
||||
return fixer.replaceText(
|
||||
node.id,
|
||||
node.id?.name.replace("Effect", ""),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ArrowFunctionExpression: checkThatNodeIsEffectComponent,
|
||||
FunctionDeclaration: checkThatNodeIsEffectComponent,
|
||||
FunctionExpression: checkThatNodeIsEffectComponent,
|
||||
};
|
||||
},
|
||||
name: "effect-components",
|
||||
meta: {
|
||||
docs: {
|
||||
description:
|
||||
"Effect components should end with the Effect suffix. This rule checks only components that are in PascalCase and that return a JSX fragment or null. Any renderProps or camelCase components are ignored.",
|
||||
},
|
||||
messages: {
|
||||
effectSuffix:
|
||||
"Effect component {{ componentName }} should end with the Effect suffix.",
|
||||
noEffectSuffix:
|
||||
"Component {{ componentName }} shouldn't end with the Effect suffix because it doesn't return a JSX fragment or null.",
|
||||
},
|
||||
type: "suggestion",
|
||||
schema: [],
|
||||
fixable: "code",
|
||||
},
|
||||
defaultOptions: [],
|
||||
});
|
||||
|
||||
module.exports = effectComponentsRule;
|
||||
|
||||
export default effectComponentsRule;
|
97
packages/eslint-plugin-twenty-ts/src/tests/all.spec.ts
Normal file
97
packages/eslint-plugin-twenty-ts/src/tests/all.spec.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
||||
|
||||
import effectComponentsRule from "../rules/effect-components";
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
ruleTester.run("effect-components", effectComponentsRule, {
|
||||
valid: [
|
||||
{
|
||||
code: `function TestComponentEffect() {
|
||||
return <></>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `function TestComponent() {
|
||||
return <div></div>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `export function useUpdateEffect() {
|
||||
return null;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `export function useUpdateEffect() {
|
||||
return <></>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `function TestComponent() {
|
||||
return <><div></div></>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `function TestComponentEffect() {
|
||||
return null;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `function TestComponentEffect() {
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return null;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `function TestComponentEffect() {
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return <></>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `const TestComponentEffect = () => {
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return <></>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
code: `const TestComponentEffect = () => {
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return null;
|
||||
}`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "function TestComponent() { return <></>; }",
|
||||
output: 'function TestComponentEffect() { return <></>; }',
|
||||
errors: [
|
||||
{
|
||||
messageId: "effectSuffix",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "function TestComponentEffect() { return <><div></div></>; }",
|
||||
output: 'function TestComponent() { return <><div></div></>; }',
|
||||
errors: [
|
||||
{
|
||||
messageId: "noEffectSuffix",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
1
packages/eslint-plugin-twenty-ts/src/tests/file.ts
Normal file
1
packages/eslint-plugin-twenty-ts/src/tests/file.ts
Normal file
@ -0,0 +1 @@
|
||||
// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing
|
1
packages/eslint-plugin-twenty-ts/src/tests/react.tsx
Normal file
1
packages/eslint-plugin-twenty-ts/src/tests/react.tsx
Normal file
@ -0,0 +1 @@
|
||||
// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing
|
6
packages/eslint-plugin-twenty-ts/src/tests/tsconfig.json
Normal file
6
packages/eslint-plugin-twenty-ts/src/tests/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./file.ts", "./react.tsx"]
|
||||
}
|
14
packages/eslint-plugin-twenty-ts/tsconfig.json
Normal file
14
packages/eslint-plugin-twenty-ts/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and in clude compatible library declarations. */
|
||||
"module": "Node16", /* Specify what module code is generated. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
"moduleResolution": "Node16",
|
||||
}
|
||||
}
|
3815
packages/eslint-plugin-twenty-ts/yarn.lock
Normal file
3815
packages/eslint-plugin-twenty-ts/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user