| /** |
| * @fileoverview An object that caches and applies source code fixes. |
| * @author Nicholas C. Zakas |
| */ |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const debug = require("debug")("eslint:source-code-fixer"); |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| const BOM = "\uFEFF"; |
| |
| /** |
| * Compares items in a messages array by range. |
| * @param {Message} a The first message. |
| * @param {Message} b The second message. |
| * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. |
| * @private |
| */ |
| function compareMessagesByFixRange(a, b) { |
| return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1]; |
| } |
| |
| /** |
| * Compares items in a messages array by line and column. |
| * @param {Message} a The first message. |
| * @param {Message} b The second message. |
| * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. |
| * @private |
| */ |
| function compareMessagesByLocation(a, b) { |
| return a.line - b.line || a.column - b.column; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Public Interface |
| //------------------------------------------------------------------------------ |
| |
| /** |
| * Utility for apply fixes to source code. |
| * @constructor |
| */ |
| function SourceCodeFixer() { |
| Object.freeze(this); |
| } |
| |
| /** |
| * Applies the fixes specified by the messages to the given text. Tries to be |
| * smart about the fixes and won't apply fixes over the same area in the text. |
| * @param {string} sourceText The text to apply the changes to. |
| * @param {Message[]} messages The array of messages reported by ESLint. |
| * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed |
| * @returns {Object} An object containing the fixed text and any unfixed messages. |
| */ |
| SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { |
| debug("Applying fixes"); |
| |
| if (shouldFix === false) { |
| debug("shouldFix parameter was false, not attempting fixes"); |
| return { |
| fixed: false, |
| messages, |
| output: sourceText |
| }; |
| } |
| |
| // clone the array |
| const remainingMessages = [], |
| fixes = [], |
| bom = sourceText.startsWith(BOM) ? BOM : "", |
| text = bom ? sourceText.slice(1) : sourceText; |
| let lastPos = Number.NEGATIVE_INFINITY, |
| output = bom; |
| |
| /** |
| * Try to use the 'fix' from a problem. |
| * @param {Message} problem The message object to apply fixes from |
| * @returns {boolean} Whether fix was successfully applied |
| */ |
| function attemptFix(problem) { |
| const fix = problem.fix; |
| const start = fix.range[0]; |
| const end = fix.range[1]; |
| |
| // Remain it as a problem if it's overlapped or it's a negative range |
| if (lastPos >= start || start > end) { |
| remainingMessages.push(problem); |
| return false; |
| } |
| |
| // Remove BOM. |
| if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) { |
| output = ""; |
| } |
| |
| // Make output to this fix. |
| output += text.slice(Math.max(0, lastPos), Math.max(0, start)); |
| output += fix.text; |
| lastPos = end; |
| return true; |
| } |
| |
| messages.forEach(problem => { |
| if (Object.prototype.hasOwnProperty.call(problem, "fix")) { |
| fixes.push(problem); |
| } else { |
| remainingMessages.push(problem); |
| } |
| }); |
| |
| if (fixes.length) { |
| debug("Found fixes to apply"); |
| let fixesWereApplied = false; |
| |
| for (const problem of fixes.sort(compareMessagesByFixRange)) { |
| if (typeof shouldFix !== "function" || shouldFix(problem)) { |
| attemptFix(problem); |
| |
| /* |
| * The only time attemptFix will fail is if a previous fix was |
| * applied which conflicts with it. So we can mark this as true. |
| */ |
| fixesWereApplied = true; |
| } else { |
| remainingMessages.push(problem); |
| } |
| } |
| output += text.slice(Math.max(0, lastPos)); |
| |
| return { |
| fixed: fixesWereApplied, |
| messages: remainingMessages.sort(compareMessagesByLocation), |
| output |
| }; |
| } |
| |
| debug("No fixes to apply"); |
| return { |
| fixed: false, |
| messages, |
| output: bom + text |
| }; |
| |
| }; |
| |
| module.exports = SourceCodeFixer; |