affiance
Version:
A configurable and extendable Git hook manager for node projects
210 lines (170 loc) • 6.54 kB
JavaScript
'use strict';
const ERRORS_MODIFIED_HEADER = 'Errors on modified lines:';
const WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:';
const ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:";
const WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:";
const ERRORS_GENERIC_HEADER = 'Errors:';
const WARNINGS_GENERIC_HEADER = 'Warnings:';
class HookMessageProcessor {
/**
*
* @param {HookBase} hook - the hook to process messages for
* @param {String} unmodifiedLinesSetting - the "problemOnUnmodifiedLines" setting for this hook
* @param {String} ignoreMessagePatternSetting - the "ignoreMessagePattern" setting for this hook.
*/
constructor(hook, unmodifiedLinesSetting, ignoreMessagePatternSetting) {
this.hook = hook;
this.unmodifiedLinesSetting = unmodifiedLinesSetting;
this.ignoreMessagePattern = ignoreMessagePatternSetting ? RegExp(ignoreMessagePatternSetting) : null;
}
hookResult(messages) {
// Remove any messages we are configured to ignore
messages = this.removeIgnoredMessages(messages);
// Gather a basic run result from the unfiltered messages.
let runResult = this.basicStatusAndOutput(messages);
// Nothing special to do if everything passed.
if (runResult.status === 'pass') { return runResult; }
// Return result as is if there is no concept of modified lines
if (!this.canFilterMessages()) { return runResult; }
// Filter the status and output based on the settings for the hook
return this.filterMessagesAndUpdateStatus(messages, runResult.status);
}
basicStatusAndOutput(messages) {
let result = {
status: null,
output: ''
};
messages.forEach((message) => {
if(message.isError()) {
result.status = 'fail';
} else if(result.status !== 'fail' && message.isWarning()) {
result.status = 'warn';
}
if(message.content) {
result.output += message.content + '\n';
}
});
if (!result.status) {
result.status = 'pass';
}
return result;
}
hasMessageFiltering() {
return !!(this.ignoreMessagePattern || this.unmodifiedLinesSetting === 'ignore');
}
canFilterMessages() {
return !!(
this.ignoreMessagePattern ||
typeof this.hook.context.modifiedLinesInFile === 'function'
);
}
filterMessagesAndUpdateStatus(messages, status) {
let result = {
output: '',
status: status
};
let genericMessages = [];
let messagesWithLine = [];
let messagesOnModifiedLines = [];
let messagesOnUnmodifiedLines = [];
messages.forEach((message) => {
if(message.line) {
messagesWithLine.push(message);
} else {
genericMessages.push(message);
}
});
result.output = this.outputMessages(genericMessages, ERRORS_GENERIC_HEADER, WARNINGS_GENERIC_HEADER);
messagesWithLine.forEach((message) => {
if(this.isMessageOnModifiedLine(message)) {
messagesOnModifiedLines.push(message);
} else {
messagesOnUnmodifiedLines.push(message);
}
});
result.output += this.outputMessages(messagesOnModifiedLines, ERRORS_MODIFIED_HEADER, WARNINGS_MODIFIED_HEADER);
result.output += this.outputMessages(messagesOnUnmodifiedLines, ERRORS_UNMODIFIED_HEADER, WARNINGS_UNMODIFIED_HEADER);
result.status = this.transformModifiedLineStatus(status, genericMessages.concat(messagesOnModifiedLines));
return result;
}
transformModifiedLineStatus(status, messagesOnModifiedLines) {
// `report` indicates user wants the original status
if (this.unmodifiedLinesSetting === 'report') { return status; }
let errorMessages = [];
let warningMessages = [];
messagesOnModifiedLines.forEach((message) => {
if (message.isError()) { return errorMessages.push(message); }
if (message.isWarning()) { return warningMessages.push(message); }
});
if (this.canUpgradeToWarning(status, errorMessages)) {
status = 'warn';
}
if (this.canUpgradeToPassing(status, warningMessages)) {
status = 'pass';
}
return status;
}
canUpgradeToWarning(status, errorMessages) {
return (status === 'fail' && !errorMessages.length);
}
canUpgradeToPassing(status, warningMessages) {
return (
status === 'warn' &&
this.unmodifiedLinesSetting === 'ignore' &&
!warningMessages.length
);
}
removeIgnoredMessages(messages) {
if(!this.hasMessageFiltering()) { return messages; }
return messages.filter((message) => {
// If we are meant to ignore unmodified lines, return false if this message is about
// an unmodified line.
if (this.unmodifiedLinesSetting === 'ignore' && !this.isMessageOnModifiedLine(message)) {
return false;
}
// Return false if the message matches a configured ignore pattern
if (this.matchesIgnorePattern(message)) {
return false;
}
// Unless actively ignored, return true to include the message.
return true;
});
}
isMessageOnModifiedLine(message) {
// Message without line number assumed to apply to entire file
if (!message.line) { return true; }
let modifiedLinesInFile = this.hook.context.modifiedLinesInFile(message.file);
return (modifiedLinesInFile.indexOf('' + message.line) > -1);
}
matchesIgnorePattern(message) {
if (!this.ignoreMessagePattern) { return false; }
return this.ignoreMessagePattern.test(message.content || '');
}
outputMessages(messages, errorHeading, warningHeading) {
let output = '';
let errors = [];
let warnings = [];
messages.forEach((message) => {
if (message.isError()) {
errors.push(message.content);
}
if (message.isWarning()) {
warnings.push(message.content);
}
});
if (errors.length) {
output += `${errorHeading}\n${errors.join('\n')}\n`;
}
if (warnings.length) {
output += `${warningHeading}\n${warnings.join('\n')}\n`;
}
return output;
}
}
HookMessageProcessor.ERRORS_MODIFIED_HEADER = ERRORS_MODIFIED_HEADER;
HookMessageProcessor.WARNINGS_MODIFIED_HEADER = WARNINGS_MODIFIED_HEADER;
HookMessageProcessor.ERRORS_UNMODIFIED_HEADER = ERRORS_UNMODIFIED_HEADER;
HookMessageProcessor.WARNINGS_UNMODIFIED_HEADER = WARNINGS_UNMODIFIED_HEADER;
HookMessageProcessor.ERRORS_GENERIC_HEADER = ERRORS_GENERIC_HEADER;
HookMessageProcessor.WARNINGS_GENERIC_HEADER = WARNINGS_GENERIC_HEADER;
module.exports = HookMessageProcessor;