@linthtml/gulp-linthtml
Version:
A gulp plugin for processing files with LintHTML
239 lines (218 loc) • 7.25 kB
JavaScript
const { Transform } = require("stream");
// const relative = require('path').relative;
const linthtml = require("@linthtml/linthtml");
const PluginError = require("plugin-error");
const fancy = require("fancy-log");
const chalk = require("chalk");
const Table = require("table-layout");
const path = require("path");
const PLUGIN_NAME = "gulp-linthtml";
/**
* @typedef {Object} Issue
* @property {Number} line
* @property {Number} column
* @property {String} code
* @property {Object} data
*/
/**
* @typedef {Object} Report
* @property {String} fileName - File path of the analysed file
* @property {Issue[]} issues - The issues found during fil analysis
*/
function print_position({ position: { start } }, maxLine, maxColumn) {
const line = start.line.toString();
const column = start.column.toString();
return `${line.padStart(maxLine, " ")}:${column.padEnd(maxColumn, " ")}`;
}
function printLevel(issue) {
return `${{
warning: "yellow warning",
error: "red error"
}[issue.severity]}`;
}
function print_error(error) {
if (error.code) {
const ctx = new chalk.Instance({ level: 0 });
const [type, code] = error.code.split("-");
const error_message = linthtml.messages[`${type}_ERRORS`][code];
return error_message(ctx, error.meta);
}
return error.message;
}
/**
* Output a report
*
* @param {Report} report
*/
function lintHTMLreporter(report) {
let output = chalk`\n{underline ${report.fileName}}`;
const maxLine = report.issues.reduce((max, cv) => Math.max(max, cv.line), -1).toString().length;
const maxColumn = report.issues.reduce((max, cv) => Math.max(max, cv.column), -1).toString().length;
const issues = [];
report.issues.forEach(function(issue) {
const msg = linthtml.messages.renderIssue(issue);
const positionTxt = print_position(issue, maxLine, maxColumn);
const level = printLevel(issue);
issues.push({
positions: chalk`{gray ${positionTxt}}`,
level: chalk`{${level}}`,
msg,
rule: chalk`{gray ${issue.rule}}`
});
});
const table = new Table(issues, { noTrim: true });
output = `${output}\n${table.toString()}`;
return output;
}
/**
* Convenience method for creating a transform stream in object mode
*
* @param {Function} transform - An async function that is called for each stream chunk
* @param {Function} [flush] - An async function that is called before closing the stream
* @returns {stream} A transform stream
*/
function transform(transform, flush) {
if (typeof flush === "function") {
return new Transform({
objectMode: true,
transform,
flush
});
}
return new Transform({
objectMode: true,
transform
});
}
/**
* @typedef {Object} GulpLintHTMLOptions
* @property {Object} [rules] - An object containing a liste of valid LintHTML's rules
* @property {string} [configFile] - File path to the LintHTML config file
*/
/**
* @param {(String|GulpLintHTMLOptions)} [options] - Rules to convert
* @returns {Object} converted options
*/
function convertOptions(options = {}) {
if (typeof options === "string") {
// basic config path overload: gulpLintHTML('path/to/config.json')
options = {
configFile: options
};
}
return options;
}
/**
*
* @param {(Strint|GulpLintHTMLOptions)} [options] - Configure rules for running LintHTML
* @param {Function} [reporter] - A custom reporter to format LintHTML errors
*
* @returns {stream} gulp file stream
*/
function gulpLintHTML(options) {
options = convertOptions(options);
return transform((file, enc, cb) => {
if (file.isNull()) {
return cb(null, file);
}
if (file.isStream()) {
return cb(new PluginError(PLUGIN_NAME, "gulp-linthtml doesn't support vinyl files with Stream contents."));
}
let linter = null;
try {
if (options.configFile) {
linter = linthtml.from_config_path(options.configFile);
} else if (options.rules) {
linter = linthtml.fromConfig(options);
} else {
linter = linthtml.create_linters_for_files([path.relative(process.cwd(), file.path)]);
linter = linter[0] ? linter[0].linter : null;
}
} catch (error) {
return cb(new PluginError(PLUGIN_NAME, `gulp-linthtml - ${print_error(error)}`));
}
return getLintReport(file, linter, /* options */ cb);
});
}
/**
*
* @param {*} file
* @param {*} cb
*/
function getLintReport(file, linter, /* options, */ cb) {
try {
const p = linter.lint(file.contents.toString());
p.catch(e => cb(new PluginError(PLUGIN_NAME, e)));
p.then(reports => {
file.linthtml = reports;
}).then(() => cb(null, file));
} catch (error) {
return cb(new PluginError(PLUGIN_NAME, error.message));
}
}
/**
* Wait until all files have been linted and format all results at once.
*
* @param {(String|Function)} [formatter=stylish] - The name or function for a LintHTML result formatter
* @returns {stream} gulp file stream
*/
gulpLintHTML.format = (/* formatter */) => {
const results = [];
results.errorsCount = 0;
results.warningsCount = 0;
// results.warningCount = 0;
return transform((file, enc, done) => {
if (file.linthtml && file.linthtml.length > 0) {
results.push({
fileName: file.path,
issues: file.linthtml
});
// results.push(file.linthtml);
// // collect total error/warning count
const errorsCount = file.linthtml.reduce((count, issue) => issue.severity === "error" ? count + 1 : count, 0);
const warningsCount = file.linthtml.reduce((count, issue) => issue.severity === "warning" ? count + 1 : count, 0);
results.errorsCount += errorsCount;
results.warningsCount += warningsCount;
}
done(null, file);
}, done => {
const { errorsCount, warningsCount } = results;
const problemsCount = errorsCount + warningsCount;
let output = "\n";
output = results.reduce((out, result) => {
return out + lintHTMLreporter(result);
}, output);
output += "\n";
if (results.errorCount !== 0) {
output += chalk` {red ✖ ${problemsCount} ${problemsCount > 1 ? "problems" : "problem"} (${errorsCount} ${errorsCount > 1 ? "errors" : "error"}, ${warningsCount} ${warningsCount > 1 ? "warnings" : "warning"})}`;
output += "\n";
fancy(output);
} else if (results.warningCount !== 0) {
output += chalk` {yellow ✖ ${problemsCount} ${problemsCount > 1 ? "problems" : "problem"} (0 error, ${warningsCount} ${warningsCount > 1 ? "warnings" : "warning"})}`;
output += "\n";
fancy(output);
}
return done();
});
};
/**
* Fail when an LintHTML error is found in LintHTML results.
*
* @returns {stream} gulp file stream
*/
gulpLintHTML.failOnError = () => {
return transform((file, enc, done) => {
const errors = file.linthtml.filter(_ => _.severity === "error");
if (errors.length > 0) {
const error = errors[0];
return done(new PluginError(PLUGIN_NAME, {
name: "LintHTMLError",
fileName: file.path,
message: linthtml.messages.renderIssue(error),
lineNumber: error.position.start.line
}));
}
return done(null, file);
});
};
module.exports = gulpLintHTML;