/
source-code-fixer.js
145 lines (118 loc) · 4.2 KB
/
source-code-fixer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* @fileoverview An object that caches and applies source code fixes.
* @author Nicholas C. Zakas
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const debug = require("debug")("eslint:text-fixer");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const BOM = "\uFEFF";
/**
* 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) {
const lineDiff = a.line - b.line;
if (lineDiff === 0) {
return a.column - b.column;
} else {
return lineDiff;
}
}
//------------------------------------------------------------------------------
// 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 {SourceCode} sourceCode The source code to apply the changes to.
* @param {Message[]} messages The array of messages reported by ESLint.
* @returns {Object} An object containing the fixed text and any unfixed messages.
*/
SourceCodeFixer.applyFixes = function(sourceCode, messages) {
debug("Applying fixes");
if (!sourceCode) {
debug("No source code to fix");
return {
fixed: false,
messages,
output: ""
};
}
// clone the array
const remainingMessages = [];
const problems = [];
let text = sourceCode.text;
let prefix = (sourceCode.hasBOM ? BOM : "");
messages.forEach(function(problem) {
if (problem.hasOwnProperty("fix")) {
problems.push(problem);
} else {
remainingMessages.push(problem);
}
});
if (problems.length) {
debug("Found fixes to apply");
const allFixes = problems.reduce((fixes, problem) => fixes.concat(problem.fix), []).sort((a, b) => a.range[1] - b.range[1] || a.range[0] - b.range[0]);
let lastFixPos = Infinity;
const fixesToApply = allFixes.reduceRight((fixes, fix) => {
// If a fix conflicts with a previous fix, don't apply it.
if (fix.range[1] < lastFixPos) {
fixes.add(fix);
lastFixPos = fix.range[0];
}
return fixes;
}, new Set());
problems.forEach(problem => {
const fixes = [].concat(problem.fix);
if (!fixes.every(fix => fixesToApply.has(fix))) {
// If not every fix for a given problem can be applied, don't apply any of the fixes.
fixes.filter(fix => fixesToApply.has(fix)).forEach(fix => fixesToApply.delete(fix));
remainingMessages.push(problem);
}
});
allFixes.filter(fix => fixesToApply.has(fix)).reverse().forEach(fix => {
let start = fix.range[0];
const end = fix.range[1];
let insertionText = fix.text;
if (start < 0) {
// Remove BOM.
prefix = "";
start = 0;
}
if (start === 0 && insertionText[0] === BOM) {
// Set BOM.
prefix = BOM;
insertionText = insertionText.slice(1);
}
text = text.slice(0, start) + insertionText + text.slice(end);
});
return {
fixed: true,
messages: remainingMessages.sort(compareMessagesByLocation),
output: prefix + text
};
} else {
debug("No fixes to apply");
return {
fixed: false,
messages,
output: prefix + text
};
}
};
module.exports = SourceCodeFixer;