Skip to content

Commit

Permalink
Find XO config based on linted file path (#425)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
pvdlg and sindresorhus authored Feb 17, 2020
1 parent a8f9a34 commit e0f81a7
Show file tree
Hide file tree
Showing 23 changed files with 609 additions and 365 deletions.
83 changes: 32 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ const path = require('path');
const eslint = require('eslint');
const globby = require('globby');
const isEqual = require('lodash/isEqual');
const multimatch = require('multimatch');
const micromatch = require('micromatch');
const arrify = require('arrify');
const optionsManager = require('./lib/options-manager');
const {DEFAULT_EXTENSION} = require('./lib/constants');
const {
normalizeOptions,
getIgnores,
mergeWithFileConfig,
mergeWithFileConfigs,
buildConfig
} = require('./lib/options-manager');

const mergeReports = reports => {
// Merge multiple reports into a single report
Expand All @@ -32,32 +39,19 @@ const processReport = (report, options) => {
};

const runEslint = (paths, options) => {
const config = optionsManager.buildConfig(options);
const engine = new eslint.CLIEngine(config);
const engine = new eslint.CLIEngine(options);
const report = engine.executeOnFiles(
paths.filter(path => !engine.isPathIgnored(path)),
config
options
);
return processReport(report, options);
};

module.exports.lintText = (string, options) => {
options = optionsManager.preprocess(options);

if (options.overrides && options.overrides.length > 0) {
const {overrides} = options;
delete options.overrides;

const filename = path.relative(options.cwd, options.filename);

const foundOverrides = optionsManager.findApplicableOverrides(filename, overrides);
options = optionsManager.mergeApplicableOverrides(options, foundOverrides.applicable);
}

options = optionsManager.buildConfig(options);
const defaultIgnores = optionsManager.getIgnores({}).ignores;
const lintText = (string, options) => {
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
options = buildConfig(foundOptions, prettierOptions);

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

Expand All @@ -67,7 +61,7 @@ module.exports.lintText = (string, options) => {
const filename = path.relative(options.cwd, options.filename);

if (
multimatch(filename, options.ignores).length > 0 ||
micromatch.isMatch(filename, options.ignores) ||
globby.gitignore.sync({cwd: options.cwd, ignore: options.ignores})(options.filename) ||
engine.isPathIgnored(options.filename)
) {
Expand All @@ -89,43 +83,30 @@ module.exports.lintText = (string, options) => {
return processReport(report, options);
};

module.exports.lintFiles = async (patterns, options) => {
options = optionsManager.preprocess(options);
const lintFiles = async (patterns, options) => {
options = normalizeOptions(options);

const isEmptyPatterns = patterns.length === 0;
const defaultPattern = `**/*.{${options.extensions.join(',')}}`;
const defaultPattern = `**/*.{${DEFAULT_EXTENSION.concat(options.extensions || []).join(',')}}`;

let paths = await globby(
const paths = await globby(
isEmptyPatterns ? [defaultPattern] : arrify(patterns),
{
ignore: options.ignores,
ignore: getIgnores(options),
gitignore: true,
cwd: options.cwd
cwd: options.cwd || process.cwd()
}
);
paths = paths.map(x => path.relative(options.cwd, path.resolve(options.cwd, x)));

// Filter out unwanted file extensions
// For silly users that don't specify an extension in the glob pattern
if (!isEmptyPatterns) {
paths = paths.filter(filePath => {
const extension = path.extname(filePath).replace('.', '');
return options.extensions.includes(extension);
});
}

if (!(options.overrides && options.overrides.length > 0)) {
return runEslint(paths, options);
}

const {overrides} = options;
delete options.overrides;

const grouped = optionsManager.groupConfigs(paths, options, overrides);

return mergeReports(grouped.map(data => runEslint(data.paths, data.options)));
return mergeReports((await mergeWithFileConfigs(paths, options)).map(
({files, options, prettierOptions}) => runEslint(files, buildConfig(options, prettierOptions)))
);
};

module.exports.getFormatter = eslint.CLIEngine.getFormatter;
module.exports.getErrorResults = eslint.CLIEngine.getErrorResults;
module.exports.outputFixes = eslint.CLIEngine.outputFixes;
module.exports = {
getFormatter: eslint.CLIEngine.getFormatter,
getErrorResults: eslint.CLIEngine.getErrorResults,
outputFixes: eslint.CLIEngine.outputFixes,
lintText,
lintFiles
};
105 changes: 105 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const DEFAULT_IGNORES = [
'**/node_modules/**',
'**/bower_components/**',
'flow-typed/**',
'coverage/**',
'{tmp,temp}/**',
'**/*.min.js',
'vendor/**',
'dist/**',
'tap-snapshots/*.js'
];

const DEFAULT_EXTENSION = ['js', 'jsx'];

/**
* Define the rules config that are overwritten only for specific version of Node.js based on `engines.node` in package.json or the `node-version` option.
*
* The keys are rule names and the values are an Object with a valid semver (`4.0.0` is valid `4` is not) as keys and the rule configuration as values.
*
* Each entry define the rule config and the maximum Node.js version for which to set it.
* The entry with the lowest version that is compliant with the `engines.node`/`node-version` range will be used.
*
* @type {Object}
*
* @example
* ```js
* {
* 'plugin/rule': {
* '6.0.0': ['error', {prop: 'node-6-conf'}],
* '8.0.0': ['error', {prop: 'node-8-conf'}]
* }
* }
*```
* With `engines.node` set to `>=4` the rule `plugin/rule` will not be used.
* With `engines.node` set to `>=6` the rule `plugin/rule` will be used with the config `{prop: 'node-6-conf'}`.
* With `engines.node` set to `>=8` the rule `plugin/rule` will be used with the config `{prop: 'node-8-conf'}`.
*/
const ENGINE_RULES = {
'unicorn/prefer-spread': {
'5.0.0': 'off'
},
'unicorn/no-new-buffer': {
'5.10.0': 'off'
},
'prefer-rest-params': {
'6.0.0': 'off'
},
'prefer-destructuring': {
'6.0.0': 'off'
},
'promise/prefer-await-to-then': {
'7.6.0': 'off'
},
'prefer-object-spread': {
'8.3.0': 'off'
},
'node/prefer-global/url-search-params': {
'10.0.0': 'off'
},
'node/prefer-global/url': {
'10.0.0': 'off'
},
'no-useless-catch': {
'10.0.0': 'off'
},
'prefer-named-capture-group': {
'10.0.0': 'off'
},
'node/prefer-global/text-encoder': {
'11.0.0': 'off'
},
'node/prefer-global/text-decoder': {
'11.0.0': 'off'
},
'unicorn/prefer-flat-map': {
'11.0.0': 'off'
},
'node/prefer-promises/dns': {
'11.14.0': 'off'
},
'node/prefer-promises/fs': {
'11.14.0': 'off'
}
};

const PRETTIER_CONFIG_OVERRIDE = {
'@typescript-eslint/eslint-plugin': 'prettier/@typescript-eslint',
'eslint-plugin-babel': 'prettier/babel',
'eslint-plugin-flowtype': 'prettier/flowtype',
'eslint-plugin-react': 'prettier/react',
'eslint-plugin-standard': 'prettier/standard',
'eslint-plugin-vue': 'prettier/vue'
};

const MODULE_NAME = 'xo';

const CONFIG_FILES = [
'package.json',
`.${MODULE_NAME}-config`,
`.${MODULE_NAME}-config.json`,
`.${MODULE_NAME}-config.js`,
`${MODULE_NAME}.config.js`
];

module.exports = {DEFAULT_IGNORES, DEFAULT_EXTENSION, ENGINE_RULES, PRETTIER_CONFIG_OVERRIDE, MODULE_NAME, CONFIG_FILES};
Loading

0 comments on commit e0f81a7

Please sign in to comment.