1 | /**
|
2 | * @fileoverview An object that caches and applies source code fixes.
|
3 | * @author Nicholas C. Zakas
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Requirements
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | const debug = require("debug")("eslint:text-fixer");
|
12 |
|
13 | //------------------------------------------------------------------------------
|
14 | // Helpers
|
15 | //------------------------------------------------------------------------------
|
16 |
|
17 | const BOM = "\uFEFF";
|
18 |
|
19 | /**
|
20 | * Compares items in a messages array by line and column.
|
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 | */
|
26 | function compareMessagesByLocation(a, b) {
|
27 | const lineDiff = a.line - b.line;
|
28 |
|
29 | if (lineDiff === 0) {
|
30 | return a.column - b.column;
|
31 | } else {
|
32 | return lineDiff;
|
33 | }
|
34 | }
|
35 |
|
36 | //------------------------------------------------------------------------------
|
37 | // Public Interface
|
38 | //------------------------------------------------------------------------------
|
39 |
|
40 | /**
|
41 | * Utility for apply fixes to source code.
|
42 | * @constructor
|
43 | */
|
44 | function SourceCodeFixer() {
|
45 | Object.freeze(this);
|
46 | }
|
47 |
|
48 | /**
|
49 | * Applies the fixes specified by the messages to the given text. Tries to be
|
50 | * smart about the fixes and won't apply fixes over the same area in the text.
|
51 | * @param {SourceCode} sourceCode The source code to apply the changes to.
|
52 | * @param {Message[]} messages The array of messages reported by ESLint.
|
53 | * @returns {Object} An object containing the fixed text and any unfixed messages.
|
54 | */
|
55 | SourceCodeFixer.applyFixes = function(sourceCode, messages) {
|
56 |
|
57 | debug("Applying fixes");
|
58 |
|
59 | if (!sourceCode) {
|
60 | debug("No source code to fix");
|
61 | return {
|
62 | fixed: false,
|
63 | messages,
|
64 | output: ""
|
65 | };
|
66 | }
|
67 |
|
68 | // clone the array
|
69 | const remainingMessages = [],
|
70 | fixes = [],
|
71 | text = sourceCode.text;
|
72 | let lastFixPos = text.length + 1,
|
73 | prefix = (sourceCode.hasBOM ? BOM : "");
|
74 |
|
75 | messages.forEach(function(problem) {
|
76 | if (problem.hasOwnProperty("fix")) {
|
77 | fixes.push(problem);
|
78 | } else {
|
79 | remainingMessages.push(problem);
|
80 | }
|
81 | });
|
82 |
|
83 | if (fixes.length) {
|
84 | debug("Found fixes to apply");
|
85 |
|
86 | // sort in reverse order of occurrence
|
87 | fixes.sort(function(a, b) {
|
88 | return b.fix.range[1] - a.fix.range[1] || b.fix.range[0] - a.fix.range[0];
|
89 | });
|
90 |
|
91 | // split into array of characters for easier manipulation
|
92 | const chars = text.split("");
|
93 |
|
94 | fixes.forEach(function(problem) {
|
95 | const fix = problem.fix;
|
96 | let start = fix.range[0];
|
97 | const end = fix.range[1];
|
98 | let insertionText = fix.text;
|
99 |
|
100 | if (end < lastFixPos) {
|
101 | if (start < 0) {
|
102 |
|
103 | // Remove BOM.
|
104 | prefix = "";
|
105 | start = 0;
|
106 | }
|
107 |
|
108 | if (start === 0 && insertionText[0] === BOM) {
|
109 |
|
110 | // Set BOM.
|
111 | prefix = BOM;
|
112 | insertionText = insertionText.slice(1);
|
113 | }
|
114 |
|
115 | chars.splice(start, end - start, insertionText);
|
116 | lastFixPos = start;
|
117 | } else {
|
118 | remainingMessages.push(problem);
|
119 | }
|
120 | });
|
121 |
|
122 | return {
|
123 | fixed: true,
|
124 | messages: remainingMessages.sort(compareMessagesByLocation),
|
125 | output: prefix + chars.join("")
|
126 | };
|
127 | } else {
|
128 | debug("No fixes to apply");
|
129 | return {
|
130 | fixed: false,
|
131 | messages,
|
132 | output: prefix + text
|
133 | };
|
134 | }
|
135 | };
|
136 |
|
137 | module.exports = SourceCodeFixer;
|