From a333c5bbb64088bbff21e1d9aec4fbecfdcf7de3 Mon Sep 17 00:00:00 2001 From: Nikolay Akhmetov Date: Wed, 18 Dec 2024 11:21:06 -0500 Subject: [PATCH] CAT-1079 NickAkhmetov/Linting updates (#3646) --- CHANGELOG-eslint-v9.md | 2 + context/.eslintignore | 4 - context/.eslintrc.yml | 134 - .../components/cells/CellTypeResults/utils.ts | 2 +- .../DatasetsSelectedByExpression/hooks.tsx | 5 +- .../detailPage/files/DataProducts/hooks.ts | 2 +- .../RelatedMultiAssayLinks.tsx | 1 - .../provenance/ProvGraph/ProvGraph.spec.js | 1 - .../visualization/Visualization/hooks.ts | 2 +- .../VisualizationWrapper.tsx | 1 - .../components/publications/AggsList/hooks.js | 1 - .../SavedEntitiesTable/SavedEntitiesTable.tsx | 1 - .../savedLists/listTextFields/index.js | 1 - .../static/js/components/search/SearchBar.tsx | 2 +- .../app/static/js/components/search/store.ts | 2 +- .../__tests__/SelectedFilter.spec.js | 2 +- .../__tests__/SortingTableHead.spec.js | 2 +- .../static/js/components/searchPage/config.js | 2 +- .../filters/Filters/Filters.spec.js | 2 +- .../static/js/components/workspaces/hooks.ts | 6 +- context/app/static/js/helpers/functions.ts | 2 +- context/app/static/js/helpers/swr/fetchers.ts | 4 +- .../js/hooks/useImmediateDescendantProv.js | 2 +- .../static/js/pages/Workspace/Workspace.tsx | 1 + .../app/static/js/pages/search/Search.spec.js | 2 +- .../AccordionSteps/AccordionSteps.tsx | 1 + .../TooltipButton/TooltipIconButton.tsx | 6 +- context/app/static/js/theme/theme.tsx | 3 +- context/build-utils/webpack.plugins.js | 2 +- context/eslint.config.mjs | 312 ++ context/package-lock.json | 3299 ++++++++--------- context/package.json | 81 +- 32 files changed, 1834 insertions(+), 2056 deletions(-) create mode 100644 CHANGELOG-eslint-v9.md delete mode 100644 context/.eslintignore delete mode 100644 context/.eslintrc.yml create mode 100644 context/eslint.config.mjs diff --git a/CHANGELOG-eslint-v9.md b/CHANGELOG-eslint-v9.md new file mode 100644 index 0000000000..5ca828c50f --- /dev/null +++ b/CHANGELOG-eslint-v9.md @@ -0,0 +1,2 @@ +- Update to ESLint v9. +- Update minor versions of development dependencies to resolve security concerns. diff --git a/context/.eslintignore b/context/.eslintignore deleted file mode 100644 index 6d2709bb7e..0000000000 --- a/context/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -**/public/ -**/app/organ/*.y*ml -**/app/templates/**/*.html -**/app/static/js/maintenance/*.html \ No newline at end of file diff --git a/context/.eslintrc.yml b/context/.eslintrc.yml deleted file mode 100644 index ccc5f5e7da..0000000000 --- a/context/.eslintrc.yml +++ /dev/null @@ -1,134 +0,0 @@ -extends: - - eslint:recommended - - plugin:react/recommended - - airbnb - - airbnb/hooks - - prettier - - plugin:prettier/recommended - - plugin:import/errors - - plugin:import/warnings -parser: '@typescript-eslint/parser' -globals: - # Provided by webpack plugins - CDN_URL: readonly - PACKAGE_VERSION: readonly - # Provided by flask server - flaskData: readonly - groupsToken: readonly - isAuthenticated: readonly - userEmail: readonly - workspacesToken: readonly - userGroups: readonly - sentryEnv: readonly -env: - browser: true - node: true -plugins: - - react - - '@typescript-eslint' - - import - - prettier -settings: - react: - version: detect - import/resolver: - webpack: - config: ./build-utils/webpack.common.js -rules: - camelcase: [0] # Preserving original name will make searching across codebase easier. - no-underscore-dangle: [2, { allow: ['_id', '_source'] }] # Allow underscore prefixes for ElasticSearch fields - no-console: [error, { allow: [warn, error] }] - react/jsx-filename-extension: [0] # Imports don"t work if I change extension. - import/extensions: [0] # Same as above; we can eventually remove these rules to convert to ESM - react/sort-comp: [0] # Non-alphabetical groupings can make more sense. - react/jsx-one-expression-per-line: [0] # Makes punctuation after tab awkward. - react/prop-types: [0] - react/forbid-prop-types: [0] - react/jsx-key: [2] # enforce keys on all elements in a `.map` call - no-param-reassign: [2, { ignorePropertyModificationsFor: ['state'] }] # Allow state mutations via immer - react/require-default-props: [0] # Not necessary; we can specify default props in the component signature. - import/prefer-default-export: [0] # Hit eslint error: SyntaxError: Unexpected token, expected { - react/jsx-props-no-spreading: [0] # common pattern, but with TypeScript it"s pretty safe - jsx-a11y/label-has-associated-control: - - 2 - - controlComponents: - - Switch - react/jsx-no-bind: [0] # We can revisit this later if we detect performance issues from unstable functions - '@typescript-eslint/no-unused-vars': - - 2 # Error on unmarked unused variables - - argsIgnorePattern: ^_ # Allow unused variables that start with _ - ignoreRestSiblings: true # Allow unused variables when using destructuring to remove keys from an object - -overrides: - - files: ['**.{ts,tsx}'] - extends: - - plugin:@typescript-eslint/recommended-type-checked - - plugin:@typescript-eslint/stylistic-type-checked - parserOptions: - project: ./tsconfig.json - rules: - no-shadow: [0] # Use TS rule instead of eslint rule - '@typescript-eslint/no-shadow': ["error"] # Use TS rule instead of eslint rule - - files: ['**/*.spec.{js,jsx,ts,tsx}', '**/__mocks__/**/*.{js,jsx,ts,tsx}', 'test-utils/**/*.{js,jsx,ts,tsx}'] - plugins: - - jest - extends: - - plugin:jest/recommended - - plugin:jest-dom/recommended - - plugin:testing-library/react - settings: - import/resolver: - jest: - jestConfigFile: ./jest.config.js - env: - jest: true - jest/globals: true - globals: - jest: readonly - rules: - import/no-extraneous-dependencies: [0] - '@typescript-eslint/no-var-requires': [0] # allow use of require in jest tests - jest/no-standalone-expect: [error, { additionalTestBlockFunctions: [test.each] }] # Detect test.each as a test block function - jest/expect-expect: [error, { assertFunctionNames: ['expect*'] }] # allow expect calls to be contained within helper functions that start with expect - '@typescript-eslint/no-empty-function': [0] # allow empty function placeholders in jest tests - - files: ['**/*.stories.{js,jsx,ts,tsx}'] - extends: - - plugin:storybook/recommended - rules: - import/no-extraneous-dependencies: [0] - '@typescript-eslint/no-empty-function': [0] # allow empty function placeholders in stories - storybook/no-redundant-story-name: [0] # Some are needed for single story hoisting for multi word component names per comments - may need to be revisited/retested for the highlighted cases - - files: ['**/end-to-end/**/*.{js,jsx,ts,tsx}'] - plugins: - - cypress - extends: - - plugin:cypress/recommended - env: - cypress/globals: true - rules: - import/no-extraneous-dependencies: [0] - - files: ['**/build-utils/*.js'] - rules: - '@typescript-eslint/no-var-requires': [0] # allow use of require - import/no-extraneous-dependencies: [0] # Allow devDependencies in webpack config - - files: ['**/__tests__/**/*.{js,jsx,ts,tsx}'] # Allow the "test" global in test fixtures - globals: - test: readonly - rules: - import/no-extraneous-dependencies: [0] # Allow devDependencies in test fixtures - '@typescript-eslint/no-empty-function': [0] # allow empty function placeholders in test fixtures - '@typescript-eslint/no-unsafe-return': [0] # allow less type-safe code usage in tests - - files: ['**/*.json'] - plugins: [json] - extends: [plugin:json/recommended] - - files: ['**/*.md'] - plugins: [markdown, prettier] - processor: markdown/markdown - extends: [plugin:markdown/recommended-legacy] - rules: - no-console: [0] # Allow console in markdown files - import/no-unresolved: [0] # Allow unresolved imports in markdown files - import/no-extraneous-dependencies: [0] # Allow devDependencies in markdown files - - files: ['**/*.{yml,yaml}'] - parser: yaml-eslint-parser - extends: ['plugin:yml/standard', 'plugin:yml/prettier', prettier] diff --git a/context/app/static/js/components/cells/CellTypeResults/utils.ts b/context/app/static/js/components/cells/CellTypeResults/utils.ts index 9381e42eb5..d007a9277b 100644 --- a/context/app/static/js/components/cells/CellTypeResults/utils.ts +++ b/context/app/static/js/components/cells/CellTypeResults/utils.ts @@ -1,6 +1,6 @@ // Extracts the CLID from a cell name string provided in the format () export function extractCLID(cellName: string) { - const match = cellName.match(/\((CL[^)]+)\)/); + const match = /\((CL[^)]+)\)/.exec(cellName); return match ? match[1] : null; } diff --git a/context/app/static/js/components/cells/DatasetsSelectedByExpression/hooks.tsx b/context/app/static/js/components/cells/DatasetsSelectedByExpression/hooks.tsx index 0f938897f8..057b5c542e 100644 --- a/context/app/static/js/components/cells/DatasetsSelectedByExpression/hooks.tsx +++ b/context/app/static/js/components/cells/DatasetsSelectedByExpression/hooks.tsx @@ -115,7 +115,6 @@ function useDatasetsSelectedByExpression() { }; try { completeStep(createStepText(queryType, cellVariableNames, minExpressionLog, minCellPercentage)); - /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ const serviceResults = await new CellsService().getDatasets(queryParams); const datasets = 'list' in serviceResults ? serviceResults.list : serviceResults; @@ -136,7 +135,7 @@ function useDatasetsSelectedByExpression() { return acc; }, [] as WrappedCellsResultsDataset[]), ); - /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ + if ('list' in serviceResults) { setResultCounts(serviceResults); } @@ -150,7 +149,7 @@ function useDatasetsSelectedByExpression() { // but after the user submits their data, the component collapses, // so the message is hidden, and the user just sees the please wait. // Not sure what the best long term solution is, but this unblocks Nils. - // eslint-disable-next-line no-alert + // alert(e.message); } } diff --git a/context/app/static/js/components/detailPage/files/DataProducts/hooks.ts b/context/app/static/js/components/detailPage/files/DataProducts/hooks.ts index c9cd3bb3df..008e850c7c 100644 --- a/context/app/static/js/components/detailPage/files/DataProducts/hooks.ts +++ b/context/app/static/js/components/detailPage/files/DataProducts/hooks.ts @@ -24,7 +24,7 @@ const originDenylist = ['https://github.com/hubmapconsortium/portal-containers'] const nameDenylist = ['pipeline.cwl']; export function getGithubRepoName(origin: string) { - const match = origin.match(/github.com\/([^/]+)\/([^/]+)(\/|$)/); + const match = /github.com\/([^/]+)\/([^/]+)(\/|$)/.exec(origin); if (match) { return match[2]; } diff --git a/context/app/static/js/components/detailPage/multi-assay/RelatedMultiAssayLinks/RelatedMultiAssayLinks.tsx b/context/app/static/js/components/detailPage/multi-assay/RelatedMultiAssayLinks/RelatedMultiAssayLinks.tsx index 55de4dfab5..2a96bab9bd 100644 --- a/context/app/static/js/components/detailPage/multi-assay/RelatedMultiAssayLinks/RelatedMultiAssayLinks.tsx +++ b/context/app/static/js/components/detailPage/multi-assay/RelatedMultiAssayLinks/RelatedMultiAssayLinks.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/context/app/static/js/components/detailPage/provenance/ProvGraph/ProvGraph.spec.js b/context/app/static/js/components/detailPage/provenance/ProvGraph/ProvGraph.spec.js index a338be3051..9f6fe04346 100644 --- a/context/app/static/js/components/detailPage/provenance/ProvGraph/ProvGraph.spec.js +++ b/context/app/static/js/components/detailPage/provenance/ProvGraph/ProvGraph.spec.js @@ -3,7 +3,6 @@ import { fireEvent, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; -// eslint-disable-next-line import { render, screen, appProviderEndpoints } from 'test-utils/functions'; import sampleProv, { sampleDescendantsProv } from './fixtures/sample_prov'; import donorProv from './fixtures/donor_prov'; diff --git a/context/app/static/js/components/detailPage/visualization/Visualization/hooks.ts b/context/app/static/js/components/detailPage/visualization/Visualization/hooks.ts index 83de191a9c..2aa566b4f3 100644 --- a/context/app/static/js/components/detailPage/visualization/Visualization/hooks.ts +++ b/context/app/static/js/components/detailPage/visualization/Visualization/hooks.ts @@ -62,7 +62,7 @@ export function useVitessceConfig({ vitData, setVitessceState, markerGene }: Use let vitessceURLConf; try { vitessceURLConf = fragment.length > 0 ? decodeURLParamsToConf(fragment) : null; - } catch (err) { + } catch { // If URL cannot be parsed, display error and show Vitessce. toastError('View configuration from URL was not able to be parsed.'); setVitessceDefaults(vitData); diff --git a/context/app/static/js/components/detailPage/visualization/VisualizationWrapper/VisualizationWrapper.tsx b/context/app/static/js/components/detailPage/visualization/VisualizationWrapper/VisualizationWrapper.tsx index 56a44b2a92..7f51153be6 100644 --- a/context/app/static/js/components/detailPage/visualization/VisualizationWrapper/VisualizationWrapper.tsx +++ b/context/app/static/js/components/detailPage/visualization/VisualizationWrapper/VisualizationWrapper.tsx @@ -4,7 +4,6 @@ import VisualizationErrorBoundary from './VisualizationError'; import { VizContainerStyleContext } from './ContainerStylingContext'; import { VisualizationSuspenseFallback } from './VisualizationSuspenseFallback'; -// eslint-disable-next-line @typescript-eslint/no-unsafe-return const Visualization = React.lazy(() => import('../Visualization')); interface VisualizationWrapperProps { diff --git a/context/app/static/js/components/publications/AggsList/hooks.js b/context/app/static/js/components/publications/AggsList/hooks.js index 2b714dd42e..001cdb83d8 100644 --- a/context/app/static/js/components/publications/AggsList/hooks.js +++ b/context/app/static/js/components/publications/AggsList/hooks.js @@ -27,7 +27,6 @@ function usePublicationDatasetsAggs({ descendantUUID, aggsField, associatedColle const { searchHits: collectionDatasets } = useSearchHits(getCollectionDatasetsQuery(associatedCollectionUUID)); const collectionDatasetsUUIDs = - // eslint-disable-next-line no-underscore-dangle collectionDatasets.length > 0 ? collectionDatasets[0]?._source?.datasets.map(({ uuid }) => uuid) : []; const query = associatedCollectionUUID diff --git a/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.tsx b/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.tsx index accae09a6d..e21d19fe9c 100644 --- a/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.tsx +++ b/context/app/static/js/components/savedLists/SavedEntitiesTable/SavedEntitiesTable.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-underscore-dangle */ import React, { useState } from 'react'; import Paper from '@mui/material/Paper'; import Table from '@mui/material/Table'; diff --git a/context/app/static/js/components/savedLists/listTextFields/index.js b/context/app/static/js/components/savedLists/listTextFields/index.js index d85fc2aa8a..e0ffc466d5 100644 --- a/context/app/static/js/components/savedLists/listTextFields/index.js +++ b/context/app/static/js/components/savedLists/listTextFields/index.js @@ -13,7 +13,6 @@ function TitleTextField({ handleChange, title }) { label="Title" fullWidth variant="outlined" - // eslint-disable-next-line prettier/prettier placeholder="Like “Spleen-Related Data” or “ATAC-seq Visualizations”" inputProps={{ maxLength: maxTitleLength }} onChange={handleChange} diff --git a/context/app/static/js/components/search/SearchBar.tsx b/context/app/static/js/components/search/SearchBar.tsx index 0a58d6cf8c..2804eed280 100644 --- a/context/app/static/js/components/search/SearchBar.tsx +++ b/context/app/static/js/components/search/SearchBar.tsx @@ -20,7 +20,7 @@ function SearchBar() { (e: React.FormEvent) => { e.preventDefault(); - setSearch(input.match(/^\s*HBM\S+\s*$/i) ? `"${input}"` : input); + setSearch(/^\s*HBM\S+\s*$/i.exec(input) ? `"${input}"` : input); const category = 'Free Text Search'; diff --git a/context/app/static/js/components/search/store.ts b/context/app/static/js/components/search/store.ts index 054e837d1e..8f2d2e0299 100644 --- a/context/app/static/js/components/search/store.ts +++ b/context/app/static/js/components/search/store.ts @@ -171,7 +171,7 @@ export function parseURLState(stateJSON: string) { try { const parsed = searchURLStateSchema.parse(JSON.parse(stateJSON)); return parsed; - } catch (e) { + } catch { return {}; } } diff --git a/context/app/static/js/components/searchPage/__tests__/SelectedFilter.spec.js b/context/app/static/js/components/searchPage/__tests__/SelectedFilter.spec.js index 56f26c4c2c..25b8e8640c 100644 --- a/context/app/static/js/components/searchPage/__tests__/SelectedFilter.spec.js +++ b/context/app/static/js/components/searchPage/__tests__/SelectedFilter.spec.js @@ -1,5 +1,5 @@ import React from 'react'; -// eslint-disable-next-line + import { render, screen } from 'test-utils/functions'; import SelectedFilter from '../SelectedFilter'; diff --git a/context/app/static/js/components/searchPage/__tests__/SortingTableHead.spec.js b/context/app/static/js/components/searchPage/__tests__/SortingTableHead.spec.js index 380f67b382..885f96f2db 100644 --- a/context/app/static/js/components/searchPage/__tests__/SortingTableHead.spec.js +++ b/context/app/static/js/components/searchPage/__tests__/SortingTableHead.spec.js @@ -1,5 +1,5 @@ import React from 'react'; -// eslint-disable-next-line + import { render, screen } from 'test-utils/functions'; import SortingTableHead from '../SortingTableHead'; diff --git a/context/app/static/js/components/searchPage/config.js b/context/app/static/js/components/searchPage/config.js index 06d56b6efd..d58eb9e7de 100644 --- a/context/app/static/js/components/searchPage/config.js +++ b/context/app/static/js/components/searchPage/config.js @@ -1,5 +1,5 @@ /* eslint-disable prettier/prettier */ -// eslint-disable-next-line import/named + import { capitalizeString } from 'js/helpers/functions'; import { listFilter, rangeFilter, field, hierarchicalFilter, boolListFilter } from './utils'; diff --git a/context/app/static/js/components/searchPage/filters/Filters/Filters.spec.js b/context/app/static/js/components/searchPage/filters/Filters/Filters.spec.js index 22df2996af..86ae513b7b 100644 --- a/context/app/static/js/components/searchPage/filters/Filters/Filters.spec.js +++ b/context/app/static/js/components/searchPage/filters/Filters/Filters.spec.js @@ -1,5 +1,5 @@ import React from 'react'; -// eslint-disable-next-line + import { render, screen } from 'test-utils/functions'; import { SearchkitProvider, SearchkitManager } from 'searchkit'; diff --git a/context/app/static/js/components/workspaces/hooks.ts b/context/app/static/js/components/workspaces/hooks.ts index abe9bcd377..25d863d8a5 100644 --- a/context/app/static/js/components/workspaces/hooks.ts +++ b/context/app/static/js/components/workspaces/hooks.ts @@ -154,7 +154,7 @@ function useMatchingWorkspaceTemplates(workspace: MergedWorkspace | Record { // match the filename without extension given a file path. const regex = /[\w-]+?(?=\.)/; - const fileNameMatch = getWorkspaceFileName(file).match(regex); + const fileNameMatch = regex.exec(getWorkspaceFileName(file)); const templateName = fileNameMatch ? fileNameMatch[0] : ''; if (templateName && templateName in templates) { return { ...acc, [templateName]: templates[templateName] }; @@ -284,7 +284,7 @@ export function useCreateAndLaunchWorkspace() { try { workspace = await createWorkspace(body); - } catch (e) { + } catch { toastErrorCreateWorkspace(); return; } @@ -298,7 +298,7 @@ export function useCreateAndLaunchWorkspace() { templatePath, resourceOptions, }); - } catch (e) { + } catch { toastErrorLaunchWorkspace(); } }, diff --git a/context/app/static/js/helpers/functions.ts b/context/app/static/js/helpers/functions.ts index bc158c3000..0628a628df 100644 --- a/context/app/static/js/helpers/functions.ts +++ b/context/app/static/js/helpers/functions.ts @@ -30,7 +30,7 @@ export function shouldCapitalizeString(s: string, idx = 1) { if (idx === 0) { return true; } - return NOT_CAPITALIZED_WORDS.indexOf(lowerCase) === -1; + return !NOT_CAPITALIZED_WORDS.includes(lowerCase); } export function capitalizeAndReplaceDashes(s: string) { diff --git a/context/app/static/js/helpers/swr/fetchers.ts b/context/app/static/js/helpers/swr/fetchers.ts index c6e6d7b8e0..bb9133a10b 100644 --- a/context/app/static/js/helpers/swr/fetchers.ts +++ b/context/app/static/js/helpers/swr/fetchers.ts @@ -34,7 +34,7 @@ async function f({ let errorBody: Record = { error: rawText }; try { errorBody = JSON.parse(rawText) as Record; - } catch (e) { + } catch { // Ignore and use the raw text instead since error was not returned as json } const message = errorMessages[response.status] ?? `The request to ${url} failed.`; @@ -48,7 +48,7 @@ async function f({ if (returnResponse) { return response; } - return response.json(); + return response.json() as Promise; }); } diff --git a/context/app/static/js/hooks/useImmediateDescendantProv.js b/context/app/static/js/hooks/useImmediateDescendantProv.js index c108a101fe..e5fb543469 100644 --- a/context/app/static/js/hooks/useImmediateDescendantProv.js +++ b/context/app/static/js/hooks/useImmediateDescendantProv.js @@ -16,7 +16,7 @@ async function getEntityData(hubmapID, elasticsearchEndpoint, groupsToken) { return {}; } const results = await response.json(); - // eslint-disable-next-line no-underscore-dangle + return results.hits.hits[0]._source; } diff --git a/context/app/static/js/pages/Workspace/Workspace.tsx b/context/app/static/js/pages/Workspace/Workspace.tsx index dd2169f8e5..2ec580f51f 100644 --- a/context/app/static/js/pages/Workspace/Workspace.tsx +++ b/context/app/static/js/pages/Workspace/Workspace.tsx @@ -54,6 +54,7 @@ interface WorkspacePageProps { } function LaunchStopButton(props: ButtonProps) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- this is a valid use case const variant = props?.children?.toString() === 'Stop Jobs' ? 'outlined' : 'contained'; return