| /** | |
|  * @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;
 |