Skip to content

Commit

Permalink
Pass current directory to formatter function
Browse files Browse the repository at this point in the history
This replicates a new feature in ESLint 8.4:
eslint/eslint#13392
  • Loading branch information
fasttime committed Dec 6, 2021
1 parent b70f6a0 commit 031fee6
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 53 deletions.
55 changes: 27 additions & 28 deletions index.js
Expand Up @@ -15,15 +15,15 @@ const {
const { ESLint } = require('eslint');
const { promisify } = require('util');

function getESLintInstance(file) {
const eslintInstance = file._eslintInstance;
if (eslintInstance != null) {
return eslintInstance;
function getESLintInfo(file) {
const eslintInfo = file._eslintInfo;
if (eslintInfo != null) {
return eslintInfo;
}
throw createPluginError({ fileName: file.path, message: 'ESLint instance not found' });
throw createPluginError({ fileName: file.path, message: 'ESLint information not available' });
}

async function lintFile(eslintInstance, file, cwd, quiet, warnIgnored) {
async function lintFile(eslintInfo, file, quiet, warnIgnored) {
if (file.isNull()) {
return;
}
Expand All @@ -32,49 +32,48 @@ async function lintFile(eslintInstance, file, cwd, quiet, warnIgnored) {
throw 'gulp-eslint-new doesn\'t support Vinyl files with Stream contents.';
}

const { eslint } = eslintInfo;
// The "path" property of a Vinyl file should be always an absolute path.
// See https://gulpjs.com/docs/en/api/vinyl/#instance-properties.
const filePath = file.path;
if (await eslintInstance.isPathIgnored(filePath)) {
if (await eslint.isPathIgnored(filePath)) {
// Note: ESLint doesn't adjust file paths relative to an ancestory .eslintignore path.
// E.g., If ../.eslintignore has "foo/*.js", ESLint will ignore ./foo/*.js, instead of ../foo/*.js.
// ESLint rolls this into `ESLint.prototype.lintText`. So, gulp-eslint-new must account for this limitation.

if (warnIgnored) {
// Warn that gulp.src is needlessly reading files that ESLint ignores.
file.eslint = createIgnoreResult(filePath, cwd);
file.eslint = createIgnoreResult(filePath, eslintInfo.cwd);
}
return;
}

const [result] = await eslintInstance.lintText(file.contents.toString(), { filePath });
let [result] = await eslint.lintText(file.contents.toString(), { filePath });
// Note: Fixes are applied as part of `lintText`.
// Any applied fix messages have been removed from the result.

let eslint;
if (quiet) {
// Ignore some messages.
const filter = typeof quiet === 'function' ? quiet : isErrorMessage;
eslint = filterResult(result, filter);
} else {
eslint = result;
result = filterResult(result, filter);
}
file.eslint = eslint;
file._eslintInstance = eslintInstance;
file.eslint = result;
file._eslintInfo = eslintInfo;

// Update the fixed output; otherwise, fixable messages are simply ignored.
if (hasOwn(eslint, 'output')) {
file.contents = Buffer.from(eslint.output);
eslint.fixed = true;
if (hasOwn(result, 'output')) {
file.contents = Buffer.from(result.output);
result.fixed = true;
}
}

function gulpEslint(options) {
const { eslintOptions, quiet, warnIgnored } = migrateOptions(options);
const eslintInstance = new ESLint(eslintOptions);
const cwd = eslintOptions.cwd || process.cwd();
const eslint = new ESLint(eslintOptions);
const eslintInfo = { cwd, eslint };
return createTransform(
file => lintFile(eslintInstance, file, cwd, quiet, warnIgnored)
file => lintFile(eslintInfo, file, quiet, warnIgnored)
);
}

Expand Down Expand Up @@ -156,25 +155,25 @@ gulpEslint.formatEach = (formatter, writable) => {
return createTransform(async file => {
const { eslint } = file;
if (eslint) {
const eslintInstance = getESLintInstance(file);
await writeResults([eslint], eslintInstance, formatter, writable);
const eslintInfo = getESLintInfo(file);
await writeResults([eslint], eslintInfo, formatter, writable);
}
});
};

gulpEslint.format = (formatter, writable) => {
writable = resolveWritable(writable);
const results = [];
let commonInstance;
let commonInfo;
return createTransform(
file => {
const { eslint } = file;
if (eslint) {
const eslintInstance = getESLintInstance(file);
if (commonInstance == null) {
commonInstance = eslintInstance;
const eslintInfo = getESLintInfo(file);
if (commonInfo == null) {
commonInfo = eslintInfo;
} else {
if (eslintInstance !== commonInstance) {
if (eslintInfo !== commonInfo) {
throw createPluginError({
name: 'ESLintError',
message: 'The files in the stream were not processes by the same '
Expand All @@ -187,7 +186,7 @@ gulpEslint.format = (formatter, writable) => {
},
async () => {
if (results.length) {
await writeResults(results, commonInstance, formatter, writable);
await writeResults(results, commonInfo, formatter, writable);
}
}
);
Expand Down
6 changes: 3 additions & 3 deletions test/format.spec.js
Expand Up @@ -116,7 +116,7 @@ describe('gulp-eslint-new format function', () => {
.format()
.on('error', err => {
assert.equal(err.fileName, file.path);
assert.equal(err.message, 'ESLint instance not found');
assert.equal(err.message, 'ESLint information not available');
assert.equal(err.plugin, 'gulp-eslint-new');
done();
})
Expand All @@ -139,7 +139,7 @@ describe('gulp-eslint-new format function', () => {
function addFile(path) {
const file = createVinylFile(path, '');
file.eslint = { };
file._eslintInstance = new ESLint();
file._eslintInfo = { cwd: process.cwd(), eslint: new ESLint() };
formatStream.write(file);
}

Expand Down Expand Up @@ -216,7 +216,7 @@ describe('gulp-eslint-new format function', () => {
.formatEach()
.on('error', err => {
assert.equal(err.fileName, file.path);
assert.equal(err.message, 'ESLint instance not found');
assert.equal(err.message, 'ESLint information not available');
assert.equal(err.plugin, 'gulp-eslint-new');
done();
})
Expand Down
28 changes: 15 additions & 13 deletions test/util.spec.js
Expand Up @@ -380,8 +380,8 @@ describe('utility methods', () => {
];

it('should default to the "stylish" formatter', async () => {
const eslintInstance = new ESLint();
const formatter = await util.resolveFormatter(eslintInstance);
const eslintInfo = { eslint: new ESLint() };
const formatter = await util.resolveFormatter(eslintInfo);
const text = formatter.format(testResults);
assert.equal(
text.replace(/\x1b\[\d+m/g, ''), // eslint-disable-line no-control-regex
Expand All @@ -390,8 +390,8 @@ describe('utility methods', () => {
});

it('should resolve a predefined formatter', async () => {
const eslintInstance = new ESLint();
const formatter = await util.resolveFormatter(eslintInstance, 'compact');
const eslintInfo = { eslint: new ESLint() };
const formatter = await util.resolveFormatter(eslintInfo, 'compact');
const text = formatter.format(testResults);
assert.equal(
text.replace(/\x1b\[\d+m/g, ''), // eslint-disable-line no-control-regex
Expand All @@ -400,30 +400,32 @@ describe('utility methods', () => {
});

it('should resolve a custom formatter', async () => {
const eslintInstance = new ESLint({ cwd: __dirname });
const formatter = await util.resolveFormatter(eslintInstance, './custom-formatter');
const eslintInfo = { eslint: new ESLint({ cwd: __dirname }) };
const formatter = await util.resolveFormatter(eslintInfo, './custom-formatter');
formatter.format(testResults);
const { args } = require('./custom-formatter');
assert.equal(args[0], testResults);
assert.equal(args[1].cwd, __dirname);
assert(args[1].rulesMeta);
});

it('should wrap an ESLint 6 style formatter function into a formatter', async () => {
const eslintInstance = new ESLint();
const eslintInfo = { cwd: 'TEST CWD', eslint: new ESLint() };
const legacyFormatter = (actualResults, data) => {
assert.equal(actualResults, testResults);
assert(data.rulesMeta);
assert.equal(data.cwd, 'TEST CWD');
return 'foo';
};
const formatter = await util.resolveFormatter(eslintInstance, legacyFormatter);
const formatter = await util.resolveFormatter(eslintInfo, legacyFormatter);
const text = await formatter.format(testResults);
assert.equal(text, 'foo');
});

it('should throw an error if a formatter cannot be resolved', async () => {
const eslintInstance = new ESLint();
const eslintInfo = { eslint: new ESLint() };
await assert.rejects(
() => util.resolveFormatter(eslintInstance, 'missing-formatter'),
() => util.resolveFormatter(eslintInfo, 'missing-formatter'),
/\bThere was a problem loading formatter\b/
);
});
Expand Down Expand Up @@ -476,7 +478,7 @@ describe('utility methods', () => {
const formattedText = 'something happened';
await util.writeResults(
testResults,
testInstance,
{ cwd: process.cwd(), eslint: testInstance },
(results, { rulesMeta }) => {
assert(results);
assert.equal(results, testResults);
Expand All @@ -495,7 +497,7 @@ describe('utility methods', () => {
it('should not write an empty formatted text', async () => {
await util.writeResults(
testResults,
testInstance,
{ cwd: process.cwd(), eslint: testInstance },
(results, { rulesMeta }) => {
assert(results);
assert.equal(results, testResults);
Expand All @@ -509,7 +511,7 @@ describe('utility methods', () => {
it('should not write an undefined', async () => {
await util.writeResults(
testResults,
testInstance,
{ cwd: process.cwd(), eslint: testInstance },
(results, { rulesMeta }) => {
assert(results);
assert.equal(results, testResults);
Expand Down
19 changes: 10 additions & 9 deletions util.js
Expand Up @@ -339,24 +339,25 @@ const { defineProperty } = Object;
* If a function is specified, it will be treated as an ESLint 6 style formatter function and
* wrapped into an object appropriately.
*
* @param {ESLint} eslintInstance
* Instance of ESLint used to load the formatter.
* @param {{ cwd: string, eslint: ESLint }} eslintInfo
* Current directory and instance of ESLint used to load and configure the formatter.
*
* @param {string|CLIEngine.Formatter} [formatter]
* A name or path of a formatter, or an ESLint 6 style formatter function to resolve as a formatter.
*
* @returns {Promise<ESLint.Formatter>} An ESLint formatter.
*/
async function resolveFormatter(eslintInstance, formatter) {
async function resolveFormatter({ cwd, eslint }, formatter) {
if (typeof formatter === 'function') {
return {
format: results => {
results.sort(compareResultsByFilePath);
return formatter(
results,
{
cwd,
get rulesMeta() {
const rulesMeta = eslintInstance.getRulesMetaForResults(results);
const rulesMeta = eslint.getRulesMetaForResults(results);
defineProperty(this, 'rulesMeta', { value: rulesMeta });
return rulesMeta;
}
Expand All @@ -366,7 +367,7 @@ async function resolveFormatter(eslintInstance, formatter) {
};
}
// Use ESLint to look up formatter references.
return eslintInstance.loadFormatter(formatter);
return eslint.loadFormatter(formatter);
}
exports.resolveFormatter = resolveFormatter;

Expand All @@ -390,17 +391,17 @@ exports.resolveWritable = (writable = fancyLog) => {
* @param {ESLint.LintResult[]} results
* A list of ESLint results.
*
* @param {ESLint} eslintInstance
* Instance of ESLint, used to resolve the formatter.
* @param {{ cwd: string, eslint: ESLint }} eslintInfo
* Current directory and instance of ESLint used to load and configure the formatter.
*
* @param {string|CLIEngine.Formatter} [formatter]
* A name or path of a formatter, or an ESLint 6 style formatter function to resolve as a formatter.
*
* @param {Function} [writable]
* A function used to write formatted ESLint results.
*/
exports.writeResults = async (results, eslintInstance, formatter, writable) => {
const formatterObj = await resolveFormatter(eslintInstance, formatter);
exports.writeResults = async (results, eslintInfo, formatter, writable) => {
const formatterObj = await resolveFormatter(eslintInfo, formatter);
const message = formatterObj.format(results);
if (writable && message != null && message !== '') {
writable(message);
Expand Down

0 comments on commit 031fee6

Please sign in to comment.