UNPKG

4.69 kBJavaScriptView Raw
1/**
2 * @fileoverview An object that caches and applies source code fixes.
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const debug = require("debug")("eslint:source-code-fixer");
12
13//------------------------------------------------------------------------------
14// Helpers
15//------------------------------------------------------------------------------
16
17const BOM = "\uFEFF";
18
19/**
20 * Compares items in a messages array by range.
21 * @param {Message} a The first message.
22 * @param {Message} b The second message.
23 * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
24 * @private
25 */
26function compareMessagesByFixRange(a, b) {
27 return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1];
28}
29
30/**
31 * Compares items in a messages array by line and column.
32 * @param {Message} a The first message.
33 * @param {Message} b The second message.
34 * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
35 * @private
36 */
37function compareMessagesByLocation(a, b) {
38 return a.line - b.line || a.column - b.column;
39}
40
41//------------------------------------------------------------------------------
42// Public Interface
43//------------------------------------------------------------------------------
44
45/**
46 * Utility for apply fixes to source code.
47 * @constructor
48 */
49function SourceCodeFixer() {
50 Object.freeze(this);
51}
52
53/**
54 * Applies the fixes specified by the messages to the given text. Tries to be
55 * smart about the fixes and won't apply fixes over the same area in the text.
56 * @param {string} sourceText The text to apply the changes to.
57 * @param {Message[]} messages The array of messages reported by ESLint.
58 * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed
59 * @returns {Object} An object containing the fixed text and any unfixed messages.
60 */
61SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
62 debug("Applying fixes");
63
64 if (shouldFix === false) {
65 debug("shouldFix parameter was false, not attempting fixes");
66 return {
67 fixed: false,
68 messages,
69 output: sourceText
70 };
71 }
72
73 // clone the array
74 const remainingMessages = [],
75 fixes = [],
76 bom = sourceText.startsWith(BOM) ? BOM : "",
77 text = bom ? sourceText.slice(1) : sourceText;
78 let lastPos = Number.NEGATIVE_INFINITY,
79 output = bom;
80
81 /**
82 * Try to use the 'fix' from a problem.
83 * @param {Message} problem The message object to apply fixes from
84 * @returns {boolean} Whether fix was successfully applied
85 */
86 function attemptFix(problem) {
87 const fix = problem.fix;
88 const start = fix.range[0];
89 const end = fix.range[1];
90
91 // Remain it as a problem if it's overlapped or it's a negative range
92 if (lastPos >= start || start > end) {
93 remainingMessages.push(problem);
94 return false;
95 }
96
97 // Remove BOM.
98 if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
99 output = "";
100 }
101
102 // Make output to this fix.
103 output += text.slice(Math.max(0, lastPos), Math.max(0, start));
104 output += fix.text;
105 lastPos = end;
106 return true;
107 }
108
109 messages.forEach(problem => {
110 if (Object.prototype.hasOwnProperty.call(problem, "fix")) {
111 fixes.push(problem);
112 } else {
113 remainingMessages.push(problem);
114 }
115 });
116
117 if (fixes.length) {
118 debug("Found fixes to apply");
119 let fixesWereApplied = false;
120
121 for (const problem of fixes.sort(compareMessagesByFixRange)) {
122 if (typeof shouldFix !== "function" || shouldFix(problem)) {
123 attemptFix(problem);
124
125 /*
126 * The only time attemptFix will fail is if a previous fix was
127 * applied which conflicts with it. So we can mark this as true.
128 */
129 fixesWereApplied = true;
130 } else {
131 remainingMessages.push(problem);
132 }
133 }
134 output += text.slice(Math.max(0, lastPos));
135
136 return {
137 fixed: fixesWereApplied,
138 messages: remainingMessages.sort(compareMessagesByLocation),
139 output
140 };
141 }
142
143 debug("No fixes to apply");
144 return {
145 fixed: false,
146 messages,
147 output: bom + text
148 };
149
150};
151
152module.exports = SourceCodeFixer;