Update React Lint rules for TSX

pull/7039/head
Mark McDowall 7 months ago committed by Mark McDowall
parent 4c0de55672
commit 1299a97579

@ -359,11 +359,16 @@ module.exports = {
], ],
rules: Object.assign(typescriptEslintRecommended.rules, { rules: Object.assign(typescriptEslintRecommended.rules, {
'no-shadow': 'off', '@typescript-eslint/no-unused-vars': [
// These should be enabled after cleaning things up 'error',
'@typescript-eslint/no-unused-vars': 'warn', {
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off', 'no-shadow': 'off',
'prettier/prettier': 'error', 'prettier/prettier': 'error',
'simple-import-sort/imports': [ 'simple-import-sort/imports': [
'error', 'error',
@ -376,7 +381,41 @@ module.exports = {
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$'] ['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
] ]
} }
] ],
// React Hooks
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
// React
'react/function-component-definition': 'error',
'react/hook-use-state': 'error',
'react/jsx-boolean-value': ['error', 'always'],
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never' }
],
'react/jsx-fragments': 'error',
'react/jsx-handler-names': [
'error',
{
eventHandlerPrefix: 'on',
eventHandlerPropPrefix: 'on'
}
],
'react/jsx-no-bind': ['error', { ignoreRefs: true }],
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
'react/jsx-sort-props': [
'error',
{
callbacksLast: true,
noSortAlphabetically: true,
reservedFirst: true
}
],
'react/prop-types': 'off',
'react/self-closing-comp': 'error'
}) })
}, },
{ {

@ -1,4 +1,4 @@
import React, { Fragment, useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState'; import AppState from 'App/State/AppState';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
@ -31,7 +31,6 @@ function QueueOptions() {
); );
return ( return (
<Fragment>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel> <FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel>
@ -43,7 +42,6 @@ function QueueOptions() {
onChange={handleOptionChange} onChange={handleOptionChange}
/> />
</FormGroup> </FormGroup>
</Fragment>
); );
} }

@ -35,6 +35,10 @@ import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector'; import MissingConnector from 'Wanted/Missing/MissingConnector';
function RedirectWithUrlBase() {
return <Redirect to={getPathWithUrlBase('/')} />;
}
function AppRoutes() { function AppRoutes() {
return ( return (
<Switch> <Switch>
@ -51,9 +55,7 @@ function AppRoutes() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
addUrlBase={false} addUrlBase={false}
render={() => { render={RedirectWithUrlBase}
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/> />
)} )}
@ -61,21 +63,9 @@ function AppRoutes() {
<Route path="/add/import" component={ImportSeries} /> <Route path="/add/import" component={ImportSeries} />
<Route <Route path="/serieseditor" exact={true} render={RedirectWithUrlBase} />
path="/serieseditor"
exact={true}
render={() => {
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/>
<Route <Route path="/seasonpass" exact={true} render={RedirectWithUrlBase} />
path="/seasonpass"
exact={true}
render={() => {
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/>
<Route path="/series/:titleSlug" component={SeriesDetailsPageConnector} /> <Route path="/series/:titleSlug" component={SeriesDetailsPageConnector} />

@ -64,7 +64,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
<div>{info.componentStack}</div> <div>{info.componentStack}</div>
)} )}
{<div className={styles.version}>Version: {window.Sonarr.version}</div>} <div className={styles.version}>Version: {window.Sonarr.version}</div>
</details> </details>
</div> </div>
); );

@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
@ -82,9 +82,7 @@ function EpisodeNumber(props: EpisodeNumberProps) {
<Popover <Popover
anchor={ anchor={
<span> <span>
{showSeasonNumber && seasonNumber != null && ( {showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
<Fragment>{seasonNumber}x</Fragment>
)}
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber} {showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}
@ -111,9 +109,7 @@ function EpisodeNumber(props: EpisodeNumberProps) {
/> />
) : ( ) : (
<span> <span>
{showSeasonNumber && seasonNumber != null && ( {showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
<Fragment>{seasonNumber}x</Fragment>
)}
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber} {showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}

@ -3,15 +3,15 @@ import { useCallback, useState } from 'react';
export default function useModalOpenState( export default function useModalOpenState(
initialState: boolean initialState: boolean
): [boolean, () => void, () => void] { ): [boolean, () => void, () => void] {
const [isOpen, setOpen] = useState(initialState); const [isOpen, setIsOpen] = useState(initialState);
const setModalOpen = useCallback(() => { const setModalOpen = useCallback(() => {
setOpen(true); setIsOpen(true);
}, [setOpen]); }, [setIsOpen]);
const setModalClosed = useCallback(() => { const setModalClosed = useCallback(() => {
setOpen(false); setIsOpen(false);
}, [setOpen]); }, [setIsOpen]);
return [isOpen, setModalOpen, setModalClosed]; return [isOpen, setModalOpen, setModalClosed];
} }

@ -857,7 +857,7 @@ function InteractiveImportModalContent(
<MenuContent> <MenuContent>
<SelectedMenuItem <SelectedMenuItem
name={'all'} name="all"
isSelected={!filterExistingFiles} isSelected={!filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
@ -865,7 +865,7 @@ function InteractiveImportModalContent(
</SelectedMenuItem> </SelectedMenuItem>
<SelectedMenuItem <SelectedMenuItem
name={'new'} name="new"
isSelected={filterExistingFiles} isSelected={filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
@ -945,7 +945,7 @@ function InteractiveImportModalContent(
<SelectInput <SelectInput
className={styles.bulkSelect} className={styles.bulkSelect}
name="select" name="select"
value={'select'} value="select"
values={bulkSelectOptions} values={bulkSelectOptions}
isDisabled={!selectedIds.length} isDisabled={!selectedIds.length}
onChange={onSelectModalSelect} onChange={onSelectModalSelect}

@ -17,7 +17,7 @@ function SelectLanguageModal(props: SelectLanguageModalProps) {
props; props;
return ( return (
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}> <Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<SelectLanguageModalContent <SelectLanguageModalContent
languageIds={languageIds} languageIds={languageIds}
modalTitle={modalTitle} modalTitle={modalTitle}

@ -64,18 +64,19 @@ interface RowItemData {
onSeriesSelect(seriesId: number): void; onSeriesSelect(seriesId: number): void;
} }
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, columns, onSeriesSelect } = data; const { items, columns, onSeriesSelect } = data;
const series = index >= items.length ? null : items[index];
if (index >= items.length) { const handlePress = useCallback(() => {
return null; if (series?.id) {
onSeriesSelect(series.id);
} }
}, [series?.id, onSeriesSelect]);
const series = items[index]; if (series == null) {
return null;
}
return ( return (
<VirtualTableRowButton <VirtualTableRowButton
@ -84,7 +85,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
justifyContent: 'space-between', justifyContent: 'space-between',
...style, ...style,
}} }}
onPress={() => onSeriesSelect(series.id)} onPress={handlePress}
> >
<SelectSeriesRow <SelectSeriesRow
key={series.id} key={series.id}
@ -98,7 +99,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
/> />
</VirtualTableRowButton> </VirtualTableRowButton>
); );
}; }
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) { function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
const { modalTitle, onSeriesSelect, onModalClose } = props; const { modalTitle, onSeriesSelect, onModalClose } = props;
@ -197,9 +198,9 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
/> />
<Scroller <Scroller
ref={scrollerRef}
className={styles.scroller} className={styles.scroller}
autoFocus={false} autoFocus={false}
ref={scrollerRef}
> >
<SelectSeriesModalTableHeader columns={columns} /> <SelectSeriesModalTableHeader columns={columns} />
<List<RowItemData> <List<RowItemData>

@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
props; props;
return ( return (
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}> <Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<SelectDownloadClientModalContent <SelectDownloadClientModalContent
protocol={protocol} protocol={protocol}
modalTitle={modalTitle} modalTitle={modalTitle}

@ -1,4 +1,4 @@
import React, { Fragment, useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import ParseModal from 'Parse/ParseModal'; import ParseModal from 'Parse/ParseModal';
@ -16,7 +16,7 @@ function ParseToolbarButton() {
}, [setIsParseModalOpen]); }, [setIsParseModalOpen]);
return ( return (
<Fragment> <>
<PageToolbarButton <PageToolbarButton
label={translate('TestParsing')} label={translate('TestParsing')}
iconName={icons.PARSE} iconName={icons.PARSE}
@ -24,7 +24,7 @@ function ParseToolbarButton() {
/> />
<ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} /> <ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} />
</Fragment> </>
); );
} }

@ -42,11 +42,7 @@ interface SeriesIndexOverviewsProps {
isSmallScreen: boolean; isSmallScreen: boolean;
} }
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, ...otherData } = data; const { items, ...otherData } = data;
if (index >= items.length) { if (index >= items.length) {
@ -60,7 +56,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
<SeriesIndexOverview seriesId={series.id} {...otherData} /> <SeriesIndexOverview seriesId={series.id} {...otherData} />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -60,12 +60,12 @@ const seriesIndexSelector = createSelector(
} }
); );
const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({ function Cell({
columnIndex, columnIndex,
rowIndex, rowIndex,
style, style,
data, data,
}) => { }: GridChildComponentProps<CellItemData>) {
const { layout, items, sortKey, isSelectMode } = data; const { layout, items, sortKey, isSelectMode } = data;
const { columnCount, padding, posterWidth, posterHeight } = layout; const { columnCount, padding, posterWidth, posterHeight } = layout;
const index = rowIndex * columnCount + columnIndex; const index = rowIndex * columnCount + columnIndex;
@ -92,7 +92,7 @@ const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
/> />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -45,11 +45,7 @@ const columnsSelector = createSelector(
(columns) => columns (columns) => columns
); );
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, sortKey, columns, isSelectMode } = data; const { items, sortKey, columns, isSelectMode } = data;
if (index >= items.length) { if (index >= items.length) {
@ -75,7 +71,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
/> />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -1,4 +1,4 @@
import React, { Fragment, useCallback } from 'react'; import React, { useCallback } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@ -32,7 +32,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
); );
return ( return (
<Fragment> <>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowBanners')}</FormLabel> <FormLabel>{translate('ShowBanners')}</FormLabel>
@ -56,7 +56,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
onChange={onTableOptionChangeWrapper} onChange={onTableOptionChangeWrapper}
/> />
</FormGroup> </FormGroup>
</Fragment> </>
); );
} }

