]> source.dussan.org Git - sonarqube.git/commitdiff
Activate useful @typescript-eslint rules & fix warnings in `design-system/` and relat...
authorDavid Cho-Lerat <117642976+david-cho-lerat-sonarsource@users.noreply.github.com>
Mon, 13 Mar 2023 16:51:08 +0000 (17:51 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 13 Mar 2023 20:02:45 +0000 (20:02 +0000)
28 files changed:
server/sonar-web/.eslintrc
server/sonar-web/.eslintrc-typescript [new file with mode: 0644]
server/sonar-web/design-system/.eslintrc
server/sonar-web/design-system/package.json
server/sonar-web/design-system/src/components/Checkbox.tsx
server/sonar-web/design-system/src/components/ClickEventBoundary.tsx
server/sonar-web/design-system/src/components/DeferredSpinner.tsx
server/sonar-web/design-system/src/components/MainMenu.tsx
server/sonar-web/design-system/src/components/OutsideClickHandler.tsx
server/sonar-web/design-system/src/components/Tooltip.tsx
server/sonar-web/design-system/src/components/__tests__/InputSearch-test.tsx
server/sonar-web/design-system/src/components/__tests__/Link-test.tsx
server/sonar-web/design-system/src/components/__tests__/NavLink-test.tsx
server/sonar-web/design-system/src/components/__tests__/Tooltip-test.tsx
server/sonar-web/design-system/src/components/clipboard.tsx
server/sonar-web/design-system/src/components/icons/Icon.tsx
server/sonar-web/design-system/src/components/popups.tsx
server/sonar-web/design-system/src/helpers/__tests__/positioning-test.ts
server/sonar-web/design-system/src/helpers/positioning.ts
server/sonar-web/design-system/src/helpers/testUtils.tsx
server/sonar-web/design-system/src/helpers/theme.ts
server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx
server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx
server/sonar-web/src/main/js/app/components/global-search/GlobalSearchShowMore.tsx
server/sonar-web/src/main/js/app/components/global-search/utils.ts
server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx
server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx

index ca97fff5e6d289196a3ec7feda7dfbf446a5c19c..1e45ca41a4f4c20671e2311df04504ec66e74f9f 100644 (file)
@@ -1,5 +1,5 @@
 {
-  "extends": "sonarqube",
+  "extends": ["sonarqube", "./.eslintrc-typescript"],
   "rules": {
     "camelcase": "off",
     "promise/no-return-wrap": "warn",
diff --git a/server/sonar-web/.eslintrc-typescript b/server/sonar-web/.eslintrc-typescript
new file mode 100644 (file)
index 0000000..22573c4
--- /dev/null
@@ -0,0 +1,54 @@
+{
+  "extends": [
+    "plugin:@typescript-eslint/recommended",
+    "plugin:@typescript-eslint/recommended-requiring-type-checking",
+    "plugin:@typescript-eslint/strict"
+  ],
+  "parserOptions": { "project": "./tsconfig.json" },
+  "rules": {
+    "@typescript-eslint/adjacent-overload-signatures": "warn",
+    "@typescript-eslint/array-type": ["warn", { "default": "array-simple" }],
+    "@typescript-eslint/await-thenable": "off",
+    "@typescript-eslint/ban-ts-comment": "warn",
+    "@typescript-eslint/ban-types": "warn",
+    "@typescript-eslint/consistent-indexed-object-style": "off",
+    "@typescript-eslint/default-param-last": "warn",
+    "@typescript-eslint/no-array-constructor": "warn",
+    "@typescript-eslint/no-confusing-non-null-assertion": "off",
+    "@typescript-eslint/no-confusing-void-expression": "warn",
+    "@typescript-eslint/no-empty-function": "warn",
+    "@typescript-eslint/no-empty-interface": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/no-extra-non-null-assertion": "off",
+    "@typescript-eslint/no-floating-promises": "warn",
+    "@typescript-eslint/no-for-in-array": "warn",
+    "@typescript-eslint/no-inferrable-types": "off",
+    "@typescript-eslint/no-invalid-this": "warn",
+    "@typescript-eslint/no-loss-of-precision": "warn",
+    "@typescript-eslint/no-misused-new": "warn",
+    "@typescript-eslint/no-misused-promises": "warn",
+    "@typescript-eslint/no-namespace": "warn",
+    "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "off",
+    "@typescript-eslint/no-non-null-asserted-optional-chain": "off",
+    "@typescript-eslint/no-redundant-type-constituents": "warn",
+    "@typescript-eslint/no-this-alias": "warn",
+    "@typescript-eslint/no-unnecessary-type-assertion": "off",
+    "@typescript-eslint/no-unnecessary-type-constraint": "warn",
+    "@typescript-eslint/no-unsafe-argument": "warn",
+    "@typescript-eslint/no-unsafe-assignment": "warn",
+    "@typescript-eslint/no-unsafe-call": "warn",
+    "@typescript-eslint/no-unsafe-member-access": "warn",
+    "@typescript-eslint/no-unsafe-return": "warn",
+    "@typescript-eslint/no-unused-vars": "off",
+    "@typescript-eslint/no-var-requires": "warn",
+    "@typescript-eslint/non-nullable-type-assertion-style": "off",
+    "@typescript-eslint/prefer-as-const": "warn",
+    "@typescript-eslint/prefer-namespace-keyword": "warn",
+    "@typescript-eslint/require-await": "warn",
+    "@typescript-eslint/restrict-plus-operands": "warn",
+    "@typescript-eslint/restrict-template-expressions": "warn",
+    "@typescript-eslint/switch-exhaustiveness-check": "warn",
+    "@typescript-eslint/triple-slash-reference": "warn",
+    "@typescript-eslint/unbound-method": "off"
+  }
+}
index ead8e7410dcd8921acfbd5842100d4ce02fb7c0b..181af256e976d7d39caa18db2ac7d96a29b244a0 100644 (file)
@@ -1,5 +1,5 @@
 {
-  "extends": ["sonarqube"],
+  "extends": ["sonarqube", "../.eslintrc-typescript"],
   "plugins": ["header", "typescript-sort-keys"],
   "rules": {
     // Custom SonarCloud config that differs from eslint-config-sonarqube
index e4f4d91ea325de874de7951213ed826a6089f81d..bbdc8c8535dd37adaa969b3e22e3e10d9c3720fc 100644 (file)
@@ -6,7 +6,7 @@
   "scripts": {
     "build": "yarn lint && yarn ts-check && vite build",
     "build-release": "yarn install --immutable && yarn build",
-    "lint": "eslint --ext js,ts,tsx,snap --quiet src",
+    "lint": "eslint --ext js,ts,tsx,snap src",
     "lint-report-ci": "yarn install --immutable && eslint --ext js,ts,tsx -f json -o eslint-report/eslint-report.json src  || yarn lint",
     "test": "jest",
     "ts-check": "tsc --noEmit",
index 7e352d04d3d222c6262aa913aefa798ed2e74b0c..80bd53e02d51042c52749ebcca73c7d6027e2942 100644 (file)
@@ -66,7 +66,7 @@ export default function Checkbox({
       <AccessibleCheckbox
         aria-label={title}
         checked={checked}
-        disabled={disabled || loading}
+        disabled={disabled ?? loading}
         id={id}
         onChange={handleChange}
         onClick={onClick}
index d2c5b85f0e7ee6ad9ddc8679204647287b25ccf1..e5ed8fc53983a096109913d89ab97f2b1e610791 100644 (file)
 import React from 'react';
 
 export interface ClickEventBoundaryProps {
-  children: React.ReactElement;
+  children: React.ReactElement<{ onClick?: (e: React.SyntheticEvent<MouseEvent>) => void }>;
 }
 
 export default function ClickEventBoundary({ children }: ClickEventBoundaryProps) {
   return React.cloneElement(children, {
     onClick: (e: React.SyntheticEvent<MouseEvent>) => {
       e.stopPropagation();
+
       if (typeof children.props.onClick === 'function') {
         children.props.onClick(e);
       }
index 50df8bc2bf7dad234f43f14a8d09563fe2edc045..fad7efff8b0514e59d930a23aa7da1f0dd142ff9 100644 (file)
@@ -46,7 +46,7 @@ export default class DeferredSpinner extends React.PureComponent<Props, State> {
   state: State = { showSpinner: false };
 
   componentDidMount() {
-    if (this.props.loading == null || this.props.loading === true) {
+    if (this.props.loading == null || this.props.loading) {
       this.startTimer();
     }
   }
@@ -67,10 +67,9 @@ export default class DeferredSpinner extends React.PureComponent<Props, State> {
   }
 
   startTimer = () => {
-    this.timer = window.setTimeout(
-      () => this.setState({ showSpinner: true }),
-      this.props.timeout || DEFAULT_TIMEOUT
-    );
+    this.timer = window.setTimeout(() => {
+      this.setState({ showSpinner: true });
+    }, this.props.timeout ?? DEFAULT_TIMEOUT);
   };
 
   stopTimer = () => {
index e61964a77e3de1d89c038b664930c523f367f04c..68745af6f4c2a98a0488638795590db7a210e243 100644 (file)
@@ -25,6 +25,6 @@ const MainMenuUl = styled.ul`
   ${tw`sw-flex sw-gap-8 sw-items-center`}
 `;
 
-export function MainMenu({ children }: React.PropsWithChildren<{}>) {
+export function MainMenu({ children }: React.PropsWithChildren<object>) {
   return <MainMenuUl>{children}</MainMenuUl>;
 }
index 07de4f5422fe0ea9f976d27f98feaf4ba5e08ec5..fb33549c1d1e3797a9fc47a96b34aa2b5f49f988 100644 (file)
@@ -56,7 +56,8 @@ export default class OutsideClickHandler extends React.Component<Props> {
     if (this.mounted) {
       // eslint-disable-next-line react/no-find-dom-node
       const node = findDOMNode(this);
-      if (!node || !node.contains(event.target as Node)) {
+
+      if (!node?.contains(event.target as Node)) {
         this.props.onClickOutside();
       }
     }
index a298b7fdfd08a6bf9d88f4b194fc44f3fab9766e..cb51f9dbb70aa36529afc01bfc66839ed1cfce43 100644 (file)
@@ -36,7 +36,7 @@ import { themeColor, themeContrast } from '../helpers/theme';
 const MILLISECONDS_IN_A_SECOND = 1000;
 
 export interface TooltipProps {
-  children: React.ReactElement<{}>;
+  children: React.ReactElement;
   mouseEnterDelay?: number;
   mouseLeaveDelay?: number;
   onHide?: VoidFunction;
@@ -88,16 +88,19 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
 
   constructor(props: TooltipProps) {
     super(props);
+
     this.state = {
       flipped: false,
       placement: props.placement,
       visible: props.visible !== undefined ? props.visible : false,
     };
+
     this.throttledPositionTooltip = throttle(this.positionTooltip, THROTTLE_SCROLL_DELAY);
   }
 
   componentDidMount() {
     this.mounted = true;
+
     if (this.props.visible === true) {
       this.positionTooltip();
       this.addEventListeners();
@@ -106,9 +109,9 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
 
   componentDidUpdate(prevProps: TooltipProps, prevState: State) {
     if (this.props.placement !== prevProps.placement) {
-      this.setState({ placement: this.props.placement }, () =>
-        this.onUpdatePlacement(this.hasVisibleChanged(prevState.visible, prevProps.visible))
-      );
+      this.setState({ placement: this.props.placement }, () => {
+        this.onUpdatePlacement(this.hasVisibleChanged(prevState.visible, prevProps.visible));
+      });
     } else if (this.hasVisibleChanged(prevState.visible, prevProps.visible)) {
       this.onUpdateVisible();
     } else if (!this.state.flipped && this.needsFlipping(this.state)) {
@@ -175,18 +178,15 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
 
   hasVisibleChanged = (prevStateVisible: boolean, prevPropsVisible?: boolean) => {
     if (this.props.visible === undefined) {
-      return prevPropsVisible || this.state.visible !== prevStateVisible;
+      return prevPropsVisible ?? this.state.visible !== prevStateVisible;
     }
+
     return this.props.visible !== prevPropsVisible;
   };
 
-  isVisible = () => {
-    return this.props.visible ?? this.state.visible;
-  };
+  isVisible = () => this.props.visible ?? this.state.visible;
 
-  getPlacement = (): PopupPlacement => {
-    return this.state.placement || PopupPlacement.Bottom;
-  };
+  getPlacement = (): PopupPlacement => this.state.placement ?? PopupPlacement.Bottom;
 
   tooltipNodeRef = (node: HTMLElement | null) => {
     this.tooltipNode = node;
@@ -217,6 +217,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
 
     // eslint-disable-next-line react/no-find-dom-node
     const toggleNode = findDOMNode(this);
+
     if (toggleNode && toggleNode instanceof Element && this.tooltipNode) {
       const { height, left, leftFix, top, topFix, width } = popupPositioning(
         toggleNode,
@@ -262,7 +263,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
       ) {
         this.setState({ visible: true });
       }
-    }, (this.props.mouseEnterDelay || 0) * MILLISECONDS_IN_A_SECOND);
+    }, (this.props.mouseEnterDelay ?? 0) * MILLISECONDS_IN_A_SECOND);
 
     if (this.props.onShow) {
       this.props.onShow();
@@ -280,7 +281,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
         if (this.mounted && this.props.visible === undefined && !this.mouseIn) {
           this.setState({ visible: false });
         }
-      }, (this.props.mouseLeaveDelay || 0) * MILLISECONDS_IN_A_SECOND);
+      }, (this.props.mouseLeaveDelay ?? 0) * MILLISECONDS_IN_A_SECOND);
 
       if (this.props.onHide) {
         this.props.onHide();
@@ -301,8 +302,11 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
     this.handlePointerEnter();
 
     const { children } = this.props;
-    if (typeof children.props.onPointerEnter === 'function') {
-      children.props.onPointerEnter();
+
+    const props = children.props as { onPointerEnter?: VoidFunction };
+
+    if (typeof props.onPointerEnter === 'function') {
+      props.onPointerEnter();
     }
   };
 
@@ -310,8 +314,11 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
     this.handlePointerLeave();
 
     const { children } = this.props;
-    if (typeof children.props.onPointerLeave === 'function') {
-      children.props.onPointerLeave();
+
+    const props = children.props as { onPointerLeave?: VoidFunction };
+
+    if (typeof props.onPointerLeave === 'function') {
+      props.onPointerLeave();
     }
   };
 
@@ -378,7 +385,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
 class TooltipPortal extends React.Component {
   el: HTMLElement;
 
-  constructor(props: {}) {
+  constructor(props: object) {
     super(props);
     this.el = document.createElement('div');
   }
index 1d9f6068e560f4bdfdbaf5d7518bd3a339e03ce7..96d24f7f7bc62f9615b974c794f44d1cc8d405ab 100644 (file)
@@ -37,7 +37,7 @@ it('should show clear button only when there is a value', async () => {
 });
 
 it('should attach ref', () => {
-  const ref = jest.fn();
+  const ref = jest.fn() as jest.Mock<unknown, unknown[]>;
   setupWithProps({ innerRef: ref });
   expect(ref).toHaveBeenCalled();
   expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLInputElement);
index fde843342b26d31b0b0ad39ec5aaca4e821d77c2..25163a7fd22b1ba0ed3006c9cc9c365243aa09eb 100644 (file)
@@ -26,7 +26,7 @@ import Link, { DiscreetLink } from '../Link';
 
 beforeAll(() => {
   const { location } = window;
-  delete (window as any).location;
+  delete (window as { location?: Location }).location;
   window.location = { ...location, href: '' };
 });
 
index 45f967d930bd4f85a58012ca3cb250bf54dec296..650e5c73037b51c820d15d1e43222b963c3eaf59 100644 (file)
@@ -26,7 +26,7 @@ import NavLink from '../NavLink';
 
 beforeAll(() => {
   const { location } = window;
-  delete (window as any).location;
+  delete (window as { location?: Location }).location;
   window.location = { ...location, href: '' };
 });
 
index 8b448d17521b29144854c70d7f1d7324a2a819bd..95ce51cc9b0fde1ea3217d20976c7fd026052dd5 100644 (file)
@@ -23,7 +23,7 @@ import { FCProps } from '../../types/misc';
 import Tooltip, { TooltipInner } from '../Tooltip';
 
 jest.mock('react-dom', () => {
-  const reactDom = jest.requireActual('react-dom');
+  const reactDom = jest.requireActual('react-dom') as object;
   return { ...reactDom, findDOMNode: jest.fn().mockReturnValue(undefined) };
 });
 
index ea05963f7728711cf96a5620f3ad663d9afca41c..1e3763cb91160e3a43a45472389e08e01af6cf1b 100644 (file)
@@ -120,7 +120,7 @@ export function ClipboardButton({
             icon={icon}
             innerRef={setCopyButton}
           >
-            {children || translate('copy')}
+            {children ?? translate('copy')}
           </ButtonSecondary>
         </Tooltip>
       )}
index 0603fe83cfeb9499976d93f791d6553ddf9d7876..0f5f743f4d42523846146858dc3d1359d0b28669 100644 (file)
@@ -81,6 +81,6 @@ export function OcticonHoc(
     );
   }
 
-  IconWrapper.displayName = displayName || WrappedOcticon.displayName || WrappedOcticon.name;
+  IconWrapper.displayName = displayName ?? WrappedOcticon.displayName ?? WrappedOcticon.name;
   return IconWrapper;
 }
index e517ceb7f7daf3cbfc57587bd7b0f042d059f7ba..f2ed594ede7b090dbe14742485da7b053df7f62a 100644 (file)
@@ -52,7 +52,7 @@ function PopupBase(props: PopupProps, ref: React.Ref<HTMLDivElement>) {
     <ClickEventBoundary>
       <PopupWrapper
         className={classNames(`is-${placement}`, className)}
-        ref={ref || React.createRef()}
+        ref={ref ?? React.createRef()}
         style={style}
         zLevel={zLevel}
         {...ariaProps}
@@ -237,7 +237,7 @@ const PopupWrapper = styled.div<{ zLevel: PopupZLevel }>`
 class PortalWrapper extends React.Component {
   el: HTMLElement;
 
-  constructor(props: {}) {
+  constructor(props: object) {
     super(props);
     this.el = document.createElement('div');
   }
index 7953d3531c9edfcd73b83c6c44d804a42cd87899..9c90bbe92e3ada3013dde5ed6f34300a62fabb2d 100644 (file)
@@ -26,14 +26,14 @@ const toggleRect = {
     width: 50,
     height: 20,
   }),
-} as any;
+} as Element & { getBoundingClientRect: jest.Mock };
 
 const popupRect = {
   getBoundingClientRect: jest.fn().mockReturnValue({
     width: 200,
     height: 100,
   }),
-} as any;
+} as Element & { getBoundingClientRect: jest.Mock };
 
 beforeAll(() => {
   Object.defineProperties(document.documentElement, {
index 09384e2299b8c8f97b43ed3b33d0456829f3bd2a..4cceaccc23d516d4929270b0d916829fe70907cc 100644 (file)
@@ -32,6 +32,7 @@
  * - TopLeft = above the block, horizontally left-aligned
  * - TopRight = above the block, horizontally right-aligned
  */
+
 export enum PopupPlacement {
   Bottom = 'bottom',
   BottomLeft = 'bottom-left',
@@ -83,57 +84,52 @@ export function popupPositioning(
   const toggleRect = toggleNode.getBoundingClientRect();
   const popupRect = popupNode.getBoundingClientRect();
 
-  let left = 0;
-  let top = 0;
+  let { left, top } = toggleRect;
 
   switch (placement) {
     case PopupPlacement.Bottom:
-      left = toggleRect.left + toggleRect.width / 2 - popupRect.width / 2;
-      top = toggleRect.top + toggleRect.height;
+      left += toggleRect.width / 2 - popupRect.width / 2;
+      top += toggleRect.height;
       break;
     case PopupPlacement.BottomLeft:
-      left = toggleRect.left;
-      top = toggleRect.top + toggleRect.height;
+      top += toggleRect.height;
       break;
     case PopupPlacement.BottomRight:
-      left = toggleRect.left + toggleRect.width - popupRect.width;
-      top = toggleRect.top + toggleRect.height;
+      left += toggleRect.width - popupRect.width;
+      top += toggleRect.height;
       break;
     case PopupPlacement.Left:
-      left = toggleRect.left - popupRect.width;
-      top = toggleRect.top + toggleRect.height / 2 - popupRect.height / 2;
+      left -= popupRect.width;
+      top += toggleRect.height / 2 - popupRect.height / 2;
       break;
     case PopupPlacement.LeftTop:
-      left = toggleRect.left - popupRect.width;
-      top = toggleRect.top;
+      left -= popupRect.width;
       break;
     case PopupPlacement.LeftBottom:
-      left = toggleRect.left - popupRect.width;
-      top = toggleRect.top + toggleRect.height - popupRect.height;
+      left -= popupRect.width;
+      top += toggleRect.height - popupRect.height;
       break;
     case PopupPlacement.Right:
-      left = toggleRect.left + toggleRect.width;
-      top = toggleRect.top + toggleRect.height / 2 - popupRect.height / 2;
+      left += toggleRect.width;
+      top += toggleRect.height / 2 - popupRect.height / 2;
       break;
     case PopupPlacement.RightTop:
-      left = toggleRect.left + toggleRect.width;
-      top = toggleRect.top;
+      left += toggleRect.width;
       break;
     case PopupPlacement.RightBottom:
-      left = toggleRect.left + toggleRect.width;
-      top = toggleRect.top + toggleRect.height - popupRect.height;
+      left += toggleRect.width;
+      top += toggleRect.height - popupRect.height;
       break;
     case PopupPlacement.Top:
-      left = toggleRect.left + toggleRect.width / 2 - popupRect.width / 2;
-      top = toggleRect.top - popupRect.height;
+      left += toggleRect.width / 2 - popupRect.width / 2;
+      top -= popupRect.height;
       break;
     case PopupPlacement.TopLeft:
-      left = toggleRect.left;
-      top = toggleRect.top - popupRect.height;
+      top -= popupRect.height;
       break;
     case PopupPlacement.TopRight:
-      left = toggleRect.left + toggleRect.width - popupRect.width;
-      top = toggleRect.top - popupRect.height;
+      left += toggleRect.width - popupRect.width;
+      top -= popupRect.height;
       break;
   }
 
@@ -141,6 +137,7 @@ export function popupPositioning(
     Math.max(left, getMinLeftPlacement(toggleRect)),
     getMaxLeftPlacement(toggleRect, popupRect)
   );
+
   const inBoundariesTop = Math.min(
     Math.max(top, getMinTopPlacement(toggleRect)),
     getMaxTopPlacement(toggleRect, popupRect)
index 558906fe0dc45d6e1c47c62c372be34bfa0f3d53..275881c194a91739ad342cd312cbbb7728afc69d 100644 (file)
@@ -47,7 +47,9 @@ export function renderWithContext(
   return render(ui, { ...options, wrapper: getContextWrapper() }, userEventOptions);
 }
 
-type RenderRouterOptions = { additionalRoutes?: ReactNode };
+interface RenderRouterOptions {
+  additionalRoutes?: ReactNode;
+}
 
 export function renderWithRouter(
   ui: React.ReactElement,
@@ -55,7 +57,7 @@ export function renderWithRouter(
 ) {
   const { additionalRoutes, userEventOptions, ...renderOptions } = options;
 
-  function RouterWrapper({ children }: React.PropsWithChildren<{}>) {
+  function RouterWrapper({ children }: React.PropsWithChildren<object>) {
     return (
       <HelmetProvider>
         <MemoryRouter>
@@ -72,7 +74,7 @@ export function renderWithRouter(
 }
 
 function getContextWrapper() {
-  return function ContextWrapper({ children }: React.PropsWithChildren<{}>) {
+  return function ContextWrapper({ children }: React.PropsWithChildren<object>) {
     return (
       <HelmetProvider>
         <IntlProvider defaultLocale="en" locale="en">
@@ -92,17 +94,25 @@ export function mockComponent(name: string, transformProps: (props: any) => any
   return MockedComponent;
 }
 
-export const debounceTimer = jest.fn().mockImplementation((callback, timeout) => {
-  let timeoutId: number;
-  const debounced = jest.fn((...args) => {
-    window.clearTimeout(timeoutId);
-    timeoutId = window.setTimeout(() => callback(...args), timeout);
-  });
-  (debounced as any).cancel = jest.fn(() => {
-    window.clearTimeout(timeoutId);
+export const debounceTimer = jest
+  .fn()
+  .mockImplementation((callback: (...args: unknown[]) => void, timeout: number) => {
+    let timeoutId: number;
+
+    const debounced = jest.fn((...args: unknown[]) => {
+      window.clearTimeout(timeoutId);
+
+      timeoutId = window.setTimeout(() => {
+        callback(...args);
+      }, timeout);
+    });
+
+    (debounced as typeof debounced & { cancel: () => void }).cancel = jest.fn(() => {
+      window.clearTimeout(timeoutId);
+    });
+
+    return debounced;
   });
-  return debounced;
-});
 
 export function flushPromises(usingFakeTime = false): Promise<void> {
   return new Promise((resolve) => {
index 6dab879cf4a44929b790259e4bd5eed158077fd1..77048771012bd68f07e946986143886bf3158c66 100644 (file)
@@ -101,20 +101,21 @@ function getColor(
   }
   // Is theme color overridden by a color name ?
   const color = colorOverride ? theme.colors[colorOverride as ThemeColors] : [r, g, b];
+
   if (typeof color === 'string') {
     return color as CSSColor;
   }
 
-  return getRGBAString(color, opacityOverride ?? color[3] ?? a);
+  return getRGBAString(color, opacityOverride ?? (color[3] as number | string | undefined) ?? a);
 }
 
 // Simplified version of getColor for contrast colors, fallback to colors if contrast isn't found
 function getContrast(theme: Theme, colorOverride: ThemeContrasts | ThemeColors | CSSColor) {
   // Custom CSS property or rgb(a) color, return it directly
   if (
-    colorOverride?.startsWith('var(--') ||
-    colorOverride?.startsWith('rgb(') ||
-    colorOverride?.startsWith('rgba(')
+    colorOverride.startsWith('var(--') ||
+    colorOverride.startsWith('rgb(') ||
+    colorOverride.startsWith('rgba(')
   ) {
     return colorOverride as CSSColor;
   }
index 24b96e70a5d130d3c09a82772d9a5e7896e04e0f..182e8ae11dded43ddb72780330913245add00a14 100644 (file)
@@ -64,7 +64,7 @@ const MIN_SEARCH_QUERY_LENGTH = 2;
 export class GlobalSearch extends React.PureComponent<Props, State> {
   input?: HTMLInputElement | null;
   node?: HTMLElement | null;
-  nodes: Dict<HTMLElement>;
+  nodes: Dict<HTMLElement | undefined>;
   mounted = false;
 
   constructor(props: Props) {
@@ -109,11 +109,9 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
   handleFocus = () => {
     if (!this.state.open) {
       // simulate click to close any other dropdowns
-      const body = document.documentElement;
-      if (body) {
-        body.click();
-      }
+      document.documentElement.click();
     }
+
     this.openSearch();
   };
 
@@ -143,7 +141,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
 
   getPlainComponentsList = (results: Results, more: More) =>
     sortQualifiers(Object.keys(results)).reduce((components, qualifier) => {
-      const next = [...components, ...results[qualifier].map((component) => component.key)];
+      const next = [...components, ...(results[qualifier] ?? []).map((component) => component.key)];
       if (more[qualifier]) {
         next.push('qualifier###' + qualifier);
       }
@@ -203,7 +201,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
           more: { ...state.more, [qualifier]: 0 },
           results: {
             ...state.results,
-            [qualifier]: uniqBy([...state.results[qualifier], ...moreResults], 'key'),
+            [qualifier]: uniqBy([...(state.results[qualifier] ?? []), ...moreResults], 'key'),
           },
           selected: moreResults.length > 0 ? moreResults[0].key : state.selected,
         }));
@@ -266,6 +264,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
   scrollToSelected = () => {
     if (this.state.selected) {
       const node = this.nodes[this.state.selected];
+
       if (node && this.node) {
         scrollToElement(node, {
           topOffset: 30,
index 8e112b048e77ea5a083abf375cfba187a3ec2cef..71779ed36cd7a0e71eb0e8e0167c9116bbf76de3 100644 (file)
@@ -45,7 +45,9 @@ export default class GlobalSearchResult extends React.PureComponent<Props> {
         className={classNames('sw-flex sw-flex-col sw-items-start sw-space-y-1', {
           active: selected,
         })}
-        innerRef={(node: HTMLAnchorElement | null) => this.props.innerRef(component.key, node)}
+        innerRef={(node: HTMLAnchorElement | null) => {
+          this.props.innerRef(component.key, node);
+        }}
         key={component.key}
         onClick={this.props.onClose}
         onPointerEnter={this.doSelect}
index 5ee4ca6f24c841b4e31443daa063a91cf4aa9bd9..fb1ef0017e37f71eeda7532175bad80f6a0fd1d1 100644 (file)
@@ -29,7 +29,7 @@ export interface Props {
   more: More;
   onMoreClick: (qualifier: string) => void;
   onSelect: (componentKey: string) => void;
-  renderNoResults: () => React.ReactElement<any>;
+  renderNoResults: () => React.ReactElement;
   renderResult: (component: ComponentResult) => React.ReactNode;
   results: Results;
   selected?: string;
@@ -42,8 +42,10 @@ export default function GlobalSearchResults(props: Props): React.ReactElement<Pr
 
   sortQualifiers(qualifiers).forEach((qualifier) => {
     const components = props.results[qualifier];
-    if (components.length > 0) {
+
+    if (components?.length) {
       const more = props.more[qualifier];
+
       renderedComponents.push(
         <li key={`group-${qualifier}`}>
           <ul key={`header-${qualifier}`} aria-labelledby={translate('qualifiers', qualifier)}>
index f16d54b2f5770334d929e7882078c731661e3694..ded1f4142560be58085d6d584dd5467c3fb28385 100644 (file)
@@ -54,7 +54,9 @@ export default class GlobalSearchShowMore extends React.PureComponent<Props> {
       <ItemButton
         className={classNames({ active: selected })}
         disabled={!allowMore}
-        onClick={(e: React.MouseEvent<HTMLButtonElement>) => this.handleMoreClick(e, qualifier)}
+        onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
+          this.handleMoreClick(e, qualifier);
+        }}
         onPointerEnter={() => {
           this.handleMouseEnter(qualifier);
         }}
index 51f9fc636e53895b64b6296056cecc2e1275d58d..4fbc1c4b1dbfcec3fe153e68a56b699290f33a8b 100644 (file)
@@ -42,9 +42,9 @@ export interface ComponentResult {
 }
 
 export interface Results {
-  [qualifier: string]: ComponentResult[];
+  [qualifier: string]: ComponentResult[] | undefined;
 }
 
 export interface More {
-  [qualifier: string]: number;
+  [qualifier: string]: number | undefined;
 }
index 597c031da3cf6669410163da36f36bd56282ad04..e7f0afadc52b766389c6afdfd84a70e0121e6d2d 100644 (file)
@@ -37,6 +37,6 @@ function LogoWithAriaText() {
   );
 }
 
-export default function MainSonarQubeBar({ children }: React.PropsWithChildren<{}>) {
+export default function MainSonarQubeBar({ children }: React.PropsWithChildren<object>) {
   return <MainAppBar Logo={LogoWithAriaText}>{children}</MainAppBar>;
 }
index 6f383868948d9f3e35df557e42e477755a009c96..234a5b9d70b83ac4d5ed049cfc64f97aeb9068a1 100644 (file)
@@ -72,7 +72,12 @@ function renderEmbedDocsPopup() {
         <button onClick={addSuggestion} type="button">
           add.suggestion
         </button>
-        <button onClick={() => setSuggestions([])} type="button">
+        <button
+          onClick={() => {
+            setSuggestions([]);
+          }}
+          type="button"
+        >
           remove.suggestion
         </button>
         <EmbedDocsPopupHelper />