mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 19:00:54 -06:00
8c41137bcf
* chore(eslint): bump all eslint related packages to latest * chore(eslint): update linting scripts work with v9 * chore(eslint): introduce flat config * chore(eslint): delete legacy configs * feat(grafana-eslint-rules): update rules to use eslint 9 APIs * chore(eslint): migrate all nested eslintrc files over to root config * chore(packages): bump eslint dependencies * feat(betterer): make it work with eslint 9 * style(grafana-data): remove non-existant ban-types rule from disable declarations * chore(wip): [wip] link eslint-config-grafana * chore(packages): add @eslint/compat * chore(eslint): add compat to testing library and fix alerting rules * chore(eslint): bump grafana eslint-config to v8 * chore(explore): delete legacy eslint config * chore: clean codeowners file, remove grafana/eslint-config from e2e plugins * test(eslint-rules): fix no-border-radius-literal and no-aria-label-e2e-selectors rule tests * Add .js to prettier checks so new eslint.config.js file isn't missed * chore(eslint): move emotion/syntax-preference to grafana/defaults * test(eslint): use core-js structured-clone * revert(services): undo merge backend-format githook changes * test(eslint-rules): remove structured-clone polyfill from tests * chore(eslint): add back public/lib/monaco to ignore list, sort alphabetically * chore(e2e-plugins): remove eslint config 7 from plugins package.json --------- Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
import { BettererFileTest } from '@betterer/betterer';
|
|
import { ESLint, Linter } from 'eslint';
|
|
import { promises as fs } from 'fs';
|
|
|
|
// Why are we ignoring these?
|
|
// They're all deprecated/being removed so doesn't make sense to fix types
|
|
const eslintPathsToIgnore = [
|
|
'packages/grafana-ui/src/graveyard', // will be removed alongside angular in Grafana 12
|
|
'public/app/angular', // will be removed in Grafana 12
|
|
'public/app/plugins/panel/graph', // will be removed alongside angular in Grafana 12
|
|
'public/app/plugins/panel/table-old', // will be removed alongside angular in Grafana 12
|
|
'e2e/test-plugins',
|
|
];
|
|
|
|
// Avoid using functions that report the position of the issues, as this causes a lot of merge conflicts
|
|
export default {
|
|
'better eslint': () =>
|
|
countEslintErrors()
|
|
.include('**/*.{ts,tsx}')
|
|
.exclude(new RegExp(eslintPathsToIgnore.join('|'))),
|
|
'no undocumented stories': () => countUndocumentedStories().include('**/!(*.internal).story.tsx'),
|
|
'no gf-form usage': () =>
|
|
regexp(/gf-form/gm, 'gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.')
|
|
.include('**/*.{ts,tsx,html}')
|
|
.exclude(new RegExp('packages/grafana-ui/src/themes/GlobalStyles')),
|
|
};
|
|
|
|
function countUndocumentedStories() {
|
|
return new BettererFileTest(async (filePaths, fileTestResult) => {
|
|
await Promise.all(
|
|
filePaths.map(async (filePath) => {
|
|
// look for .mdx import in the story file
|
|
const mdxImportRegex = new RegExp("^import.*\\.mdx';$", 'gm');
|
|
// Looks for the "autodocs" string in the file
|
|
const autodocsStringRegex = /autodocs/;
|
|
|
|
const fileText = await fs.readFile(filePath, 'utf8');
|
|
|
|
const hasMdxImport = mdxImportRegex.test(fileText);
|
|
const hasAutodocsString = autodocsStringRegex.test(fileText);
|
|
// If both .mdx import and autodocs string are missing, add an issue
|
|
if (!hasMdxImport && !hasAutodocsString) {
|
|
// In this case the file contents don't matter:
|
|
const file = fileTestResult.addFile(filePath, '');
|
|
// Add the issue to the first character of the file:
|
|
file.addIssue(0, 0, 'No undocumented stories are allowed, please add an .mdx file with some documentation');
|
|
}
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generic regexp pattern matcher, similar to @betterer/regexp.
|
|
* The only difference is that the positions of the errors are not reported, as this may cause a lot of merge conflicts.
|
|
*/
|
|
function regexp(pattern: RegExp, issueMessage: string) {
|
|
return new BettererFileTest(async (filePaths, fileTestResult) => {
|
|
await Promise.all(
|
|
filePaths.map(async (filePath) => {
|
|
const fileText = await fs.readFile(filePath, 'utf8');
|
|
const matches = fileText.match(pattern);
|
|
if (matches) {
|
|
// File contents doesn't matter, since we're not reporting the position
|
|
const file = fileTestResult.addFile(filePath, '');
|
|
matches.forEach(() => {
|
|
file.addIssue(0, 0, issueMessage);
|
|
});
|
|
}
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
function countEslintErrors() {
|
|
return new BettererFileTest(async (filePaths, fileTestResult, resolver) => {
|
|
// Just bail early if there's no files to test. Prevents trying to get the base config from failing
|
|
if (filePaths.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const { baseDirectory } = resolver;
|
|
|
|
const baseRules: Partial<Linter.RulesRecord> = {
|
|
'@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.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const config: Linter.Config[] = [
|
|
{
|
|
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
rules: baseRules,
|
|
},
|
|
{
|
|
files: ['**/*.{ts,tsx}'],
|
|
ignores: ['**/*.{test,spec}.{ts,tsx}', '**/__mocks__/**', '**/public/test/**'],
|
|
rules: {
|
|
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
|
|
},
|
|
},
|
|
{
|
|
files: ['public/app/**/*.{ts,tsx}'],
|
|
rules: {
|
|
'no-barrel-files/no-barrel-files': 'error',
|
|
},
|
|
},
|
|
{
|
|
files: ['public/**/*.tsx', 'packages/grafana-ui/**/*.tsx'],
|
|
ignores: [
|
|
'public/app/plugins/**',
|
|
'**/*.story.tsx',
|
|
'**/*.{test,spec}.{ts,tsx}',
|
|
'**/__mocks__/',
|
|
'public/test',
|
|
],
|
|
rules: {
|
|
'@grafana/no-untranslated-strings': 'error',
|
|
},
|
|
},
|
|
];
|
|
|
|
const runner = new ESLint({
|
|
overrideConfig: config,
|
|
cwd: baseDirectory,
|
|
warnIgnored: false,
|
|
});
|
|
|
|
const lintResults = await runner.lintFiles(Array.from(filePaths));
|
|
|
|
lintResults.forEach(({ messages, filePath }) => {
|
|
const file = fileTestResult.addFile(filePath, '');
|
|
messages.forEach((message, index) => {
|
|
file.addIssue(0, 0, message.message, `${index}`);
|
|
});
|
|
});
|
|
});
|
|
}
|