@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
@ -17,11 +17,11 @@ function CustomFormatSettingsPage() {
// @ts-ignore // @ts-ignore
showSave={false} showSave={false}
additionalButtons={ additionalButtons={
<Fragment> <>
<PageToolbarSeparator /> <PageToolbarSeparator />
<ParseToolbarButton /> <ParseToolbarButton />
</Fragment> </>
} }
/> />

@ -231,9 +231,9 @@ function ManageDownloadClientsModalContent(
selectAll={true} selectAll={true}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress} onSortPress={onSortPress}
> >
<TableBody> <TableBody>
@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
<ManageDownloadClientsEditModal <ManageDownloadClientsEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
downloadClientIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
downloadClientIds={selectedIds}
/> />
<TagsModal <TagsModal

@ -261,9 +261,9 @@ function ManageImportListsModalContent(
<ManageImportListsEditModal <ManageImportListsEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
importListIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
importListIds={selectedIds}
/> />
<TagsModal <TagsModal

@ -226,9 +226,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
selectAll={true} selectAll={true}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress} onSortPress={onSortPress}
> >
<TableBody> <TableBody>
@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
<ManageIndexersEditModal <ManageIndexersEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
indexerIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
indexerIds={selectedIds}
/> />
<TagsModal <TagsModal

@ -66,7 +66,7 @@ function About() {
) : null} ) : null}
{isDocker ? ( {isDocker ? (
<DescriptionListItem title={translate('Docker')} data={'Yes'} /> <DescriptionListItem title={translate('Docker')} data="Yes" />
) : null} ) : null}
<DescriptionListItem <DescriptionListItem

@ -1,10 +1,4 @@
import React, { import React, { useCallback, useEffect, useMemo, useState } from 'react';
Fragment,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import AppState from 'App/State/AppState'; import AppState from 'App/State/AppState';
@ -158,7 +152,7 @@ function Updates() {
{translate('InstallLatest')} {translate('InstallLatest')}
</SpinnerButton> </SpinnerButton>
) : ( ) : (
<Fragment> <>
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} /> <Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
<div className={styles.message}> <div className={styles.message}>
@ -171,7 +165,7 @@ function Updates() {
} }
/> />
</div> </div>
</Fragment> </>
)} )}
{isFetching ? ( {isFetching ? (

Loading…
Cancel
Save