diff --git a/front/.eslintrc.js b/front/.eslintrc.js index 9926d52ca..9432bdbf4 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -16,7 +16,8 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:storybook/recommended', - 'react-app', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', ], root: true, env: { @@ -33,6 +34,12 @@ module.exports = { { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], rules: { + 'react/no-unescaped-entities': 'off', + 'react/prop-types': 'off', + 'react/jsx-key': 'off', + 'react/display-name': 'off', + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', 'no-control-regex': 0, 'simple-import-sort/imports': [ 'error', @@ -72,6 +79,7 @@ module.exports = { 'twenty/component-props-naming': 'error', 'twenty/sort-css-properties-alphabetically': 'error', 'twenty/styled-components-prefixed-with-styled': 'error', + 'twenty/no-state-useref': 'error', 'func-style':['error', 'declaration', { 'allowArrowFunctions': true }], "@typescript-eslint/no-unused-vars": "off", "no-unused-vars": "off", @@ -108,5 +116,10 @@ module.exports = { ], "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }], 'no-console': ['error', { allow: ['group', 'groupCollapsed', 'groupEnd'] }], + }, + settings: { + "react": { + "version": "detect" + } } }; diff --git a/front/package.json b/front/package.json index 1fc0c8af4..46174efb0 100644 --- a/front/package.json +++ b/front/package.json @@ -87,22 +87,6 @@ "chromatic": "dotenv cross-var npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN", "install": "yarn eslint-plugin:setup" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ], - "overrides": [ - { - "files": [ - "**/*.stories.*" - ], - "rules": { - "import/no-anonymous-default-export": "off" - } - } - ] - }, "overrides": { "react-refresh": "0.14.0" }, @@ -157,7 +141,8 @@ "@types/react-datepicker": "^4.11.2", "@types/scroll-into-view": "^1.16.0", "@types/uuid": "^9.0.1", - "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "@typescript-eslint/utils": "^6.7.0", "babel-plugin-named-exports-order": "^0.0.2", "chromatic": "^6.18.0", @@ -166,7 +151,6 @@ "dotenv-cli": "^7.2.1", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", - "eslint-config-react-app": "^7.0.1", "eslint-config-standard-with-typescript": "^23.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-n": "^15.5.1", diff --git a/front/src/hooks/useFirstMountState.ts b/front/src/hooks/useFirstMountState.ts index 2bfde14ce..cc8a7145b 100644 --- a/front/src/hooks/useFirstMountState.ts +++ b/front/src/hooks/useFirstMountState.ts @@ -1,6 +1,7 @@ import { useRef } from 'react'; export const useFirstMountState = (): boolean => { + // eslint-disable-next-line twenty/no-state-useref const isFirst = useRef(true); if (isFirst.current) { diff --git a/front/src/modules/apollo/hooks/useApolloFactory.ts b/front/src/modules/apollo/hooks/useApolloFactory.ts index 2ee75762a..9f996cf60 100644 --- a/front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/front/src/modules/apollo/hooks/useApolloFactory.ts @@ -13,6 +13,7 @@ import { useUpdateEffect } from '~/hooks/useUpdateEffect'; import { ApolloFactory } from '../services/apollo.factory'; export const useApolloFactory = () => { + // eslint-disable-next-line twenty/no-state-useref const apolloRef = useRef | null>(null); const [isDebugMode] = useRecoilState(isDebugModeState); diff --git a/front/src/modules/ui/data/field/meta-types/input/components/internal/DateInput.tsx b/front/src/modules/ui/data/field/meta-types/input/components/internal/DateInput.tsx index cc46ef768..6b509b44e 100644 --- a/front/src/modules/ui/data/field/meta-types/input/components/internal/DateInput.tsx +++ b/front/src/modules/ui/data/field/meta-types/input/components/internal/DateInput.tsx @@ -50,7 +50,7 @@ export const DateInput = ({ const [internalValue, setInternalValue] = useState(value); - const wrapperRef = useRef(null); + const wrapperRef = useRef(null); const { refs, floatingStyles } = useFloating({ placement: 'bottom-start', diff --git a/front/src/modules/ui/data/field/meta-types/input/components/internal/PhoneInput.tsx b/front/src/modules/ui/data/field/meta-types/input/components/internal/PhoneInput.tsx index c18b4b7f6..80f88aabd 100644 --- a/front/src/modules/ui/data/field/meta-types/input/components/internal/PhoneInput.tsx +++ b/front/src/modules/ui/data/field/meta-types/input/components/internal/PhoneInput.tsx @@ -64,7 +64,7 @@ export const PhoneInput = ({ }: PhoneInputProps) => { const [internalValue, setInternalValue] = useState(value); - const wrapperRef = useRef(null); + const wrapperRef = useRef(null); useEffect(() => { setInternalValue(value); diff --git a/front/src/modules/ui/data/field/meta-types/input/components/internal/TextInput.tsx b/front/src/modules/ui/data/field/meta-types/input/components/internal/TextInput.tsx index fdfedd63a..3581760e9 100644 --- a/front/src/modules/ui/data/field/meta-types/input/components/internal/TextInput.tsx +++ b/front/src/modules/ui/data/field/meta-types/input/components/internal/TextInput.tsx @@ -36,7 +36,7 @@ export const TextInput = ({ }: TextInputProps) => { const [internalText, setInternalText] = useState(value); - const wrapperRef = useRef(null); + const wrapperRef = useRef(null); const handleChange = (event: ChangeEvent) => { setInternalText(event.target.value); diff --git a/front/src/modules/ui/feedback/progress-bar/components/ProgressBar.tsx b/front/src/modules/ui/feedback/progress-bar/components/ProgressBar.tsx index ceb7a5c69..0627eb634 100644 --- a/front/src/modules/ui/feedback/progress-bar/components/ProgressBar.tsx +++ b/front/src/modules/ui/feedback/progress-bar/components/ProgressBar.tsx @@ -49,7 +49,9 @@ export const ProgressBar = forwardRef( const theme = useTheme(); const controls = useAnimation(); + // eslint-disable-next-line twenty/no-state-useref const startTimestamp = useRef(0); + // eslint-disable-next-line twenty/no-state-useref const remainingTime = useRef(duration); const start = useCallback(async () => { diff --git a/front/src/modules/ui/feedback/snack-bar/components/SnackBar.tsx b/front/src/modules/ui/feedback/snack-bar/components/SnackBar.tsx index 0101c709f..605ca1a4a 100644 --- a/front/src/modules/ui/feedback/snack-bar/components/SnackBar.tsx +++ b/front/src/modules/ui/feedback/snack-bar/components/SnackBar.tsx @@ -116,6 +116,7 @@ export const SnackBar = ({ }: SnackBarProps) => { const theme = useTheme(); + // eslint-disable-next-line twenty/no-state-useref const progressBarRef = useRef(null); const closeSnackbar = useCallback(() => { diff --git a/front/src/modules/ui/feedback/snack-bar/hooks/usePausableTimeout.ts b/front/src/modules/ui/feedback/snack-bar/hooks/usePausableTimeout.ts index 9b85ec626..cec0a02a6 100644 --- a/front/src/modules/ui/feedback/snack-bar/hooks/usePausableTimeout.ts +++ b/front/src/modules/ui/feedback/snack-bar/hooks/usePausableTimeout.ts @@ -1,9 +1,13 @@ import { useCallback, useEffect, useRef } from 'react'; export const usePausableTimeout = (callback: () => void, delay: number) => { + // eslint-disable-next-line twenty/no-state-useref const savedCallback = useRef<() => void>(callback); + // eslint-disable-next-line twenty/no-state-useref const remainingTime = useRef(delay); + // eslint-disable-next-line twenty/no-state-useref const startTime = useRef(Date.now()); + // eslint-disable-next-line twenty/no-state-useref const timeoutId = useRef | null>(null); const tick = () => { diff --git a/front/src/modules/ui/layout/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/layout/board/components/BoardColumnMenu.tsx index 549f9aed4..9eac9e42d 100644 --- a/front/src/modules/ui/layout/board/components/BoardColumnMenu.tsx +++ b/front/src/modules/ui/layout/board/components/BoardColumnMenu.tsx @@ -54,7 +54,7 @@ export const BoardColumnMenu = ({ const [currentMenu, setCurrentMenu] = useState('actions'); const column = useContext(BoardColumnContext); - const boardColumnMenuRef = useRef(null); + const boardColumnMenuRef = useRef(null); const { enqueueSnackBar } = useSnackBar(); const createCompanyProgress = useCreateCompanyProgress(); diff --git a/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx b/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx index 664c31423..afc2a2fe8 100644 --- a/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx +++ b/front/src/modules/ui/layout/dropdown/components/DropdownMenuContainer.tsx @@ -28,7 +28,7 @@ export const DropdownMenuContainer = ({ onClose, width, }: DropdownMenuContainerProps) => { - const dropdownRef = useRef(null); + const dropdownRef = useRef(null); useListenClickOutside({ refs: [dropdownRef], diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx index 379c4ec0e..83af5cec0 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx @@ -51,7 +51,7 @@ export const RightDrawer = () => { const { closeRightDrawer } = useRightDrawer(); - const rightDrawerRef = useRef(null); + const rightDrawerRef = useRef(null); useListenClickOutside({ refs: [rightDrawerRef], diff --git a/front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx b/front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx index 7e78bc810..3abdcfc74 100644 --- a/front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx +++ b/front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx @@ -37,7 +37,7 @@ export const ActionBar = ({ selectedIds }: ActionBarProps) => { const actionBarOpen = useRecoilValue(actionBarOpenState); const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const actionBarEntries = useRecoilValue(actionBarEntriesState); - const wrapperRef = useRef(null); + const wrapperRef = useRef(null); if (selectedIds.length === 0 || !actionBarOpen || contextMenuIsOpen) { return null; diff --git a/front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx b/front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx index e41194655..e74efe148 100644 --- a/front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx +++ b/front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx @@ -47,7 +47,7 @@ export const ContextMenu = ({ selectedIds }: ContextMenuProps) => { const contextMenuEntries = useRecoilValue(contextMenuEntriesState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const setActionBarOpenState = useSetRecoilState(actionBarOpenState); - const wrapperRef = useRef(null); + const wrapperRef = useRef(null); useListenClickOutside({ refs: [wrapperRef], diff --git a/front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx b/front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx index 39349126e..d96d092df 100644 --- a/front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx +++ b/front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx @@ -6,8 +6,8 @@ import { useListenClickOutside } from '../useListenClickOutside'; const onOutsideClick = jest.fn(); const TestComponentDomMode = () => { - const buttonRef = useRef(null); - const buttonRef2 = useRef(null); + const buttonRef = useRef(null); + const buttonRef2 = useRef(null); useListenClickOutside({ refs: [buttonRef, buttonRef2], callback: onOutsideClick, diff --git a/front/src/modules/ui/utilities/recoil-scope/components/RecoilScope.tsx b/front/src/modules/ui/utilities/recoil-scope/components/RecoilScope.tsx index 0590d6edb..0dc0ea744 100644 --- a/front/src/modules/ui/utilities/recoil-scope/components/RecoilScope.tsx +++ b/front/src/modules/ui/utilities/recoil-scope/components/RecoilScope.tsx @@ -18,6 +18,7 @@ export const RecoilScope = ({ scopeId?: string; CustomRecoilScopeContext?: RecoilScopeContextType; }) => { + // eslint-disable-next-line twenty/no-state-useref const currentScopeId = useRef(scopeId ?? v4()); return CustomRecoilScopeContext ? ( diff --git a/front/yarn.lock b/front/yarn.lock index 287572430..34257458f 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1863,6 +1863,11 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== +"@eslint-community/regexpp@^4.5.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" + integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== + "@eslint/eslintrc@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" @@ -5932,7 +5937,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.45.0", "@typescript-eslint/eslint-plugin@^5.5.0": +"@typescript-eslint/eslint-plugin@^5.5.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== @@ -5948,6 +5953,23 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/eslint-plugin@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f" + integrity sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/type-utils" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/experimental-utils@^5.0.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741" @@ -5965,6 +5987,17 @@ "@typescript-eslint/typescript-estree" "5.62.0" debug "^4.3.4" +"@typescript-eslint/parser@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.5.tgz#8d7ca3d1fbd9d5a58cc4d30b2aa797a760137886" + integrity sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw== + dependencies: + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" @@ -5981,6 +6014,14 @@ "@typescript-eslint/types" "6.7.0" "@typescript-eslint/visitor-keys" "6.7.0" +"@typescript-eslint/scope-manager@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711" + integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A== + dependencies: + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + "@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" @@ -5991,6 +6032,16 @@ debug "^4.3.4" tsutils "^3.21.0" +"@typescript-eslint/type-utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" + integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + debug "^4.3.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" @@ -6001,6 +6052,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.0.tgz#8de8ba9cafadc38e89003fe303e219c9250089ae" integrity sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q== +"@typescript-eslint/types@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790" + integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ== + "@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" @@ -6027,6 +6083,19 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39" + integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg== + dependencies: + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + 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" @@ -6041,6 +6110,19 @@ eslint-scope "^5.1.1" semver "^7.3.7" +"@typescript-eslint/utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" + integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== + 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.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + semver "^7.5.4" + "@typescript-eslint/utils@^6.7.0": version "6.7.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.0.tgz#61b6f1f1b82ad529abfcee074d21764e880886fb" @@ -6070,6 +6152,14 @@ "@typescript-eslint/types" "6.7.0" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1" + integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg== + dependencies: + "@typescript-eslint/types" "6.7.5" + 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" @@ -11596,7 +11686,7 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.1.1, ignore@^5.2.0: +ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== diff --git a/packages/eslint-plugin-twenty-ts/dist/index.js b/packages/eslint-plugin-twenty-ts/dist/index.js deleted file mode 100644 index d98d50f36..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/index.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -module.exports = { - rules: { - "effect-components": require("./src/rules/effect-components"), - "no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"), - "matching-state-variable": require("./src/rules/matching-state-variable"), - "sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"), - "styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"), - "component-props-naming": require("./src/rules/component-props-naming"), - }, -}; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/component-props-naming.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/component-props-naming.js deleted file mode 100644 index 16b32f813..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/component-props-naming.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator(() => "https://docs.twenty.com/developer/frontend/style-guide#props"); -const checkPropsTypeName = (node, context, functionName) => { - const expectedPropTypeName = `${functionName}Props`; - if (/^[A-Z]/.test(functionName)) { - node.params.forEach((param) => { - var _a, _b; - if ((param.type === utils_1.AST_NODE_TYPES.ObjectPattern || - param.type === utils_1.AST_NODE_TYPES.Identifier) && - ((_b = (_a = param.typeAnnotation) === null || _a === void 0 ? void 0 : _a.typeAnnotation) === null || _b === void 0 ? void 0 : _b.type) === - utils_1.AST_NODE_TYPES.TSTypeReference && - param.typeAnnotation.typeAnnotation.typeName.type === - utils_1.AST_NODE_TYPES.Identifier) { - const { typeName } = param.typeAnnotation.typeAnnotation; - const actualPropTypeName = typeName.name; - if (actualPropTypeName !== expectedPropTypeName) { - context.report({ - node: param, - messageId: "invalidPropsTypeName", - data: { expectedPropTypeName, actualPropTypeName }, - fix: (fixer) => fixer.replaceText(typeName, expectedPropTypeName), - }); - } - } - }); - } -}; -const componentPropsNamingRule = createRule({ - create: (context) => { - return { - ArrowFunctionExpression: (node) => { - var _a, _b; - if (node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator && - node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) { - const functionName = (_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.name; - checkPropsTypeName(node, context, functionName); - } - }, - FunctionDeclaration: (node) => { - var _a; - if ((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) { - const functionName = node.id.name; - checkPropsTypeName(node, context, functionName); - } - }, - }; - }, - name: "component-props-naming", - meta: { - type: "problem", - docs: { - description: "Ensure component props follow naming convention", - recommended: "recommended", - }, - fixable: "code", - schema: [], - messages: { - invalidPropsTypeName: "Expected prop type to be '{{ expectedPropTypeName }}' but found '{{ actualPropTypeName }}'", - }, - }, - defaultOptions: [], -}); -module.exports = componentPropsNamingRule; -exports.default = componentPropsNamingRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/effect-components.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/effect-components.js deleted file mode 100644 index 67c189e61..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/effect-components.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`); -const checkIsPascalCase = (input) => { - const pascalCaseRegex = /^(?:\p{Uppercase_Letter}\p{Letter}*)+$/u; - return pascalCaseRegex.test(input); -}; -const effectComponentsRule = createRule({ - create(context) { - const checkThatNodeIsEffectComponent = (node) => { - var _a, _b, _c, _d, _e; - const isPascalCase = checkIsPascalCase((_b = (_a = node.id) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ""); - 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 => { - var _a, _b, _c; - return statement.type === 'ReturnStatement' && - ( - // Empty JSX fragment return, e.g., return <>; - (((_a = statement.argument) === null || _a === void 0 ? void 0 : _a.type) === 'JSXFragment' && statement.argument.children.length === 0) || - // Empty React.Fragment return, e.g., return ; - (((_b = statement.argument) === null || _b === void 0 ? void 0 : _b.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; - (((_c = statement.argument) === null || _c === void 0 ? void 0 : _c.type) === 'Literal' && statement.argument.value === null)); - }))); - const hasEffectSuffix = (_c = node.id) === null || _c === void 0 ? void 0 : _c.name.endsWith("Effect"); - const hasEffectSuffixButIsNotEffectComponent = hasEffectSuffix && !isReturningFragmentOrNull; - const isEffectComponentButDoesNotHaveEffectSuffix = !hasEffectSuffix && isReturningFragmentOrNull; - if (isEffectComponentButDoesNotHaveEffectSuffix) { - context.report({ - node, - messageId: "effectSuffix", - data: { - componentName: (_d = node.id) === null || _d === void 0 ? void 0 : _d.name, - }, - fix(fixer) { - var _a; - if (node.id) { - return fixer.replaceText(node.id, ((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) + "Effect"); - } - return null; - }, - }); - } - else if (hasEffectSuffixButIsNotEffectComponent) { - context.report({ - node, - messageId: "noEffectSuffix", - data: { - componentName: (_e = node.id) === null || _e === void 0 ? void 0 : _e.name, - }, - fix(fixer) { - var _a; - if (node.id) { - return fixer.replaceText(node.id, (_a = node.id) === null || _a === void 0 ? void 0 : _a.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; -exports.default = effectComponentsRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/matching-state-variable.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/matching-state-variable.js deleted file mode 100644 index 8e065d1f2..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/matching-state-variable.js +++ /dev/null @@ -1,113 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`); -const matchingStateVariableRule = createRule({ - create: (context) => { - return { - VariableDeclarator(node) { - var _a, _b, _c, _d, _e, _f, _g; - if (((_a = node === null || node === void 0 ? void 0 : node.init) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.CallExpression && - node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier && - [ - "useRecoilState", - "useRecoilFamilyState", - "useRecoilSelector", - "useRecoilScopedState", - "useRecoilScopedFamilyState", - "useRecoilScopedSelector", - "useRecoilValue", - ].includes(node.init.callee.name)) { - const stateNameBase = ((_c = (_b = node.init.arguments) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.type) === utils_1.AST_NODE_TYPES.Identifier - ? node.init.arguments[0].name - : undefined; - if (!stateNameBase) { - return; - } - let expectedVariableNameBase = stateNameBase.replace(/(State|FamilyState|Selector|ScopedState|ScopedFamilyState|ScopedSelector)$/, ""); - if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) { - const actualVariableName = node.id.name; - if (actualVariableName !== expectedVariableNameBase) { - context.report({ - node, - messageId: "invalidVariableName", - data: { - actual: actualVariableName, - expected: expectedVariableNameBase, - callee: node.init.callee.name, - }, - fix(fixer) { - return fixer.replaceText(node.id, expectedVariableNameBase); - }, - }); - } - return; - } - if (node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern) { - const actualVariableName = ((_e = (_d = node.id.elements) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.type) === utils_1.AST_NODE_TYPES.Identifier - ? node.id.elements[0].name - : undefined; - if (actualVariableName && - actualVariableName !== expectedVariableNameBase) { - context.report({ - node, - messageId: "invalidVariableName", - data: { - actual: actualVariableName, - expected: expectedVariableNameBase, - callee: node.init.callee.name, - }, - fix(fixer) { - if (node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern) { - return fixer.replaceText(node.id.elements[0], expectedVariableNameBase); - } - return null; - }, - }); - return; - } - if (((_g = (_f = node.id.elements) === null || _f === void 0 ? void 0 : _f[1]) === null || _g === void 0 ? void 0 : _g.type) === utils_1.AST_NODE_TYPES.Identifier) { - const actualSetterName = node.id.elements[1].name; - const expectedSetterName = `set${expectedVariableNameBase - .charAt(0) - .toUpperCase()}${expectedVariableNameBase.slice(1)}`; - if (actualSetterName !== expectedSetterName) { - context.report({ - node, - messageId: "invalidSetterName", - data: { - actual: actualSetterName, - expected: expectedSetterName, - }, - fix(fixer) { - if (node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern) { - return fixer.replaceText(node.id.elements[1], expectedSetterName); - } - return null; - }, - }); - } - } - } - } - }, - }; - }, - name: "recoil-hook-naming", - meta: { - type: "problem", - docs: { - description: "Ensure recoil value and setter are named after their atom name", - recommended: "recommended", - }, - fixable: "code", - schema: [], - messages: { - invalidVariableName: "Invalid usage of {{hookName}}: the value should be named '{{expectedName}}' but found '{{actualName}}'.", - invalidSetterName: "Invalid usage of {{hookName}}: Expected setter '{{expectedName}}' but found '{{actualName}}'.", - }, - }, - defaultOptions: [], -}); -module.exports = matchingStateVariableRule; -exports.default = matchingStateVariableRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/no-hardcoded-colors.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/no-hardcoded-colors.js deleted file mode 100644 index 545d11189..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/no-hardcoded-colors.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`); -const noHardcodedColorsRule = createRule({ - create(context) { - return { - TaggedTemplateExpression(node) { - if (context.getFilename().endsWith("themes.ts")) { - return; - } - node.quasi.quasis.forEach((quasi) => { - const colorRegex = /(?:rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(,\s*\d+\.?\d*)?\))|(?:#[0-9a-fA-F]{6})/i; - if (colorRegex.test(quasi.value.raw)) { - context.report({ - node, - messageId: "hardcodedColor", - data: { - color: quasi.value.raw, - }, - }); - } - }); - }, - }; - }, - name: "no-hardcoded-colors", - meta: { - docs: { - description: "Do not use hardcoded RGBA or Hex colors. Please use a color from the theme file.", - }, - messages: { - hardcodedColor: "Hardcoded color {{ color }} found. Please use a color from the theme file.", - }, - type: "suggestion", - schema: [], - fixable: "code", - }, - defaultOptions: [], -}); -module.exports = noHardcodedColorsRule; -exports.default = noHardcodedColorsRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/sort-css-properties-alphabetically.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/sort-css-properties-alphabetically.js deleted file mode 100644 index 3733faa7d..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/sort-css-properties-alphabetically.js +++ /dev/null @@ -1,182 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const postcss_1 = __importDefault(require("postcss")); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`); -const isStyledTagname = (node) => { - return ((node.tag.type === "Identifier" && node.tag.name === "css") || - (node.tag.type === "MemberExpression" && - // @ts-ignore - node.tag.object.name === "styled") || - (node.tag.type === "CallExpression" && - // @ts-ignore - (node.tag.callee.name === "styled" || - // @ts-ignore - (node.tag.callee.object && - // @ts-ignore - ((node.tag.callee.object.callee && - // @ts-ignore - node.tag.callee.object.callee.name === "styled") || - // @ts-ignore - (node.tag.callee.object.object && - // @ts-ignore - node.tag.callee.object.object.name === "styled")))))); -}; -/** - * An atomic rule is a rule without nested rules. - */ -const isValidAtomicRule = (rule) => { - const decls = rule.nodes.filter((node) => node.type === "decl"); - if (decls.length < 0) { - return { isValid: true }; - } - for (let i = 1; i < decls.length; i++) { - const current = decls[i].prop; - const prev = decls[i - 1].prop; - if (current < prev) { - const loc = { - start: { - line: decls[i - 1].source.start.line, - column: decls[i - 1].source.start.column - 1, - }, - end: { - line: decls[i].source.end.line, - column: decls[i].source.end.column - 1, - }, - }; - return { isValid: false, loc }; - } - } - return { isValid: true }; -}; -const isValidRule = (rule) => { - // check each rule recursively - const { isValid, loc } = rule.nodes.reduce((map, node) => { - return node.type === "rule" - ? isValidRule(node) - : map; - }, { isValid: true }); - // if there is any invalid rule, return result - if (!isValid) { - return { isValid, loc }; - } - // check declarations - return isValidAtomicRule(rule); -}; -const getNodeStyles = (node) => { - const [firstQuasi, ...quasis] = node.quasi.quasis; - // remove line break added to the first quasi - const lineBreakCount = node.quasi.loc.start.line - 1; - let styles = `${"\n".repeat(lineBreakCount)}${" ".repeat(node.quasi.loc.start.column + 1)}${firstQuasi.value.raw}`; - // replace expression by spaces and line breaks - quasis.forEach(({ value, loc }, idx) => { - const prevLoc = idx === 0 ? firstQuasi.loc : quasis[idx - 1].loc; - const lineBreaksCount = loc.start.line - prevLoc.end.line; - const spacesCount = loc.start.line === prevLoc.end.line - ? loc.start.column - prevLoc.end.column + 2 - : loc.start.column + 1; - styles = `${styles}${" "}${"\n".repeat(lineBreaksCount)}${" ".repeat(spacesCount)}${value.raw}`; - }); - return styles; -}; -const fix = ({ rule, fixer, src, }) => { - let fixings = []; - // concat fixings recursively - rule.nodes.forEach((node) => { - if (node.type === "rule") { - fixings = [...fixings, ...fix({ rule: node, fixer, src })]; - } - }); - const declarations = rule.nodes.filter((node) => node.type === "decl"); - const sortedDeclarations = sortDeclarations(declarations); - declarations.forEach((decl, idx) => { - if (!areSameDeclarations(decl, sortedDeclarations[idx])) { - try { - const range = getDeclRange({ decl, src }); - const sortedDeclText = getDeclText({ - decl: sortedDeclarations[idx], - src, - }); - fixings.push(fixer.removeRange([range.startIdx, range.endIdx + 1])); - fixings.push(fixer.insertTextAfterRange([range.startIdx, range.startIdx], sortedDeclText)); - } - catch (e) { - console.log(e); - } - } - }); - return fixings; -}; -const areSameDeclarations = (a, b) => a.source.start.line === b.source.start.line && - a.source.start.column === b.source.start.column; -const getDeclRange = ({ decl, src, }) => { - const loc = { - start: { - line: decl.source.start.line, - column: decl.source.start.column - 1, - }, - end: { - line: decl.source.end.line, - column: decl.source.end.column - 1, - }, - }; - const startIdx = src.getIndexFromLoc(loc.start); - const endIdx = src.getIndexFromLoc(loc.end); - return { startIdx, endIdx }; -}; -const getDeclText = ({ decl, src, }) => { - const { startIdx, endIdx } = getDeclRange({ decl, src }); - return src.getText().substring(startIdx, endIdx + 1); -}; -const sortDeclarations = (declarations) => declarations - .slice() - .sort((declA, declB) => (declA.prop > declB.prop ? 1 : -1)); -const sortCssPropertiesAlphabeticallyRule = createRule({ - create(context) { - return { - TaggedTemplateExpression(node) { - if (isStyledTagname(node)) { - try { - const root = postcss_1.default.parse(getNodeStyles(node)); - const { isValid, loc } = isValidRule(root); - if (!isValid) { - return context.report({ - node, - messageId: "sort-css-properties-alphabetically", - loc, - fix: (fixer) => fix({ - // @ts-ignore - rule: root, - fixer, - src: context.getSourceCode(), - }), - }); - } - } - catch (e) { - return true; - } - } - }, - }; - }, - name: "sort-css-properties-alphabetically", - meta: { - docs: { - description: "Styles are sorted alphabetically.", - recommended: "recommended", - }, - messages: { - "sort-css-properties-alphabetically": "Declarations should be sorted alphabetically.", - }, - type: "suggestion", - schema: [], - fixable: "code", - }, - defaultOptions: [], -}); -module.exports = sortCssPropertiesAlphabeticallyRule; -exports.default = sortCssPropertiesAlphabeticallyRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/rules/styled-components-prefixed-with-styled.js b/packages/eslint-plugin-twenty-ts/dist/src/rules/styled-components-prefixed-with-styled.js deleted file mode 100644 index 8cf2a8f52..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/rules/styled-components-prefixed-with-styled.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("@typescript-eslint/utils"); -const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docs.twenty.com`); -const styledComponentsPrefixedWithStyledRule = createRule({ - create(context) { - return { - VariableDeclarator: (node) => { - const templateExpr = node.init; - if ((templateExpr === null || templateExpr === void 0 ? void 0 : templateExpr.type) !== utils_1.AST_NODE_TYPES.TaggedTemplateExpression) { - return; - } - const tag = templateExpr.tag; - const tagged = tag.type === utils_1.AST_NODE_TYPES.MemberExpression ? tag.object - : tag.type === utils_1.AST_NODE_TYPES.CallExpression ? tag.callee - : null; - if ((tagged === null || tagged === void 0 ? void 0 : tagged.type) === utils_1.AST_NODE_TYPES.Identifier && tagged.name === 'styled') { - const variable = node.id; - if (variable.name.startsWith('Styled')) { - return; - } - context.report({ - node, - messageId: 'noStyledPrefix', - data: { - componentName: variable.name - } - }); - } - }, - }; - }, - name: 'styled-components-prefixed-with-styled', - meta: { - type: 'suggestion', - docs: { - description: 'Warn when StyledComponents are not prefixed with Styled', - recommended: "recommended" - }, - messages: { - noStyledPrefix: '{{componentName}} is a StyledComponent and is not prefixed with Styled.', - }, - fixable: 'code', - schema: [], - }, - defaultOptions: [] -}); -module.exports = styledComponentsPrefixedWithStyledRule; -exports.default = styledComponentsPrefixedWithStyledRule; diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/component-props-naming.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/component-props-naming.spec.js deleted file mode 100644 index e42778378..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/component-props-naming.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { RuleTester } from "@typescript-eslint/rule-tester"; - -import componentPropsNamingRule from "../rules/component-props.naming"; - -const ruleTester = new RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); - -ruleTester.run("component-props-naming", componentPropsNamingRule, { - valid: [ - { - code: ` - type MyComponentProps = { id: number; }; - const MyComponent: React.FC = (props) =>
{props.id}
; - `, - }, - { - code: ` - type AnotherComponentProps = { message: string; }; - export const AnotherComponent: React.FC = (props) =>
{props.message}
; - `, - }, - ], - invalid: [ - { - code: ` - type UnmatchedComponentProps = { id: number; }; - `, - errors: [ - { - messageId: "invalidPropsTypeName", - }, - ], - }, - { - code: ` - type UnmatchedComponentProps = { message: string; }; - const DifferentComponent: React.FC = (props) =>
{props.message}
; - `, - errors: [ - { - messageId: "invalidPropsTypeName", - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/effect-components.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/effect-components.spec.js deleted file mode 100644 index 922f6c9d8..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/effect-components.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const rule_tester_1 = require("@typescript-eslint/rule-tester"); -const effect_components_1 = __importDefault(require("../rules/effect-components")); -const ruleTester = new rule_tester_1.RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); -ruleTester.run("effect-components", effect_components_1.default, { - valid: [ - { - code: `const TestComponentEffect = () => <>;`, - }, - { - code: `const TestComponent = () =>
;`, - }, - { - code: `export const useUpdateEffect = () => null;`, - }, - { - code: `export const useUpdateEffect = () => <>;`, - }, - { - code: `const TestComponent = () => <>
;`, - }, - { - code: `const TestComponentEffect = () => null;`, - }, - { - code: `const TestComponentEffect = () => { - useEffect(() => {}, []); - - return null; - }`, - }, - { - code: `const TestComponentEffect = () => { - useEffect(() => {}, []); - - return <>; - }`, - }, - { - code: `const TestComponentEffect = () => { - useEffect(() => {}, []); - - return <>; - }`, - }, - { - code: `const TestComponentEffect = () => { - useEffect(() => {}, []); - - return null; - }`, - }, - ], - invalid: [ - { - code: "const TestComponent = () => <>;", - output: 'const TestComponentEffect = () => <>;', - errors: [ - { - messageId: "effectSuffix", - }, - ], - }, - { - code: "const TestComponentEffect = () => <>
;", - output: 'const TestComponent = () => <>
;', - errors: [ - { - messageId: "noEffectSuffix", - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/file.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/file.js deleted file mode 100644 index 603e5d606..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/file.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/matching-state-variable.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/matching-state-variable.spec.js deleted file mode 100644 index dfe4adfc3..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/matching-state-variable.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const rule_tester_1 = require("@typescript-eslint/rule-tester"); -const matching_state_variable_1 = __importDefault(require("../rules/matching-state-variable")); -const ruleTester = new rule_tester_1.RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); -ruleTester.run('matching-state-variable', matching_state_variable_1.default, { - valid: [ - { - code: 'const variable = useRecoilValue(variableState);', - }, - { - code: 'const [variable, setVariable] = useRecoilState(variableState);', - }, - ], - invalid: [ - { - code: 'const myValue = useRecoilValue(variableState);', - errors: [ - { - messageId: 'invalidVariableName', - }, - ], - output: 'const variable = useRecoilValue(variableState);', - }, - { - code: 'const [myValue, setMyValue] = useRecoilState(variableState);', - errors: [ - { - messageId: 'invalidVariableName', - }, - { - messageId: 'invalidSetterName', - }, - ], - output: 'const [variable, setVariable] = useRecoilState(variableState);', - }, - ], -}); diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/no-hardcoded-colors.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/no-hardcoded-colors.spec.js deleted file mode 100644 index 27315b711..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/no-hardcoded-colors.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const rule_tester_1 = require("@typescript-eslint/rule-tester"); -const no_hardcoded_colors_1 = __importDefault(require("../rules/no-hardcoded-colors")); -const ruleTester = new rule_tester_1.RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); -ruleTester.run("no-hardcoded-colors", no_hardcoded_colors_1.default, { - valid: [ - { - code: "const color = theme.background.secondary;", - }, - { - code: 'const color = "#000000";', - }, - ], - invalid: [ - { - code: 'const color = "rgb(154,205,50)";', - errors: [ - { - messageId: "hardcodedColor", - }, - ], - }, - { - code: 'const color = "#ADFF2F";', - errors: [ - { - messageId: "hardcodedColor", - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/react.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/react.js deleted file mode 100644 index 603e5d606..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/react.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/sort-css-properties-alphabetically.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/sort-css-properties-alphabetically.spec.js deleted file mode 100644 index 256110ca8..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/sort-css-properties-alphabetically.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const rule_tester_1 = require("@typescript-eslint/rule-tester"); -const sort_css_properties_alphabetically_1 = __importDefault(require("../rules/sort-css-properties-alphabetically")); -const ruleTester = new rule_tester_1.RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); -ruleTester.run("sort-css-properties-alphabetically", sort_css_properties_alphabetically_1.default, { - valid: [ - { - code: 'const style = css`color: red;`;', - filename: 'react.tsx', - }, - { - code: 'const style = styled.div`background-color: $bgColor;`;', - filename: 'react.tsx', - }, - ], - invalid: [ - { - code: 'const style = css`color: #FF0000;`;', - filename: 'react.tsx', - errors: [ - { - messageId: "sort-css-properties-alphabetically", - suggestions: [ - { - messageId: "sort-css-properties-alphabetically", - output: 'const style = css`color: red;`;', - }, - ], - }, - ], - }, - { - code: 'const style = styled.div`background-color: $bgColor; color: #FFFFFF;`;', - filename: 'react.tsx', - errors: [ - { - messageId: "sort-css-properties-alphabetically", - suggestions: [ - { - messageId: "sort-css-properties-alphabetically", - output: 'const style = styled.div`background-color: $bgColor; color: white;`;', - }, - ], - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-twenty-ts/dist/src/tests/styled-components-prefixed-with-styled.spec.js b/packages/eslint-plugin-twenty-ts/dist/src/tests/styled-components-prefixed-with-styled.spec.js deleted file mode 100644 index eb0e8e1ba..000000000 --- a/packages/eslint-plugin-twenty-ts/dist/src/tests/styled-components-prefixed-with-styled.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const rule_tester_1 = require("@typescript-eslint/rule-tester"); -const styled_components_prefixed_with_styled_1 = __importDefault(require("../rules/styled-components-prefixed-with-styled")); -const ruleTester = new rule_tester_1.RuleTester({ - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true, - }, - }, -}); -ruleTester.run("styled-components-prefixed-with-styled", styled_components_prefixed_with_styled_1.default, { - valid: [ - { - code: 'const StyledButton = styled.button``;', - filename: 'react.tsx', - }, - { - code: 'const StyledComponent = styled.div``;', - filename: 'react.tsx', - }, - ], - invalid: [ - { - code: 'const Button = styled.button``;', - filename: 'react.tsx', - errors: [ - { - messageId: 'noStyledPrefix', - }, - ], - }, - { - code: 'const Component = styled.div``;', - filename: 'react.tsx', - errors: [ - { - messageId: 'noStyledPrefix', - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-twenty/index.ts b/packages/eslint-plugin-twenty/index.ts index 20a91daf4..d4da697a7 100644 --- a/packages/eslint-plugin-twenty/index.ts +++ b/packages/eslint-plugin-twenty/index.ts @@ -6,5 +6,6 @@ module.exports = { "sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"), "styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"), "component-props-naming": require("./src/rules/component-props-naming"), + "no-state-useref": require("./src/rules/no-state-useref"), }, }; diff --git a/packages/eslint-plugin-twenty/src/rules/no-state-useref.ts b/packages/eslint-plugin-twenty/src/rules/no-state-useref.ts new file mode 100644 index 000000000..571eee31b --- /dev/null +++ b/packages/eslint-plugin-twenty/src/rules/no-state-useref.ts @@ -0,0 +1,68 @@ +import { ESLintUtils } from "@typescript-eslint/utils"; + +const createRule = ESLintUtils.RuleCreator(() => `https://docs.twenty.com`); + +const noStateUseRef = createRule({ + create: (context) => { + return { + CallExpression: (node) => { + if ( + node.callee.type !== "Identifier" || + node.callee.name !== "useRef" + ) { + return; + } + + if (!node.typeArguments || !node.typeArguments.params?.length) { + context.report({ + node, + messageId: "noStateUseRef", + }); + return; + } + const typeParam = node.typeArguments.params[0]; + + if (typeParam.type !== "TSTypeReference") { + context.report({ + node, + messageId: "noStateUseRef", + }); + return; + } + + if (typeParam.typeName.type !== "Identifier") { + context.report({ + node, + messageId: "noStateUseRef", + }); + return; + } + + if (!typeParam.typeName.name.match(/^(HTML.*Element|Element)$/)) { + context.report({ + node, + messageId: "test", + }); + } + }, + }; + }, + name: "no-state-useref", + meta: { + docs: { + description: "Don't use useRef for state management", + }, + messages: { + test: "test", + noStateUseRef: + "Don't use useRef for state management. See https://docs.twenty.com/developer/frontend/best-practices#do-not-use-useref-to-store-state for more details.", + }, + type: "suggestion", + schema: [], + }, + defaultOptions: [], +}); + +module.exports = noStateUseRef; + +export default noStateUseRef; diff --git a/packages/eslint-plugin-twenty/src/tests/no-state-useref.spec.ts b/packages/eslint-plugin-twenty/src/tests/no-state-useref.spec.ts new file mode 100644 index 000000000..7bac36d37 --- /dev/null +++ b/packages/eslint-plugin-twenty/src/tests/no-state-useref.spec.ts @@ -0,0 +1,51 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import noStateUseRefRule from "../rules/no-state-useref"; + +const ruleTester = new RuleTester({ + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: __dirname, + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run("no-state-useref", noStateUseRefRule, { + valid: [ + { + code: "const scrollableRef = useRef(null);", + }, + { + code: "const ref = useRef(null);", + }, + ], + invalid: [ + { + code: "const ref = useRef(null);", + errors: [ + { + messageId: "noStateUseRef", + }, + ], + }, + { + code: "const ref = useRef(null);", + errors: [ + { + messageId: "noStateUseRef", + }, + ], + }, + { + code: "const ref = useRef('');", + errors: [ + { + messageId: "noStateUseRef", + }, + ], + }, + ], +});