From 743bc1cfe2f399d11517fb95ceb5bb992d9ad757 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 22 Aug 2025 15:38:02 +0100 Subject: [PATCH] Chore: Upgrade grafana eslint config and re-enable rule of hooks rules (#110029) --- .betterer.eslint.config.js | 24 ++--- .betterer.results | 180 +++++++++++++++++++++++++++++++++++-- eslint.config.js | 19 ++-- package.json | 4 +- yarn.lock | 20 ++--- 5 files changed, 199 insertions(+), 48 deletions(-) diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 141ad441208..81b97bb03f3 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -6,18 +6,20 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const lodashPlugin = require('eslint-plugin-lodash'); const barrelPlugin = require('eslint-plugin-no-barrel-files'); const reactPlugin = require('eslint-plugin-react'); -const hooksPlugin = require('eslint-plugin-react-hooks'); const testingLibraryPlugin = require('eslint-plugin-testing-library'); const grafanaConfig = require('@grafana/eslint-config/flat'); const grafanaPlugin = require('@grafana/eslint-plugin'); const grafanaI18nPlugin = require('@grafana/i18n/eslint-plugin'); -// Include the Grafana config and remove the rules, +// Include the base Grafana configs and remove the rules, // as we just want to pull in all of the necessary configuration but not run the rules // (this should only be concerned with checking rules that we want to improve, // so there's no need to try and run the rules that will be linted properly anyway) -const { rules, ...baseConfig } = grafanaConfig.find((config) => config?.name === '@grafana/eslint-config/flat'); +const mappedBaseConfigs = grafanaConfig.map((config) => { + const { rules, ...baseConfig } = config; + return baseConfig; +}); /** * @type {Array} @@ -50,22 +52,13 @@ module.exports = [ ], }, { - name: 'react/jsx-runtime', - // @ts-ignore - not sure why but flat config is typed as a maybe? - ...reactPlugin.configs.flat['jsx-runtime'], - }, - // FIXME: Remove once eslint-config-grafana is updated to include correct plugin - { - name: 'react-hooks-plugin', - plugins: { - 'react-hooks': hooksPlugin, - }, + name: 'react/jsx-runtime-rules', + rules: reactPlugin.configs.flat['jsx-runtime'].rules, }, + ...mappedBaseConfigs, { files: ['**/*.{ts,tsx,js}'], - ...baseConfig, plugins: { - ...baseConfig.plugins, '@emotion': emotionPlugin, lodash: lodashPlugin, jest: jestPlugin, @@ -85,6 +78,7 @@ module.exports = [ { files: ['**/*.{js,jsx,ts,tsx}'], rules: { + 'react-hooks/rules-of-hooks': 'error', '@typescript-eslint/no-explicit-any': 'error', '@grafana/no-aria-label-selectors': 'error', 'no-restricted-imports': [ diff --git a/.betterer.results b/.betterer.results index 7712f1a83a9..6eb58950612 100644 --- a/.betterer.results +++ b/.betterer.results @@ -548,6 +548,16 @@ exports[`better eslint`] = { "packages/grafana-runtime/src/utils/queryResponse.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "packages/grafana-runtime/src/utils/useFavoriteDatasources.ts:5381": [ + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"], + [0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "4"], + [0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "5"], + [0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "6"], + [0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "7"] + ], "packages/grafana-runtime/src/utils/userStorage.tsx:5381": [ [0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "0"], [0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "1"], @@ -596,6 +606,20 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "packages/grafana-ui/src/components/Combobox/Combobox.story.tsx:5381": [ + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"], + [0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"], + [0, 0, 0, "React Hook \\"useState\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"] + ], + "packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx:5381": [ + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"], + [0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"] + ], "packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -616,6 +640,33 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "packages/grafana-ui/src/components/ElementSelectionContext/ElementSelectionContext.tsx:5381": [ + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"], + [0, 0, 0, "React Hook \\"useContext\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"] + ], + "packages/grafana-ui/src/components/Forms/Form.story.tsx:5381": [ + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "5"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "6"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "7"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "8"] + ], + "packages/grafana-ui/src/components/Forms/InlineField.story.tsx:5381": [ + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"basic: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"error: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"grow: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "5"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"withCombobox: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "6"], + [0, 0, 0, "React Hook \\"useId\\" is called in function \\"withTooltip: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "7"], + [0, 0, 0, "React Hook \\"useState\\" is called in function \\"withCombobox: StoryFn\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "8"] + ], "packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -864,7 +915,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/api/clients/folder/v1beta1/hooks.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "React Hook \\"useGetDisplayMappingQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"], + [0, 0, 0, "React Hook \\"useGetFolderParentsQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"], + [0, 0, 0, "React Hook \\"useGetFolderQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"] ], "public/app/core/TableModel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -1274,6 +1328,9 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx:5381": [ + [0, 0, 0, "React Hook \\"useStyles2\\" is called in function \\"getPreviewResults\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], "public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -1403,6 +1460,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx:5381": [ + [0, 0, 0, "React Hook \\"useStyles2\\" is called in function \\"createMetadata\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], "public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -1554,6 +1614,18 @@ exports[`better eslint`] = { "public/app/features/browse-dashboards/components/NewFolderForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/commandPalette/actions/recentScopesActions.ts:5381": [ + [0, 0, 0, "React Hook \\"useScopesServices\\" is called in function \\"getRecentScopesActions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], + "public/app/features/commandPalette/actions/scopeActions.tsx:5381": [ + [0, 0, 0, "React Hook \\"useGlobalScopesSearch\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"], + [0, 0, 0, "React Hook \\"useRegisterActions\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"], + [0, 0, 0, "React Hook \\"useScopeTreeActions\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"], + [0, 0, 0, "React Hook \\"useScopesRow\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"] + ], + "public/app/features/commandPalette/actions/scopesUtils.ts:5381": [ + [0, 0, 0, "React Hook \\"useObservable\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"] + ], "public/app/features/connections/components/ConnectionsRedirectNotice/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] ], @@ -1633,9 +1705,23 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx:5381": [ + [0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"], + [0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"], + [0, 0, 0, "React Hook \\"useSceneObjectState\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"], + [0, 0, 0, "React Hook \\"useSnappingSplitter\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"] + ], + "public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx:5381": [ + [0, 0, 0, "React Hook \\"useLayoutCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"], + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"] + ], "public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx:5381": [ + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"], + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"] + ], "public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -1645,7 +1731,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"] ], "public/app/features/dashboard-scene/pages/DashboardScenePage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -1662,7 +1749,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"], + [0, 0, 0, "React Hook \\"useToggle\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"] ], "public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] @@ -1673,6 +1762,9 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"] ], + "public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx:5381": [ + [0, 0, 0, "React Hook \\"useIsProvisionedNG\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"] + ], "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -1712,21 +1804,39 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "9"], [0, 0, 0, "Unexpected any. Specify a different type.", "10"] ], + "public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemEditor.tsx:5381": [ + [0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" is called in function \\"getOptions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], "public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManagerEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"] ], + "public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx:5381": [ + [0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" is called in function \\"getDashboardGridItemOptions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], + "public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx:5381": [ + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"], + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"] + ], "public/app/features/dashboard-scene/scene/layout-default/row-actions/RowOptionsForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx:5381": [ + [0, 0, 0, "React Hook \\"useEditOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"] + ], "public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.", "1"] + ], + "public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx:5381": [ + [0, 0, 0, "React Hook \\"useEditOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"] ], "public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.", "1"] ], "public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -1751,6 +1861,15 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx:5381": [ + [0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"] + ], + "public/app/features/dashboard-scene/settings/JsonModelEditView.tsx:5381": [ + [0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"], + [0, 0, 0, "React Hook \\"useSaveDashboard\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"], + [0, 0, 0, "React Hook \\"useState\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"], + [0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "3"] + ], "public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -1772,11 +1891,19 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "8"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "9"] ], + "public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx:5381": [ + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"] + ], "public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useLocalVariableOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"], + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"], + [0, 0, 0, "React Hook \\"useVariableSelectionOptionsCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "3"], + [0, 0, 0, "React Hook \\"useVariableTypeCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "4"] ], "public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"] ], "public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] @@ -2231,6 +2358,10 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/features/datasources/components/EditDataSource.test.tsx:5381": [ + [0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"component\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"], + [0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"component\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"] + ], "public/app/features/datasources/components/picker/DataSourceCard.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -2311,9 +2442,16 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx:5381": [ + [0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"], + [0, 0, 0, "React Hook \\"usePluginLinks\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"] + ], "public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"] ], + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx:5381": [ + [0, 0, 0, "React Hook \\"usePluginLinks\\" is called in function \\"getSpanDetailLinkButtons\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"] + ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx:5381": [ [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] ], @@ -2424,6 +2562,19 @@ exports[`better eslint`] = { "public/app/features/live/centrifuge/channel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/logs/components/panel/LogLineDetailsFields.tsx:5381": [ + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"], + [0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"], + [0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"], + [0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "4"], + [0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "5"] + ], + "public/app/features/logs/components/panel/LogList.tsx:5381": [ + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"], + [0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"] + ], "public/app/features/logs/logsFrame.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -2615,14 +2766,24 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/features/provisioning/GettingStarted/Sidebar.tsx:5381": [ + [0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"] + ], "public/app/features/provisioning/GettingStarted/SidebarItem.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/provisioning/Job/JobContent.tsx:5381": [ + [0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"] + ], + "public/app/features/provisioning/Job/RecentJobs.tsx:5381": [ + [0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"] + ], "public/app/features/query/components/QueryEditorRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"] + [0, 0, 0, "React Hook \\"usePluginComponents\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"] ], "public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] @@ -2793,6 +2954,9 @@ exports[`better eslint`] = { "public/app/features/transformers/editors/GroupToNestedTableTransformerEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx:5381": [ + [0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"] + ], "public/app/features/transformers/editors/ReduceTransformerEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/eslint.config.js b/eslint.config.js index 664de667de6..f0958ee1346 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,8 +8,6 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const lodashPlugin = require('eslint-plugin-lodash'); const barrelPlugin = require('eslint-plugin-no-barrel-files'); const reactPlugin = require('eslint-plugin-react'); -// FIXME: Remove once eslint-config-grafana is updated to include correct plugin -const hooksPlugin = require('eslint-plugin-react-hooks'); const testingLibraryPlugin = require('eslint-plugin-testing-library'); const unicornPlugin = require('eslint-plugin-unicorn'); @@ -60,17 +58,7 @@ module.exports = [ 'public/build-swagger', // swagger build output ], }, - // FIXME: Remove once eslint-config-grafana is updated to include correct plugin - { - name: 'react-hooks-plugin', - plugins: { - 'react-hooks': hooksPlugin, - }, - }, - // Conditionally run the betterer rules if enabled in dev's config - ...(enableBettererRules ? bettererConfig : []), - // FIXME: Remove filtering once eslint-config-grafana is updated to include correct plugin - ...grafanaConfig.filter(Boolean), + ...grafanaConfig, { name: 'react/jsx-runtime-rules', rules: reactPlugin.configs.flat['jsx-runtime'].rules, @@ -176,6 +164,8 @@ module.exports = [ ], // FIXME: Fix these in follow up PR 'react/no-unescaped-entities': 'off', + // Turn off react-hooks/rules-of-hooks whilst present in betterer + 'react-hooks/rules-of-hooks': 'off', }, }, { @@ -455,4 +445,7 @@ module.exports = [ ], }, }, + // Conditionally run the betterer rules if enabled in dev's config + // Should be last in the config so it can override any temporary disables in here + ...(enableBettererRules ? bettererConfig : []), ]; diff --git a/package.json b/package.json index 42ecab60013..388b925397a 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@crowdin/crowdin-api-client": "^1.42.0", "@cypress/webpack-preprocessor": "6.0.4", "@emotion/eslint-plugin": "11.12.0", - "@grafana/eslint-config": "8.1.0", + "@grafana/eslint-config": "8.2.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", "@grafana/plugin-e2e": "2.1.7", "@grafana/test-utils": "workspace:*", @@ -195,7 +195,7 @@ "eslint-plugin-lodash": "8.0.0", "eslint-plugin-no-barrel-files": "^1.1.1", "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "5.1.0", + "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-testing-library": "^7.0.0", "eslint-plugin-unicorn": "^56.0.0", "eslint-scope": "^8.1.0", diff --git a/yarn.lock b/yarn.lock index 794ab15ef98..65b8f22dd32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3171,9 +3171,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/eslint-config@npm:8.1.0": - version: 8.1.0 - resolution: "@grafana/eslint-config@npm:8.1.0" +"@grafana/eslint-config@npm:8.2.0": + version: 8.2.0 + resolution: "@grafana/eslint-config@npm:8.2.0" peerDependencies: "@stylistic/eslint-plugin-ts": ">=2.9.0" "@typescript-eslint/eslint-plugin": ">=6.18.0" @@ -3184,7 +3184,7 @@ __metadata: eslint-plugin-react: ">=7.33.0" eslint-plugin-react-hooks: ">=4.6.0" typescript: ">=5.2.0" - checksum: 10/d82f169de300dbb2ca62f74a4cb80461ab43e1adb5885e59a02c6080d9cef93f9aaac584b58b91f360014edf52b3dd1e81aad97e67011edc99de54a49deba7f2 + checksum: 10/ba006bf69401678be446901d1e35e121f0a220181798bc873b123112206b5544dc86c2f69fd401a57d38ba5427926ceaea651d7e3025e41a497787cacbb4ec0b languageName: node linkType: hard @@ -16344,12 +16344,12 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:5.1.0": - version: 5.1.0 - resolution: "eslint-plugin-react-hooks@npm:5.1.0" +"eslint-plugin-react-hooks@npm:5.2.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - checksum: 10/b6778fd9e1940b06868921309e8b269426e17eda555816d4b71def4dcf0572de1199fdb627ac09ce42160b9569a93cd9b0fd81b740ab4df98205461c53997a43 + checksum: 10/ebb79e9cf69ae06e3a7876536653c5e556b5fd8cd9dc49577f10a6e728360e7b6f5ce91f4339b33e93b26e3bb23805418f8b5e75db80baddd617b1dffe73bed1 languageName: node linkType: hard @@ -18304,7 +18304,7 @@ __metadata: "@grafana/azure-sdk": "npm:0.0.7" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/eslint-config": "npm:8.1.0" + "@grafana/eslint-config": "npm:8.2.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" "@grafana/faro-core": "npm:^1.19.0" "@grafana/faro-web-sdk": "npm:^1.19.0" @@ -18471,7 +18471,7 @@ __metadata: eslint-plugin-lodash: "npm:8.0.0" eslint-plugin-no-barrel-files: "npm:^1.1.1" eslint-plugin-react: "npm:7.37.5" - eslint-plugin-react-hooks: "npm:5.1.0" + eslint-plugin-react-hooks: "npm:5.2.0" eslint-plugin-testing-library: "npm:^7.0.0" eslint-plugin-unicorn: "npm:^56.0.0" eslint-scope: "npm:^8.1.0"