Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group files by config #622

Merged
merged 9 commits into from Oct 27, 2021
98 changes: 59 additions & 39 deletions index.js
@@ -1,7 +1,7 @@
import path from 'node:path';
import {ESLint} from 'eslint';
import {globby, isGitIgnoredSync} from 'globby';
import {isEqual} from 'lodash-es';
import {isEqual, groupBy} from 'lodash-es';
import micromatch from 'micromatch';
import arrify from 'arrify';
import slash from 'slash';
Expand All @@ -12,30 +12,6 @@ import {
} from './lib/options-manager.js';
import {mergeReports, processReport, getIgnoredReport} from './lib/report.js';

const runEslint = async (lint, options) => {
const {filePath, eslintOptions, isQuiet} = options;
const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;

if (
filePath
&& (
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
)
) {
return getIgnoredReport(filePath);
}

const eslint = new ESLint(eslintOptions);

if (filePath && await eslint.isPathIgnored(filePath)) {
return getIgnoredReport(filePath);
}

const report = await lint(eslint);
return processReport(report, {isQuiet});
};

const globFiles = async (patterns, options) => {
const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options;

Expand All @@ -59,32 +35,76 @@ const getConfig = async options => {

const lintText = async (string, options) => {
options = await parseOptions(options);
const {filePath, warnIgnored, eslintOptions} = options;
const {ignorePatterns} = eslintOptions.baseConfig;
const {filePath, warnIgnored, eslintOptions, isQuiet} = options;
const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;

if (typeof filePath !== 'string' && !isEqual(getIgnores({}), ignorePatterns)) {
throw new Error('The `ignores` option requires the `filePath` option to be defined.');
}

return runEslint(
eslint => eslint.lintText(string, {filePath, warnIgnored}),
options,
);
};
if (
filePath
&& (
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
)
) {
return getIgnoredReport(filePath);
}

const lintFile = async (filePath, options) => runEslint(
eslint => eslint.lintFiles([filePath]),
await parseOptions({...options, filePath}),
);
const eslint = new ESLint(eslintOptions);

if (filePath && await eslint.isPathIgnored(filePath)) {
return getIgnoredReport(filePath);
}

const report = await eslint.lintText(string, {filePath, warnIgnored});
return processReport(report, {isQuiet});
};

const lintFiles = async (patterns, options) => {
const files = await globFiles(patterns, options);

const reports = await Promise.all(
files.map(filePath => lintFile(filePath, options)),
const allOptions = await Promise.all(
files.map(filePath => parseOptions({...options, filePath})),
);

const report = mergeReports(reports.filter(({isIgnored}) => !isIgnored));
// Files with same `xoConfigPath` can lint together
// https://github.com/xojs/xo/issues/599
const groups = groupBy(allOptions, 'xoConfigPath');

const reports = await Promise.all(
Object.values(groups)
.map(async filesWithOptions => {
const options = filesWithOptions[0];
const eslint = new ESLint(options.eslintOptions);
const files = [];

for (const options of filesWithOptions) {
const {filePath, eslintOptions} = options;
const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;
if (filePath
&& (
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
)) {
continue;
}

// eslint-disable-next-line no-await-in-loop
if ((await eslint.isPathIgnored(filePath))) {
continue;
}

files.push(filePath);
}

const report = await eslint.lintFiles(files);

return processReport(report, {isQuiet: options.isQuiet});
}));

const report = mergeReports(reports);

return report;
};
Expand Down
14 changes: 10 additions & 4 deletions lib/options-manager.js
Expand Up @@ -109,14 +109,19 @@ const mergeWithFileConfig = async options => {

const searchPath = options.filePath || options.cwd;

const {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {};
let {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {};
const {config: enginesOptions} = (await pkgConfigExplorer.search(searchPath)) || {};

options = mergeOptions(options, xoOptions, enginesOptions);
options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd;

if (options.filePath) {
({options} = applyOverrides(options.filePath, options));
const overrides = applyOverrides(options.filePath, options);
options = overrides.options;

if (overrides.hash) {
xoConfigPath += overrides.hash;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a new variable, maybe xoConfigHash?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to eslintConfigId to make it clear we need unique id for each unique eslint config.

}
}

const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {};
Expand All @@ -132,7 +137,7 @@ const mergeWithFileConfig = async options => {
await fs.writeFile(options.tsConfigPath, JSON.stringify(config));
}

return {options, prettierOptions};
return {options, prettierOptions, xoConfigPath};
};

/**
Expand Down Expand Up @@ -538,13 +543,14 @@ const gatherImportResolvers = options => {

const parseOptions = async options => {
options = normalizeOptions(options);
const {options: foundOptions, prettierOptions} = await mergeWithFileConfig(options);
const {options: foundOptions, prettierOptions, xoConfigPath} = await mergeWithFileConfig(options);
const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions);
return {
filePath,
warnIgnored,
isQuiet: options.quiet,
eslintOptions,
xoConfigPath,
};
};

Expand Down