summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/util/source-code-fixer.js
blob: 064a0a1f902443720b3094b99d859ce95c0f1d46 (plain)
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
/**
 * @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 = [],
        fixes = [],
        text = sourceCode.text;
    let lastFixPos = text.length + 1,
        prefix = (sourceCode.hasBOM ? BOM : "");

    messages.forEach(function(problem) {
        if (problem.hasOwnProperty("fix")) {
            fixes.push(problem);
        } else {
            remainingMessages.push(problem);
        }
    });

    if (fixes.length) {
        debug("Found fixes to apply");

        // sort in reverse order of occurrence
        fixes.sort(function(a, b) {
            return b.fix.range[1] - a.fix.range[1] || b.fix.range[0] - a.fix.range[0];
        });

        // split into array of characters for easier manipulation
        const chars = text.split("");

        fixes.forEach(function(problem) {
            const fix = problem.fix;
            let start = fix.range[0];
            const end = fix.range[1];
            let insertionText = fix.text;

            if (end < lastFixPos) {
                if (start < 0) {

                    // Remove BOM.
                    prefix = "";
                    start = 0;
                }

                if (start === 0 && insertionText[0] === BOM) {

                    // Set BOM.
                    prefix = BOM;
                    insertionText = insertionText.slice(1);
                }

                chars.splice(start, end - start, insertionText);
                lastFixPos = start;
            } else {
                remainingMessages.push(problem);
            }
        });

        return {
            fixed: true,
            messages: remainingMessages.sort(compareMessagesByLocation),
            output: prefix + chars.join("")
        };
    } else {
        debug("No fixes to apply");
        return {
            fixed: false,
            messages,
            output: prefix + text
        };
    }
};

module.exports = SourceCodeFixer;