diff --git a/package.json b/package.json index e0efe39bf1..51b04f1671 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ }, "eslintConfig": { "extends": [ + "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", @@ -142,14 +143,14 @@ ], "rules": { "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-inferrable-types": "warn", - "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/switch-exhaustiveness-check": "warn", "import/first": "warn", "import/newline-after-import": "warn", "import/no-duplicates": "warn", "import/no-named-as-default": "off", - "import/no-named-as-default-member": "off", "import/order": [ "warn", { @@ -173,9 +174,8 @@ ], "no-undef": "off", "prefer-const": "warn", - "react/display-name": "off", "react/jsx-boolean-value": "warn", - "unused-imports/no-unused-imports": "error" + "unused-imports/no-unused-imports": "warn" }, "parser": "@typescript-eslint/parser", "parserOptions": { @@ -189,6 +189,7 @@ "react", "unused-imports" ], + "root": true, "settings": { "react": { "version": "detect" diff --git a/src/components/AppBar/UserMenu.tsx b/src/components/AppBar/UserMenu.tsx index 1a256d6db5..55e65bdec2 100644 --- a/src/components/AppBar/UserMenu.tsx +++ b/src/components/AppBar/UserMenu.tsx @@ -12,7 +12,7 @@ import { MenuItem, Typography, } from "@mui/material"; -import React, { ReactElement, useState } from "react"; +import React, { forwardRef, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -114,11 +114,12 @@ export default function UserMenu(props: TabProps): ReactElement { // automatically applies a ref to its first child for anchoring. The // following prevents a console warning: "Function components cannot be given refs. // Attempts to access this ref will fail. Did you mean to use React.forwardRef()?" -const WrappedUserMenuList = React.forwardRef( - (props: React.ComponentProps, ref) => ( - - ) -); +const WrappedUserMenuList = forwardRef(function WrappedUserMenuList( + props: React.ComponentProps, + ref +) { + return ; +}); interface UserMenuListProps { isAdmin: boolean; diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx index 9d67cb6e01..499240921d 100644 --- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx +++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx @@ -12,7 +12,7 @@ interface VernWithSuggestionsProps { vernInput?: React.RefObject; updateVernField: (newValue: string, openDialog?: boolean) => void; onBlur: () => void; - onClose?: (e: React.ChangeEvent<{}>, reason: AutocompleteCloseReason) => void; + onClose?: (e: React.SyntheticEvent, reason: AutocompleteCloseReason) => void; suggestedVerns?: string[]; handleEnter: () => void; vernacularLang: WritingSystem; diff --git a/src/components/Login/LoginPage/LoginComponent.tsx b/src/components/Login/LoginPage/LoginComponent.tsx index 393864100b..f9b02ef6be 100644 --- a/src/components/Login/LoginPage/LoginComponent.tsx +++ b/src/components/Login/LoginPage/LoginComponent.tsx @@ -9,7 +9,7 @@ import { TextField, Typography, } from "@mui/material"; -import React from "react"; +import { Component } from "react"; import { withTranslation, WithTranslation } from "react-i18next"; import { BannerType } from "api/models"; @@ -57,7 +57,7 @@ interface LoginError { } /** The login page (also doubles as a logout page) */ -export class Login extends React.Component { +export class Login extends Component { constructor(props: LoginProps) { super(props); this.props.logout(); // Loading this page will reset the app, both store and localStorage diff --git a/src/components/Login/SignUpPage/SignUpComponent.tsx b/src/components/Login/SignUpPage/SignUpComponent.tsx index 7dbc46109a..5a7903f5ab 100644 --- a/src/components/Login/SignUpPage/SignUpComponent.tsx +++ b/src/components/Login/SignUpPage/SignUpComponent.tsx @@ -7,7 +7,7 @@ import { TextField, Typography, } from "@mui/material"; -import React from "react"; +import { Component } from "react"; import { withTranslation, WithTranslation } from "react-i18next"; import router from "browserRouter"; @@ -22,6 +22,7 @@ import { // Chrome silently converts non-ASCII characters in a Textfield of type="email". // Use punycode.toUnicode() to convert them from punycode back to Unicode. +// eslint-disable-next-line @typescript-eslint/no-var-requires const punycode = require("punycode/"); const idAffix = "signUp"; @@ -65,7 +66,7 @@ interface SignUpState { }; } -export class SignUp extends React.Component { +export class SignUp extends Component { constructor(props: SignUpProps) { super(props); this.state = { diff --git a/src/components/ProjectUsers/EmailInvite.tsx b/src/components/ProjectUsers/EmailInvite.tsx index 4ddc5fe9a9..4d043d25b0 100644 --- a/src/components/ProjectUsers/EmailInvite.tsx +++ b/src/components/ProjectUsers/EmailInvite.tsx @@ -43,6 +43,7 @@ export default function EmailInvite(props: InviteProps): ReactElement { }; useEffect(() => { + // eslint-disable-next-line import/no-named-as-default-member setIsValid(validator.isEmail(email) && email !== "example@gmail.com"); }, [email, setIsValid]); diff --git a/src/components/Pronunciations/Recorder.ts b/src/components/Pronunciations/Recorder.ts index 79b0064f10..7c324361c4 100644 --- a/src/components/Pronunciations/Recorder.ts +++ b/src/components/Pronunciations/Recorder.ts @@ -4,7 +4,7 @@ export default class Recorder { private toast: (text: string) => void; private recordRTC?: RecordRTC; - static blobType: "audio" = "audio"; + static blobType: RecordRTC.Options["type"] = "audio"; constructor(toast?: (text: string) => void) { this.toast = toast ?? ((text: string) => alert(text)); diff --git a/src/components/Statistics/UserStatistics.tsx b/src/components/Statistics/UserStatistics.tsx index 39eaaf3f65..93a1ec9965 100644 --- a/src/components/Statistics/UserStatistics.tsx +++ b/src/components/Statistics/UserStatistics.tsx @@ -1,6 +1,5 @@ import { Card, Grid, ListItem, List } from "@mui/material"; import { ReactElement, useState, useEffect } from "react"; -import { useTranslation } from "react-i18next"; import { SemanticDomainUserCount } from "api/models"; import { getSemanticDomainUserCount } from "backend"; @@ -17,7 +16,6 @@ export default function UserStatistics( const [domainUserCountList, setDomainUserCountList] = useState< SemanticDomainUserCount[] >([]); - const { t } = useTranslation(); useEffect(() => { const updateSemanticDomainUserCounts = async () => { diff --git a/src/components/UserSettings/ClickableAvatar.tsx b/src/components/UserSettings/ClickableAvatar.tsx index 91eed63d06..30c037bdd1 100644 --- a/src/components/UserSettings/ClickableAvatar.tsx +++ b/src/components/UserSettings/ClickableAvatar.tsx @@ -6,7 +6,7 @@ import { ReactElement, useState } from "react"; import { getAvatar } from "backend/localStorage"; import AvatarUpload from "components/UserSettings/AvatarUpload"; -const clickableAvatarClassProps: Styles = { +const clickableAvatarClassProps: Styles = { avatar: { width: 60, height: 60 }, avatarOverlay: { transition: "opacity 0.2s", diff --git a/src/components/UserSettings/UserSettings.tsx b/src/components/UserSettings/UserSettings.tsx index e96461b01b..c27e1631b6 100644 --- a/src/components/UserSettings/UserSettings.tsx +++ b/src/components/UserSettings/UserSettings.tsx @@ -23,6 +23,7 @@ import { uiWritingSystems } from "types/writingSystem"; // Chrome silently converts non-ASCII characters in a Textfield of type="email". // Use punycode.toUnicode() to convert them from punycode back to Unicode. +// eslint-disable-next-line @typescript-eslint/no-var-requires const punycode = require("punycode/"); export enum UserSettingsIds { diff --git a/src/goals/MergeDuplicates/MergeDupsContinueDialog.tsx b/src/goals/MergeDuplicates/MergeDupsContinueDialog.tsx index 3f99617c6d..e0a55d2668 100644 --- a/src/goals/MergeDuplicates/MergeDupsContinueDialog.tsx +++ b/src/goals/MergeDuplicates/MergeDupsContinueDialog.tsx @@ -1,5 +1,5 @@ import { Button, Dialog, DialogActions, DialogTitle } from "@mui/material"; -import React, { ReactElement } from "react"; +import { ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; export interface MergeDupsContinueDialogProps { @@ -9,7 +9,7 @@ export interface MergeDupsContinueDialogProps { export default function MergeDupsContinueDialog( props: MergeDupsContinueDialogProps ): ReactElement { - const [open, setOpen] = React.useState(true); + const [open, setOpen] = useState(true); const { t } = useTranslation(); const { onSelection } = props; diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index cce194e344..ac1ba4bb92 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,7 +1,7 @@ import MaterialTable from "@material-table/core"; import { Typography } from "@mui/material"; import { enqueueSnackbar } from "notistack"; -import React, { ReactElement, useEffect, useState } from "react"; +import React, { ReactElement, createRef, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; @@ -40,7 +40,7 @@ function getPageState(wordCount: number): PageState { // Constants const ROWS_PER_PAGE = [10, 50, 250]; -const tableRef: React.RefObject = React.createRef(); +const tableRef: React.RefObject = createRef(); export default function ReviewEntriesTable( props: ReviewEntriesTableProps diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/icons.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/icons.tsx index fbcfce9a62..4b03847b96 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/icons.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/icons.tsx @@ -15,6 +15,7 @@ import Search from "@mui/icons-material/Search"; import ViewColumn from "@mui/icons-material/ViewColumn"; import { forwardRef, Ref } from "react"; +/* eslint-disable react/display-name */ const tableIcons = { Add: forwardRef((props: any, ref: Ref) => ( diff --git a/src/i18n.ts b/src/i18n.ts index cbfaf22d8c..762e70b18a 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -14,6 +14,7 @@ declare module "i18next" { } } +/* eslint-disable import/no-named-as-default-member */ i18n .use(Backend) .use(LanguageDetector) diff --git a/src/index.tsx b/src/index.tsx index 0a42b5b3fd..0d68aa650e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles"; import { SnackbarProvider } from "notistack"; -import ReactDOM from "react-dom"; +import { render } from "react-dom"; import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; @@ -10,7 +10,7 @@ import { persistor, store } from "store"; import theme from "types/theme"; //Provider connects store to component containers -ReactDOM.render( +render( diff --git a/src/types/goals.ts b/src/types/goals.ts index faf7d30818..0a7e995780 100644 --- a/src/types/goals.ts +++ b/src/types/goals.ts @@ -14,7 +14,8 @@ import { import { newUser } from "types/user"; export type GoalData = CharInvData | MergeDupsData; -export type GoalStep = CharInvStepData | MergeStepData | {}; +// Record is the recommended type for an empty object. +export type GoalStep = CharInvStepData | MergeStepData | Record; export type GoalChanges = CharInvChanges | MergesCompleted; export interface GoalProps { diff --git a/src/types/writingSystem.ts b/src/types/writingSystem.ts index eb42c0897d..31182e6e4b 100644 --- a/src/types/writingSystem.ts +++ b/src/types/writingSystem.ts @@ -3,6 +3,7 @@ import { WritingSystem } from "api/models"; export enum Bcp47Code { Default = "en", Ar = "ar", // Arabic + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values En = "en", // English Es = "es", // Spanish Fr = "fr", // French diff --git a/src/utilities/dictionaryLoader.ts b/src/utilities/dictionaryLoader.ts index 1076a41d9f..fb4c2a89b8 100644 --- a/src/utilities/dictionaryLoader.ts +++ b/src/utilities/dictionaryLoader.ts @@ -35,7 +35,7 @@ export default class DictionaryLoader { .toLocaleLowerCase() .split("") .map((c) => c.charCodeAt(0)); - var key = ""; + let key = ""; while (true) { key = charCodes.join("-"); if (!key || this.keys.includes(key)) { diff --git a/src/utilities/fontCssUtilities.ts b/src/utilities/fontCssUtilities.ts index 83d3100989..7823aed50f 100644 --- a/src/utilities/fontCssUtilities.ts +++ b/src/utilities/fontCssUtilities.ts @@ -31,7 +31,7 @@ export async function fetchCss( source: string, substitute?: string ): Promise { - var cssUrl = ""; + let cssUrl = ""; switch (source) { case "local": cssUrl = `${fontDir}/${font.replace(" ", "")}.css`;