diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index ae8b600826f..141ad441208 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -6,6 +6,7 @@ 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'); @@ -16,7 +17,7 @@ const grafanaI18nPlugin = require('@grafana/i18n/eslint-plugin'); // 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; +const { rules, ...baseConfig } = grafanaConfig.find((config) => config?.name === '@grafana/eslint-config/flat'); /** * @type {Array} @@ -53,6 +54,13 @@ module.exports = [ // @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, + }, + }, { files: ['**/*.{ts,tsx,js}'], ...baseConfig, diff --git a/eslint.config.js b/eslint.config.js index 075aaaf17e7..664de667de6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,6 +8,8 @@ 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'); @@ -58,13 +60,20 @@ 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 : []), - grafanaConfig, + // FIXME: Remove filtering once eslint-config-grafana is updated to include correct plugin + ...grafanaConfig.filter(Boolean), { - name: 'react/jsx-runtime', - // @ts-ignore - not sure why but flat config is typed as a maybe? - ...reactPlugin.configs.flat['jsx-runtime'], + name: 'react/jsx-runtime-rules', + rules: reactPlugin.configs.flat['jsx-runtime'].rules, }, { name: 'grafana/defaults', @@ -165,6 +174,8 @@ module.exports = [ message: 'No bare anchor nodes containing only text. Use `TextLink` instead.', }, ], + // FIXME: Fix these in follow up PR + 'react/no-unescaped-entities': 'off', }, }, { @@ -366,6 +377,14 @@ module.exports = [ 'testing-library/no-container': 'off', }, }, + { + name: 'grafana/test-disables', + files: ['**/*.{spec,test}.{ts,tsx}'], + rules: { + 'react/display-name': 'off', + 'react/no-children-prop': 'off', + }, + }, { name: 'grafana/explore-traceview-overrides', files: ['public/app/features/explore/TraceView/components/demo/**/*.{ts,tsx,js,jsx}'], diff --git a/package.json b/package.json index b410a50e5a3..42ecab60013 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.0.0", + "@grafana/eslint-config": "8.1.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", "@grafana/plugin-e2e": "2.1.7", "@grafana/test-utils": "workspace:*", diff --git a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx index 157438b65ad..e597c329103 100644 --- a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx +++ b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx @@ -38,6 +38,7 @@ export const ValuePill = forwardRef( ); } ); +ValuePill.displayName = 'ValuePill'; const getValuePillStyles = (theme: GrafanaTheme2, disabled?: boolean) => ({ wrapper: css({ diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx index 8006c2043cf..ba8df94ac5c 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx @@ -20,6 +20,7 @@ import { SparklineCell, getStyles as getSparklineCellStyles } from './SparklineC const AutoCellRenderer = memo((props: TableCellRendererProps) => ( )); +AutoCellRenderer.displayName = 'AutoCellRenderer'; function isCustomCellOptions(options: TableCellOptions): options is TableCustomCellOptions { return options.type === TableCellDisplayMode.Custom; @@ -56,16 +57,19 @@ const CELL_REGISTRY: Record = { getStyles: mixinAutoCellStyles(getJsonCellStyles), }, [TableCellDisplayMode.Actions]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( [])} /> )), getStyles: getActionsCellStyles, }, [TableCellDisplayMode.DataLinks]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ), getStyles: getDataLinksStyles, }, [TableCellDisplayMode.Gauge]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( = { )), }, [TableCellDisplayMode.Sparkline]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( = { getStyles: getSparklineCellStyles, }, [TableCellDisplayMode.Geo]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ), getStyles: getGeoCellStyles, }, [TableCellDisplayMode.Image]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( )), getStyles: getImageStyles, }, [TableCellDisplayMode.Pill]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( )), @@ -108,6 +116,7 @@ const CELL_REGISTRY: Record = { testField: (field: Field) => field.type === FieldType.string, }, [TableCellDisplayMode.Markdown]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( )), @@ -115,6 +124,7 @@ const CELL_REGISTRY: Record = { testField: (field: Field) => field.type === FieldType.string, }, [TableCellDisplayMode.Custom]: { + // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => { if (!isCustomCellOptions(props.cellOptions) || !props.cellOptions.cellComponent) { return null; // nonsensical case, but better to typeguard it than throw. diff --git a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx index fd2559e5385..ed8e3c88a8d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx @@ -30,3 +30,4 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit // raw value return children; }); +MaybeWrapWithLink.displayName = 'MaybeWrapWithLink'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index caea5e1fcad..e53a1d9e827 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -787,6 +787,7 @@ export function TableNG(props: TableNGProps) { */ const renderRowFactory = (fields: Field[], panelContext: PanelContext, expandedRows: Set, enableSharedCrosshair: boolean) => + // eslint-disable-next-line react/display-name (key: React.Key, props: RenderRowProps): React.ReactNode => { const { row } = props; const rowIdx = row.__index; diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx index e85facade5a..2f0181672cc 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx @@ -81,3 +81,4 @@ export const TableCellActions = memo( ) ); +TableCellActions.displayName = 'TableCellActions'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx index ce707ea43e3..156854bcfd9 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx @@ -157,3 +157,4 @@ export const TableCellTooltip = memo( ); } ); +TableCellTooltip.displayName = 'TableCellTooltip'; diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx index 4d8d94dbcef..3f36302a7f5 100644 --- a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx @@ -40,6 +40,7 @@ export const TopSearchBarCommandPaletteTrigger = React.memo(() => { return ; }); +TopSearchBarCommandPaletteTrigger.displayName = 'TopSearchBarCommandPaletteTrigger'; interface PretendTextInputProps { onClick: () => void; diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index bfb965a73bd..4975bf8884d 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -104,3 +104,4 @@ export const DashboardPicker = forwardRef( ); } ); +DashboardPicker.displayName = 'DashboardPicker'; diff --git a/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx b/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx index 50dcafd10e6..ff4acf483d4 100644 --- a/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx +++ b/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx @@ -54,3 +54,4 @@ export const DeleteModal = React.memo(({ onConfirm, onDismiss, isOpen, folderNam /> ); }); +DeleteModal.displayName = 'DeleteModal'; diff --git a/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx b/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx index 9cceef11d22..ca347fc9a05 100644 --- a/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx +++ b/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx @@ -37,5 +37,6 @@ const SilenceGrafanaRuleDrawer = React.memo( ), (prevProps, nextProps) => prevProps.ruleUid === nextProps.ruleUid ); +SilenceGrafanaRuleDrawer.displayName = 'SilenceGrafanaRuleDrawer'; export default SilenceGrafanaRuleDrawer; diff --git a/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx b/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx index 7928a57ed25..48c0301ecc5 100644 --- a/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx +++ b/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx @@ -152,6 +152,7 @@ const ListItem = forwardRef( ); } ); +ListItem.displayName = 'ListItem'; const getStyles = (theme: GrafanaTheme2) => ({ listItem: css({ diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx index 2694bfc580d..f6819349f9e 100644 --- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx @@ -470,6 +470,7 @@ const DataSourceLogo = forwardRef(({ data /> ); }); +DataSourceLogo.displayName = 'DataSourceLogo'; const dataSourceLogoStyles = (theme: GrafanaTheme2) => ({ logo: css({ diff --git a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx index 7940da956f0..3196099e8a2 100644 --- a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx +++ b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx @@ -100,3 +100,4 @@ export const ResourceEditFormSharedFields = memo + // eslint-disable-next-line react/display-name memo( ({ item, diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index 7c65fa0c48a..fa95a98dbc4 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -165,6 +165,7 @@ export const OptionsPaneCategory = React.memo( ); } ); +OptionsPaneCategory.displayName = 'OptionsPaneCategory'; const getStyles = (theme: GrafanaTheme2) => ({ box: css({ diff --git a/public/app/features/logs/components/LogRows.tsx b/public/app/features/logs/components/LogRows.tsx index 062b62b09b4..85c4af15f7a 100644 --- a/public/app/features/logs/components/LogRows.tsx +++ b/public/app/features/logs/components/LogRows.tsx @@ -328,3 +328,4 @@ export const LogRows = memo( ); } ); +LogRows.displayName = 'LogRows'; diff --git a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx index cebdf95c955..47a8a0e01eb 100644 --- a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx @@ -285,7 +285,12 @@ const OrganizeFieldsTransformerEditor = ({ options, input, onChange }: OrganizeF <>
{uiOrderByItems.map((item, idx) => ( - + ))}
{provided.placeholder} diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx index f8c06b9390a..66a7357ff53 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx @@ -131,6 +131,7 @@ const makeRenderGroupBy = ( onDeleteGroupBy: (propertyName: string) => void, templateVariableOptions: SelectableValue ) => { + // eslint-disable-next-line react/display-name return ( item: BuilderQueryEditorGroupByExpression, onChangeItem: (updatedItem: BuilderQueryEditorGroupByExpression) => void, diff --git a/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx b/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx index 213e8b86c7a..b4b46a8a26b 100644 --- a/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx @@ -155,7 +155,7 @@ export const TimeRegionEditor = ({ value, onChange }: Props) => { description={ <> {t('dashboard-settings.time-regions.advanced-description-use', 'Use ')} - + {t('dashboard-settings.time-regions.advanced-description-cron', 'Cron syntax')} {t( diff --git a/yarn.lock b/yarn.lock index 682b1078897..794ab15ef98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3171,9 +3171,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/eslint-config@npm:8.0.0": - version: 8.0.0 - resolution: "@grafana/eslint-config@npm:8.0.0" +"@grafana/eslint-config@npm:8.1.0": + version: 8.1.0 + resolution: "@grafana/eslint-config@npm:8.1.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/b9a875d9e157eff1212cbd2648f18a1b04ebdd2bbea7d13ecb63d5b441fb3f550aa452eccdbf3ccadedd25996e6fb33cba615e3dfdd21ce9e00b505287a51dc4 + checksum: 10/d82f169de300dbb2ca62f74a4cb80461ab43e1adb5885e59a02c6080d9cef93f9aaac584b58b91f360014edf52b3dd1e81aad97e67011edc99de54a49deba7f2 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.0.0" + "@grafana/eslint-config": "npm:8.1.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" "@grafana/faro-core": "npm:^1.19.0" "@grafana/faro-web-sdk": "npm:^1.19.0"