From 3a56da8ce2b84d20aead3340fc3fd68eb7a6641f Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Wed, 8 Jan 2025 07:52:45 -0600 Subject: [PATCH] Fix anys And non-null assertions --- src/app/components/Button.tsx | 1 + src/app/components/Dropdown.tsx | 1 + src/app/components/GoToTopButton.tsx | 2 +- src/app/components/htmlMessage.tsx | 4 +-- src/app/content/components/Assigned.tsx | 3 +- src/app/content/components/AssignedTopBar.tsx | 2 +- src/app/content/components/Page/connector.ts | 2 +- .../content/components/Topbar/TextResizer.tsx | 3 +- .../components/ShowMyHighlights.tsx | 4 ++- .../highlights/components/cardUtils.ts | 5 ++-- .../components/utils/onClickOutside.ts | 4 +-- .../highlights/utils/selectorsUtils.ts | 10 +++++-- .../utils/summaryHighlightsUtils.ts | 8 ++++-- src/app/content/hooks/registerPageView.ts | 3 +- .../components/KeyboardShortcutsPopup.tsx | 2 +- .../studyGuides/hooks/openStudyGuides.ts | 2 +- .../studyGuides/hooks/printStudyGuides.ts | 2 +- src/app/content/studyGuides/utils.ts | 25 ++++++++++++----- src/app/content/utils.ts | 8 +++--- src/app/content/utils/domUtils.ts | 9 ++++-- src/app/context/Services.tsx | 2 +- src/app/developer/routes.ts | 1 + src/app/domUtils.ts | 1 + src/app/errors/routes.ts | 1 + src/app/fpUtils.ts | 8 +++--- src/app/guards.ts | 28 ++++++++++--------- src/app/navigation/types.ts | 6 ++-- src/app/navigation/utils/index.ts | 13 +++++---- src/app/navigation/utils/scrollTarget.ts | 6 ++-- src/app/reactUtils.ts | 6 ++-- src/app/utils.ts | 6 ++-- src/gateways/createOSWebLoader.ts | 2 +- src/helpers/PromiseCollector.ts | 6 ++-- src/helpers/Sentry.ts | 2 +- src/helpers/analytics/bindEvents.ts | 8 +++++- src/helpers/analytics/index.ts | 11 ++++++-- src/helpers/analytics/utils.ts | 13 +++++---- src/helpers/applicationMessageError.ts | 2 +- src/helpers/mathjax.ts | 3 +- src/index.tsx | 4 +-- 40 files changed, 140 insertions(+), 89 deletions(-) diff --git a/src/app/components/Button.tsx b/src/app/components/Button.tsx index bf6c3044de..6b39756bf0 100644 --- a/src/app/components/Button.tsx +++ b/src/app/components/Button.tsx @@ -25,6 +25,7 @@ const applyColor = (color: ColorSet) => ` type Variant = 'primary' | 'secondary' | 'transparent' | 'default'; type Size = 'large' | 'medium' | 'small'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ComponentType = keyof JSX.IntrinsicElements | React.JSXElementConstructor; interface ButtonProps { diff --git a/src/app/components/Dropdown.tsx b/src/app/components/Dropdown.tsx index de52e7a683..b59a7fbf7b 100644 --- a/src/app/components/Dropdown.tsx +++ b/src/app/components/Dropdown.tsx @@ -12,6 +12,7 @@ import theme, { defaultFocusOutline } from '../theme'; import { preventDefault } from '../utils'; import { textStyle } from './Typography/base'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ComponentWithRef = React.ComponentType<{ref: React.RefObject}>; interface ToggleProps { className?: string; diff --git a/src/app/components/GoToTopButton.tsx b/src/app/components/GoToTopButton.tsx index 96a192e0af..7dcb60e116 100644 --- a/src/app/components/GoToTopButton.tsx +++ b/src/app/components/GoToTopButton.tsx @@ -41,7 +41,7 @@ export const GoToTopIcon = styled(AngleUp)` interface GoToTopButtonProps { i18nAriaLabel: string; onClick: () => void; - [key: string]: any; + [key: string]: unknown; } // tslint:disable-next-line: variable-name diff --git a/src/app/components/htmlMessage.tsx b/src/app/components/htmlMessage.tsx index 9ee415091c..6cd7b7927d 100644 --- a/src/app/components/htmlMessage.tsx +++ b/src/app/components/htmlMessage.tsx @@ -2,8 +2,8 @@ import React, { ComponentType, HTMLAttributes } from 'react'; import { useIntl } from 'react-intl'; // tslint:disable-next-line:variable-name -type Type = (messageKey: string, Component: ComponentType>) => - ComponentType<{values?: Record} & HTMLAttributes>; +type Type = (messageKey: string, Component: ComponentType>) => + ComponentType<{values?: Record} & HTMLAttributes>; // tslint:disable-next-line:variable-name export const htmlMessage: Type = (messageKey, Component) => ({values, ...props}) => diff --git a/src/app/content/components/Assigned.tsx b/src/app/content/components/Assigned.tsx index a803be3425..4601fae88a 100644 --- a/src/app/content/components/Assigned.tsx +++ b/src/app/content/components/Assigned.tsx @@ -36,7 +36,8 @@ const StyledButton = styled(Button)` `; // Override layout for Toast -const assignedMobileTop = (props: any) => getMobileSearchFailureTop(props) - bookBannerMobileMiniHeight; +const assignedMobileTop = (props: {mobileToolbarOpen: boolean}) => + getMobileSearchFailureTop(props) - bookBannerMobileMiniHeight; // tslint:disable-next-line: variable-name const ToastOverride = styled(PageToasts)` top: ${topbarDesktopHeight}rem; diff --git a/src/app/content/components/AssignedTopBar.tsx b/src/app/content/components/AssignedTopBar.tsx index 8035aa7db5..6d0c49f374 100644 --- a/src/app/content/components/AssignedTopBar.tsx +++ b/src/app/content/components/AssignedTopBar.tsx @@ -53,7 +53,7 @@ const useTextResizeIntegration = (handleChange: (value: TextResizerValue) => voi const win = window; const referrer = new URL(win.document.referrer); - const handler = (event: MessageEvent) => { + const handler = (event: MessageEvent) => { if ( event.data.type === 'TextSizeUpdate' && event.origin === referrer.origin && diff --git a/src/app/content/components/Page/connector.ts b/src/app/content/components/Page/connector.ts index 30d39f86bf..ca5b2474fb 100644 --- a/src/app/content/components/Page/connector.ts +++ b/src/app/content/components/Page/connector.ts @@ -33,7 +33,7 @@ export interface PagePropTypes { systemQueryParams: SystemQueryParams; textSize: TextResizerValue; lockNavigation: boolean; - ToastOverride: StyledComponent<'div', any, {}, never>; + ToastOverride: StyledComponent<'div', object, {}, never>; topHeadingLevel?: number; } diff --git a/src/app/content/components/Topbar/TextResizer.tsx b/src/app/content/components/Topbar/TextResizer.tsx index 927741687b..043578be4b 100644 --- a/src/app/content/components/Topbar/TextResizer.tsx +++ b/src/app/content/components/Topbar/TextResizer.tsx @@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl'; import decreaseTextSizeIcon from '../../../../assets/text-size-decrease.svg'; import increaseTextSizeIcon from '../../../../assets/text-size-increase.svg'; import textSizeIcon from '../../../../assets/text-size.svg'; +import { HTMLInputElement } from '@openstax/types/lib.dom'; import { textResizerDefaultValue, textResizerMaxValue, @@ -23,7 +24,7 @@ export interface TextResizerProps { // tslint:disable-next-line:variable-name export const TextResizer = (props: TextResizerProps) => { const onChangeTextSize = (e: React.FormEvent) => { - const target = (e as any).currentTarget; + const target = e.currentTarget; const value = parseInt(target.value, 10) as TextResizerValue; if (!textResizerValues.includes(value)) { return; } props.setTextSize(value); diff --git a/src/app/content/highlights/components/ShowMyHighlights.tsx b/src/app/content/highlights/components/ShowMyHighlights.tsx index a806993c96..a4c18c347c 100644 --- a/src/app/content/highlights/components/ShowMyHighlights.tsx +++ b/src/app/content/highlights/components/ShowMyHighlights.tsx @@ -16,6 +16,7 @@ import Highlights from './Highlights'; import HighlightsToasts from './HighlightsToasts'; import * as Styled from './ShowMyHighlightsStyles'; import Filters from './SummaryPopup/Filters'; +import { ServiceConsumer } from '../../../context/Services'; interface ShowMyHighlightsProps { authenticated: User | undefined; @@ -119,4 +120,5 @@ const connector = connect( }) ); -export default flow(withServices, connector)(ShowMyHighlights as React.ComponentType); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default flow(withServices, connector)(ShowMyHighlights as unknown as React.ComponentType); diff --git a/src/app/content/highlights/components/cardUtils.ts b/src/app/content/highlights/components/cardUtils.ts index 09bc6845de..2b6dea3cd5 100644 --- a/src/app/content/highlights/components/cardUtils.ts +++ b/src/app/content/highlights/components/cardUtils.ts @@ -98,10 +98,11 @@ const updateStackedCardsPositions = ( const marginToAdd = index > 0 || addAditionalMarginForTheFirstCard ? remsToPx(cardMarginBottom) : 0; const lastVisibleCardBottom = lastVisibleCardPosition + lastVisibleCardHeight; const stackedTopOffset = Math.max(topOffset, lastVisibleCardBottom + marginToAdd); + const heightsForId = heights.get(highlight.id); - if (heights.get(highlight.id) && !checkIfHiddenByCollapsedAncestor(highlight)) { + if (heightsForId && !checkIfHiddenByCollapsedAncestor(highlight)) { lastVisibleCardPosition = stackedTopOffset; - lastVisibleCardHeight = heights.get(highlight.id)!; + lastVisibleCardHeight = heightsForId; } positions.set(highlight.id, stackedTopOffset); diff --git a/src/app/content/highlights/components/utils/onClickOutside.ts b/src/app/content/highlights/components/utils/onClickOutside.ts index 47af716e5f..e4329c833a 100644 --- a/src/app/content/highlights/components/utils/onClickOutside.ts +++ b/src/app/content/highlights/components/utils/onClickOutside.ts @@ -52,10 +52,10 @@ export const useOnClickOutside = ( export default onClickOutside; -const isRefWithHtmlElement = (el: any): el is React.RefObject => { +const isRefWithHtmlElement = (el: {current?: unknown}): el is React.RefObject => { return el instanceof Object && isHtmlElement(el.current); }; -export const isElementForOnClickOutside = (el: any): el is HTMLElement | React.RefObject => { +export const isElementForOnClickOutside = (el: object): el is HTMLElement | React.RefObject => { return isHtmlElement(el) || isRefWithHtmlElement(el); }; diff --git a/src/app/content/highlights/utils/selectorsUtils.ts b/src/app/content/highlights/utils/selectorsUtils.ts index 9c9e104a17..f79cceb8e1 100644 --- a/src/app/content/highlights/utils/selectorsUtils.ts +++ b/src/app/content/highlights/utils/selectorsUtils.ts @@ -42,6 +42,12 @@ export const getLoadedCountsPerSource = (sources: SummaryHighlights | null) => f mapValues(size) )(sources); -export const checkIfHasMoreResults = (loaded: any, filteredCounts: any, pagination: SummaryHighlightsPagination) => { - return !!(pagination || Object.keys(omit(Object.keys(loaded), filteredCounts)).length); +export const checkIfHasMoreResults = ( + loaded: object, + filteredCounts: object, + pagination: SummaryHighlightsPagination +) => { + return !!( + pagination || Object.keys(omit(Object.keys(loaded), filteredCounts)).length + ); }; diff --git a/src/app/content/highlights/utils/summaryHighlightsUtils.ts b/src/app/content/highlights/utils/summaryHighlightsUtils.ts index 133820ec96..ab33873ad3 100644 --- a/src/app/content/highlights/utils/summaryHighlightsUtils.ts +++ b/src/app/content/highlights/utils/summaryHighlightsUtils.ts @@ -243,12 +243,16 @@ export const removeFromTotalCounts = ( totalCounts: CountsPerSource, highlight: HighlightData ) => { - if (totalCounts[highlight.sourceId] && totalCounts[highlight.sourceId][highlight.color]) { + // Type issue: totalCounts does not allow members to be undefined, + // but in practice they are + const tc = totalCounts[highlight.sourceId]?.[highlight.color]; + + if (tc) { const newTotal = { ...totalCounts, [highlight.sourceId]: { ...totalCounts[highlight.sourceId], - [highlight.color]: totalCounts[highlight.sourceId][highlight.color]! - 1, + [highlight.color]: tc - 1, }, }; diff --git a/src/app/content/hooks/registerPageView.ts b/src/app/content/hooks/registerPageView.ts index 7e67533635..8bd32a1c3f 100644 --- a/src/app/content/hooks/registerPageView.ts +++ b/src/app/content/hooks/registerPageView.ts @@ -2,9 +2,10 @@ import googleAnalyticsClient from '../../../gateways/googleAnalyticsClient'; import * as selectNavigation from '../../navigation/selectors'; import { AnyRoute, RouteHookBody } from '../../navigation/types'; import { AppServices, MiddlewareAPI } from '../../types'; +import { OutputParams } from 'query-string'; export const hookBody: RouteHookBody = (services: MiddlewareAPI & AppServices) => { - let lastTrackedLocation: any; + let lastTrackedLocation: { query: OutputParams; pathname: string; }; return async(action) => { const state = services.getState(); diff --git a/src/app/content/keyboardShortcuts/components/KeyboardShortcutsPopup.tsx b/src/app/content/keyboardShortcuts/components/KeyboardShortcutsPopup.tsx index 9412d12e82..979df2fbaf 100644 --- a/src/app/content/keyboardShortcuts/components/KeyboardShortcutsPopup.tsx +++ b/src/app/content/keyboardShortcuts/components/KeyboardShortcutsPopup.tsx @@ -14,7 +14,7 @@ import * as ksSelectors from '../selectors'; import ShowKeyboardShortcuts from './ShowKeyboardShortcuts'; // tslint:disable-next-line:variable-name -const StyledModal = styled(Modal)` +const StyledModal = styled(Modal)` max-width: 92.8rem; `; diff --git a/src/app/content/studyGuides/hooks/openStudyGuides.ts b/src/app/content/studyGuides/hooks/openStudyGuides.ts index b540722d3e..4ae70baddd 100644 --- a/src/app/content/studyGuides/hooks/openStudyGuides.ts +++ b/src/app/content/studyGuides/hooks/openStudyGuides.ts @@ -20,7 +20,7 @@ export const hookBody: ActionHookBody = (services) => as if (loggedOutAndQueryMissingFirstChapter || (!notLoggedIn && defaultFilter)) { services.dispatch(replace(match as AnyMatch, { - search: getQueryForParam(summaryFilters as any as Record, query), + search: getQueryForParam(summaryFilters as unknown as Record, query), })); } else { const studyGuides = select.summaryStudyGuides(state); diff --git a/src/app/content/studyGuides/hooks/printStudyGuides.ts b/src/app/content/studyGuides/hooks/printStudyGuides.ts index 9217accf1b..89eee0369f 100644 --- a/src/app/content/studyGuides/hooks/printStudyGuides.ts +++ b/src/app/content/studyGuides/hooks/printStudyGuides.ts @@ -11,7 +11,7 @@ export const asyncHelper = async(services: MiddlewareAPI & AppServices) => { try { response = await loadMore(services); - } catch (error: any) { + } catch (error: unknown) { services.dispatch(toggleStudyGuidesSummaryLoading(false)); throw ensureApplicationErrorType(error, new StudyGuidesPopupPrintError({ destination: 'studyGuides' })); } diff --git a/src/app/content/studyGuides/utils.ts b/src/app/content/studyGuides/utils.ts index d5a6fdc8f6..731d659938 100644 --- a/src/app/content/studyGuides/utils.ts +++ b/src/app/content/studyGuides/utils.ts @@ -26,18 +26,29 @@ export const getFiltersFromQuery = (query: OutputParams) => { return { colors, locationIds }; }; -export const updateQueryFromFilterChange = (dispatch: Dispatch, state: AppState, change: SummaryFiltersUpdate) => { - const updatedFilters: {[key: string]: any} = updateSummaryFilters(selectors.summaryFilters(state), change); +export const updateQueryFromFilterChange = ( + dispatch: Dispatch, + state: AppState, + change: SummaryFiltersUpdate +) => { + const updatedFilters = updateSummaryFilters( + selectors.summaryFilters(state), + change + ) as unknown as Parameters[0]; // convert empty filter arrys to null so they are preserved in query for (const filter in updatedFilters) { - if (updatedFilters[filter] && !updatedFilters[filter].length) { + if (updatedFilters[filter] && !updatedFilters[filter]?.length) { updatedFilters[filter] = null; } } const match = navigation.match(state); const existingQuery = navigation.query(state); - if (!match ) { return; } - dispatch(replace(match, { - search: getQueryForParam(updatedFilters as any as Record, existingQuery), - })); + if (!match) { + return; + } + dispatch( + replace(match, { + search: getQueryForParam(updatedFilters, existingQuery), + }) + ); }; diff --git a/src/app/content/utils.ts b/src/app/content/utils.ts index d78b53056c..7af9443755 100644 --- a/src/app/content/utils.ts +++ b/src/app/content/utils.ts @@ -77,7 +77,7 @@ export const parseContents = (book: ArchiveBook, contents: Array ({ +const pickArchiveFields = (archiveBook: VersionedArchiveBookWithConfig): VersionedArchiveBookWithConfig => ({ archiveVersion: archiveBook.archiveVersion, contentVersion: archiveBook.contentVersion, id: archiveBook.id, @@ -100,7 +100,8 @@ export const formatBookData = ( ): O extends OSWebBook ? BookWithOSWebData : VersionedArchiveBookWithConfig => { if (osWebBook === undefined) { // as any necessary https://github.com/Microsoft/TypeScript/issues/13995 - return pickArchiveFields(archiveBook) as VersionedArchiveBookWithConfig as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return pickArchiveFields(archiveBook) as any; } return { ...pickArchiveFields(archiveBook), @@ -116,8 +117,7 @@ export const formatBookData = ( subject: osWebBook.book_subjects[0]?.subject_name, subjects: osWebBook.book_subjects, theme: osWebBook.cover_color, - // as any necessary https://github.com/Microsoft/TypeScript/issues/13995 - } as BookWithOSWebData as any; + } as BookWithOSWebData; }; export const makeUnifiedBookLoader = ( diff --git a/src/app/content/utils/domUtils.ts b/src/app/content/utils/domUtils.ts index d5e9436d39..4e9684d21f 100644 --- a/src/app/content/utils/domUtils.ts +++ b/src/app/content/utils/domUtils.ts @@ -1,4 +1,5 @@ import { HTMLDetailsElement, HTMLElement, MouseEvent } from '@openstax/types/lib.dom'; +import React from 'react'; if (typeof(document) !== 'undefined') { import(/* webpackChunkName: "Node.children" */ 'mdn-polyfills/Node.prototype.children'); @@ -99,9 +100,11 @@ export const setSidebarHeight = (sidebar: HTMLElement, window: Window) => { }; }; -export const fixSafariScrolling = (event: any) => { - event.target.style.overflowY = 'hidden'; - setTimeout(() => { event.target.style.overflowY = 'auto'; }); +export const fixSafariScrolling = (event: Event) => { + const target = event.target as HTMLElement; + + target.style.overflowY = 'hidden'; + setTimeout(() => { target.style.overflowY = 'auto'; }); }; export const isClickWithModifierKeys = (e: React.MouseEvent | MouseEvent) => diff --git a/src/app/context/Services.tsx b/src/app/context/Services.tsx index 6eb128201c..075b1e2194 100644 --- a/src/app/context/Services.tsx +++ b/src/app/context/Services.tsx @@ -20,7 +20,7 @@ export { Provider, }; -interface ServiceConsumer { +export interface ServiceConsumer { services: AppServices & MiddlewareAPI; } diff --git a/src/app/developer/routes.ts b/src/app/developer/routes.ts index 1a34b9bf85..9e9f2e69ae 100644 --- a/src/app/developer/routes.ts +++ b/src/app/developer/routes.ts @@ -9,6 +9,7 @@ export const developerHome: Route = { loader: () => import(/* webpackChunkName: "route-developer-home" */ './components/Home'), loading: () => null, modules: ['route-developer-home'], + // eslint-disable-next-line @typescript-eslint/no-explicit-any webpack: /* istanbul ignore next */ () => [(require as any).resolveWeak('./components/Home')], }), getUrl: (): string => pathToRegexp.compile(ROUTES_PATH)(), diff --git a/src/app/domUtils.ts b/src/app/domUtils.ts index b2299c5fb8..eee8437a9c 100644 --- a/src/app/domUtils.ts +++ b/src/app/domUtils.ts @@ -74,6 +74,7 @@ export const findFirstAncestorOrSelfOfType = export const findFirstAncestorOrSelf = ( node: Node, + // eslint-disable-next-line @typescript-eslint/no-explicit-any predicate: ((e: any) => boolean) | ((e: any) => e is T) ): T | void => { if (isHtmlElement(node) && predicate(node)) { diff --git a/src/app/errors/routes.ts b/src/app/errors/routes.ts index 2141308606..73d0fd7965 100644 --- a/src/app/errors/routes.ts +++ b/src/app/errors/routes.ts @@ -11,6 +11,7 @@ const loadableArgs = { loader: () => import(/* webpackChunkName: "LoaderCentered" */ './components/LoaderCentered'), loading: () => null, modules: ['LoaderCentered'], + // eslint-disable-next-line @typescript-eslint/no-explicit-any webpack: /* istanbul ignore next */ () => [(require as any).resolveWeak('./components/LoaderCentered')], }; diff --git a/src/app/fpUtils.ts b/src/app/fpUtils.ts index f8ca177b0a..67a5784c6a 100644 --- a/src/app/fpUtils.ts +++ b/src/app/fpUtils.ts @@ -1,9 +1,9 @@ import { FirstArgumentType } from './types'; -export const and = (...predicates: Array<(...args: A) => boolean>) => (...args: A) => +export const and = (...predicates: Array<(...args: A) => boolean>) => (...args: A) => predicates.reduce((result, predicate) => result && predicate(...args), true); -export const or = (...predicates: Array<(...args: A) => boolean>) => (...args: A) => +export const or = (...predicates: Array<(...args: A) => boolean>) => (...args: A) => predicates.reduce((result, predicate) => result || predicate(...args), false); export const ifUndefined = (item: I | undefined, defaultValue: D): I | D => @@ -12,13 +12,13 @@ export const ifUndefined = (item: I | undefined, defaultValue: D): I | D /* * returns a function that inverts the result of the passed in function */ -export const not = (wrapped: (...args: A) => any) => (...args: A) => !wrapped(...args); +export const not = (wrapped: (...args: A) => unknown) => (...args: A) => !wrapped(...args); /* * returns a function that evaluates its argument against the given predicate */ // tslint:disable-next-line:ban-types -export const match = (predicate: T) => (arg: T extends Function ? FirstArgumentType : T) => { +export const match = (predicate: T) => (arg: T extends Function ? FirstArgumentType : T) => { if (typeof predicate === 'function') { return !!predicate(arg); } diff --git a/src/app/guards.ts b/src/app/guards.ts index ae8127045f..f3e5f0a147 100644 --- a/src/app/guards.ts +++ b/src/app/guards.ts @@ -3,21 +3,23 @@ import { assertWindow } from './utils'; export const isDefined = (x: X): x is Exclude => x !== undefined; -export const isNode = (thing: any): thing is dom.Node => +type PossibleNode = {nodeType?: number; nodeName?: string}; + +export const isNode = (thing: unknown): thing is dom.Node => typeof thing === 'object' && thing !== null - && thing.nodeType === 1 - && typeof thing.nodeName === 'string' + && (thing as PossibleNode).nodeType === 1 + && typeof (thing as PossibleNode).nodeName === 'string' ; -export const isElement = (thing: any): thing is dom.Element => +export const isElement = (thing: unknown): thing is dom.Element => isNode(thing) - && (thing as any).tagName !== undefined + && (thing as {tagName?: unknown}).tagName !== undefined ; -export const isHtmlElement = (thing: any): thing is dom.HTMLElement => +export const isHtmlElement = (thing: unknown): thing is dom.HTMLElement => isElement(thing) - && (thing as any).title !== undefined + && (thing as {title?: unknown}).title !== undefined ; const inputTypesWithoutTextInput: Array = [ @@ -26,7 +28,7 @@ const inputTypesWithoutTextInput: Array = [ const contenteditableEnabledValues: Array = [ '', 'true' ]; -export const isTextInputHtmlElement = (thing: any): thing is dom.HTMLElement => +export const isTextInputHtmlElement = (thing: unknown): thing is dom.HTMLElement => isHtmlElement(thing) && ( thing.tagName === 'TEXTAREA' || ( thing.tagName === 'INPUT' && !inputTypesWithoutTextInput.includes(thing.getAttribute('type')) @@ -34,14 +36,14 @@ export const isTextInputHtmlElement = (thing: any): thing is dom.HTMLElement => ) ; -export const isPlainObject = (thing: any): thing is {} => - thing instanceof Object && thing.__proto__.constructor === Object; +export const isPlainObject = (thing: object): thing is {} => + thing instanceof Object && Object.getPrototypeOf(thing).constructor === Object; -export const isWindow = (thing: any): thing is Window => +export const isWindow = (thing: unknown): thing is Window => assertWindow() === thing; -export const isHtmlElementWithHighlight = (thing: any): thing is dom.HTMLElement => +export const isHtmlElementWithHighlight = (thing: unknown): thing is dom.HTMLElement => isHtmlElement(thing) && thing.hasAttribute('data-highlight-id'); -export const isKeyOf = (obj: O, thing: any): thing is keyof O => +export const isKeyOf = (obj: O, thing: unknown): thing is keyof O => typeof thing === 'string' && thing in obj; diff --git a/src/app/navigation/types.ts b/src/app/navigation/types.ts index 31912d9f41..ca28758cbf 100644 --- a/src/app/navigation/types.ts +++ b/src/app/navigation/types.ts @@ -10,7 +10,7 @@ export type State = Location & { }; export type RouteParams = R extends Route ? P : never; -export type RouteState = R extends Route ? S : never; +export type RouteState = R extends Route ? S : never; type UnionRouteMatches = R extends AnyRoute ? Match : never; type UnionHistoryActions = R extends AnyRoute ? HistoryAction : never; @@ -37,7 +37,7 @@ export interface RouteParamsType { [key: string]: string | RouteParamsType; } export interface RouteStateType { - [key: string]: any; + [key: string]: unknown; } export interface Route< @@ -65,7 +65,7 @@ export type AnyMatch = UnionRouteMatches; export type RouteHookBody = (helpers: MiddlewareAPI & AppServices) => (locationChange: Required>>) => - Promise | void; + Promise | void; export interface ScrollTarget { type: string; diff --git a/src/app/navigation/utils/index.ts b/src/app/navigation/utils/index.ts index 31cfed1912..66ad537659 100644 --- a/src/app/navigation/utils/index.ts +++ b/src/app/navigation/utils/index.ts @@ -92,7 +92,10 @@ export const findRouteMatch = (routes: AnyRoute[], location: Location | string): } }; -export const matchSearch = >>(action: M, search?: queryString.OutputParams) => { +// issue with passing AnyMatch into these https://stackoverflow.com/q/65727184/14809536 +type MatchAnyRoute = Match>; // eslint-disable-line @typescript-eslint/no-explicit-any + +export const matchSearch = (action: M, search?: queryString.OutputParams) => { const route = querystring.parse( action.route.getSearch ? action.route.getSearch(action.params) : '' ); @@ -103,11 +106,9 @@ export const matchSearch = >>(action: M, search? }); }; -// issue with passing AnyMatch into this https://stackoverflow.com/q/65727184/14809536 -export const matchPathname = >>(action: M) => action.route.getUrl(action.params); +export const matchPathname = (action: M) => action.route.getUrl(action.params); -// issue with passing AnyMatch into this https://stackoverflow.com/q/65727184/14809536 -export const matchUrl = >>(action: M) => { +export const matchUrl = (action: M) => { const path = matchPathname(action); const search = matchSearch(action); return `${path}${search ? `?${search}` : ''}`; @@ -153,7 +154,7 @@ export const findPathForParams = (params: object, paths: string[]) => { }; export const getQueryForParam = ( - values: Record, + values: Record, existingQuery?: string | OutputParams ) => { if (existingQuery) { diff --git a/src/app/navigation/utils/scrollTarget.ts b/src/app/navigation/utils/scrollTarget.ts index 078e5719ec..4a407fe655 100644 --- a/src/app/navigation/utils/scrollTarget.ts +++ b/src/app/navigation/utils/scrollTarget.ts @@ -3,7 +3,7 @@ import { isPlainObject } from '../../guards'; import { ScrollTarget } from '../types'; export const isScrollTarget = ( - object: { [key: string]: any } + object: Record ): object is ScrollTarget => { if ( !object.elementId @@ -18,14 +18,14 @@ export const getScrollTargetFromQuery = ( hash: string ): ScrollTarget | null => { if (!hash || !query.target || Array.isArray(query.target)) { return null; } - let parsed: any; + let parsed: Record; try { parsed = JSON.parse(decodeURIComponent(query.target)); } catch { return null; } if (isPlainObject(parsed)) { - (parsed as {[key: string]: any}).elementId = hash.replace('#', ''); + (parsed as {[key: string]: unknown}).elementId = hash.replace('#', ''); if (isScrollTarget(parsed)) { return parsed; } } return null; diff --git a/src/app/reactUtils.ts b/src/app/reactUtils.ts index c66c004960..99a473bdc6 100644 --- a/src/app/reactUtils.ts +++ b/src/app/reactUtils.ts @@ -114,7 +114,7 @@ export const onFocusInOrOutHandler = ( cb: () => void, type: 'focusin' | 'focusout' ) => () => { - const el = ref && ref.current; + const el = ref?.current; if (!el) { return; } const handler = (event: FocusEvent) => { @@ -124,12 +124,12 @@ export const onFocusInOrOutHandler = ( if ( type === 'focusout' - && (!isElement(target) || !ref.current!.contains(target)) + && (!isElement(target) || !(ref.current as HTMLElement).contains(target)) ) { cb(); } else if ( type === 'focusin' - && (isElement(target) && ref.current!.contains(target)) + && (isElement(target) && (ref.current as HTMLElement).contains(target)) ) { cb(); } diff --git a/src/app/utils.ts b/src/app/utils.ts index 897b412140..f4a03bad90 100644 --- a/src/app/utils.ts +++ b/src/app/utils.ts @@ -71,7 +71,7 @@ const makeCatchError = ({dispatch, getState}: MiddlewareAPI) => (e: Error) => { dispatch(showErrorDialog()); }; -export const isNetworkError = (error: any) => { +export const isNetworkError = (error: unknown) => { return error instanceof TypeError && error.message.includes('Failed to fetch'); }; @@ -81,7 +81,7 @@ export const mergeRefs = (...refs: Array | undefined>) => (ref: T) => if (typeof resolvableRef === 'function') { resolvableRef(ref); } else if (resolvableRef) { - (resolvableRef as any).current = ref; + (resolvableRef as React.MutableRefObject).current = ref; } }); }; @@ -161,7 +161,7 @@ export const memoizeStateToProps = (fun: (state: AppState) => }; }; -export const tuple = (...args: A) => args; +export const tuple = (...args: A) => args; // tslint:disable-next-line: max-classes-per-file export class UnauthenticatedError extends ApplicationError {} diff --git a/src/gateways/createOSWebLoader.ts b/src/gateways/createOSWebLoader.ts index d975fb8726..5767171774 100644 --- a/src/gateways/createOSWebLoader.ts +++ b/src/gateways/createOSWebLoader.ts @@ -58,7 +58,7 @@ const defaultOptions = () => ({ export default (prefix: string, options: Options = {}) => { const {cache} = {...defaultOptions(), ...options}; const baseUrl = `${prefix}/v2/pages`; - const toJson = (response: any) => response.json() as Promise; + const toJson = (response: Response) => response.json(); const firstRecord = (data: OSWebResponse) => data.items[0]; diff --git a/src/helpers/PromiseCollector.ts b/src/helpers/PromiseCollector.ts index 70ab060f7e..0910004368 100644 --- a/src/helpers/PromiseCollector.ts +++ b/src/helpers/PromiseCollector.ts @@ -1,11 +1,11 @@ export default class PromiseCollector { - private _promises: Array> = []; + private _promises: Array> = []; - public get promises(): ReadonlyArray> { + public get promises(): ReadonlyArray> { return this._promises; } - public add(promise: Promise): void { + public add(promise: Promise): void { this._promises.push(promise); // ignore thrown errors because we are forking to remove from the collection promise.catch(() => null).finally(() => { diff --git a/src/helpers/Sentry.ts b/src/helpers/Sentry.ts index f34110d834..8877f64d9b 100644 --- a/src/helpers/Sentry.ts +++ b/src/helpers/Sentry.ts @@ -59,7 +59,7 @@ export default { return typeof(window) !== 'undefined' && config.SENTRY_ENABLED; }, - captureException(error: any, level: Sentry.SeverityLevel = 'error') { + captureException(error: unknown, level: Sentry.SeverityLevel = 'error') { if (!error) { return; } diff --git a/src/helpers/analytics/bindEvents.ts b/src/helpers/analytics/bindEvents.ts index b9b0bb373b..eefd60ccdc 100644 --- a/src/helpers/analytics/bindEvents.ts +++ b/src/helpers/analytics/bindEvents.ts @@ -25,6 +25,7 @@ import * as openStudyGuides from './events/studyGuides/openPopUp'; import * as openUTG from './events/studyGuides/openUTG'; import * as unload from './events/unload'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type EventConstructor = (...args: Args) => (AnalyticsEvent | void); type Selector = (state: AppState) => object; interface Event {track: EventConstructor; selector: Selector; } @@ -41,7 +42,12 @@ const triggerEvent = (event: E): E['track'] => (...args) => { }; const bindTrackSelector = (event: E, track: E['track']) => (state: AppState | (() => AppState)) => { - type RemainingArgumentTypes = E['track'] extends (d: ReturnType, ...args: infer A) => any ? A : never; + type RemainingArgumentTypes = E['track'] extends ( + d: ReturnType, + ...args: infer A + ) => unknown + ? A + : never; return (...args: RemainingArgumentTypes) => { const data = event.selector(typeof state === 'function' ? state() : state); diff --git a/src/helpers/analytics/index.ts b/src/helpers/analytics/index.ts index c19b8272eb..79dee61761 100644 --- a/src/helpers/analytics/index.ts +++ b/src/helpers/analytics/index.ts @@ -64,13 +64,18 @@ export const useAnalyticsEvent = (eventType: T) = // but the returned function has the correct args so whatever const services = useServices(); const event = services.analytics[eventType]; - const data = useSelector(event.selector as any); + const data = useSelector(event.selector); type E = typeof services['analytics'][T]; - type RemainingArgumentTypes = E['track'] extends (d: ReturnType, ...args: infer A) => any ? A : never; + type RemainingArgumentTypes = E['track'] extends ( + d: ReturnType, + ...args: infer A + ) => unknown + ? A + : never; return (...args: RemainingArgumentTypes) => { - (event.track as any)(data, ...args); + (event.track as (...args: unknown[]) => void)(data, ...args); }; }; diff --git a/src/helpers/analytics/utils.ts b/src/helpers/analytics/utils.ts index 4b512d622e..6e8a3211b0 100644 --- a/src/helpers/analytics/utils.ts +++ b/src/helpers/analytics/utils.ts @@ -1,4 +1,5 @@ import { + Event, HTMLAnchorElement, HTMLButtonElement, HTMLDetailsElement, @@ -18,7 +19,7 @@ type InteractableConfig = { getState?: (element: T) => S, getStateChange: (element: T, state: S) => string | undefined; getTarget?: (element: T, state: S) => HTMLElement | undefined, - match: (element: any) => element is T; + match: (element: unknown) => element is T; state?: S; }; @@ -34,13 +35,13 @@ export const interactableElementEvents = [ (summary.parentElement as HTMLDetailsElement | undefined)?.open ? 'close' : 'open', getTarget: (summary: HTMLElement) => summary.parentElement?.tagName === 'DETAILS' ? summary.parentElement : undefined, - match: (element: any): element is HTMLElement => + match: (element: unknown): element is HTMLElement => isHtmlElement(element) && element.tagName === 'SUMMARY', }), makeInteractableConfig({ events: ['click'], getStateChange: (_element: HTMLAnchorElement | HTMLButtonElement) => undefined, - match: (element: any): element is HTMLAnchorElement | HTMLButtonElement => + match: (element: unknown): element is HTMLAnchorElement | HTMLButtonElement => isHtmlElement(element) && ['A', 'BUTTON'].includes(element.tagName), }), makeInteractableConfig({ @@ -54,12 +55,12 @@ export const interactableElementEvents = [ window.document.activeElement instanceof window.HTMLIFrameElement ? window.document.activeElement : state.iframe, - match: (element: any): element is Window => isWindow(element), + match: (element: unknown): element is Window => isWindow(element), state: {iframe: undefined} as {iframe: HTMLIFrameElement | undefined}, }), ]; -type CombineGuardResults = UnionToIntersection x is infer T) ? T : never>; +type CombineGuardResults = UnionToIntersection x is infer T) ? T : never>; export const addInteractiveListeners = ( window: Window, @@ -71,7 +72,7 @@ export const addInteractiveListeners = ( for (const event of config.events) { // eslint-disable-next-line no-loop-func window.addEventListener(event, (e: Event) => { - const match = (el: any): el is CombineGuardResults => config.match(el); + const match = (el: unknown): el is CombineGuardResults => config.match(el); const target = e.target && match(e.target) ? e.target : ( isNode(e.target) && findFirstAncestorOrSelf(e.target, match) diff --git a/src/helpers/applicationMessageError.ts b/src/helpers/applicationMessageError.ts index 50ad50b5aa..3d7a437ef9 100644 --- a/src/helpers/applicationMessageError.ts +++ b/src/helpers/applicationMessageError.ts @@ -24,7 +24,7 @@ export const makeToastMessageError = (messageKey: string) => class extends Toast /** * Return @param error if it is instance of ApplicationError or @param customError in other cases */ -export const ensureApplicationErrorType = (error: any, customError: Error | (() => Error)) => { +export const ensureApplicationErrorType = (error: unknown, customError: Error | (() => Error)) => { if (error instanceof ApplicationError) { return error; } diff --git a/src/helpers/mathjax.ts b/src/helpers/mathjax.ts index bcd2f49277..13a5f8b5b0 100644 --- a/src/helpers/mathjax.ts +++ b/src/helpers/mathjax.ts @@ -28,6 +28,7 @@ const MATHJAX_CONFIG = { displayMath: [[MATH_MARKER_BLOCK, MATH_MARKER_BLOCK]], inlineMath: [[MATH_MARKER_INLINE, MATH_MARKER_INLINE]], }, + AuthorInit: undefined as unknown, }; const findProcessedMath = (root: Element): Element[] => Array.from(root.querySelectorAll('.MathJax math')); @@ -160,7 +161,7 @@ function startMathJax() { // Call MathJax.Configured once MathJax loads and // loads this config JSON since the CDN URL // says to `delayStartupUntil=configured` - (MATHJAX_CONFIG as any).AuthorInit = configuredCallback; + MATHJAX_CONFIG.AuthorInit = configuredCallback; return window.MathJax = MATHJAX_CONFIG; } } diff --git a/src/index.tsx b/src/index.tsx index 06d1646bdc..2add4d9f9f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -105,8 +105,8 @@ if (window.__PRELOADED_STATE__) { function doneRendering() { const initialActions = queryString.parse(window.location.search).initialActions; if (typeof(initialActions) === 'string') { - const actions = JSON.parse(initialActions); - actions.forEach((action: any) => app.store.dispatch(action)); + const actions: Parameters[0][] = JSON.parse(initialActions); + actions.forEach((action) => app.store.dispatch(action)); } }