Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 130 additions & 53 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,169 @@
import process from 'node:process';
import path from 'node:path';
import {ESLint} from 'eslint';
import {globby, isGitIgnoredSync} from 'globby';
import {isEqual} from 'lodash-es';
import micromatch from 'micromatch';
import arrify from 'arrify';
import pReduce from 'p-reduce';
import pMap from 'p-map';
import {cosmiconfig, defaultLoaders} from 'cosmiconfig';
import defineLazyProperty from 'define-lazy-prop';
import pFilter from 'p-filter';
import slash from 'slash';
import {CONFIG_FILES, MODULE_NAME, DEFAULT_IGNORES} from './lib/constants.js';
import {
parseOptions,
normalizeOptions,
getIgnores,
mergeWithFileConfig,
mergeWithFileConfigs,
buildConfig,
mergeOptions,
} 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);

/** Merge multiple reports into a single report */
const mergeReports = reports => {
const report = {
results: [],
errorCount: 0,
warningCount: 0,
};

for (const currentReport of reports) {
report.results.push(...currentReport.results);
report.errorCount += currentReport.errorCount;
report.warningCount += currentReport.warningCount;
}

const eslint = new ESLint(eslintOptions);
return report;
};

if (filePath && await eslint.isPathIgnored(filePath)) {
return getIgnoredReport(filePath);
const getReportStatistics = results => {
const statistics = {
errorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
};

for (const result of results) {
statistics.errorCount += result.errorCount;
statistics.warningCount += result.warningCount;
statistics.fixableErrorCount += result.fixableErrorCount;
statistics.fixableWarningCount += result.fixableWarningCount;
}

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

const globFiles = async (patterns, options) => {
const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options;
const processReport = (report, {isQuiet = false} = {}) => {
if (isQuiet) {
report = ESLint.getErrorResults(report);
}

patterns = patterns.length === 0
? [`**/*.{${extensions.join(',')}}`]
: arrify(patterns).map(pattern => slash(pattern));
const result = {
results: report,
...getReportStatistics(report),
};

const files = await globby(
patterns,
{ignore: ignores, gitignore: true, absolute: true, cwd},
);
defineLazyProperty(result, 'usedDeprecatedRules', () => {
const seenRules = new Set();
const rules = [];

return files.filter(file => extensions.includes(path.extname(file).slice(1)));
for (const {usedDeprecatedRules} of report) {
for (const rule of usedDeprecatedRules) {
if (seenRules.has(rule.ruleId)) {
continue;
}

seenRules.add(rule.ruleId);
rules.push(rule);
}
}

return rules;
});

return result;
};

const runEslint = async (paths, options, processorOptions) => {
const engine = new ESLint(options);

const report = await engine.lintFiles(await pFilter(paths, async path => !(await engine.isPathIgnored(path))));
return processReport(report, processorOptions);
};

const globFiles = async (patterns, {ignores, extensions, cwd}) => (
await globby(
patterns.length === 0 ? [`**/*.{${extensions.join(',')}}`] : arrify(patterns).map(pattern => slash(pattern)),
{ignore: ignores, gitignore: true, absolute: true, cwd},
)).filter(file => extensions.includes(path.extname(file).slice(1)));

const getConfig = async options => {
const {filePath, eslintOptions} = await parseOptions(options);
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions);
const engine = new ESLint(eslintOptions);
return engine.calculateConfigForFile(filePath);
};

const lintText = async (string, options) => {
options = await parseOptions(options);
const {filePath, warnIgnored, eslintOptions} = options;
const {ignorePatterns} = eslintOptions.baseConfig;
const lintText = async (string, inputOptions = {}) => {
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(inputOptions));
const options = buildConfig(foundOptions, prettierOptions);

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

return runEslint(
eslint => eslint.lintText(string, {filePath, warnIgnored}),
options,
);
};

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

const lintFiles = async (patterns, options) => {
const files = await globFiles(patterns, options);
if (filePath) {
const filename = path.relative(options.cwd, filePath);

if (
micromatch.isMatch(filename, options.baseConfig.ignorePatterns)
|| isGitIgnoredSync({cwd: options.cwd, ignore: options.baseConfig.ignorePatterns})(filePath)
|| await engine.isPathIgnored(filePath)
) {
return {
errorCount: 0,
warningCount: 0,
results: [{
errorCount: 0,
filePath: filename,
messages: [],
warningCount: 0,
}],
};
}
}

const reports = await Promise.all(
files.map(filePath => lintFile(filePath, options)),
);
const report = await engine.lintText(string, {filePath, warnIgnored});

const report = mergeReports(reports.filter(({isIgnored}) => !isIgnored));
return processReport(report, {isQuiet: inputOptions.quiet});
};

return report;
const lintFiles = async (patterns, inputOptions = {}) => {
inputOptions.cwd = path.resolve(inputOptions.cwd || process.cwd());
const configExplorer = cosmiconfig(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: inputOptions.cwd});

const configFiles = (await Promise.all(
(await globby(
CONFIG_FILES.map(configFile => `**/${configFile}`),
{ignore: DEFAULT_IGNORES, gitignore: true, absolute: true, cwd: inputOptions.cwd},
)).map(configFile => configExplorer.load(configFile)),
)).filter(Boolean);

const paths = configFiles.length > 0
? await pReduce(
configFiles,
async (paths, {filepath, config}) =>
[...paths, ...(await globFiles(patterns, {...mergeOptions(inputOptions, config), cwd: path.dirname(filepath)}))],
[])
: await globFiles(patterns, mergeOptions(inputOptions));

return mergeReports(await pMap(await mergeWithFileConfigs([...new Set(paths)], inputOptions, configFiles), async ({files, options, prettierOptions}) => runEslint(files, buildConfig(options, prettierOptions), {isQuiet: options.quiet})));
};

const getFormatter = async name => {
Expand Down
Loading