diff --git a/cli.js b/cli.js
index a12b8d0c..1284a2dc 100755
--- a/cli.js
+++ b/cli.js
@@ -149,7 +149,7 @@ if (process.env.GITHUB_ACTIONS && !options.fix && !options.reporter) {
const log = async report => {
const reporter = options.reporter || process.env.GITHUB_ACTIONS ? await xo.getFormatter(options.reporter || 'compact') : formatterPretty;
- process.stdout.write(reporter(report.results));
+ process.stdout.write(reporter(report.results, {rulesMeta: report.rulesMeta}));
process.exitCode = report.errorCount === 0 ? 0 : 1;
};
diff --git a/index.js b/index.js
index 142ff668..44e77b22 100644
--- a/index.js
+++ b/index.js
@@ -66,7 +66,10 @@ const lintText = async (string, options) => {
}
const report = await eslint.lintText(string, {filePath, warnIgnored});
- return processReport(report, {isQuiet});
+
+ const rulesMeta = eslint.getRulesMetaForResults(report);
+
+ return processReport(report, {isQuiet, rulesMeta});
};
const lintFiles = async (patterns, options) => {
@@ -102,7 +105,9 @@ const lintFiles = async (patterns, options) => {
const report = await eslint.lintFiles(files);
- return processReport(report, {isQuiet: options.isQuiet});
+ const rulesMeta = eslint.getRulesMetaForResults(report);
+
+ return processReport(report, {isQuiet: options.isQuiet, rulesMeta});
}));
const report = mergeReports(reports);
diff --git a/lib/report.js b/lib/report.js
index f6daf0fa..a6faf102 100644
--- a/lib/report.js
+++ b/lib/report.js
@@ -13,18 +13,20 @@ const mergeReports = reports => {
report.results.push(...currentReport.results);
report.errorCount += currentReport.errorCount;
report.warningCount += currentReport.warningCount;
+ report.rulesMeta = {...report.rulesMeta, ...currentReport.rulesMeta};
}
return report;
};
-const processReport = (report, {isQuiet = false} = {}) => {
+const processReport = (report, {isQuiet = false, rulesMeta} = {}) => {
if (isQuiet) {
report = ESLint.getErrorResults(report);
}
const result = {
results: report,
+ rulesMeta,
...getReportStatistics(report),
};
diff --git a/test/lint-files.js b/test/lint-files.js
index fab99a84..f1b4e455 100644
--- a/test/lint-files.js
+++ b/test/lint-files.js
@@ -7,9 +7,11 @@ import xo from '../index.js';
const {__dirname} = createEsmUtils(import.meta);
process.chdir(__dirname);
-const hasRule = (results, filePath, ruleId) => {
+const hasRule = (results, filePath, ruleId, rulesMeta) => {
const result = results.find(x => x.filePath === filePath);
- return result ? result.messages.some(x => x.ruleId === ruleId) : false;
+ const hasRuleInResults = result ? result.messages.some(x => x.ruleId === ruleId) : false;
+ const hasRuleInResultsMeta = rulesMeta ? typeof rulesMeta[ruleId] === 'object' : true;
+ return hasRuleInResults && hasRuleInResultsMeta;
};
test('only accepts allowed extensions', async t => {
@@ -120,7 +122,7 @@ test('multiple negative patterns should act as positive patterns', async t => {
});
test.failing('enable rules based on nodeVersion', async t => {
- const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/engines-overrides'});
+ const {results, rulesMeta} = await xo.lintFiles('**/*', {cwd: 'fixtures/engines-overrides'});
// The transpiled file (as specified in `overrides`) should use `await`
t.true(
@@ -128,6 +130,7 @@ test.failing('enable rules based on nodeVersion', async t => {
results,
path.resolve('fixtures/engines-overrides/promise-then-transpile.js'),
'promise/prefer-await-to-then',
+ rulesMeta,
),
);
// The non transpiled files can use `.then`
@@ -136,6 +139,7 @@ test.failing('enable rules based on nodeVersion', async t => {
results,
path.resolve('fixtures/engines-overrides/promise-then.js'),
'promise/prefer-await-to-then',
+ rulesMeta,
),
);
});
@@ -152,13 +156,14 @@ test('do not lint eslintignored files', async t => {
});
test('find configurations close to linted file', async t => {
- const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/nested-configs'});
+ const {results, rulesMeta} = await xo.lintFiles('**/*', {cwd: 'fixtures/nested-configs'});
t.true(
hasRule(
results,
path.resolve('fixtures/nested-configs/child/semicolon.js'),
'semi',
+ rulesMeta,
),
);
@@ -167,6 +172,7 @@ test('find configurations close to linted file', async t => {
results,
path.resolve('fixtures/nested-configs/child-override/child-prettier-override/semicolon.js'),
'prettier/prettier',
+ rulesMeta,
),
);
@@ -175,6 +181,7 @@ test('find configurations close to linted file', async t => {
results,
path.resolve('fixtures/nested-configs/no-semicolon.js'),
'semi',
+ rulesMeta,
),
);
@@ -183,18 +190,20 @@ test('find configurations close to linted file', async t => {
results,
path.resolve('fixtures/nested-configs/child-override/two-spaces.js'),
'indent',
+ rulesMeta,
),
);
});
test.serial('typescript files', async t => {
- const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/typescript'});
+ const {results, rulesMeta} = await xo.lintFiles('**/*', {cwd: 'fixtures/typescript'});
t.true(
hasRule(
results,
path.resolve('fixtures/typescript/two-spaces.tsx'),
'@typescript-eslint/indent',
+ rulesMeta,
),
);
@@ -203,6 +212,7 @@ test.serial('typescript files', async t => {
results,
path.resolve('fixtures/typescript/child/extra-semicolon.ts'),
'@typescript-eslint/no-extra-semi',
+ rulesMeta,
),
);
@@ -211,6 +221,7 @@ test.serial('typescript files', async t => {
results,
path.resolve('fixtures/typescript/child/sub-child/four-spaces.ts'),
'@typescript-eslint/indent',
+ rulesMeta,
),
);
});
@@ -287,13 +298,14 @@ test('webpack import resolver is used if {webpack: true}', async t => {
});
async function configType(t, {dir}) {
- const {results} = await xo.lintFiles('**/*', {cwd: path.resolve('fixtures', 'config-files', dir)});
+ const {results, rulesMeta} = await xo.lintFiles('**/*', {cwd: path.resolve('fixtures', 'config-files', dir)});
t.true(
hasRule(
results,
path.resolve('fixtures', 'config-files', dir, 'file.js'),
'indent',
+ rulesMeta,
),
);
}
diff --git a/test/lint-text.js b/test/lint-text.js
index a4950b1a..d5e6b32a 100644
--- a/test/lint-text.js
+++ b/test/lint-text.js
@@ -8,11 +8,15 @@ import xo from '../index.js';
const {__dirname} = createEsmUtils(import.meta);
process.chdir(__dirname);
-const hasRule = (results, expectedRuleId) => results[0].messages.some(({ruleId}) => ruleId === expectedRuleId);
+const hasRule = (results, expectedRuleId, rulesMeta) => {
+ const hasRuleInResults = results[0].messages.some(({ruleId}) => ruleId === expectedRuleId);
+ const hasRuleInMeta = rulesMeta ? typeof rulesMeta[expectedRuleId] === 'object' : true;
+ return hasRuleInResults && hasRuleInMeta;
+};
test('.lintText()', async t => {
- const {results} = await xo.lintText('\'use strict\'\nconsole.log(\'unicorn\');\n');
- t.true(hasRule(results, 'semi'));
+ const {results, rulesMeta} = await xo.lintText('\'use strict\'\nconsole.log(\'unicorn\');\n');
+ t.true(hasRule(results, 'semi', rulesMeta));
});
test('default `ignores`', async t => {
@@ -83,50 +87,50 @@ test('`ignores` option without filename', async t => {
});
test('JSX support', async t => {
- const {results} = await xo.lintText('const app =
Hello, React!
;\n');
- t.true(hasRule(results, 'no-unused-vars'));
+ const {results, rulesMeta} = await xo.lintText('const app = Hello, React!
;\n');
+ t.true(hasRule(results, 'no-unused-vars', rulesMeta));
});
test('plugin support', async t => {
- const {results} = await xo.lintText('var React;\nReact.render();\n', {
+ const {results, rulesMeta} = await xo.lintText('var React;\nReact.render();\n', {
plugins: ['react'],
rules: {'react/jsx-no-undef': 'error'},
});
- t.true(hasRule(results, 'react/jsx-no-undef'));
+ t.true(hasRule(results, 'react/jsx-no-undef', rulesMeta));
});
test('prevent use of extended native objects', async t => {
- const {results} = await xo.lintText('[].unicorn();\n');
- t.true(hasRule(results, 'no-use-extend-native/no-use-extend-native'));
+ const {results, rulesMeta} = await xo.lintText('[].unicorn();\n');
+ t.true(hasRule(results, 'no-use-extend-native/no-use-extend-native', rulesMeta));
});
test('extends support', async t => {
- const {results} = await xo.lintText('var React;\nReact.render();\n', {
+ const {results, rulesMeta} = await xo.lintText('var React;\nReact.render();\n', {
extends: 'xo-react',
});
- t.true(hasRule(results, 'react/jsx-no-undef'));
+ t.true(hasRule(results, 'react/jsx-no-undef', rulesMeta));
});
test('disable style rules when `prettier` option is enabled', async t => {
- const {results: withoutPrettier} = await xo.lintText('(a) => {}\n', {filePath: 'test.js'});
+ const {results: withoutPrettier, rulesMeta} = await xo.lintText('(a) => {}\n', {filePath: 'test.js'});
// `arrow-parens` is enabled
- t.true(hasRule(withoutPrettier, 'arrow-parens'));
+ t.true(hasRule(withoutPrettier, 'arrow-parens', rulesMeta));
// `prettier/prettier` is disabled
- t.false(hasRule(withoutPrettier, 'prettier/prettier'));
+ t.false(hasRule(withoutPrettier, 'prettier/prettier', rulesMeta));
const {results: withPrettier} = await xo.lintText('(a) => {}\n', {prettier: true, filePath: 'test.js'});
// `arrow-parens` is disabled by `eslint-config-prettier`
- t.false(hasRule(withPrettier, 'arrow-parens'));
- // `prettier/prettier` is enabled
+ t.false(hasRule(withPrettier, 'arrow-parens', rulesMeta));
+ // `prettier/prettier` is enabled - this is a special case for rulesMeta - so we remove it from this test
t.true(hasRule(withPrettier, 'prettier/prettier'));
});
test('extends `react` support with `prettier` option', async t => {
- const {results} = await xo.lintText(';\n', {extends: 'xo-react', prettier: true, filePath: 'test.jsx'});
+ const {results, rulesMeta} = await xo.lintText(';\n', {extends: 'xo-react', prettier: true, filePath: 'test.jsx'});
// `react/jsx-curly-spacing` is disabled by `eslint-config-prettier`
- t.false(hasRule(results, 'react/jsx-curly-spacing'));
+ t.false(hasRule(results, 'react/jsx-curly-spacing', rulesMeta));
// `prettier/prettier` is enabled
- t.true(hasRule(results, 'prettier/prettier'));
+ t.true(hasRule(results, 'prettier/prettier', rulesMeta));
});
test('regression test for #71', async t => {
@@ -210,11 +214,11 @@ test.failing('enable rules based on nodeVersion', async t => {
const filePath = path.join(cwd, 'promise-then.js');
const text = await fs.readFile(filePath, 'utf8');
- let {results} = await xo.lintText(text, {nodeVersion: '>=8.0.0'});
- t.true(hasRule(results, 'promise/prefer-await-to-then'));
+ let {results, rulesMeta} = await xo.lintText(text, {nodeVersion: '>=8.0.0'});
+ t.true(hasRule(results, 'promise/prefer-await-to-then', rulesMeta));
- ({results} = await xo.lintText(text, {nodeVersion: '>=6.0.0'}));
- t.false(hasRule(results, 'promise/prefer-await-to-then'));
+ ({results, rulesMeta} = await xo.lintText(text, {nodeVersion: '>=6.0.0'}));
+ t.false(hasRule(results, 'promise/prefer-await-to-then', rulesMeta));
});
test.failing('enable rules based on nodeVersion in override', async t => {
@@ -222,7 +226,7 @@ test.failing('enable rules based on nodeVersion in override', async t => {
const filePath = path.join(cwd, 'promise-then.js');
const text = await fs.readFile(filePath, 'utf8');
- let {results} = await xo.lintText(text, {
+ let {results, rulesMeta} = await xo.lintText(text, {
nodeVersion: '>=8.0.0',
filePath: 'promise-then.js',
overrides: [
@@ -232,9 +236,9 @@ test.failing('enable rules based on nodeVersion in override', async t => {
},
],
});
- t.false(hasRule(results, 'promise/prefer-await-to-then'));
+ t.false(hasRule(results, 'promise/prefer-await-to-then', rulesMeta));
- ({results} = await xo.lintText(text, {
+ ({results, rulesMeta} = await xo.lintText(text, {
nodeVersion: '>=6.0.0',
filePath: 'promise-then.js',
overrides: [
@@ -244,47 +248,52 @@ test.failing('enable rules based on nodeVersion in override', async t => {
},
],
}));
- t.true(hasRule(results, 'promise/prefer-await-to-then'));
+ t.true(hasRule(results, 'promise/prefer-await-to-then', rulesMeta));
});
test('allow unassigned stylesheet imports', async t => {
- let {results} = await xo.lintText('import \'stylesheet.css\'');
- t.false(hasRule(results, 'import/no-unassigned-import'));
+ let {results, rulesMeta} = await xo.lintText('import \'stylesheet.css\'');
+ t.false(hasRule(results, 'import/no-unassigned-import', rulesMeta));
- ({results} = await xo.lintText('import \'stylesheet.scss\''));
- t.false(hasRule(results, 'import/no-unassigned-import'));
+ ({results, rulesMeta} = await xo.lintText('import \'stylesheet.scss\''));
+ t.false(hasRule(results, 'import/no-unassigned-import', rulesMeta));
- ({results} = await xo.lintText('import \'stylesheet.sass\''));
- t.false(hasRule(results, 'import/no-unassigned-import'));
+ ({results, rulesMeta} = await xo.lintText('import \'stylesheet.sass\''));
+ t.false(hasRule(results, 'import/no-unassigned-import', rulesMeta));
- ({results} = await xo.lintText('import \'stylesheet.less\''));
- t.false(hasRule(results, 'import/no-unassigned-import'));
+ ({results, rulesMeta} = await xo.lintText('import \'stylesheet.less\''));
+ t.false(hasRule(results, 'import/no-unassigned-import', rulesMeta));
});
test('find configurations close to linted file', async t => {
- let {results} = await xo.lintText('console.log(\'semicolon\');\n', {filePath: 'fixtures/nested-configs/child/semicolon.js'});
- t.true(hasRule(results, 'semi'));
+ let {results, rulesMeta} = await xo.lintText('console.log(\'semicolon\');\n', {filePath: 'fixtures/nested-configs/child/semicolon.js'});
+ t.true(hasRule(results, 'semi', rulesMeta));
- ({results} = await xo.lintText('console.log(\'semicolon\');\n', {filePath: 'fixtures/nested-configs/child-override/child-prettier-override/semicolon.js'}));
- t.true(hasRule(results, 'prettier/prettier'));
+ ({results, rulesMeta} = await xo.lintText('console.log(\'semicolon\');\n', {filePath: 'fixtures/nested-configs/child-override/child-prettier-override/semicolon.js'}));
+ t.true(hasRule(results, 'prettier/prettier', rulesMeta));
- ({results} = await xo.lintText('console.log(\'no-semicolon\')\n', {filePath: 'fixtures/nested-configs/no-semicolon.js'}));
- t.true(hasRule(results, 'semi'));
+ ({results, rulesMeta} = await xo.lintText('console.log(\'no-semicolon\')\n', {filePath: 'fixtures/nested-configs/no-semicolon.js'}));
+ t.true(hasRule(results, 'semi', rulesMeta));
- ({results} = await xo.lintText(`console.log([
+ ({results, rulesMeta} = await xo.lintText(`console.log([
2
]);\n`, {filePath: 'fixtures/nested-configs/child-override/two-spaces.js'}));
- t.true(hasRule(results, 'indent'));
+ t.true(hasRule(results, 'indent', rulesMeta));
+});
+
+test('rulesMeta is added to the report by default', async t => {
+ const report = await xo.lintText('\'use strict\'\nconsole.log(\'unicorn\');\n');
+ t.is(typeof report.rulesMeta, 'object');
});
test('typescript files: two spaces fails', async t => {
const twoSpacesCwd = path.resolve('fixtures', 'typescript');
const twoSpacesfilePath = path.resolve(twoSpacesCwd, 'two-spaces.tsx');
const twoSpacesText = await fs.readFile(twoSpacesfilePath, 'utf8');
- const {results} = await xo.lintText(twoSpacesText, {
+ const {results, rulesMeta} = await xo.lintText(twoSpacesText, {
filePath: twoSpacesfilePath,
});
- t.true(hasRule(results, '@typescript-eslint/indent'));
+ t.true(hasRule(results, '@typescript-eslint/indent', rulesMeta));
});
test('typescript files: two spaces pass', async t => {
@@ -302,10 +311,10 @@ test('typescript files: extra semi fail', async t => {
const extraSemiCwd = path.resolve('fixtures', 'typescript', 'child');
const extraSemiFilePath = path.resolve(extraSemiCwd, 'extra-semicolon.ts');
const extraSemiText = await fs.readFile(extraSemiFilePath, 'utf8');
- const {results} = await xo.lintText(extraSemiText, {
+ const {results, rulesMeta} = await xo.lintText(extraSemiText, {
filePath: extraSemiFilePath,
});
- t.true(hasRule(results, '@typescript-eslint/no-extra-semi'));
+ t.true(hasRule(results, '@typescript-eslint/no-extra-semi', rulesMeta));
});
test('typescript files: extra semi pass', async t => {
@@ -323,10 +332,10 @@ test('typescript files: four space fail', async t => {
const fourSpacesCwd = path.resolve('fixtures', 'typescript', 'child', 'sub-child');
const fourSpacesFilePath = path.resolve(fourSpacesCwd, 'four-spaces.ts');
const fourSpacesText = await fs.readFile(fourSpacesFilePath, 'utf8');
- const {results} = await xo.lintText(fourSpacesText, {
+ const {results, rulesMeta} = await xo.lintText(fourSpacesText, {
filePath: fourSpacesFilePath,
});
- t.true(hasRule(results, '@typescript-eslint/indent'));
+ t.true(hasRule(results, '@typescript-eslint/indent', rulesMeta));
});
test('typescript files: four space pass', async t => {
@@ -350,8 +359,8 @@ test('deprecated rules', async t => {
});
async function configType(t, {dir}) {
- const {results} = await xo.lintText('var obj = { a: 1 };\n', {cwd: path.resolve('fixtures', 'config-files', dir), filePath: 'file.js'});
- t.true(hasRule(results, 'no-var'));
+ const {results, rulesMeta} = await xo.lintText('var obj = { a: 1 };\n', {cwd: path.resolve('fixtures', 'config-files', dir), filePath: 'file.js'});
+ t.true(hasRule(results, 'no-var', rulesMeta));
}
configType.title = (_, {type}) => `load config from ${type}`.trim();