2024-11-07 23:31:06 +08:00
// @ts-check
const emotionPlugin = require ( '@emotion/eslint-plugin' ) ;
2025-05-12 17:38:26 +08:00
const restrictedGlobals = require ( 'confusing-browser-globals' ) ;
2024-11-07 23:31:06 +08:00
const importPlugin = require ( 'eslint-plugin-import' ) ;
const jestPlugin = require ( 'eslint-plugin-jest' ) ;
const jestDomPlugin = require ( 'eslint-plugin-jest-dom' ) ;
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' ) ;
2024-11-11 21:02:46 +08:00
const unicornPlugin = require ( 'eslint-plugin-unicorn' ) ;
2024-11-07 23:31:06 +08:00
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-11-07 23:31:06 +08:00
2025-05-30 01:13:25 +08:00
const pluginsToTranslate = [
'public/app/plugins/panel' ,
'public/app/plugins/datasource/azuremonitor' ,
'public/app/plugins/datasource/mssql' ,
] ;
2024-11-15 22:01:39 +08:00
2025-09-04 17:47:13 +08:00
const commonTestIgnores = [
'**/*.{test,spec}.{ts,tsx}' ,
'**/__mocks__/**' ,
'**/mocks/**/*.{ts,tsx}' ,
'**/public/test/**' ,
'**/mocks.{ts,tsx}' ,
2025-09-11 19:55:54 +08:00
'**/*.mock.{ts,tsx}' ,
'**/{test-helpers,testHelpers}.{ts,tsx}' ,
'**/{spec,test-helpers}/**/*.{ts,tsx}' ,
2025-09-04 17:47:13 +08:00
] ;
2025-09-11 19:55:54 +08:00
const generatedFiles = [ '**/*.gen.ts' , '**/*_gen.ts' ] ;
2025-09-04 17:47:13 +08:00
const enterpriseIgnores = [ 'public/app/extensions/**/*' , 'e2e/extensions/**/*' ] ;
2025-08-28 17:19:36 +08:00
// [FIXME] add comment about this applying everywhere
const baseImportConfig = {
patterns : [
{
group : [ 'react-i18next' , 'i18next' ] ,
importNames : [ 't' ] ,
message : 'Please import from @grafana/i18n instead' ,
} ,
{
group : [ 'react-i18next' ] ,
importNames : [ 'Trans' ] ,
message : 'Please import from @grafana/i18n instead' ,
} ,
2025-08-29 18:33:14 +08:00
{
group : [ '@grafana/ui*' , '*/Layout/*' ] ,
importNames : [ 'Layout' , 'HorizontalGroup' , 'VerticalGroup' ] ,
message : 'Use Stack component instead.' ,
} ,
2025-08-28 17:19:36 +08:00
{
regex : '\\.test$' ,
message :
'Do not import test files. If you require reuse of constants/mocks across files, create a separate file with no tests' ,
} ,
2025-09-04 17:47:13 +08:00
{
group : [ '@grafana/ui/src/*' , '@grafana/runtime/src/*' , '@grafana/data/src/*' ] ,
message : 'Import from the public export instead.' ,
} ,
2025-08-28 17:19:36 +08:00
] ,
paths : [
{
name : 'react-redux' ,
importNames : [ 'useDispatch' , 'useSelector' ] ,
message : 'Please import from app/types/store instead.' ,
} ,
] ,
} ;
/ * *
*
* @ param { { patterns ? : Array < object > , paths ? : Array < object > } } config
* @ returns
* /
function withBaseRestrictedImportsConfig ( config = { } ) {
const finalConfig = {
patterns : [ ... baseImportConfig . patterns , ... ( config ? . patterns ? ? [ ] ) ] ,
paths : [ ... baseImportConfig . paths , ... ( config ? . paths ? ? [ ] ) ] ,
} ;
return finalConfig ;
}
2024-11-07 23:31:06 +08:00
/ * *
* @ type { Array < import ( 'eslint' ) . Linter . Config > }
* /
module . exports = [
{
name : 'grafana/ignores' ,
ignores : [
'.github' ,
'.yarn' ,
'**/.*' , // dotfiles aren't ignored by default in FlatConfig
2025-09-11 19:55:54 +08:00
... generatedFiles ,
2024-11-07 23:31:06 +08:00
'**/build/' ,
'**/compiled/' ,
'**/dist/' ,
'data/' ,
'deployment_tools_config.json' ,
'devenv' ,
2025-07-11 17:31:33 +08:00
'e2e-playwright/test-plugins' ,
2024-11-07 23:31:06 +08:00
'e2e/tmp' ,
'packages/grafana-ui/src/components/Icon/iconBundle.ts' ,
'pkg' ,
'playwright-report' ,
'public/lib/monaco/' , // this path is no longer required but local dev environments may still have it
'public/locales/_build' ,
'public/locales/**/*.js' ,
'public/vendor/' ,
'scripts/grafana-server/tmp' ,
2025-04-22 20:56:25 +08:00
'packages/grafana-ui/src/graveyard' , // deprecated UI components slated for removal
2025-04-30 21:03:54 +08:00
'public/build-swagger' , // swagger build output
2024-11-07 23:31:06 +08:00
] ,
} ,
2025-08-22 22:38:02 +08:00
... grafanaConfig ,
2024-11-07 23:31:06 +08:00
{
2025-08-21 18:59:41 +08:00
name : 'react/jsx-runtime-rules' ,
rules : reactPlugin . configs . flat [ 'jsx-runtime' ] . rules ,
2024-11-07 23:31:06 +08:00
} ,
{
name : 'grafana/defaults' ,
linterOptions : {
// This reports unused disable directives that we can clean up but
// it also conflicts with the betterer eslint rules so disabled
reportUnusedDisableDirectives : false ,
} ,
files : [ '**/*.{ts,tsx,js}' ] ,
plugins : {
'@emotion' : emotionPlugin ,
lodash : lodashPlugin ,
jest : jestPlugin ,
import : importPlugin ,
'jsx-a11y' : jsxA11yPlugin ,
'no-barrel-files' : barrelPlugin ,
'@grafana' : grafanaPlugin ,
2025-03-17 20:54:12 +08:00
unicorn : unicornPlugin ,
2024-11-07 23:31:06 +08:00
} ,
settings : {
'import/internal-regex' : '^(app/)|(@grafana)' ,
'import/external-module-folders' : [ 'node_modules' , '.yarn' ] ,
2024-11-26 18:09:27 +08:00
// Silences a warning when linting enterprise code
react : {
version : 'detect' ,
} ,
2024-11-07 23:31:06 +08:00
} ,
rules : {
2024-12-12 01:33:31 +08:00
'no-duplicate-case' : 'error' ,
2024-11-07 23:31:06 +08:00
'@grafana/no-border-radius-literal' : 'error' ,
'@grafana/no-unreduced-motion' : 'error' ,
2025-05-12 17:34:17 +08:00
'@grafana/no-restricted-img-srcs' : 'error' ,
2024-11-07 23:31:06 +08:00
'react/prop-types' : 'off' ,
// need to ignore emotion's `css` prop, see https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md#rule-options
'react/no-unknown-property' : [ 'error' , { ignore : [ 'css' ] } ] ,
'@emotion/jsx-import' : 'error' ,
'@emotion/syntax-preference' : [ 2 , 'object' ] ,
'lodash/import-scope' : [ 2 , 'member' ] ,
'jest/no-focused-tests' : 'error' ,
'import/order' : [
'error' ,
{
2025-05-07 18:41:56 +08:00
pathGroups : [
{
pattern : 'img/**' ,
group : 'internal' ,
} ,
] ,
2024-11-07 23:31:06 +08:00
groups : [ [ 'builtin' , 'external' ] , 'internal' , 'parent' , 'sibling' , 'index' ] ,
'newlines-between' : 'always' ,
alphabetize : { order : 'asc' } ,
2025-05-07 18:41:56 +08:00
pathGroupsExcludedImportTypes : [ 'builtin' ] ,
2024-11-07 23:31:06 +08:00
} ,
] ,
2025-08-28 17:19:36 +08:00
'no-restricted-imports' : [ 'error' , baseImportConfig ] ,
2025-05-12 17:38:26 +08:00
'no-restricted-globals' : [ 'error' ] . concat ( restrictedGlobals ) ,
2024-11-07 23:31:06 +08:00
// Use typescript's no-redeclare for compatibility with overrides
'no-redeclare' : 'off' ,
'@typescript-eslint/no-redeclare' : [ 'error' ] ,
2025-03-17 20:54:12 +08:00
'unicorn/no-empty-file' : 'error' ,
2025-04-24 16:50:29 +08:00
'no-constant-condition' : 'error' ,
2025-08-06 23:04:03 +08:00
'no-restricted-syntax' : [
'error' ,
{
// value regex is to filter out whitespace-only text nodes (e.g. new lines and spaces in the JSX)
selector : "JSXElement[openingElement.name.name='a'] > JSXText[value!=/^\\s*$/]" ,
message : 'No bare anchor nodes containing only text. Use `TextLink` instead.' ,
} ,
] ,
2024-11-07 23:31:06 +08:00
} ,
} ,
2025-08-28 17:19:36 +08:00
{
name : 'grafana/no-extensions-imports' ,
files : [ 'public/**/*.{ts,tsx,js}' ] ,
ignores : [ 'public/app/extensions/**/*' ] ,
rules : {
'no-restricted-imports' : [
'error' ,
withBaseRestrictedImportsConfig ( {
patterns : [
{
group : [ 'app/extensions' , 'app/extensions/*' ] ,
message : 'Importing from app/extensions is not allowed' ,
} ,
] ,
} ) ,
] ,
} ,
} ,
2024-11-07 23:31:06 +08:00
{
name : 'grafana/uplot-overrides' ,
files : [ 'packages/grafana-ui/src/components/uPlot/**/*.{ts,tsx}' ] ,
rules : {
'react-hooks/rules-of-hooks' : 'off' ,
'react-hooks/exhaustive-deps' : 'off' ,
} ,
} ,
{
name : 'grafana/theme-demo-overrides' ,
files : [ 'packages/grafana-ui/src/components/ThemeDemos/**/*.{ts,tsx}' ] ,
rules : {
'@emotion/jsx-import' : 'off' ,
'react/jsx-uses-react' : 'off' ,
'react/react-in-jsx-scope' : 'off' ,
} ,
} ,
2025-07-08 20:37:09 +08:00
{
name : 'grafana/story-rules' ,
files : [ 'packages/grafana-ui/src/**/*.story.tsx' ] ,
rules : {
'@grafana/consistent-story-titles' : 'error' ,
} ,
} ,
2024-11-07 23:31:06 +08:00
{
name : 'grafana/public-dashboards-overrides' ,
files : [ 'public/dashboards/scripted*.js' ] ,
rules : {
'no-redeclare' : 'error' ,
'@typescript-eslint/no-redeclare' : 'off' ,
} ,
} ,
{
name : 'grafana/jsx-a11y-overrides' ,
files : [ '**/*.tsx' ] ,
ignores : [ '**/*.{spec,test}.tsx' ] ,
rules : {
2025-05-07 22:43:48 +08:00
... jsxA11yPlugin . configs . recommended . rules ,
2024-11-07 23:31:06 +08:00
'jsx-a11y/no-autofocus' : [
'error' ,
{
ignoreNonDOM : true ,
} ,
] ,
'jsx-a11y/label-has-associated-control' : [
'error' ,
{
controlComponents : [ 'NumberInput' ] ,
depth : 2 ,
} ,
] ,
} ,
} ,
2025-08-28 17:19:36 +08:00
2024-11-07 23:31:06 +08:00
{
2025-08-28 17:19:36 +08:00
// No NPM package should import from @grafana/*/internal because it does not exist
// outside of this repo - they're not published to NPM.
name : 'grafana/packages-overrides' ,
files : [ 'packages/**/*.{ts,tsx}' ] ,
ignores : [ ] ,
2024-11-07 23:31:06 +08:00
rules : {
'no-restricted-imports' : [
'error' ,
2025-08-28 17:19:36 +08:00
withBaseRestrictedImportsConfig ( {
patterns : [
2024-11-07 23:31:06 +08:00
{
2025-08-28 17:19:36 +08:00
group : [ '@grafana/*/internal' ] ,
message : "'internal' exports are not available in NPM packages because they are not published to NPM" ,
2024-11-07 23:31:06 +08:00
} ,
] ,
2025-08-28 17:19:36 +08:00
} ) ,
2024-11-07 23:31:06 +08:00
] ,
} ,
} ,
2025-08-28 17:19:36 +08:00
2024-11-07 23:31:06 +08:00
{
2025-08-28 17:19:36 +08:00
// @grafana/runtime shouldn't be imported from our 'library' NPM packages
name : 'grafana/packages-that-cant-import-runtime' ,
files : [
'packages/grafana-ui/**/*.{ts,tsx}' ,
'packages/grafana-data/**/*.{ts,tsx}' ,
'packages/grafana-schema/**/*.{ts,tsx}' ,
'packages/grafana-e2e-selectors/**/*.{ts,tsx}' ,
] ,
ignores : [ ] ,
2024-11-07 23:31:06 +08:00
rules : {
'no-restricted-imports' : [
'error' ,
2025-08-28 17:19:36 +08:00
withBaseRestrictedImportsConfig ( {
patterns : [
{
// Duplicated because these rules override the previous grafana/packages-overrides
group : [ '@grafana/*/internal' ] ,
message : "'internal' exports are not available in NPM packages because they are not published to NPM" ,
} ,
{
group : [ '@grafana/runtime' ] ,
message : "'@grafana/runtime' should not be imported from library packages" ,
} ,
] ,
} ) ,
2024-11-07 23:31:06 +08:00
] ,
} ,
} ,
2025-08-28 17:19:36 +08:00
2024-11-07 23:31:06 +08:00
{
name : 'grafana/alerting-overrides' ,
2024-11-11 21:02:46 +08:00
plugins : {
unicorn : unicornPlugin ,
2025-01-09 18:02:52 +08:00
react : reactPlugin ,
2025-04-17 00:45:12 +08:00
'@grafana' : grafanaPlugin ,
2024-11-11 21:02:46 +08:00
} ,
2025-05-15 02:30:52 +08:00
files : [ 'public/app/features/alerting/**/*.{ts,tsx,js,jsx}' , 'packages/grafana-alerting/**/*.{ts,tsx,js,jsx}' ] ,
2024-11-07 23:31:06 +08:00
rules : {
2024-12-11 01:42:33 +08:00
'sort-imports' : [ 'error' , { ignoreDeclarationSort : true } ] ,
2024-11-07 23:31:06 +08:00
'dot-notation' : 'error' ,
'prefer-const' : 'error' ,
'react/no-unused-prop-types' : 'error' ,
2025-01-09 18:02:52 +08:00
'react/self-closing-comp' : 'error' ,
2025-03-12 04:31:47 +08:00
'react/jsx-no-useless-fragment' : [ 'error' , { allowExpressions : true } ] ,
2024-11-11 21:02:46 +08:00
'unicorn/no-unused-properties' : 'error' ,
2025-04-23 19:00:40 +08:00
'no-nested-ternary' : 'error' ,
2024-11-07 23:31:06 +08:00
} ,
} ,
2025-04-17 00:45:12 +08:00
{
// Sections of codebase that have all translation markup issues fixed
name : 'grafana/i18n-overrides' ,
plugins : {
'@grafana' : grafanaPlugin ,
2025-05-29 22:11:59 +08:00
'@grafana/i18n' : grafanaI18nPlugin ,
2025-04-17 00:45:12 +08:00
} ,
2025-05-15 21:31:49 +08:00
files : [
'public/app/!(plugins)/**/*.{ts,tsx,js,jsx}' ,
'packages/grafana-ui/**/*.{ts,tsx,js,jsx}' ,
2025-07-23 00:57:05 +08:00
'packages/grafana-data/**/*.{ts,tsx,js,jsx}' ,
2025-06-12 17:52:04 +08:00
'packages/grafana-sql/**/*.{ts,tsx,js,jsx}' ,
2025-06-12 18:29:33 +08:00
'packages/grafana-prometheus/**/*.{ts,tsx,js,jsx}' ,
2025-05-15 21:31:49 +08:00
... pluginsToTranslate . map ( ( plugin ) => ` ${ plugin } /**/*.{ts,tsx,js,jsx} ` ) ,
2025-04-22 22:40:06 +08:00
] ,
2025-05-29 04:14:43 +08:00
ignores : [
'public/test/**' ,
'**/*.{test,spec,story}.{ts,tsx}' ,
'**/{tests,__mocks__,__tests__,fixtures,spec,mocks}/**' ,
'**/{test-utils,testHelpers,mocks}.{ts,tsx}' ,
'**/mock*.{ts,tsx}' ,
] ,
2025-04-17 00:45:12 +08:00
rules : {
2025-05-29 22:11:59 +08:00
'@grafana/i18n/no-untranslated-strings' : [ 'error' , { calleesToIgnore : [ '^css$' , 'use[A-Z].*' ] } ] ,
'@grafana/i18n/no-translation-top-level' : 'error' ,
2025-04-17 00:45:12 +08:00
} ,
} ,
2024-11-07 23:31:06 +08:00
{
2025-05-10 00:45:33 +08:00
name : 'grafana/tests' ,
2024-11-07 23:31:06 +08:00
plugins : {
2024-12-05 20:14:09 +08:00
'testing-library' : testingLibraryPlugin ,
2024-11-07 23:31:06 +08:00
'jest-dom' : jestDomPlugin ,
} ,
files : [
'public/app/features/alerting/**/__tests__/**/*.[jt]s?(x)' ,
'public/app/features/alerting/**/?(*.)+(spec|test).[jt]s?(x)' ,
2025-05-15 02:30:52 +08:00
'packages/{grafana-ui,grafana-alerting}/**/*.{spec,test}.{ts,tsx}' ,
2024-11-07 23:31:06 +08:00
] ,
rules : {
... testingLibraryPlugin . configs [ 'flat/react' ] . rules ,
... jestDomPlugin . configs [ 'flat/recommended' ] . rules ,
'testing-library/prefer-user-event' : 'error' ,
2025-05-10 00:45:33 +08:00
'jest/expect-expect' : [ 'error' , { assertFunctionNames : [ 'expect*' , 'assert*' , 'reducerTester' ] } ] ,
} ,
} ,
{
name : 'grafana/test-overrides-to-fix' ,
plugins : {
'testing-library' : testingLibraryPlugin ,
} ,
files : [ 'packages/grafana-ui/**/*.{spec,test}.{ts,tsx}' ] ,
rules : {
// grafana-ui has lots of violations of direct node access and container methods, so disabling for now
'testing-library/no-node-access' : 'off' ,
'testing-library/no-container' : 'off' ,
2024-11-07 23:31:06 +08:00
} ,
} ,
2025-08-21 18:59:41 +08:00
{
name : 'grafana/test-disables' ,
files : [ '**/*.{spec,test}.{ts,tsx}' ] ,
rules : {
'react/display-name' : 'off' ,
'react/no-children-prop' : 'off' ,
} ,
} ,
2024-11-07 23:31:06 +08:00
{
name : 'grafana/explore-traceview-overrides' ,
files : [ 'public/app/features/explore/TraceView/components/demo/**/*.{ts,tsx,js,jsx}' ] ,
rules : {
'import/no-extraneous-dependencies' : 'off' ,
} ,
} ,
{
name : 'grafana/decoupled-plugins-overrides' ,
files : [
'public/app/plugins/datasource/azuremonitor/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/cloud-monitoring/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/cloudwatch/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/elasticsearch/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/elasticsearch/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/grafana-postgresql-datasource/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/grafana-pyroscope-datasource/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/grafana-testdata-datasource/**/*.{ts,tsx}' ,
2025-09-11 01:48:29 +08:00
'public/app/plugins/datasource/graphite/**/*.{ts,tsx}' ,
2024-11-07 23:31:06 +08:00
'public/app/plugins/datasource/jaeger/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/loki/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/loki/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/mysql/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/parca/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/tempo/**/*.{ts,tsx}' ,
'public/app/plugins/datasource/zipkin/**/*.{ts,tsx}' ,
] ,
plugins : {
import : importPlugin ,
} ,
settings : {
'import/resolver' : {
node : {
extensions : [ '.ts' , '.tsx' ] ,
} ,
} ,
} ,
rules : {
'import/no-restricted-paths' : [
'error' ,
{
zones : [
{
target : './public/app/plugins' ,
from : './public' ,
except : [ './app/plugins' ] ,
message : 'Core plugins are not allowed to depend on Grafana core packages' ,
} ,
] ,
} ,
] ,
} ,
} ,
2025-08-28 17:19:36 +08:00
2025-09-04 17:47:13 +08:00
{
// custom rule for Table to avoid performance regressions
files : [ 'packages/grafana-ui/src/components/Table/TableNG/Cells/**/*.{ts,tsx}' ] ,
rules : {
'no-restricted-imports' : [
'error' ,
withBaseRestrictedImportsConfig ( {
patterns : [
{
group : [ '**/themes/ThemeContext' ] ,
importNames : [ 'useStyles2' , 'useTheme2' ] ,
message :
'Do not use "useStyles2" or "useTheme2" in a cell directly. Instead, provide styles to cells via `getDefaultCellStyles` or `getCellSpecificStyles`.' ,
} ,
] ,
} ) ,
] ,
} ,
} ,
// Old betterer rules config:
{
files : [ '**/*.{js,jsx,ts,tsx}' ] ,
ignores :
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
enterpriseIgnores ,
rules : {
'@typescript-eslint/no-explicit-any' : 'error' ,
'@grafana/no-aria-label-selectors' : 'error' ,
} ,
} ,
{
files : [ '**/*.{js,jsx,ts,tsx}' ] ,
ignores : [
... commonTestIgnores ,
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
... enterpriseIgnores ,
] ,
rules : {
'@typescript-eslint/consistent-type-assertions' : [ 'error' , { assertionStyle : 'never' } ] ,
'no-restricted-syntax' : [
'error' ,
{
selector : 'Identifier[name=localStorage]' ,
message : 'Direct usage of localStorage is not allowed. import store from @grafana/data instead' ,
} ,
{
selector : 'MemberExpression[object.name=localStorage]' ,
message : 'Direct usage of localStorage is not allowed. import store from @grafana/data instead' ,
} ,
{
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.' ,
} ,
{
selector : 'CallExpression[callee.type="MemberExpression"][callee.property.name="localeCompare"]' ,
message :
'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.' ,
} ,
{
// eslint-disable-next-line no-restricted-syntax
selector : 'Literal[value=/gf-form/], TemplateElement[value.cooked=/gf-form/]' ,
// eslint-disable-next-line no-restricted-syntax
message : 'gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.' ,
} ,
{
selector :
"Property[key.name='a11y'][value.type='ObjectExpression'] Property[key.name='test'][value.value='off']" ,
message : 'Skipping a11y tests is not allowed. Please fix the component or story instead.' ,
} ,
] ,
} ,
} ,
{
files : [ 'public/app/**/*.{ts,tsx}' ] ,
ignores : [
... commonTestIgnores ,
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
... enterpriseIgnores ,
] ,
rules : {
'no-barrel-files/no-barrel-files' : 'error' ,
} ,
} ,
2025-09-12 16:26:50 +08:00
{
// @grafana/i18n shouldn't import from our 'library' NPM packages
name : 'grafana/packages-that-i18n-cant-import' ,
files : [ 'packages/grafana-i18n/**/*.{ts,tsx}' ] ,
ignores : [ ] ,
rules : {
'no-restricted-imports' : [
'error' ,
withBaseRestrictedImportsConfig ( {
patterns : [
{
group : [ '@grafana/*' ] ,
message : "'@grafana/* packages' should not be imported in @grafana/i18n" ,
} ,
] ,
} ) ,
] ,
} ,
} ,
2024-11-07 23:31:06 +08:00
] ;