diff --git a/.betterer.ts b/.betterer.ts index 6d40d7a9179..bf1d956fe3b 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -2,6 +2,9 @@ import { regexp } from '@betterer/regexp'; import { BettererFileTest } from '@betterer/betterer'; import { ESLint, Linter } from 'eslint'; import { existsSync } from 'fs'; +import { exec } from 'child_process'; +import path from 'path'; +import glob from 'glob'; export default { 'no enzyme tests': () => regexp(/from 'enzyme'/g).include('**/*.test.*'), @@ -22,54 +25,79 @@ function countUndocumentedStories() { }); } +async function findEslintConfigFiles(): Promise { + return new Promise((resolve, reject) => { + glob('**/.eslintrc', (err, files) => { + if (err) { + reject(err); + } + resolve(files); + }); + }); +} + function countEslintErrors() { return new BettererFileTest(async (filePaths, fileTestResult, resolver) => { const { baseDirectory } = resolver; const cli = new ESLint({ cwd: baseDirectory }); - await Promise.all( - filePaths.map(async (filePath) => { - const linterOptions = (await cli.calculateConfigForFile(filePath)) as Linter.Config; + const eslintConfigFiles = await findEslintConfigFiles(); + const eslintConfigMainPaths = eslintConfigFiles.map((file) => path.resolve(path.dirname(file))); - const rules: Partial = { - '@typescript-eslint/no-explicit-any': 'error', - }; + const baseRules: Partial = { + '@typescript-eslint/no-explicit-any': 'error', + }; - const isTestFile = - filePath.endsWith('.test.tsx') || - filePath.endsWith('.test.ts') || - filePath.includes('__mocks__') || - filePath.includes('public/test/'); + const nonTestFilesRules: Partial = { + ...baseRules, + '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }], + }; - if (!isTestFile) { - rules['@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 = {}; - const runner = new ESLint({ - baseConfig: { - ...linterOptions, - rules, - }, - useEslintrc: false, - cwd: baseDirectory, - }); + 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/'); - const lintResults = await runner.lintFiles([filePath]); - lintResults - .filter((lintResult) => lintResult.source) - .forEach((lintResult) => { - const { messages } = lintResult; - const file = fileTestResult.addFile(filePath, ''); - messages.forEach((message, index) => { - file.addIssue(0, 0, message.message, `${index}`); - }); + 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}`); }); - }) - ); + }); + } }); } diff --git a/package.json b/package.json index 979ef5b3ae0..044f5c0a9ae 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "@types/enzyme-adapter-react-16": "1.0.6", "@types/eslint": "8.4.9", "@types/file-saver": "2.0.5", + "@types/glob": "^8.0.0", "@types/google.analytics": "^0.0.42", "@types/gtag.js": "^0.0.12", "@types/history": "4.7.11", diff --git a/yarn.lock b/yarn.lock index 6f4ab0374b9..9e9cc78e692 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10781,6 +10781,16 @@ __metadata: languageName: node linkType: hard +"@types/glob@npm:^8.0.0": + version: 8.0.0 + resolution: "@types/glob@npm:8.0.0" + dependencies: + "@types/minimatch": "*" + "@types/node": "*" + checksum: 1817b05f5a8aed851d102a65b5e926d5c777bef927ea62b36d635860eef5364f2046bb5a692d135b6f2b28f34e4a9d44ade9396122c0845bcc7636d35f624747 + languageName: node + linkType: hard + "@types/google.analytics@npm:^0.0.42": version: 0.0.42 resolution: "@types/google.analytics@npm:0.0.42" @@ -21518,6 +21528,7 @@ __metadata: "@types/enzyme-adapter-react-16": 1.0.6 "@types/eslint": 8.4.9 "@types/file-saver": 2.0.5 + "@types/glob": ^8.0.0 "@types/google.analytics": ^0.0.42 "@types/gtag.js": ^0.0.12 "@types/history": 4.7.11