Skip to content

Commit

Permalink
Refactor (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker authored Aug 8, 2021
1 parent 48baacc commit 3859d06
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 167 deletions.
183 changes: 52 additions & 131 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,171 +1,92 @@
import process from 'node:process';
import path from 'node:path';
import {ESLint} from 'eslint';
import {globby, isGitIgnoredSync} from 'globby';
import {omit, isEqual} from 'lodash-es';
import {isEqual} from 'lodash-es';
import micromatch from 'micromatch';
import arrify from 'arrify';
import pMap from 'p-map';
import defineLazyProperty from 'define-lazy-prop';
import slash from 'slash';
import {
normalizeOptions,
parseOptions,
getIgnores,
mergeWithFileConfig,
buildConfig,
} from './lib/options-manager.js';
import {mergeReports, processReport, getIgnoredReport} from './lib/report.js';

/** 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;
}

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

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;
if (
filePath
&& (
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
)
) {
return getIgnoredReport(filePath);
}

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

const processReport = (report, {isQuiet = false} = {}) => {
if (isQuiet) {
report = ESLint.getErrorResults(report);
if (filePath && await eslint.isPathIgnored(filePath)) {
return getIgnoredReport(filePath);
}

const result = {
results: report,
...getReportStatistics(report),
};

defineLazyProperty(result, 'usedDeprecatedRules', () => {
const seenRules = new Set();
const rules = [];

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 report = await lint(eslint);
return processReport(report, {isQuiet});
};

const runEslint = async (filePath, options, processorOptions) => {
const engine = new ESLint(omit(options, ['filePath', 'warnIgnored']));
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;
}
const globFiles = async (patterns, options) => {
const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options;

const report = await engine.lintFiles([filePath]);
return processReport(report, processorOptions);
};
patterns = patterns.length === 0
? [`**/*.{${extensions.join(',')}}`]
: arrify(patterns).map(pattern => slash(pattern));

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

return files.filter(file => extensions.includes(path.extname(file).slice(1)));
};

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

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

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

const {filePath, warnIgnored, ...eslintOptions} = options;
const engine = new ESLint(eslintOptions);

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,
}],
};
}
}
return runEslint(
eslint => eslint.lintText(string, {filePath, warnIgnored}),
options,
);
};

const report = await engine.lintText(string, {filePath, warnIgnored});
const lintFile = async (filePath, options) => runEslint(
eslint => eslint.lintFiles([filePath]),
await parseOptions({...options, filePath}),
);

return processReport(report, {isQuiet: inputOptions.quiet});
};
const lintFiles = async (patterns, options) => {
const files = await globFiles(patterns, options);

const lintFiles = async (patterns, inputOptions = {}) => {
inputOptions = normalizeOptions(inputOptions);
inputOptions.cwd = path.resolve(inputOptions.cwd || process.cwd());

const files = await globFiles(patterns, mergeWithFileConfig(inputOptions).options);

const reports = await pMap(
files,
async filePath => {
const {options: foundOptions, prettierOptions} = mergeWithFileConfig({
...inputOptions,
filePath,
});
const options = buildConfig(foundOptions, prettierOptions);
return runEslint(filePath, options, {isQuiet: inputOptions.quiet});
},
const reports = await Promise.all(
files.map(filePath => lintFile(filePath, options)),
);

return mergeReports(reports.filter(Boolean));
const report = mergeReports(reports.filter(({isIgnored}) => !isIgnored));

return report;
};

const getFormatter = async name => {
Expand Down
52 changes: 33 additions & 19 deletions lib/options-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import findUp from 'find-up';
import findCacheDir from 'find-cache-dir';
import prettier from 'prettier';
import semver from 'semver';
import {cosmiconfigSync, defaultLoaders} from 'cosmiconfig';
import {cosmiconfig, defaultLoaders} from 'cosmiconfig';
import micromatch from 'micromatch';
import JSON5 from 'json5';
import toAbsoluteGlob from 'to-absolute-glob';
Expand All @@ -31,8 +31,6 @@ import {
} from './constants.js';

const {__dirname, json, require} = createEsmUtils(import.meta);
const pkg = json.loadSync('../package.json');
const {outputJsonSync} = fsExtra;
const {normalizePackageName} = eslintrc.Legacy.naming;
const resolveModule = eslintrc.Legacy.ModuleResolver.resolve;

Expand Down Expand Up @@ -103,18 +101,18 @@ const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).sl
Find config for `lintText`.
The config files are searched starting from `options.filePath` if defined or `options.cwd` otherwise.
*/
const mergeWithFileConfig = options => {
const mergeWithFileConfig = async options => {
options.cwd = path.resolve(options.cwd || process.cwd());
const configExplorer = cosmiconfigSync(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: options.cwd});
const pkgConfigExplorer = cosmiconfigSync('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
const configExplorer = cosmiconfig(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: options.cwd});
const pkgConfigExplorer = cosmiconfig('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
if (options.filePath) {
options.filePath = path.resolve(options.cwd, options.filePath);
}

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

const {config: xoOptions, filepath: xoConfigPath} = configExplorer.search(searchPath) || {};
const {config: enginesOptions} = pkgConfigExplorer.search(searchPath) || {};
const {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;
Expand All @@ -123,15 +121,15 @@ const mergeWithFileConfig = options => {
({options} = applyOverrides(options.filePath, options));
}

const prettierOptions = options.prettier ? prettier.resolveConfig.sync(searchPath, {editorconfig: true}) || {} : {};
const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {};

if (options.filePath && isTypescript(options.filePath)) {
const tsConfigExplorer = cosmiconfigSync([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
const {config: tsConfig, filepath: tsConfigPath} = tsConfigExplorer.search(options.filePath) || {};
const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
const {config: tsConfig, filepath: tsConfigPath} = (await tsConfigExplorer.search(options.filePath)) || {};

options.tsConfigPath = getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd);
options.tsConfigPath = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd);
options.ts = true;
outputJsonSync(options.tsConfigPath, makeTSConfig(tsConfig, tsConfigPath, [options.filePath]));
await fsExtra.outputJson(options.tsConfigPath, makeTSConfig(tsConfig, tsConfigPath, [options.filePath]));
}

return {options, prettierOptions};
Expand All @@ -141,10 +139,13 @@ const mergeWithFileConfig = options => {
Generate a unique and consistent path for the temporary `tsconfig.json`.
Hashing based on https://github.com/eslint/eslint/blob/cf38d0d939b62f3670cdd59f0143fd896fccd771/lib/cli-engine/lint-result-cache.js#L30
*/
const getTsConfigCachePath = (files, tsConfigPath, cwd) => path.join(
cacheLocation(cwd),
`tsconfig.${murmur(`${pkg.version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36)}.json`,
);
const getTsConfigCachePath = async (files, tsConfigPath, cwd) => {
const {version} = await json.load('../package.json');
return path.join(
cacheLocation(cwd),
`tsconfig.${murmur(`${version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36)}.json`,
);
};

const makeTSConfig = (tsConfig, tsConfigPath, files) => {
const config = {files: files.filter(file => isTypescript(file))};
Expand Down Expand Up @@ -539,14 +540,27 @@ const gatherImportResolvers = options => {
return resolvers;
};

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

export {
normalizeOptions,
parseOptions,
getIgnores,
mergeWithFileConfig,
buildConfig,

// For tests
applyOverrides,
findApplicableOverrides,
mergeWithPrettierConfig,
normalizeOptions,
buildConfig,
};
Loading

0 comments on commit 3859d06

Please sign in to comment.