2024-11-15 22:01:39 +08:00
// @ts-check
2024-12-05 20:14:09 +08:00
const emotionPlugin = require ( '@emotion/eslint-plugin' ) ;
const importPlugin = require ( 'eslint-plugin-import' ) ;
const jestPlugin = require ( 'eslint-plugin-jest' ) ;
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 testingLibraryPlugin = require ( 'eslint-plugin-testing-library' ) ;
const grafanaConfig = require ( '@grafana/eslint-config/flat' ) ;
const grafanaPlugin = require ( '@grafana/eslint-plugin' ) ;
2025-05-29 22:11:59 +08:00
const grafanaI18nPlugin = require ( '@grafana/i18n/eslint-plugin' ) ;
2024-12-05 20:14:09 +08:00
// Include the Grafana config 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 ;
2024-11-15 22:01:39 +08:00
/ * *
* @ type { Array < import ( 'eslint' ) . Linter . Config > }
* /
module . exports = [
2024-12-05 20:14:09 +08:00
{
name : 'grafana/betterer-ignores' ,
ignores : [
'.github' ,
'.yarn' ,
'**/.*' ,
'**/*.gen.ts' ,
'**/build/' ,
'**/compiled/' ,
'**/dist/' ,
'data/' ,
'deployment_tools_config.json' ,
'devenv' ,
'e2e/test-plugins' ,
'e2e/tmp' ,
'packages/grafana-ui/src/components/Icon/iconBundle.ts' ,
'pkg' ,
'playwright-report' ,
'public/lib/monaco/' ,
'public/locales/_build' ,
'public/locales/**/*.js' ,
'public/vendor/' ,
'scripts/grafana-server/tmp' ,
'!.betterer.eslint.config.js' ,
] ,
} ,
{
name : 'react/jsx-runtime' ,
// @ts-ignore - not sure why but flat config is typed as a maybe?
... reactPlugin . configs . flat [ 'jsx-runtime' ] ,
} ,
{
files : [ '**/*.{ts,tsx,js}' ] ,
... baseConfig ,
plugins : {
... baseConfig . plugins ,
'@emotion' : emotionPlugin ,
lodash : lodashPlugin ,
jest : jestPlugin ,
import : importPlugin ,
'jsx-a11y' : jsxA11yPlugin ,
'no-barrel-files' : barrelPlugin ,
'@grafana' : grafanaPlugin ,
'testing-library' : testingLibraryPlugin ,
2025-05-29 22:11:59 +08:00
'@grafana/i18n' : grafanaI18nPlugin ,
2024-12-05 20:14:09 +08:00
} ,
linterOptions : {
// This reports unused disable directives that we can clean up but
// it also conflicts with the betterer eslint rules so disabled
reportUnusedDisableDirectives : false ,
} ,
} ,
2024-11-15 22:01:39 +08:00
{
files : [ '**/*.{js,jsx,ts,tsx}' ] ,
rules : {
'@typescript-eslint/no-explicit-any' : 'error' ,
'@grafana/no-aria-label-selectors' : 'error' ,
'no-restricted-imports' : [
'error' ,
{
patterns : [
{
group : [ '@grafana/ui*' , '*/Layout/*' ] ,
importNames : [ 'Layout' , 'HorizontalGroup' , 'VerticalGroup' ] ,
message : 'Use Stack component instead.' ,
} ,
2025-01-15 21:47:44 +08:00
{
group : [ '@grafana/ui/src/*' , '@grafana/runtime/src/*' , '@grafana/data/src/*' ] ,
message : 'Import from the public export instead.' ,
} ,
2024-11-15 22:01:39 +08:00
] ,
} ,
] ,
} ,
} ,
{
2025-04-23 17:24:26 +08:00
files : [ '**/*.{js,jsx,ts,tsx}' ] ,
ignores : [
'**/*.{test,spec}.{ts,tsx}' ,
'**/__mocks__/**' ,
'**/public/test/**' ,
'**/mocks.{ts,tsx}' ,
2025-07-08 00:02:59 +08:00
'**/mocks/**/*.{ts,tsx}' ,
2025-04-23 17:24:26 +08:00
'**/spec/**/*.{ts,tsx}' ,
] ,
2024-11-15 22:01:39 +08:00
rules : {
'@typescript-eslint/consistent-type-assertions' : [ 'error' , { assertionStyle : 'never' } ] ,
} ,
} ,
2025-05-07 16:28:32 +08:00
{
files : [ '**/*.{js,jsx,ts,tsx}' ] ,
ignores : [
'**/*.{test,spec}.{ts,tsx}' ,
'**/__mocks__/**' ,
'**/public/test/**' ,
'**/mocks.{ts,tsx}' ,
'**/spec/**/*.{ts,tsx}' ,
] ,
rules : {
'no-restricted-syntax' : [
'error' ,
{
selector : 'Identifier[name=localStorage]' ,
2025-05-28 16:23:54 +08:00
message : 'Direct usage of localStorage is not allowed. import store from @grafana/data instead' ,
2025-05-07 16:28:32 +08:00
} ,
{
selector : 'MemberExpression[object.name=localStorage]' ,
2025-05-28 16:23:54 +08:00
message : 'Direct usage of localStorage is not allowed. import store from @grafana/data instead' ,
2025-05-07 16:28:32 +08:00
} ,
2025-05-19 17:34:13 +08:00
{
selector :
'Program:has(ImportDeclaration[source.value="@grafana/ui"] ImportSpecifier[imported.name="Card"]) JSXOpeningElement[name.name="Card"]:not(:has(JSXAttribute[name.name="noMargin"]))' ,
message :
'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.' ,
} ,
{
selector :
'Program:has(ImportDeclaration[source.value="@grafana/ui"] ImportSpecifier[imported.name="Field"]) JSXOpeningElement[name.name="Field"]:not(:has(JSXAttribute[name.name="noMargin"]))' ,
message :
'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.' ,
} ,
2025-05-07 16:28:32 +08:00
] ,
} ,
} ,
2024-11-15 22:01:39 +08:00
{
files : [ 'public/app/**/*.{ts,tsx}' ] ,
rules : {
'no-barrel-files/no-barrel-files' : 'error' ,
} ,
} ,
] ;