Migrate to ESLint 9 + eslint-config-expo, fix lint issues
This commit is contained in:
16 files changed
+521
-1339
No files matched your search
@@ -1,85 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
ignorePatterns: ['coverage/'],
|
||||
extends: [
|
||||
'@react-native',
|
||||
'airbnb-base',
|
||||
'airbnb-typescript/base',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:import/typescript',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
rules: {
|
||||
'class-methods-use-this': 'error', // FIXME: refactor
|
||||
'max-len': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-console': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-use-before-define': ['error', { functions: true, classes: true, variables: false }], // disable the rule for variables, but enable it for functions and classes
|
||||
|
||||
'react/prefer-stateless-function': 1,
|
||||
'react/prop-types': 'off',
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
|
||||
'react/sort-comp': 'off',
|
||||
'react-native/no-inline-styles': 'off', // FIXME: refactor
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: [
|
||||
'**/*.test.{js,jsx,ts,tsx}',
|
||||
'**/*.spec.{js,jsx,ts,tsx}',
|
||||
'**/__tests__/**',
|
||||
'src/testUtils.tsx',
|
||||
'jest.config.js',
|
||||
'jest.setup.js',
|
||||
'__mocks__/**',
|
||||
],
|
||||
},
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: { order: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['./**/*.js'],
|
||||
},
|
||||
{
|
||||
// Jest infrastructure is CommonJS by nature
|
||||
files: ['jest.config.js', 'jest.setup.js', '__mocks__/**'],
|
||||
rules: {
|
||||
'global-require': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'import/extensions': 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
singleQuote: true,
|
||||
};
|
||||
};
|
||||
@@ -86,8 +86,6 @@ history, code and binary blobs related to React Native functionality filtered ou
|
||||
- `react-native-simple-radio-button` (last published in 2018, and untyped — it accounts for
|
||||
5 of the errors `npm run check-types` reports); `@expo/ui` ships a native `Picker` in
|
||||
segmented/radio style, see the SDK 56 follow-ups below
|
||||
- migrate ESLint 8 + airbnb config (both EOL) to ESLint 9 flat config or
|
||||
[eslint-config-expo](https://docs.expo.dev/guides/using-eslint/)
|
||||
- follow-ups from the [Expo SDK 54 upgrade](https://expo.dev/changelog/sdk-54):
|
||||
- decide on Sentry: `@sentry/react-native` is installed but never initialized (see the
|
||||
commented-out init in `index.ts`), and its config plugin (now applied via `app.json`)
|
||||
@@ -129,10 +127,7 @@ history, code and binary blobs related to React Native functionality filtered ou
|
||||
`expo-build-properties` if it causes trouble
|
||||
- now on TypeScript 6: `baseUrl` is deprecated (gone in TS 7) and was removed from
|
||||
`tsconfig.json`, and TS 6 stopped auto-including `@types/*`, hence the explicit `types` field
|
||||
- make `npm run lint` usable again: ESLint only ignores `coverage/`, so it also walks the
|
||||
gitignored `android/` and `ios/` directories and reports ~126 errors from stale build output.
|
||||
Add them to `ignorePatterns`, alongside the ESLint 9 migration above
|
||||
- fix the 20 pre-existing `npm run check-types` errors (unchanged by the SDK upgrade)
|
||||
- fix the 19 pre-existing `npm run check-types` errors (unchanged by the SDK upgrade)
|
||||
- add [Apple Pay](https://docs.stripe.com/apple-pay?platform=react-native) for membership
|
||||
payments: either natively in the payment sheet (register the merchant ID
|
||||
`merchant.no.neuf.chateau` + Apple Pay certificate, set `merchantIdentifier` in the
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
const { defineConfig, globalIgnores } = require('eslint/config');
|
||||
const expoConfig = require('eslint-config-expo/flat');
|
||||
const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');
|
||||
|
||||
module.exports = defineConfig([
|
||||
globalIgnores(['android/*', 'ios/*', 'coverage/*', '.expo/*', 'dist/*', 'web-build/*']),
|
||||
expoConfig,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
rules: {
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: { order: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
|
||||
module.exports = (() => {
|
||||
|
||||
Generated
+457
-1203
File diff suppressed because it is too large.
Load diff
+2
-9
@@ -55,21 +55,14 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.9",
|
||||
"@expo/config-plugins": "~56.0.14",
|
||||
"@react-native/eslint-config": "^0.74.85",
|
||||
"@testing-library/react-native": "^13.3.3",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/react-native-simple-radio-button": "^2.7.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-config-expo": "~56.0.4",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~56.0.5",
|
||||
"prettier": "^3.3.3",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Text, StyleSheet, View } from 'react-native';
|
||||
import theme from '@/theme';
|
||||
import IconExclamationTriangle from '@assets/icons/exclamation-triangle.svg';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const FormErrors = ({ errors }: { errors: Record<string, any> }) => {
|
||||
if (!errors) {
|
||||
return [];
|
||||
|
||||
@@ -6,9 +6,8 @@ import TextField, { TextFieldProps } from '@/components/TextField';
|
||||
import theme from '@/theme';
|
||||
|
||||
type FormInputProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errors: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
control: any;
|
||||
name: string;
|
||||
label: string;
|
||||
|
||||
@@ -13,15 +13,23 @@ export type ToastProps = {
|
||||
|
||||
const Toast = ({ message, visible, position = 'bottom', autoDismiss, onDismiss }: ToastProps) => {
|
||||
const [mounted, setMounted] = useState(!!visible);
|
||||
const opacity = useRef(new Animated.Value(0)).current;
|
||||
const [opacity] = useState(() => new Animated.Value(0));
|
||||
|
||||
if (visible && !mounted) {
|
||||
setMounted(true);
|
||||
}
|
||||
|
||||
const onDismissRef = useRef(onDismiss);
|
||||
useEffect(() => {
|
||||
onDismissRef.current = onDismiss;
|
||||
}, [onDismiss]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
if (visible) {
|
||||
setMounted(true);
|
||||
Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }).start();
|
||||
if (autoDismiss && onDismiss) {
|
||||
timer = setTimeout(onDismiss, autoDismiss);
|
||||
if (autoDismiss) {
|
||||
timer = setTimeout(() => onDismissRef.current?.(), autoDismiss);
|
||||
}
|
||||
} else {
|
||||
Animated.timing(opacity, { toValue: 0, duration: 200, useNativeDriver: true }).start(({ finished }) => {
|
||||
@@ -35,8 +43,7 @@ const Toast = ({ message, visible, position = 'bottom', autoDismiss, onDismiss }
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [visible, autoDismiss]);
|
||||
}, [visible, autoDismiss, opacity]);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
|
||||
+10
-8
@@ -1,5 +1,5 @@
|
||||
import { useStripe } from '@stripe/stripe-react-native';
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import type { TypedUseSelectorHook } from 'react-redux';
|
||||
import { useDispatch, useSelector, useStore } from 'react-redux';
|
||||
@@ -8,15 +8,17 @@ import { logout } from './actions';
|
||||
import type { AppDispatch, AppStore, RootState } from './reduxStore';
|
||||
|
||||
export function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
const [previous, setPrevious] = useState<T | undefined>(undefined);
|
||||
const [current, setCurrent] = useState(value);
|
||||
|
||||
// Store current value in ref
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]); // Only re-run if value changes
|
||||
// Adjust state during render instead of tracking the previous render's value in a ref,
|
||||
// see https://react.dev/reference/react/useState#storing-information-from-previous-renders
|
||||
if (value !== current) {
|
||||
setPrevious(current);
|
||||
setCurrent(value);
|
||||
}
|
||||
|
||||
// Return previous value (happens before update in useEffect above)
|
||||
return ref.current;
|
||||
return previous;
|
||||
}
|
||||
|
||||
/** Handles deep links. Currently only used by stripe */
|
||||
|
||||
@@ -99,13 +99,14 @@ const PurchaseButton = ({
|
||||
];
|
||||
|
||||
const defaultValue = options[1];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => onSelect(defaultValue.value), []);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
setMembershipTypeSlug(value);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => onSelect(defaultValue.value), []);
|
||||
|
||||
if (user.is_member) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -29,9 +29,14 @@ const useProof = ({ fetchUser }: UseProofProps) => {
|
||||
const { scheduleMembershipExpiryNotification, hasPermission } = useNotifications();
|
||||
const [paymentLoading, setPaymentLoading] = useState(false);
|
||||
const [membershipTypeSlug, setMembershipTypeSlug] = useState('standard');
|
||||
const [membershipPrice, setMembershipPrice] = useState(25000); // WARNING: Hardcoded due to state not being loaded initially. Should probably be fixed.
|
||||
const [toast, setToast] = useState<ToastProps | null>(null);
|
||||
|
||||
// WARNING: Hardcoded fallback price for when the query hasn't loaded yet. Should probably be fixed.
|
||||
const membershipPrice =
|
||||
data?.membershipTypes?.find(
|
||||
(membershipTypeOption: { slug: string; price: number }) => membershipTypeOption.slug === membershipTypeSlug,
|
||||
)?.price ?? 25000;
|
||||
|
||||
const openPaymentSheet = async () => {
|
||||
try {
|
||||
await initializePaymentSheet();
|
||||
@@ -85,15 +90,6 @@ const useProof = ({ fetchUser }: UseProofProps) => {
|
||||
}
|
||||
}, [membershipType, membershipEndDate, fetchUser, hasPermission, scheduleMembershipExpiryNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const membershipTypeOption of data?.membershipTypes ?? []) {
|
||||
if (membershipTypeOption.slug === membershipTypeSlug) {
|
||||
setMembershipPrice(membershipTypeOption.price);
|
||||
}
|
||||
}
|
||||
}, [membershipTypeSlug, data?.membershipTypes]);
|
||||
|
||||
return {
|
||||
isLoadingMembershipType: loading,
|
||||
membershipPrice: membershipPrice ? membershipPrice / 100 : 0,
|
||||
|
||||
+2
-3
@@ -44,7 +44,7 @@ const initialState: RootState = {
|
||||
};
|
||||
|
||||
// FIXME: refactor using redux slices
|
||||
// eslint-disable-next-line @typescript-eslint/default-param-last, @typescript-eslint/no-explicit-any
|
||||
|
||||
export default function appReducers(state = initialState, action: any) {
|
||||
switch (action.type) {
|
||||
/* Login */
|
||||
@@ -78,9 +78,8 @@ export default function appReducers(state = initialState, action: any) {
|
||||
return { ...state, isRegisteringUser: false, registerError: action.registerError };
|
||||
|
||||
case REGISTER_USER_SUCCESS:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const userData = action.data;
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
|
||||
const token = action.data.auth_token;
|
||||
delete userData.auth_token;
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const testUser: UserType = {
|
||||
|
||||
interface RenderWithProvidersOptions {
|
||||
preloadedState?: Partial<AppState>;
|
||||
apolloMocks?: ReadonlyArray<MockedResponse>;
|
||||
apolloMocks?: readonly MockedResponse[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,8 +21,9 @@ export type HomeTabParamList = {
|
||||
};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace ReactNavigation {
|
||||
// https://reactnavigation.org/docs/typescript/#specifying-default-types-for-usenavigation-link-ref-etc
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
interface RootParamList extends HomeTabParamList {}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user