Skip to content

Commit

Permalink
Merge pull request #42743 from software-mansion-labs/ts/enable-no-uns…
Browse files Browse the repository at this point in the history
…afe-call

Enable `no-unsafe-call` eslint rule
  • Loading branch information
mountiny authored Jun 12, 2024
2 parents 95c8202 + 649529e commit 3523ca6
Show file tree
Hide file tree
Showing 32 changed files with 143 additions and 55 deletions.
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
13 changes: 11 additions & 2 deletions .github/actions/javascript/getGraphiteString/getGraphiteString.ts
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 RegressionEntry = {
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: RegressionEntry = 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 CustomWebpackFunction = ({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: CustomWebpackFunction = require('../config/webpack/webpack.common').default;

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 PreloadWebpackPluginClass = {
new (options?: Options): PreloadWebpackPluginClass;
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: PreloadWebpackPluginClass = require('@vue/preload-webpack-plugin');

const includeModules = [
'react-native-animatable',
Expand Down
8 changes: 7 additions & 1 deletion desktop/createDownloadQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ type DownloadItem = {
options: Options;
};

type CreateDownloadQueue = () => {
enqueueDownloadItem: (item: DownloadItem) => void;
dequeueDownloadItem: () => DownloadItem | undefined;
};

/**
* Returns the filename with extension based on the given name and MIME type.
* @param name - The name of the file.
Expand All @@ -28,7 +33,7 @@ const getFilenameFromMime = (name: string, mime: string): string => {
return `${name}.${extensions}`;
};

const createDownloadQueue = () => {
const createDownloadQueue: CreateDownloadQueue = () => {
const downloadItemProcessor = (item: DownloadItem): Promise<void> =>
new Promise((resolve, reject) => {
let downloadTimeout: NodeJS.Timeout;
Expand Down Expand Up @@ -114,3 +119,4 @@ const createDownloadQueue = () => {
};

export default createDownloadQueue;
export type {DownloadItem, CreateDownloadQueue};
5 changes: 3 additions & 2 deletions desktop/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
import type {Locale} from '@src/types/onyx';
import type {CreateDownloadQueue, DownloadItem} from './createDownloadQueue';
import ELECTRON_EVENTS from './ELECTRON_EVENTS';

const createDownloadQueue = require('./createDownloadQueue').default;
const createDownloadQueue: CreateDownloadQueue = require('./createDownloadQueue').default;

const port = process.env.PORT ?? 8082;
const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST;
Expand Down Expand Up @@ -617,7 +618,7 @@ const mainWindow = (): Promise<void> => {

const downloadQueue = createDownloadQueue();
ipcMain.on(ELECTRON_EVENTS.DOWNLOAD, (event, downloadData) => {
const downloadItem = {
const downloadItem: DownloadItem = {
...downloadData,
win: browserWindow,
};
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 isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';
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 (!isTextInputFocused(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 && !isTextInputFocused(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 && !isTextInputFocused(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 isTextInputFocused from '@components/TextInput/BaseTextInput/isTextInputFocused';
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 && !isTextInputFocused(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 && !isTextInputFocused(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 && !isTextInputFocused(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 OnMouseEvent = (e: MouseEvent) => void;

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: OnMouseEvent = child.props.onMouseEnter;
const childOnMouseLeave: OnMouseEvent = child.props.onMouseLeave;
const childOnMouseMove: OnMouseEvent = child.props.onMouseMove;
const childOnBlur: OnMouseEvent = 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 @@ -9,6 +9,7 @@ import getOperatingSystem from '@libs/getOperatingSystem';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import shouldIgnoreSelectionWhenUpdatedManually from '@libs/shouldIgnoreSelectionWhenUpdatedManually';
import CONST from '@src/CONST';
import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';
import type {BaseTextInputRef} from './TextInput/BaseTextInput/types';
import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol';

Expand Down Expand Up @@ -201,7 +202,7 @@ function MoneyRequestAmountInput(
}));

useEffect(() => {
if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) {
if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput) {
return;
}
const frontendAmount = onFormatAmount(amount, currency);
Expand Down
7 changes: 7 additions & 0 deletions src/components/TextInput/BaseTextInput/isTextInputFocused.ts
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. */
export default function isTextInputFocused(textInput: React.MutableRefObject<BaseTextInputRef | null>): boolean | null {
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)
) {
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/WalletStatementModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {WalletStatementOnyxProps, WalletStatementProps} from './types';
import type {WalletStatementMessage, WalletStatementOnyxProps, WalletStatementProps} from './types';

function WalletStatementModal({statementPageURL, session}: WalletStatementProps) {
const styles = useThemeStyles();
Expand All @@ -18,7 +18,7 @@ function WalletStatementModal({statementPageURL, session}: WalletStatementProps)
/**
* Handles in-app navigation for iframe links
*/
const navigate = (event: MessageEvent) => {
const navigate = (event: MessageEvent<WalletStatementMessage>) => {
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
7 changes: 6 additions & 1 deletion src/components/WalletStatementModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ type WalletStatementProps = WalletStatementOnyxProps & {
statementPageURL: string;
};

export type {WalletStatementProps, WalletStatementOnyxProps};
type WalletStatementMessage = {
url: string;
type: string;
};

export type {WalletStatementProps, WalletStatementOnyxProps, WalletStatementMessage};
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};
Loading

0 comments on commit 3523ca6

Please sign in to comment.