Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable no-unsafe-call eslint rule #42743

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8f019fd
enable no-unsafe-call rule
bgawkuc May 22, 2024
df002bc
fix all text input ref errors
bgawkuc May 27, 2024
03e4302
wip
bgawkuc May 28, 2024
6cd6194
move isAnimatedTextInputRef function to common file
bgawkuc May 28, 2024
ae1804a
simplify changes
bgawkuc May 28, 2024
b82c7ed
Merge branch 'main' of https://github.com/software-mansion-labs/expen…
bgawkuc May 28, 2024
48de655
wip
bgawkuc May 28, 2024
238abd6
rename text input function & change performance types
bgawkuc May 28, 2024
9fc260d
wip
bgawkuc May 28, 2024
6ae0cca
wip
bgawkuc May 28, 2024
2cbb545
fix tests errors
bgawkuc May 28, 2024
8bdd994
define types for NativeModules
bgawkuc Jun 4, 2024
2b2ab09
add JSDoc to isAnimatedTextInputFocused
bgawkuc Jun 4, 2024
55a69fd
fix webpack plugin props
bgawkuc Jun 4, 2024
2a983c9
Merge branch 'main' of https://github.com/software-mansion-labs/expen…
bgawkuc Jun 5, 2024
0723e43
fix ts error
bgawkuc Jun 5, 2024
42f1f6d
review adjustments
bgawkuc Jun 5, 2024
75b085a
fix storybook
bgawkuc Jun 5, 2024
729aaa1
rename preload webpack plugin type
bgawkuc Jun 5, 2024
fbaa95f
add create download queue types
bgawkuc Jun 6, 2024
355deae
change parse run opts
bgawkuc Jun 7, 2024
7d8dbc4
rename text input focused function and mouse event type
bgawkuc Jun 10, 2024
4541ce2
move CreateDownloadQueue type
bgawkuc Jun 10, 2024
cbe212e
remove redundant check
bgawkuc Jun 10, 2024
90552a4
add module declaration for react-native-performance
bgawkuc Jun 10, 2024
a702e5f
Merge branch 'main' of https://github.com/software-mansion-labs/expen…
bgawkuc Jun 11, 2024
649529e
Merge branch 'main' into ts/enable-no-unsafe-call
bgawkuc Jun 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ module.exports = {
__DEV__: 'readonly',
},
rules: {
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import * as core from '@actions/core';
import fs from 'fs';

type RegressionEntryProps = {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
metadata?: {
creationDate: string;
};
name: string;
meanDuration: number;
meanCount: number;
};

const run = () => {
// Prefix path to the graphite metric
const GRAPHITE_PATH = 'reassure';
Expand All @@ -24,11 +33,11 @@ const run = () => {
}

try {
const current = JSON.parse(entry);
const current: RegressionEntryProps = JSON.parse(entry);

// Extract timestamp, Graphite accepts timestamp in seconds
if (current.metadata?.creationDate) {
timestamp = Math.floor(new Date(current.metadata.creationDate as string).getTime() / 1000);
timestamp = Math.floor(new Date(current.metadata.creationDate).getTime() / 1000);
}

if (current.name && current.meanDuration && current.meanCount && timestamp) {
Expand Down
4 changes: 2 additions & 2 deletions .github/libs/GitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ function getCommitHistoryAsJSON(fromTag: string, toTag: string): Promise<CommitT
console.log(`Running command: git ${args.join(' ')}`);
const spawnedProcess = spawn('git', args);
spawnedProcess.on('message', console.log);
spawnedProcess.stdout.on('data', (chunk) => {
spawnedProcess.stdout.on('data', (chunk: Buffer) => {
console.log(chunk.toString());
stdout += chunk.toString();
});
spawnedProcess.stderr.on('data', (chunk) => {
spawnedProcess.stderr.on('data', (chunk: Buffer) => {
console.error(chunk.toString());
stderr += chunk.toString();
});
Expand Down
9 changes: 6 additions & 3 deletions .storybook/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* eslint-disable no-param-reassign */

/* eslint-disable @typescript-eslint/naming-convention */
import type Environment from 'config/webpack/types';
import dotenv from 'dotenv';
import path from 'path';
import {DefinePlugin} from 'webpack';
Expand All @@ -18,6 +19,8 @@ type CustomWebpackConfig = {
};
};

type CustomWebpackFunctionProps = ({file, platform}: Environment) => CustomWebpackConfig;

let envFile: string;
switch (process.env.ENV) {
case 'production':
Expand All @@ -31,9 +34,9 @@ switch (process.env.ENV) {
}

const env = dotenv.config({path: path.resolve(__dirname, `../${envFile}`)});
const custom: CustomWebpackConfig = require('../config/webpack/webpack.common').default({
envFile,
});
const customFunction: CustomWebpackFunctionProps = require('../config/webpack/webpack.common');
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved

const custom: CustomWebpackConfig = customFunction({file: envFile});

const webpackConfig = ({config}: {config: Configuration}) => {
if (!config.resolve) {
Expand Down
17 changes: 15 additions & 2 deletions config/webpack/webpack.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ import dotenv from 'dotenv';
import fs from 'fs';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import path from 'path';
import type {Configuration} from 'webpack';
import type {Compiler, Configuration} from 'webpack';
import {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} from 'webpack';
import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';
import CustomVersionFilePlugin from './CustomVersionFilePlugin';
import type Environment from './types';

// importing anything from @vue/preload-webpack-plugin causes an error
type Options = {
rel: string;
as: string;
fileWhitelist: RegExp[];
include: string;
};

type PreloadWebpackPluginProps = {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
new (options?: Options): PreloadWebpackPluginProps;
apply: (compiler: Compiler) => void;
};

// require is necessary, there are no types for this package and the declaration file can't be seen by the build process which causes an error.
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
const PreloadWebpackPlugin: PreloadWebpackPluginProps = require('@vue/preload-webpack-plugin');
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved

const includeModules = [
'react-native-animatable',
Expand Down
4 changes: 2 additions & 2 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jest.mock('react-native-onyx/dist/storage', () => mockStorage);
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');

// Turn off the console logs for timing events. They are not relevant for unit tests and create a lot of noise
jest.spyOn(console, 'debug').mockImplementation((...params) => {
if (params[0].indexOf('Timing:') === 0) {
jest.spyOn(console, 'debug').mockImplementation((...params: string[]) => {
if (params[0].startsWith('Timing:')) {
return;
}

Expand Down
7 changes: 4 additions & 3 deletions src/components/AmountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import CONST from '@src/CONST';
import BigNumberPad from './BigNumberPad';
import FormHelpMessage from './FormHelpMessage';
import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused';
import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types';
import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol';
import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types';
Expand Down Expand Up @@ -94,7 +95,7 @@ function AmountForm(
if (!textInput.current) {
return;
}
if (!textInput.current.isFocused()) {
if (!isAnimatedTextInputFocused(textInput)) {
textInput.current.focus();
}
};
Expand Down Expand Up @@ -143,7 +144,7 @@ function AmountForm(
*/
const updateAmountNumberPad = useCallback(
(key: string) => {
if (shouldUpdateSelection && !textInput.current?.isFocused()) {
if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) {
textInput.current?.focus();
}
// Backspace button is pressed
Expand All @@ -168,7 +169,7 @@ function AmountForm(
*/
const updateLongPressHandlerState = useCallback((value: boolean) => {
setShouldUpdateSelection(!value);
if (!value && !textInput.current?.isFocused()) {
if (!value && !isAnimatedTextInputFocused(textInput)) {
textInput.current?.focus();
}
}, []);
Expand Down
7 changes: 4 additions & 3 deletions src/components/EmojiPicker/EmojiPickerMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {scrollTo} from 'react-native-reanimated';
import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused';
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -86,7 +87,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r
}

// If the input is not focused and the new index is out of range, focus the input
if (newIndex < 0 && !searchInputRef.current?.isFocused() && shouldFocusInputOnScreenFocus) {
if (newIndex < 0 && !isAnimatedTextInputFocused(searchInputRef) && shouldFocusInputOnScreenFocus) {
searchInputRef.current?.focus();
}
},
Expand Down Expand Up @@ -165,12 +166,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r
// Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input
// is not focused, so that the navigation and tab cycling can be done using the keyboard without
// interfering with the input behaviour.
if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !searchInputRef.current.isFocused())) {
if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef))) {
setIsUsingKeyboardMovement(true);
}

// We allow typing in the search box if any key is pressed apart from Arrow keys.
if (searchInputRef.current && !searchInputRef.current.isFocused() && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) {
if (searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) {
searchInputRef.current.focus();
}
},
Expand Down
13 changes: 8 additions & 5 deletions src/components/Hoverable/ActiveHoverable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type HoverableProps from './types';

type ActiveHoverableProps = Omit<HoverableProps, 'disabled'>;

type OnMouseEventProps = (e: MouseEvent) => void;
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved

function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreezeCapture, children}: ActiveHoverableProps, outerRef: Ref<HTMLElement>) {
const [isHovered, setIsHovered] = useState(false);

Expand Down Expand Up @@ -98,9 +100,10 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreez

const child = useMemo(() => getReturnValue(children, !isScrollingRef.current && isHovered), [children, isHovered]);

const childOnMouseEnter = child.props.onMouseEnter;
const childOnMouseLeave = child.props.onMouseLeave;
const childOnMouseMove = child.props.onMouseMove;
const childOnMouseEnter: OnMouseEventProps = child.props.onMouseEnter;
const childOnMouseLeave: OnMouseEventProps = child.props.onMouseLeave;
const childOnMouseMove: OnMouseEventProps = child.props.onMouseMove;
const childOnBlur: OnMouseEventProps = child.props.onBlur;

const hoverAndForwardOnMouseEnter = useCallback(
(e: MouseEvent) => {
Expand All @@ -127,9 +130,9 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreez
setIsHovered(false);
}

child.props.onBlur?.(event);
childOnBlur?.(event);
},
[child.props],
[childOnBlur],
);

const handleAndForwardOnMouseMove = useCallback(
Expand Down
3 changes: 2 additions & 1 deletion src/components/MoneyRequestAmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils';
import getOperatingSystem from '@libs/getOperatingSystem';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import CONST from '@src/CONST';
import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused';
import type {BaseTextInputRef} from './TextInput/BaseTextInput/types';
import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol';

Expand Down Expand Up @@ -196,7 +197,7 @@ function MoneyRequestAmountInput(
}));

useEffect(() => {
if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) {
if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputFocused(textInput))) ?? shouldKeepUserInput) {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
return;
}
const frontendAmount = onFormatAmount(amount, currency);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type {AnimatedTextInputRef} from '@components/RNTextInput';
import type {BaseTextInputRef} from './types';

/** Checks that text input has the isFocused method and is focused. */
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
export default function isAnimatedTextInputFocused(textInput: React.MutableRefObject<BaseTextInputRef | null>): boolean | null {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused();
}
3 changes: 2 additions & 1 deletion src/components/Tooltip/BaseTooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ function Tooltip(
(e: MouseEvent) => {
updateTargetAndMousePosition(e);
if (React.isValidElement(children)) {
children.props.onMouseEnter?.(e);
const onMouseEnter: (e: MouseEvent) => void | undefined = children.props.onMouseEnter;
onMouseEnter?.(e);
}
},
[children, updateTargetAndMousePosition],
Expand Down
10 changes: 8 additions & 2 deletions src/components/Tooltip/PopoverAnchorTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ function PopoverAnchorTooltip({shouldRender = true, children, ...props}: Tooltip

const isPopoverRelatedToTooltipOpen = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/dot-notation
const tooltipNode = tooltipRef.current?.['_childNode'] ?? null;
if (isOpen && popover?.anchorRef?.current && tooltipNode && (tooltipNode.contains(popover.anchorRef.current) || tooltipNode === popover.anchorRef.current)) {
const tooltipNode: Node | null = tooltipRef.current?.['_childNode'] ?? null;

if (
isOpen &&
popover?.anchorRef?.current &&
tooltipNode &&
((popover.anchorRef.current instanceof Node && tooltipNode.contains(popover.anchorRef.current)) || tooltipNode === popover.anchorRef.current)
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
) {
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/WalletStatementModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function WalletStatementModal({statementPageURL, session}: WalletStatementProps)
/**
* Handles in-app navigation for iframe links
*/
const navigate = (event: MessageEvent) => {
const navigate = (event: MessageEvent<{url: string; type: string}>) => {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
if (!event.data?.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Environment/betaChecker/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as AppUpdate from '@libs/actions/AppUpdate';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import pkg from '../../../../package.json';
import type IsBetaBuild from './types';
import type {IsBetaBuild} from './types';

let isLastSavedBeta = false;
Onyx.connect({
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Environment/betaChecker/index.ios.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {NativeModules} from 'react-native';
import type IsBetaBuild from './types';
import type {IsBetaBuild} from './types';

/**
* Check to see if the build is staging (TestFlight) or production
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Environment/betaChecker/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type IsBetaBuild from './types';
import type {IsBetaBuild} from './types';

/**
* There's no beta build in non native
Expand Down
6 changes: 5 additions & 1 deletion src/libs/Environment/betaChecker/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
type IsBetaBuild = Promise<boolean>;

export default IsBetaBuild;
type EnvironmentCheckerModule = {
isBeta: () => IsBetaBuild;
};

export type {IsBetaBuild, EnvironmentCheckerModule};
10 changes: 8 additions & 2 deletions src/libs/Performance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import isObject from 'lodash/isObject';
import lodashTransform from 'lodash/transform';
import React, {forwardRef, Profiler} from 'react';
import {Alert, InteractionManager} from 'react-native';
import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance as RNPerformance} from 'react-native-performance';
import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance as RNPerformance, PerformanceObserver as RNPerformanceObserver} from 'react-native-performance';
import type {PerformanceObserverEntryList} from 'react-native-performance/lib/typescript/performance-observer';
import CONST from '@src/CONST';
import isE2ETestSession from './E2E/isE2ETestSession';
Expand Down Expand Up @@ -46,6 +46,12 @@ type PerformanceModule = {
subscribeToMeasurements: SubscribeToMeasurements;
};

type ReactNativePerformance = {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
default: RNPerformance;
setResourceLoggingEnabled: (enabled?: boolean) => void;
PerformanceObserver: typeof RNPerformanceObserver;
};

let rnPerformance: RNPerformance;

/**
Expand Down Expand Up @@ -86,7 +92,7 @@ const Performance: PerformanceModule = {
};

if (Metrics.canCapturePerformanceMetrics()) {
const perfModule = require('react-native-performance');
const perfModule: ReactNativePerformance = require('react-native-performance');
perfModule.setResourceLoggingEnabled(true);
rnPerformance = perfModule.default;

Expand Down
6 changes: 3 additions & 3 deletions src/libs/updateMultilineInputRange/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const updateMultilineInputRange: UpdateMultilineInputRange = (input, shouldAutoF
}

if ('value' in input && input.value && input.setSelectionRange) {
const length = input.value.length;
if (shouldAutoFocus) {
input.setSelectionRange(length, length);
const length = input.value.length as number;
if (shouldAutoFocus && 'setSelectionRange' in input) {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
(input as HTMLInputElement).setSelectionRange(length, length);
}
// eslint-disable-next-line no-param-reassign
input.scrollTop = input.scrollHeight;
Expand Down
8 changes: 5 additions & 3 deletions src/pages/iou/MoneyRequestAmountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput';
import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput';
import ScrollView from '@components/ScrollView';
import SettlementButton from '@components/SettlementButton';
import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -130,7 +131,8 @@ function MoneyRequestAmountForm(
if (!textInput.current) {
return;
}
if (!textInput.current.isFocused()) {

if (!isAnimatedTextInputFocused(textInput)) {
bgawkuc marked this conversation as resolved.
Show resolved Hide resolved
textInput.current.focus();
}
};
Expand Down Expand Up @@ -171,7 +173,7 @@ function MoneyRequestAmountForm(
*/
const updateAmountNumberPad = useCallback(
(key: string) => {
if (shouldUpdateSelection && !textInput.current?.isFocused()) {
if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) {
textInput.current?.focus();
}
const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? '';
Expand All @@ -198,7 +200,7 @@ function MoneyRequestAmountForm(
*/
const updateLongPressHandlerState = useCallback((value: boolean) => {
setShouldUpdateSelection(!value);
if (!value && !textInput.current?.isFocused()) {
if (!value && !isAnimatedTextInputFocused(textInput)) {
textInput.current?.focus();
}
}, []);
Expand Down
Loading
Loading