diff --git a/.eslintrc b/.eslintrc index 20c00deb09..edc9e07fdd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,26 @@ { - "extends": ["react-app"], + "extends": [ + "react-app", + "react-app/jest", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended" + ], "rules": { - "import/no-anonymous-default-export": "off" - } + "import/no-anonymous-default-export": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "no-prototype-builtins": "off", + "react/prop-types": "off", + "react/display-name": "off" + }, + "ignorePatterns": [ + "node_modules", + "build", + "coverage", + "src/test", + "*.js", + "*.spec.*" + ] } diff --git a/src/app/components/AccessibilityButtonsWrapper.tsx b/src/app/components/AccessibilityButtonsWrapper.tsx index 1058069aa2..d2f3288dea 100644 --- a/src/app/components/AccessibilityButtonsWrapper.tsx +++ b/src/app/components/AccessibilityButtonsWrapper.tsx @@ -26,7 +26,7 @@ const OpenKeyboardShortcutsMenuLink = () => { ; }; -export default class AccessibilityButtonsWrapper extends Component { +export default class AccessibilityButtonsWrapper extends Component> { public mainContent: HTMLDivElement | undefined; public render() { diff --git a/src/app/components/DotMenu.tsx b/src/app/components/DotMenu.tsx index 828d5dbe42..817eaf322f 100644 --- a/src/app/components/DotMenu.tsx +++ b/src/app/components/DotMenu.tsx @@ -20,8 +20,8 @@ export const DotMenuIcon = styled(EllipsisV)` // tslint:disable-next-line:variable-name export const DotMenuToggle = styled( - React.forwardRef( - ({isOpen, ...props}, ref) => { + React.forwardRef( + ({isOpen, ...props}: {isOpen: boolean}, ref) => { return ( diff --git a/src/app/components/ScrollOffset.tsx b/src/app/components/ScrollOffset.tsx index 7bb670b55a..29b18b94de 100644 --- a/src/app/components/ScrollOffset.tsx +++ b/src/app/components/ScrollOffset.tsx @@ -107,7 +107,7 @@ export default class ScrollOffset extends React.Component { } }; - private checkScroll = (maxChecks: number = 1) => { + private checkScroll = (maxChecks = 1) => { let scrolls = 0; const handler = () => { scrolls++; diff --git a/src/app/content/components/Topbar/index.tsx b/src/app/content/components/Topbar/index.tsx index 72c4f8605d..5109f57a17 100644 --- a/src/app/content/components/Topbar/index.tsx +++ b/src/app/content/components/Topbar/index.tsx @@ -38,7 +38,7 @@ interface Props { bookTheme: string; textSize: TextResizerValue | null; setTextSize: (size: TextResizerValue) => void; - selectedResult: any; + selectedResult: unknown; } type CommonSearchInputParams = Pick< @@ -217,7 +217,9 @@ function AltSCycler({hasSearchResults}: {hasSearchResults: boolean}) { ].map((q) => document?.querySelector(q)); // Determine which region we are in (if any) - const currentSectionIndex = targets.findIndex((el) => el?.contains(document?.activeElement!)); + const currentSectionIndex = targets.findIndex((el) => + document?.activeElement && el?.contains(document.activeElement) + ); // If not in any, go to search input if (currentSectionIndex < 0) { diff --git a/src/app/content/components/popUp/ChapterFilter.tsx b/src/app/content/components/popUp/ChapterFilter.tsx index 9ca7ddb600..c4750f50a0 100644 --- a/src/app/content/components/popUp/ChapterFilter.tsx +++ b/src/app/content/components/popUp/ChapterFilter.tsx @@ -54,7 +54,7 @@ const ChapterTitle = styled.span` } `; -const chunk = (sections: T[]) => { +const chunk = (sections: T[]) => { const cutoff = Math.max(20, Math.ceil(sections.length / 2)); return [sections.slice(0, cutoff), sections.slice(cutoff)].filter((arr) => arr.length > 0); }; diff --git a/src/app/content/highlights/components/SummaryPopup/HighlightListElement.tsx b/src/app/content/highlights/components/SummaryPopup/HighlightListElement.tsx index 22f965c4c5..5b46c80825 100644 --- a/src/app/content/highlights/components/SummaryPopup/HighlightListElement.tsx +++ b/src/app/content/highlights/components/SummaryPopup/HighlightListElement.tsx @@ -175,7 +175,7 @@ const HighlightListElement = ({ highlight, locationFilterId, pageId }: Highlight } {isDeleting && setIsDeleting(false)} onDelete={confirmDelete} />} diff --git a/src/app/content/launchToken.ts b/src/app/content/launchToken.ts index cf6e4ea932..2462878b03 100644 --- a/src/app/content/launchToken.ts +++ b/src/app/content/launchToken.ts @@ -24,7 +24,9 @@ export const decodeToken = (launchToken: string | undefined) => { // and into their own claims of the token. during transition try to decode // sub and apply it to the token data so it works either way. Object.assign(token, JSON.parse(token.sub)); - } catch (e) { } // tslint:disable-line + } catch (e) { + // let it go + } return token; }; diff --git a/src/app/content/practiceQuestions/reducer.ts b/src/app/content/practiceQuestions/reducer.ts index cbb2f1b29d..268dc274ca 100644 --- a/src/app/content/practiceQuestions/reducer.ts +++ b/src/app/content/practiceQuestions/reducer.ts @@ -40,7 +40,7 @@ const reducer: Reducer = (state = initialState, action): State return {...state, currentQuestionIndex: state.currentQuestionIndex === null ? 0 : state.currentQuestionIndex + 1}; case getType(actions.setQuestions): return {...state, loading: false, questions: action.payload}; - case getType(actions.setAnswer): + case getType(actions.setAnswer): { const { questionId, answer } = action.payload; return { ...state, @@ -49,6 +49,7 @@ const reducer: Reducer = (state = initialState, action): State [questionId]: answer, }, }; + } case getType(actions.finishQuestions): return {...state, currentQuestionIndex: null}; default: diff --git a/src/app/content/search/components/SearchResultsSidebar/SidebarSearchInput.tsx b/src/app/content/search/components/SearchResultsSidebar/SidebarSearchInput.tsx index bc58265792..25b514c548 100644 --- a/src/app/content/search/components/SearchResultsSidebar/SidebarSearchInput.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/SidebarSearchInput.tsx @@ -5,6 +5,7 @@ import theme from '../../../../theme'; import { assertDocument } from '../../../../utils'; import * as TopbarStyled from '../../../components/Topbar/styled'; import { ResultsSidebarProps } from './SearchResultsBarWrapper'; +import { HTMLInputElement } from '@openstax/types/lib.dom'; import * as Styled from './styled'; interface State { @@ -59,7 +60,7 @@ export class SidebarSearchInput extends Component { public newButtonEnabled = !!this.props.searchButtonColor; public onSearchChange = (e: React.FormEvent) => { - this.setState({ query: (e.currentTarget as any).value, formSubmitted: false }); + this.setState({ query: (e.currentTarget as HTMLInputElement).value, formSubmitted: false }); }; public onSearchSubmit = (e: React.FormEvent) => { diff --git a/src/app/content/search/components/SearchResultsSidebar/styled.tsx b/src/app/content/search/components/SearchResultsSidebar/styled.tsx index aa718378c2..3109371dd5 100644 --- a/src/app/content/search/components/SearchResultsSidebar/styled.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/styled.tsx @@ -276,7 +276,7 @@ interface SectionContentPreviewProps extends React.ComponentProps( - ({selectedResult, ...props}, ref) => + ({selectedResult, ...props}: {selectedResult: unknown}, ref) => ) )` ${labelStyle} diff --git a/src/app/context/Services.tsx b/src/app/context/Services.tsx index b19f127877..6eb128201c 100644 --- a/src/app/context/Services.tsx +++ b/src/app/context/Services.tsx @@ -28,7 +28,7 @@ interface ServiceConsumer { export default

(Component: React.ComponentType

) => (props: Pick>) => {(services) => { - // @ts-ignore - remove this when https://github.com/Microsoft/TypeScript/issues/28748 is resolved + // @ts-expect-error - remove this when https://github.com/Microsoft/TypeScript/issues/28748 is resolved return ; }} ; diff --git a/src/app/developer/components/Panel.tsx b/src/app/developer/components/Panel.tsx index 1794274c58..2473bae2ee 100644 --- a/src/app/developer/components/Panel.tsx +++ b/src/app/developer/components/Panel.tsx @@ -12,7 +12,7 @@ const Wrappper = styled.div` `; // tslint:disable-next-line:variable-name -const Panel: React.SFC = ({title, children}) =>

+const Panel = ({title, children}: React.PropsWithChildren) =>

{title}

{children} diff --git a/src/app/domUtils.ts b/src/app/domUtils.ts index de26be07aa..b2299c5fb8 100644 --- a/src/app/domUtils.ts +++ b/src/app/domUtils.ts @@ -7,8 +7,8 @@ import { AppServices, Store } from './types'; import { assertDefined, assertDocument, assertWindow } from './utils'; import debounce from 'lodash/debounce'; -export const SCROLL_UP: 'scroll_up' = 'scroll_up'; -export const SCROLL_DOWN: 'scroll_down' = 'scroll_down'; +export const SCROLL_UP = 'scroll_up'; +export const SCROLL_DOWN = 'scroll_down'; export const getTouchDirection = (last: TouchEvent, next: TouchEvent) => next.touches[0].clientY > last.touches[0].clientY @@ -174,8 +174,8 @@ export const onPageFocusChange = ( }; const eventTypeMap = { - focusin: 'FocusEvent' as 'FocusEvent', - focusout: 'FocusEvent' as 'FocusEvent', + focusin: 'FocusEvent' as const, + focusout: 'FocusEvent' as const, }; type EventTypeMap = typeof eventTypeMap; // this ['prototype'] is only necessary because of the duplicate names in lib.dom.d.ts, if we diff --git a/src/app/featureFlags/reducer.ts b/src/app/featureFlags/reducer.ts index 2b21e7eba8..c40e7d865a 100644 --- a/src/app/featureFlags/reducer.ts +++ b/src/app/featureFlags/reducer.ts @@ -7,10 +7,12 @@ import { State } from './types'; export const initialState: State = {}; -const reducer: Reducer = (state = initialState, action): any => { +const reducer: Reducer = (state = initialState, action) => { + switch (action.type) { - case getType(receiveExperiments): + case getType(receiveExperiments): { const [id, variant] = action.payload; + if (variant && experimentIds[id]) { const experimentName = experimentIds[id]; const variantIndex = parseInt(variant, 10); @@ -18,6 +20,7 @@ const reducer: Reducer = (state = initialState, action): any = return {...state, [experimentName]: variantName}; } return state; + } case getType(receiveFeatureFlags): return action.payload.reduce((result, flag) => ({...result, [flag]: true}), {...state}); default: diff --git a/src/app/navigation/types.ts b/src/app/navigation/types.ts index 2160a7a483..31912d9f41 100644 --- a/src/app/navigation/types.ts +++ b/src/app/navigation/types.ts @@ -42,7 +42,7 @@ export interface RouteStateType { export interface Route< P extends RouteParamsType = {}, - // @ts-ignore: 'S' is declared but its value is never read. + // @ts-expect-error: 'S' is declared but its value is never read. // eslint-disable-next-line @typescript-eslint/no-unused-vars S extends RouteStateType = {} > { diff --git a/src/app/notifications/acceptCookies.ts b/src/app/notifications/acceptCookies.ts index e4f0dd3b13..17a0421f63 100644 --- a/src/app/notifications/acceptCookies.ts +++ b/src/app/notifications/acceptCookies.ts @@ -1,6 +1,6 @@ import * as Cookies from 'js-cookie'; -const acknowledgedKey: string = 'cookie_notice_acknowledged'; +const acknowledgedKey = 'cookie_notice_acknowledged'; export const doAcceptCookies = () => { Cookies.set(acknowledgedKey, 'true', {expires: 365 * 20}); diff --git a/src/app/notifications/dismissAppMessages.ts b/src/app/notifications/dismissAppMessages.ts index 57150b76c9..f50be01f54 100644 --- a/src/app/notifications/dismissAppMessages.ts +++ b/src/app/notifications/dismissAppMessages.ts @@ -2,7 +2,7 @@ import { differenceInDays } from 'date-fns'; import * as Cookies from 'js-cookie'; import { Message } from './types'; -const messageDismissedPrefix: string = 'message_dismissed'; +const messageDismissedPrefix = 'message_dismissed'; const getMessageKey = (message: Message) => `${messageDismissedPrefix}_${message.id}`; export const dismissAppMessage = (message: Message) => { diff --git a/src/app/notifications/reducer.ts b/src/app/notifications/reducer.ts index 95c2fa3cfa..0fea066173 100644 --- a/src/app/notifications/reducer.ts +++ b/src/app/notifications/reducer.ts @@ -14,7 +14,7 @@ export const initialState: State = { toastNotifications: [], }; -export const appMessageType = 'Notification/appMessage' as 'Notification/appMessage'; +export const appMessageType = 'Notification/appMessage' as const; const isNewMessage = (state: State, message: Message) => !state.modalNotifications.find((existingMessage) => { diff --git a/src/app/utils/browser-assertions.ts b/src/app/utils/browser-assertions.ts index 21fe097f77..2b74c9faf3 100644 --- a/src/app/utils/browser-assertions.ts +++ b/src/app/utils/browser-assertions.ts @@ -1,4 +1,4 @@ -export const assertWindow = (message: string = 'BUG: Window is undefined') => { +export const assertWindow = (message = 'BUG: Window is undefined') => { if (typeof(window) === 'undefined') { throw new Error(message); } @@ -6,7 +6,7 @@ export const assertWindow = (message: string = 'BUG: Window is undefined') => { return window; }; -export const assertDocument = (message: string = 'BUG: Document is undefined') => { +export const assertDocument = (message = 'BUG: Document is undefined') => { if (typeof(document) === 'undefined') { throw new Error(message); } @@ -14,7 +14,7 @@ export const assertDocument = (message: string = 'BUG: Document is undefined') = return document; }; -export const assertDocumentElement = (message: string = 'BUG: Document Element is null') => { +export const assertDocumentElement = (message = 'BUG: Document Element is null') => { const documentElement = assertDocument().documentElement; if (documentElement === null) { diff --git a/src/canonicalBookMap/americanGovernment.ts b/src/canonicalBookMap/americanGovernment.ts index ca4281ac32..3c1196f5cb 100644 --- a/src/canonicalBookMap/americanGovernment.ts +++ b/src/canonicalBookMap/americanGovernment.ts @@ -50,7 +50,7 @@ export default { 'ed22cb13-2f14-460f-a603-fb1631f4c6f6': '7760a2b3-c3b9-416c-9520-b8cc4fa954fa', /* 4.4 Interpreting the Bill of Rights to the same module in 2e */ 'fdf44e79-776e-477e-963c-a6c0a98c434a': 'cb752abe-3cf9-4da4-8fc5-60b9e3752e5a', - /* 5.0 Introduction to the same module in 2e */ + /* 5.0 Introduction to the same module in 2e */ '9fab3e71-ddb5-4dd9-bfd4-c9bc9b98141e': '9d5dd1d4-a37c-45be-a6c5-1af41cabed33', /* 5.1 What Are Civil Rights and How Do We Identify Them? to the same module in 2e */ '75bad956-ba62-4639-92cf-d25fc3bdd9f1': '2d27051a-78f8-4af2-acc1-d6ca2217ef70', diff --git a/src/gateways/googleAnalyticsClient.spec.ts b/src/gateways/googleAnalyticsClient.spec.ts index cd351683dd..e2a6fa2dcb 100644 --- a/src/gateways/googleAnalyticsClient.spec.ts +++ b/src/gateways/googleAnalyticsClient.spec.ts @@ -215,7 +215,7 @@ describe('GoogleAnalyticsClient', () => { client.setTagIds(['foo']); mockGtag.mockClear(); client.trackPageView('/some/path', { utm_source: 'source' }); - expect(mockGtag).toHaveBeenCalledWith('set', { + expect(mockGtag).toHaveBeenCalledWith('set', undefined, { campaignMedium: 'unset', campaignSource: 'source', }); diff --git a/src/gateways/googleAnalyticsClient.ts b/src/gateways/googleAnalyticsClient.ts index 87c207763a..04c2b3ab98 100644 --- a/src/gateways/googleAnalyticsClient.ts +++ b/src/gateways/googleAnalyticsClient.ts @@ -207,9 +207,9 @@ class GoogleAnalyticsClient { this.pendingCommands = []; } - private executeCommand(command: Command, queueTime: number = 0) { + private executeCommand(command: Command, queueTime = 0) { if (command.name === 'set') { - this.gtag('set', command.payload); + this.gtag('set', undefined, command.payload); return; } @@ -231,7 +231,7 @@ class GoogleAnalyticsClient { } // The real, low-level Google Analytics gtag function - private gtag(commandName: string, ...params: any[]) { + private gtag(commandName: string, ...params: [string?, object?]) { return assertWindow().gtag(commandName, ...params); } diff --git a/src/typings/WeakMap.d.ts b/src/typings/WeakMap.d.ts index abe9b6a9b4..353d158005 100644 --- a/src/typings/WeakMap.d.ts +++ b/src/typings/WeakMap.d.ts @@ -35,10 +35,6 @@ declare module 'weak-map' { clear(): void; } - interface WeakMapConstructor { - new(): WeakMap; - } - class WeakMap { constructor(); } diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts index c337a49dee..1648c77721 100644 --- a/src/typings/global.d.ts +++ b/src/typings/global.d.ts @@ -56,7 +56,7 @@ declare global { prototype: Event; new(typeArg: string, eventInitDict?: EventInit): Event; }; - MathJax: any; + MathJax: any; // eslint-disable-line @typescript-eslint/no-explicit-any ga: UniversalAnalytics.ga; dataLayer: object[]; oxDLF: object[]; @@ -64,6 +64,7 @@ declare global { gtag: (eventKey?: string, eventVal?: string, eventObj?: object) => boolean | void; } + /* eslint-disable no-var */ var fetch: (input: dom.RequestInfo, init?: dom.RequestInit) => Promise; var window: Window | undefined; var document: dom.Document | undefined; diff --git a/src/typings/rangy.d.ts b/src/typings/rangy.d.ts index 1c2131d2d2..1ea628dfcb 100644 --- a/src/typings/rangy.d.ts +++ b/src/typings/rangy.d.ts @@ -57,6 +57,6 @@ declare module 'rangy' { } export default rangy; - declare var rangy: RangyStatic; + declare const rangy: RangyStatic; type NativeRange = import ('@openstax/types/lib.dom').Range; }