Skip to content

Commit

Permalink
vendor eslint formatters
Browse files Browse the repository at this point in the history
- copied the current version of eslint formatters into our codebase
  since they're no longer accesible from importing the `eslint` package
  (see discussions in protofire#380) for
  details
- only imported those already supported, there are many other eslint
  formatters in the current eslint version (8.32.0) than there were in
  the one we were using previously (5.6.0)
- table formatter was dropped by the latest version of eslint, so I
  fetched it from 5.6.0 instead
- while building this commit I realized it was possible to remove a
  formatter entirely and all existing tests would pass, so I created an
  issue for discussing that
- made eslint a dev dependency to reduce install size, since it's only
  used for linting this codebase.
- added some dependencies of the eslint formatters. Used 'old'
  strip-ansi version 6.0.1 which seems to still be maintained instead of
  7.0.1 since from version 7 onwards it's a pure ESM package
  • Loading branch information
juanpcapurro committed Jan 19, 2023
1 parent 4c4f127 commit 6beb38f
Show file tree
Hide file tree
Showing 7 changed files with 597 additions and 51 deletions.
101 changes: 101 additions & 0 deletions lib/formatters/stylish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @fileoverview Stylish reporter
* @author Sindre Sorhus
*/
"use strict";

const chalk = require("chalk"),
stripAnsi = require("strip-ansi"),
table = require("text-table");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Given a word and a count, append an s if count is not one.
* @param {string} word A word in its singular form.
* @param {int} count A number controlling whether word should be pluralized.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : `${word}s`);
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = function(results) {

let output = "\n",
errorCount = 0,
warningCount = 0,
fixableErrorCount = 0,
fixableWarningCount = 0,
summaryColor = "yellow";

results.forEach(result => {
const messages = result.messages;

if (messages.length === 0) {
return;
}

errorCount += result.errorCount;
warningCount += result.warningCount;
fixableErrorCount += result.fixableErrorCount;
fixableWarningCount += result.fixableWarningCount;

output += `${chalk.underline(result.filePath)}\n`;

output += `${table(
messages.map(message => {
let messageType;
if (message.fatal || message.severity === 2) {
messageType = chalk.red("error");
summaryColor = "red";
} else {
messageType = chalk.yellow("warning");
}
return [
"",
message.line || 0,
message.column || 0,
messageType,
message.message.replace(/([^ ])\.$/u, "$1"),
chalk.dim(message.ruleId || "")
];
}),
{
align: ["", "r", "l"],
stringLength(str) {
return stripAnsi(str).length;
}
}
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
});

const total = errorCount + warningCount;

if (total > 0) {
output += chalk[summaryColor].bold([
"\u2716 ", total, pluralize(" problem", total),
" (", errorCount, pluralize(" error", errorCount), ", ",
warningCount, pluralize(" warning", warningCount), ")\n"
].join(""));

if (fixableErrorCount > 0 || fixableWarningCount > 0) {
output += chalk[summaryColor].bold([
" ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
fixableWarningCount, pluralize(" warning", fixableWarningCount),
" potentially fixable with the `--fix` option.\n"
].join(""));
}
}

// Resets output color, for prevent change on top level
return total > 0 ? chalk.reset(output) : "";
};
150 changes: 150 additions & 0 deletions lib/formatters/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @fileoverview "table reporter.
* @author Gajus Kuizinas <[email protected]>
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const chalk = require("chalk"),
table = require("table").table,
pluralize = require("pluralize");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Draws text table.
* @param {Array<Object>} messages Error messages relating to a specific file.
* @returns {string} A text table.
*/
function drawTable(messages) {
const rows = [];

if (messages.length === 0) {
return "";
}

rows.push([
chalk.bold("Line"),
chalk.bold("Column"),
chalk.bold("Type"),
chalk.bold("Message"),
chalk.bold("Rule ID")
]);

messages.forEach(message => {
let messageType;

if (message.fatal || message.severity === 2) {
messageType = chalk.red("error");
} else {
messageType = chalk.yellow("warning");
}

rows.push([
message.line || 0,
message.column || 0,
messageType,
message.message,
message.ruleId || ""
]);
});

return table(rows, {
columns: {
0: {
width: 8,
wrapWord: true
},
1: {
width: 8,
wrapWord: true
},
2: {
width: 8,
wrapWord: true
},
3: {
paddingRight: 5,
width: 50,
wrapWord: true
},
4: {
width: 20,
wrapWord: true
}
},
drawHorizontalLine(index) {
return index === 1;
}
});
}

/**
* Draws a report (multiple tables).
* @param {Array} results Report results for every file.
* @returns {string} A column of text tables.
*/
function drawReport(results) {
let files;

files = results.map(result => {
if (!result.messages.length) {
return "";
}

return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
});

files = files.filter(content => content.trim());

return files.join("");
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = function(report) {
let result,
errorCount,
warningCount;

result = "";
errorCount = 0;
warningCount = 0;

report.forEach(fileReport => {
errorCount += fileReport.errorCount;
warningCount += fileReport.warningCount;
});

if (errorCount || warningCount) {
result = drawReport(report);
}

result += `\n${table([
[
chalk.red(pluralize("Error", errorCount, true))
],
[
chalk.yellow(pluralize("Warning", warningCount, true))
]
], {
columns: {
0: {
width: 110,
wrapWord: true
}
},
drawHorizontalLine() {
return true;
}
})}`;

return result;
};
95 changes: 95 additions & 0 deletions lib/formatters/tap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* @fileoverview TAP reporter
* @author Jonathan Kingston
*/
"use strict";

const yaml = require("js-yaml");

//------------------------------------------------------------------------------
// Helper Functions
//------------------------------------------------------------------------------

/**
* Returns a canonical error level string based upon the error message passed in.
* @param {Object} message Individual error message provided by eslint
* @returns {string} Error level string
*/
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "error";
}
return "warning";
}

/**
* Takes in a JavaScript object and outputs a TAP diagnostics string
* @param {Object} diagnostic JavaScript object to be embedded as YAML into output.
* @returns {string} diagnostics string with YAML embedded - TAP version 13 compliant
*/
function outputDiagnostics(diagnostic) {
const prefix = " ";
let output = `${prefix}---\n`;

output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`);
output += "...\n";
return output;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = function(results) {
let output = `TAP version 13\n1..${results.length}\n`;

results.forEach((result, id) => {
const messages = result.messages;
let testResult = "ok";
let diagnostics = {};

if (messages.length > 0) {
messages.forEach(message => {
const severity = getMessageType(message);
const diagnostic = {
message: message.message,
severity,
data: {
line: message.line || 0,
column: message.column || 0,
ruleId: message.ruleId || ""
}
};

// This ensures a warning message is not flagged as error
if (severity === "error") {
testResult = "not ok";
}

/*
* If we have multiple messages place them under a messages key
* The first error will be logged as message key
* This is to adhere to TAP 13 loosely defined specification of having a message key
*/
if ("message" in diagnostics) {
if (typeof diagnostics.messages === "undefined") {
diagnostics.messages = [];
}
diagnostics.messages.push(diagnostic);
} else {
diagnostics = diagnostic;
}
});
}

output += `${testResult} ${id + 1} - ${result.filePath}\n`;

// If we have an error include diagnostics
if (messages.length > 0) {
output += outputDiagnostics(diagnostics);
}

});

return output;
};
Loading

0 comments on commit 6beb38f

Please sign in to comment.