diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 81b97bb03f3..77a920179e5 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -16,7 +16,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 mappedBaseConfigs = grafanaConfig.map((config) => { +const mappedBaseConfigs = grafanaConfig.map((/** @type {import('eslint').Linter.Config} */ config) => { const { rules, ...baseConfig } = config; return baseConfig; }); diff --git a/eslint.config.js b/eslint.config.js index f0958ee1346..3b3209f20a6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -26,6 +26,48 @@ const pluginsToTranslate = [ 'public/app/plugins/datasource/mssql', ]; +// [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', + }, + { + regex: '\\.test$', + message: + 'Do not import test files. If you require reuse of constants/mocks across files, create a separate file with no tests', + }, + ], + paths: [ + { + name: 'react-redux', + importNames: ['useDispatch', 'useSelector'], + message: 'Please import from app/types/store instead.', + }, + ], +}; + +/** + * + * @param {{ patterns?: Array, paths?: Array }} config + * @returns + */ +function withBaseRestrictedImportsConfig(config = {}) { + const finalConfig = { + patterns: [...baseImportConfig.patterns, ...(config?.patterns ?? [])], + paths: [...baseImportConfig.paths, ...(config?.paths ?? [])], + }; + + return finalConfig; +} + /** * @type {Array} */ @@ -118,35 +160,7 @@ module.exports = [ pathGroupsExcludedImportTypes: ['builtin'], }, ], - 'no-restricted-imports': [ - 'error', - { - 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', - }, - { - regex: '\\.test$', - message: - 'Do not import test files. If you require reuse of constants/mocks across files, create a separate file with no tests', - }, - ], - paths: [ - { - name: 'react-redux', - importNames: ['useDispatch', 'useSelector'], - message: 'Please import from app/types instead.', - }, - ], - }, - ], + 'no-restricted-imports': ['error', baseImportConfig], 'no-restricted-globals': ['error'].concat(restrictedGlobals), // Use typescript's no-redeclare for compatibility with overrides @@ -168,6 +182,26 @@ module.exports = [ 'react-hooks/rules-of-hooks': 'off', }, }, + + { + 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', + }, + ], + }), + ], + }, + }, + { name: 'grafana/uplot-overrides', files: ['packages/grafana-ui/src/components/uPlot/**/*.{ts,tsx}'], @@ -221,76 +255,58 @@ module.exports = [ ], }, }, + { - name: 'grafana/data-overrides', - files: ['packages/grafana-data/**/*.{ts,tsx}'], - ignores: ['packages/grafana-data/src/**/*.{spec,test}.{ts,tsx}'], + // 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: [], rules: { 'no-restricted-imports': [ 'error', - { - patterns: ['@grafana/runtime', '@grafana/ui', '@grafana/data'], - }, - ], - }, - }, - { - name: 'grafana/ui-overrides', - files: ['packages/grafana-ui/**/*.{ts,tsx}'], - rules: { - 'no-restricted-imports': [ - 'error', - { - patterns: ['@grafana/runtime', '@grafana/data/*', '@grafana/ui', '@grafana/e2e-selectors/*'], - paths: [ + withBaseRestrictedImportsConfig({ + patterns: [ { - name: 'react-i18next', - importNames: ['Trans', 't'], - message: 'Please import from grafana-ui/src/utils/i18n instead', + group: ['@grafana/*/internal'], + message: "'internal' exports are not available in NPM packages because they are not published to NPM", }, ], - }, + }), ], }, }, + { - name: 'grafana/schema-overrides', - files: ['packages/grafana-schema/**/*.{ts,tsx}'], - ignores: ['packages/grafana-schema/**/*.test.{ts,tsx}'], + // @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: [], rules: { 'no-restricted-imports': [ 'error', - { - patterns: ['@grafana/*'], - }, - ], - }, - }, - { - name: 'grafana/runtime-overrides', - files: ['packages/grafana-runtime/**/*.{ts,tsx}'], - rules: { - 'no-restricted-imports': [ - 'error', - { - patterns: ['@grafana/runtime', '@grafana/data/*', '@grafana/ui/*', '@grafana/e2e/*'], - }, - ], - }, - }, - { - name: 'grafana/flamegraph-overrides', - files: ['packages/grafana-flamegraph/**/*.{ts,tsx}'], - ignores: ['packages/grafana-flamegraph/**/*.{test,story}.{ts,tsx}'], - rules: { - 'no-restricted-imports': [ - 'error', - { - patterns: ['@grafana/runtime', '@grafana/e2e', '@grafana/e2e-selectors/*'], - }, + 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", + }, + ], + }), ], }, }, + { name: 'grafana/alerting-overrides', plugins: { @@ -427,24 +443,7 @@ module.exports = [ ], }, }, - { - name: 'grafana/no-extensions-imports', - files: ['**/*.{ts,tsx,js}'], - ignores: ['public/app/extensions/**/*'], - rules: { - 'no-restricted-imports': [ - 'error', - { - patterns: [ - { - group: ['app/extensions', 'app/extensions/*'], - message: 'Importing from app/extensions is not allowed', - }, - ], - }, - ], - }, - }, + // 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/public/app/features/provisioning/components/BulkActions/useFolderNameFromSelection.ts b/public/app/features/provisioning/components/BulkActions/useFolderNameFromSelection.ts index b365831882e..354022d25e4 100644 --- a/public/app/features/provisioning/components/BulkActions/useFolderNameFromSelection.ts +++ b/public/app/features/provisioning/components/BulkActions/useFolderNameFromSelection.ts @@ -1,10 +1,10 @@ import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; import { ManagerKind } from 'app/features/apiserver/types'; import { rootItemsSelector, useChildrenByParentUIDState } from 'app/features/browse-dashboards/state/hooks'; import { findItem } from 'app/features/browse-dashboards/state/utils'; import { DashboardTreeSelection } from 'app/features/browse-dashboards/types'; +import { useSelector } from 'app/types/store'; // This hook retrieves the folder UID from the selection state. Because search endpoint currently does not return resource metadata // NOTE: This is a temporary workaround until the search endpoint is updated diff --git a/public/app/types/eslint.d.ts b/public/app/types/eslint.d.ts new file mode 100644 index 00000000000..e2c94a5a069 --- /dev/null +++ b/public/app/types/eslint.d.ts @@ -0,0 +1,3 @@ +declare module 'eslint-plugin-jsx-a11y'; +declare module 'eslint-plugin-lodash'; +declare module '@grafana/eslint-config/flat'; diff --git a/tsconfig.json b/tsconfig.json index 172830e1c6a..963aa84555c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,7 @@ } }, "include": [ + "eslint.config.js", "public/app/**/*.ts*", "public/swagger/**/*.ts*", "public/e2e-test/**/*.ts",