From d3737cea978e2f681f479751f7dcc40296ef5abb Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 22 Jan 2023 16:01:15 +0800 Subject: [PATCH 1/4] simplify eslint configs + add CI job --- .github/workflows/ci.yml | 24 ++++++ apps/landing/.eslintrc.js | 5 +- apps/landing/package.json | 2 +- apps/mobile/.eslintrc.js | 2 +- apps/mobile/package.json | 2 +- package.json | 3 +- packages/client/.eslintrc.js | 2 +- packages/client/package.json | 2 +- packages/client/src/index.ts | 1 + packages/config/eslint-react-native.js | 59 --------------- .../{eslint-react.js => eslint/base.js} | 13 ++-- packages/config/eslint/reactNative.js | 27 +++++++ packages/config/eslint/web.js | 8 ++ packages/config/index.js | 3 + packages/config/package.json | 8 +- packages/interface/.eslintrc.js | 2 +- packages/interface/package.json | 3 +- .../components/dialog/AddLocationDialog.tsx | 2 +- .../components/dialog/BackupRestoreDialog.tsx | 2 +- .../components/dialog/CreateLibraryDialog.tsx | 2 +- .../src/components/dialog/KeyViewerDialog.tsx | 2 +- .../dialog/MasterPasswordChangeDialog.tsx | 2 +- .../src/components/explorer/Explorer.tsx | 74 +++++++++---------- .../components/explorer/VirtualizedList.tsx | 27 ++----- .../settings/node/LibrariesSettings.tsx | 2 +- packages/interface/tsconfig.json | 1 + packages/ui/.eslintrc.js | 7 ++ packages/ui/package.json | 1 + packages/ui/src/Dialog.tsx | 2 +- packages/ui/src/Dropdown.tsx | 2 +- packages/ui/src/utils.tsx | 2 +- pnpm-lock.yaml | 31 ++++++-- 32 files changed, 174 insertions(+), 151 deletions(-) delete mode 100644 packages/config/eslint-react-native.js rename packages/config/{eslint-react.js => eslint/base.js} (80%) create mode 100644 packages/config/eslint/reactNative.js create mode 100644 packages/config/eslint/web.js create mode 100644 packages/config/index.js create mode 100644 packages/ui/.eslintrc.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67a28e164..fec3e3dee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,30 @@ jobs: - name: Perform typechecks run: pnpm typecheck + eslint: + name: TypeScript + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install pnpm + uses: pnpm/action-setup@v2.2.2 + with: + version: 7.x.x + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'pnpm' + + - name: Install pnpm dependencies + run: pnpm i --frozen-lockfile + + - name: Perform typechecks + run: pnpm lint + rustfmt: name: rustfmt runs-on: ubuntu-latest diff --git a/apps/landing/.eslintrc.js b/apps/landing/.eslintrc.js index 01aae4aa3..615ceae2b 100644 --- a/apps/landing/.eslintrc.js +++ b/apps/landing/.eslintrc.js @@ -1,8 +1,7 @@ module.exports = { - ...require('@sd/config/eslint-react.js'), + extends: [require.resolve('@sd/config/eslint/web.js')], parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.json' - }, - ignorePatterns: ['**/*.js', '**/*.json', 'node_modules', 'public', 'dist', 'vite.config.ts'] + } }; diff --git a/apps/landing/package.json b/apps/landing/package.json index 7a472f641..39b0b7c97 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -7,7 +7,7 @@ "build": "vite build", "server": "ts-node ./server", "server:prod": "cross-env NODE_ENV=production ts-node ./server", - "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit", + "lint": "eslint src", "typecheck": "tsc -b" }, "dependencies": { diff --git a/apps/mobile/.eslintrc.js b/apps/mobile/.eslintrc.js index 11305df15..cd041390b 100644 --- a/apps/mobile/.eslintrc.js +++ b/apps/mobile/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - ...require('@sd/config/eslint-react-native.js'), + extends: [require.resolve('@sd/config/eslint/reactNative.js')], parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.json' diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 56aacbf7c..424e2f782 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -10,7 +10,7 @@ "ios": "expo run:ios", "xcode": "open ios/spacedrive.xcworkspace", "android-studio": "open -a '/Applications/Android Studio.app' ./android", - "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit", + "lint": "eslint src", "postinstall": "node scripts/postinstall.js", "typecheck": "tsc -b" }, diff --git a/package.json b/package.json index d99727a08..5cdb88169 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "client": "pnpm --filter @sd/client -- ", "prisma": "cd core && cargo prisma", "codegen": "cargo test -p sd-core api::tests::test_and_export_rspc_bindings -- --exact", - "typecheck": "turbo run typecheck" + "typecheck": "turbo run typecheck", + "lint": "turbo run lint" }, "pnpm": { "overrides": { diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index b3e0f403d..615ceae2b 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - ...require('@sd/config/eslint-react.js'), + extends: [require.resolve('@sd/config/eslint/web.js')], parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.json' diff --git a/packages/client/package.json b/packages/client/package.json index 10dc94712..cb36007cd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -8,7 +8,7 @@ ], "scripts": { "test": "jest", - "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit", + "lint": "eslint src", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "typecheck": "tsc -b", "build": "tsc" diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6628c16f5..1f1b0fc64 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,4 +1,5 @@ declare global { + // eslint-disable-next-line var isDev: boolean; } diff --git a/packages/config/eslint-react-native.js b/packages/config/eslint-react-native.js deleted file mode 100644 index f8009642e..000000000 --- a/packages/config/eslint-react-native.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports = { - env: { - 'react-native/react-native': true - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaFeatures: { - jsx: true - }, - ecmaVersion: 12, - sourceType: 'module' - }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - 'plugin:@typescript-eslint/recommended' - ], - plugins: ['react', 'react-native'], - rules: { - 'react/display-name': 'off', - 'react/prop-types': 'off', - 'react/no-unescaped-entities': 'off', - 'react/react-in-jsx-scope': 'off', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - 'no-control-regex': 'off', - 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'], - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'react-native', - importNames: ['SafeAreaView'], - message: 'Import SafeAreaView from react-native-safe-area-context instead' - }, - { - name: 'react-native', - importNames: ['Button'], - message: 'Import Button from ~/components instead.' - } - ] - } - ] - }, - ignorePatterns: ['**/*.js', '**/*.json', 'node_modules', 'android', 'ios', '.expo'], - settings: { - react: { - version: 'detect' - } - } -}; diff --git a/packages/config/eslint-react.js b/packages/config/eslint/base.js similarity index 80% rename from packages/config/eslint-react.js rename to packages/config/eslint/base.js index 5a232b963..d82d3192a 100644 --- a/packages/config/eslint-react.js +++ b/packages/config/eslint/base.js @@ -1,8 +1,4 @@ module.exports = { - env: { - browser: true, - node: true - }, parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { @@ -16,7 +12,8 @@ module.exports = { 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:@typescript-eslint/recommended', - 'prettier' + 'prettier', + 'turbo' ], plugins: ['react'], rules: { @@ -24,7 +21,7 @@ module.exports = { 'react/prop-types': 'off', 'react/no-unescaped-entities': 'off', 'react/react-in-jsx-scope': 'off', - 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/rules-of-hooks': 'warn', 'react-hooks/exhaustive-deps': 'warn', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/ban-ts-comment': 'off', @@ -32,10 +29,12 @@ module.exports = { '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-empty-function': 'off', 'no-control-regex': 'off', 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'] }, - ignorePatterns: ['**/*.js', '**/*.json', 'node_modules'], + ignorePatterns: ['dist', '**/*.js', '**/*.json', 'node_modules'], settings: { react: { version: 'detect' diff --git a/packages/config/eslint/reactNative.js b/packages/config/eslint/reactNative.js new file mode 100644 index 000000000..9350c1333 --- /dev/null +++ b/packages/config/eslint/reactNative.js @@ -0,0 +1,27 @@ +module.exports = { + extends: [require.resolve('./base.js')], + env: { + 'react-native/react-native': true + }, + plugins: ['react-native'], + ignorePatterns: ['android', 'ios', '.expo'], + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react-native', + importNames: ['SafeAreaView'], + message: 'Import SafeAreaView from react-native-safe-area-context instead' + } + // { + // name: 'react-native', + // importNames: ['Button'], + // message: 'Import Button from ~/components instead.' + // } + ] + } + ] + } +}; diff --git a/packages/config/eslint/web.js b/packages/config/eslint/web.js new file mode 100644 index 000000000..ef780ffc1 --- /dev/null +++ b/packages/config/eslint/web.js @@ -0,0 +1,8 @@ +module.exports = { + extends: [require.resolve('./base.js')], + ignorePatterns: ['public', 'vite.config.ts'], + env: { + browser: true, + node: true + } +}; diff --git a/packages/config/index.js b/packages/config/index.js new file mode 100644 index 000000000..524af3ba4 --- /dev/null +++ b/packages/config/index.js @@ -0,0 +1,3 @@ +module.exports = { + vite: require('./vite') +}; diff --git a/packages/config/package.json b/packages/config/package.json index d2dd65834..2fee6855e 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -3,16 +3,18 @@ "version": "0.0.0", "license": "GPL-3.0-only", "exports": { - "./vite": "./vite/index.js" + "./*": "./*", + "./vite": "./vite.js" }, "files": [ "eslint-react.js" ], "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.39.0", - "@typescript-eslint/parser": "^5.39.0", + "@typescript-eslint/eslint-plugin": "^5.48.2", + "@typescript-eslint/parser": "^5.48.2", "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", + "eslint-config-turbo": "^0.0.7", "eslint-plugin-react": "^7.31.8", "eslint-plugin-react-hooks": "^4.6.0" } diff --git a/packages/interface/.eslintrc.js b/packages/interface/.eslintrc.js index b3e0f403d..615ceae2b 100644 --- a/packages/interface/.eslintrc.js +++ b/packages/interface/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - ...require('@sd/config/eslint-react.js'), + extends: [require.resolve('@sd/config/eslint/web.js')], parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.json' diff --git a/packages/interface/package.json b/packages/interface/package.json index 630002813..409893235 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -13,7 +13,7 @@ }, "scripts": { "icons": "./scripts/generateSvgImports.mjs", - "lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit", + "lint": "eslint src", "typecheck": "tsc -b", "build": "tsc" }, @@ -65,6 +65,7 @@ }, "devDependencies": { "@sd/config": "workspace:*", + "eslint-config-sd": "workspace:*", "@types/babel-core": "^6.25.7", "@types/byte-size": "^8.1.0", "@types/loadable__component": "^5.13.4", diff --git a/packages/interface/src/components/dialog/AddLocationDialog.tsx b/packages/interface/src/components/dialog/AddLocationDialog.tsx index ad02c59a5..46a6a14b7 100644 --- a/packages/interface/src/components/dialog/AddLocationDialog.tsx +++ b/packages/interface/src/components/dialog/AddLocationDialog.tsx @@ -4,7 +4,7 @@ import { Input, useZodForm, z } from '@sd/ui/src/forms'; const schema = z.object({ path: z.string() }); -interface Props extends UseDialogProps {} +type Props = UseDialogProps; export default function AddLocationDialog(props: Props) { const dialog = useDialog(props); diff --git a/packages/interface/src/components/dialog/BackupRestoreDialog.tsx b/packages/interface/src/components/dialog/BackupRestoreDialog.tsx index a39defc3e..32f11179e 100644 --- a/packages/interface/src/components/dialog/BackupRestoreDialog.tsx +++ b/packages/interface/src/components/dialog/BackupRestoreDialog.tsx @@ -14,7 +14,7 @@ const schema = z.object({ filePath: z.string() }); -export interface BackupRestorationDialogProps extends UseDialogProps {} +export type BackupRestorationDialogProps = UseDialogProps; export const BackupRestoreDialog = (props: BackupRestorationDialogProps) => { const platform = usePlatform(); diff --git a/packages/interface/src/components/dialog/CreateLibraryDialog.tsx b/packages/interface/src/components/dialog/CreateLibraryDialog.tsx index e07eecb50..1310409b3 100644 --- a/packages/interface/src/components/dialog/CreateLibraryDialog.tsx +++ b/packages/interface/src/components/dialog/CreateLibraryDialog.tsx @@ -20,7 +20,7 @@ const schema = z.object({ hashing_algorithm: z.string() }); -interface Props extends UseDialogProps {} +type Props = UseDialogProps; export default function CreateLibraryDialog(props: Props) { const dialog = useDialog(props); diff --git a/packages/interface/src/components/dialog/KeyViewerDialog.tsx b/packages/interface/src/components/dialog/KeyViewerDialog.tsx index e5b8615bd..8d5821014 100644 --- a/packages/interface/src/components/dialog/KeyViewerDialog.tsx +++ b/packages/interface/src/components/dialog/KeyViewerDialog.tsx @@ -7,7 +7,7 @@ import { useZodForm, z } from '@sd/ui/src/forms'; import { getHashingAlgorithmString } from '~/screens/settings/library/KeysSetting'; import { SelectOptionKeyList } from '../key/KeyList'; -interface KeyViewerDialogProps extends UseDialogProps {} +type KeyViewerDialogProps = UseDialogProps; export const KeyUpdater = (props: { uuid: string; diff --git a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx index 17c1b102f..9c1e7c483 100644 --- a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx +++ b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx @@ -9,7 +9,7 @@ import { showAlertDialog } from '~/util/dialog'; import { generatePassword } from '../key/KeyMounter'; import { PasswordMeter } from '../key/PasswordMeter'; -export interface MasterPasswordChangeDialogProps extends UseDialogProps {} +export type MasterPasswordChangeDialogProps = UseDialogProps; const schema = z.object({ masterPassword: z.string(), diff --git a/packages/interface/src/components/explorer/Explorer.tsx b/packages/interface/src/components/explorer/Explorer.tsx index 2b476e791..ecad2b6fc 100644 --- a/packages/interface/src/components/explorer/Explorer.tsx +++ b/packages/interface/src/components/explorer/Explorer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { ExplorerData, rspc, useCurrentLibrary } from '@sd/client'; import { useExplorerStore } from '~/hooks/useExplorerStore'; import { Inspector } from '../explorer/Inspector'; @@ -32,50 +32,50 @@ export default function Explorer(props: Props) { } }); - return ( - <> -
- -
- + const onScroll = useCallback((y: number) => { + setScrollSegments((old) => { + return { + ...old, + mainList: y + }; + }); + }, []); + + return ( +
+ +
+ + +
+ {props.data && ( + + )} + {expStore.showInspector && ( +
+ { + const y = (e.target as HTMLElement).scrollTop; -
- {props.data && ( - { setScrollSegments((old) => { return { ...old, - mainList: y + inspector: y }; }); }} + key={props.data?.items[expStore.selectedRowIndex]?.id} + data={props.data?.items[expStore.selectedRowIndex]} /> - )} - {expStore.showInspector && ( -
- { - const y = (e.target as HTMLElement).scrollTop; - - setScrollSegments((old) => { - return { - ...old, - inspector: y - }; - }); - }} - key={props.data?.items[expStore.selectedRowIndex]?.id} - data={props.data?.items[expStore.selectedRowIndex]} - /> -
- )} -
+
+ )}
- -
- +
+ +
); } diff --git a/packages/interface/src/components/explorer/VirtualizedList.tsx b/packages/interface/src/components/explorer/VirtualizedList.tsx index 4bab217d9..6fa443aa4 100644 --- a/packages/interface/src/components/explorer/VirtualizedList.tsx +++ b/packages/interface/src/components/explorer/VirtualizedList.tsx @@ -1,5 +1,5 @@ import { useVirtualizer } from '@tanstack/react-virtual'; -import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useKey, useOnWindowResize } from 'rooks'; import { ExplorerContext, ExplorerItem } from '@sd/client'; @@ -17,7 +17,7 @@ interface Props { onScroll?: (posY: number) => void; } -export const VirtualizedList: React.FC = ({ data, context, onScroll }) => { +export const VirtualizedList = memo(({ data, context, onScroll }: Props) => { const scrollRef = useRef(null); const innerRef = useRef(null); @@ -56,7 +56,7 @@ export const VirtualizedList: React.FC = ({ data, context, onScroll }) => el.addEventListener('scroll', onElementScroll); return () => el.removeEventListener('scroll', onElementScroll); - }, [scrollRef, onScroll]); + }, [onScroll]); const rowVirtualizer = useVirtualizer({ count: amountOfRows, @@ -169,7 +169,7 @@ export const VirtualizedList: React.FC = ({ data, context, onScroll }) =>
); -}; +}); interface WrappedItemProps { item: ExplorerItem; @@ -179,7 +179,7 @@ interface WrappedItemProps { } // Wrap either list item or grid item with click logic as it is the same for both -const WrappedItem: React.FC = ({ item, index, isSelected, kind }) => { +const WrappedItem = memo(({ item, index, isSelected, kind }: WrappedItemProps) => { const [_, setSearchParams] = useSearchParams(); const onDoubleClick = useCallback(() => { @@ -191,6 +191,7 @@ const WrappedItem: React.FC = ({ item, index, isSelected, kind }, [isSelected, index]); const ItemComponent = kind === 'list' ? FileRow : FileItem; + return ( = ({ item, index, isSelected, kind selected={isSelected} /> ); - - // // Memorize the item so that it doesn't get re-rendered when the selection changes - // return useMemo(() => { - // const ItemComponent = kind === 'list' ? FileRow : FileItem; - // return ( - // - // ); - // }, [item, index, isSelected]); -}; +}); diff --git a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx index 3378b3340..e323ee1a9 100644 --- a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx +++ b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx @@ -22,7 +22,7 @@ function LibraryListItem(props: { library: LibraryConfigWrapped; current: boolea

{props.library.uuid}

-