grafana/.betterer.ts

144 lines
5.4 KiB
TypeScript
Raw Normal View History

import { BettererFileTest } from '@betterer/betterer';
2023-03-30 11:11:30 -05:00
import { promises as fs } from 'fs';
import { ESLint, Linter } from 'eslint';
import path from 'path';
import { glob } from 'glob';
// Why are we ignoring these?
// They're all deprecated/being removed so doesn't make sense to fix types
const eslintPathsToIgnore = [
'packages/grafana-e2e', // deprecated.
'public/app/angular', // will be removed in Grafana 11
'public/app/plugins/panel/graph', // will be removed alongside angular
'public/app/plugins/panel/table-old', // will be removed alongside angular
];
// 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('|'))),
Chore: Upgrade Storybook to v7 (#65943) * chore(grafana-ui): begin migration to storybook7 * refactor(storybook): begin cleanup of storybook and addon configs * chore(storybook): add storybook/blocks to keep yarn berry happy * chore(storybook): rename intro story for storybook 7 * chore(stories): rename internal stories to support SB7 story name mapper * chore(betterer): update glob to support internal story renaming * chore(stories): silence TS errors for subcomponents in SB7 * fix(clickoutsidewrapper): window | document can be undefined not null * chore(storybook): remove patch for 6.5.16 * revert(storybook): put back story globs * docs(storybook): replace removed <Props /> with <ArgsTypes /> in mdx files * docs(storybook): use ArgTypes instead of ArgsTable * chore(storybook): use correct ArgTypes import name in mdx files * chore(storybook): patch blocks to expose Preview component for docs * chore(storybook): rename deprecated ComponentStory and ComponentMeta for v7 * feat(storybook): add STORY env var to customise which stories storybook should load * chore(storybook): bump to 7.0.4 * fix(storybook): set esbuild minify target to fix erroring docs in production builds * fix(toolbarbuttonrow): fix import path to prevent error in storybook doc * docs(storybook): fix up some more stories * chore(storybook): more config updates to match storybook documentation * chore(storybook): bump to 7.0.5 * Apply suggestions from code review Co-authored-by: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> * chore(storybook): fix broken merge causing types issues * chore(storybook): mimic broken alphabetical storySort and docs overview ordering * docs(storybook): fix button docs adding p tags due to mdx2 * chore(storybook): bump to 7.0.10 * chore(storybook): apply patch on yarn install * chore(text): update stories for storybook 7 * fix(storybook): make sure globs don't include internal stories in production --------- Co-authored-by: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com>
2023-05-11 07:26:12 -05:00
'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}'),
};
function countUndocumentedStories() {
return new BettererFileTest(async (filePaths, fileTestResult) => {
2023-03-30 11:11:30 -05:00
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/;
2023-03-30 11:11:30 -05:00
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) {
2023-03-30 11:11:30 -05:00
// 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) => {
const { baseDirectory } = resolver;
const cli = new ESLint({ cwd: baseDirectory });
const eslintConfigFiles = await glob('**/.eslintrc');
const eslintConfigMainPaths = eslintConfigFiles.map((file) => path.resolve(path.dirname(file)));
const baseRules: Partial<Linter.RulesRecord> = {
'@emotion/syntax-preference': [2, 'object'],
'@typescript-eslint/no-explicit-any': 'error',
'@grafana/no-aria-label-selectors': 'error',
};
const nonTestFilesRules: Partial<Linter.RulesRecord> = {
...baseRules,
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
};
// group files by eslint config file
// this will create two file groups for each eslint config file
// one for test files and one for non-test files
const fileGroups: Record<string, string[]> = {};
for (const filePath of filePaths) {
let configPath = eslintConfigMainPaths.find((configPath) => filePath.startsWith(configPath)) ?? '';
const isTestFile =
filePath.endsWith('.test.tsx') ||
filePath.endsWith('.test.ts') ||
filePath.includes('__mocks__') ||
filePath.includes('public/test/');
if (isTestFile) {
configPath += '-test';
}
if (!fileGroups[configPath]) {
fileGroups[configPath] = [];
}
fileGroups[configPath].push(filePath);
}
for (const configPath of Object.keys(fileGroups)) {
const rules = configPath.endsWith('-test') ? baseRules : nonTestFilesRules;
// this is by far the slowest part of this code. It takes eslint about 2 seconds just to find the config
const linterOptions = (await cli.calculateConfigForFile(fileGroups[configPath][0])) as Linter.Config;
const runner = new ESLint({
baseConfig: {
...linterOptions,
rules: rules,
},
useEslintrc: false,
cwd: baseDirectory,
});
const lintResults = await runner.lintFiles(fileGroups[configPath]);
lintResults
.filter((lintResult) => lintResult.source)
.forEach((lintResult) => {
const { messages } = lintResult;
const filePath = lintResult.filePath;
const file = fileTestResult.addFile(filePath, '');
messages.forEach((message, index) => {
file.addIssue(0, 0, message.message, `${index}`);
});
});
}
});
}