clangproblem.cpp 9.55 KB
Newer Older
Kevin Funk's avatar
Kevin Funk committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 * Copyright 2014 Kevin Funk <kfunk@kde.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License or (at your option) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "clangproblem.h"
23
24
#include <interfaces/idocumentcontroller.h>
#include <interfaces/icore.h>
Kevin Funk's avatar
Kevin Funk committed
25

26
#include "util/clangtypes.h"
27
#include "util/clangdebug.h"
Kevin Funk's avatar
Kevin Funk committed
28

Kevin Funk's avatar
Kevin Funk committed
29
30
31
#include <language/duchain/duchainlock.h>
#include <language/codegen/documentchangeset.h>

Milian Wolff's avatar
Milian Wolff committed
32
#include <KLocalizedString>
Kevin Funk's avatar
Kevin Funk committed
33
34
35

using namespace KDevelop;

Kevin Funk's avatar
Kevin Funk committed
36
37
namespace {

38
IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName)
Kevin Funk's avatar
Kevin Funk committed
39
40
41
42
{
    switch (severity) {
    case CXDiagnostic_Fatal:
    case CXDiagnostic_Error:
43
        return IProblem::Error;
Kevin Funk's avatar
Kevin Funk committed
44
    case CXDiagnostic_Warning:
45
46
47
        if (optionName.startsWith(QLatin1String("-Wunused-"))) {
            return IProblem::Hint;
        }
48
        return IProblem::Warning;
Kevin Funk's avatar
Kevin Funk committed
49
    default:
50
        return IProblem::Hint;
Kevin Funk's avatar
Kevin Funk committed
51
52
53
54
55
56
57
58
    }
}

/**
 * Clang diagnostic messages always start with a lowercase character
 *
 * @return Prettified version, starting with uppercase character
 */
59
inline QString prettyDiagnosticSpelling(const QString& str)
Kevin Funk's avatar
Kevin Funk committed
60
{
61
    QString ret = str;
Kevin Funk's avatar
Kevin Funk committed
62
63
64
65
66
67
68
69
70
71
72
    if (ret.isEmpty()) {
      return {};
    }
    ret[0] = ret[0].toUpper();
    return ret;
}

ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic)
{
    ClangFixits fixits;
    auto numFixits = clang_getDiagnosticNumFixIts(diagnostic);
73
    fixits.reserve(numFixits);
Kevin Funk's avatar
Kevin Funk committed
74
75
76
    for (uint i = 0; i < numFixits; ++i) {
        CXSourceRange range;
        const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString();
77

78
79
80
        const auto docRange = ClangRange(range).toDocumentRange();
        auto doc = KDevelop::ICore::self()->documentController()->documentForUrl(docRange.document.toUrl());
        const QString original = doc ? doc->text(docRange) : QString{};
81

82
        fixits << ClangFixit{replacementText, docRange, QString(), original};
Kevin Funk's avatar
Kevin Funk committed
83
84
85
86
87
88
    }
    return fixits;
}

}

Kevin Funk's avatar
Kevin Funk committed
89
90
91
92
93
94
95
96
97
98
QDebug operator<<(QDebug debug, const ClangFixit& fixit)
{
    debug.nospace() << "ClangFixit["
        << "replacementText=" << fixit.replacementText
        << ", range=" << fixit.range
        << ", description=" << fixit.description
        << "]";
    return debug;
}

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
ClangProblem::ClangProblem() = default;

ClangProblem::ClangProblem(const ClangProblem& other)
    : Problem(),
      m_fixits(other.m_fixits)
{
    setSource(other.source());
    setFinalLocation(other.finalLocation());
    setFinalLocationMode(other.finalLocationMode());
    setDescription(other.description());
    setExplanation(other.explanation());
    setSeverity(other.severity());

    auto diagnostics = other.diagnostics();
    for (auto& diagnostic : diagnostics) {
        auto* clangDiagnostic = dynamic_cast<ClangProblem*>(diagnostic.data());
        Q_ASSERT(clangDiagnostic);
        diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic));
    }
    setDiagnostics(diagnostics);
}

121
ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit)
Kevin Funk's avatar
Kevin Funk committed
122
{
123
124
125
    const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString();

    auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption);
Kevin Funk's avatar
Kevin Funk committed
126
127
    setSeverity(severity);

128
    QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString();
129
    if (!diagnosticOption.isEmpty()) {
130
        description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']'));
131
    }
Kevin Funk's avatar
Kevin Funk committed
132
133
134
135
136
137
    setDescription(prettyDiagnosticSpelling(description));

    ClangLocation location(clang_getDiagnosticLocation(diagnostic));
    CXFile diagnosticFile;
    clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr);
    const ClangString fileName(clang_getFileName(diagnosticFile));
138
    DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location));
Kevin Funk's avatar
Kevin Funk committed
139
140
    const uint numRanges = clang_getDiagnosticNumRanges(diagnostic);
    for (uint i = 0; i < numRanges; ++i) {
Kevin Funk's avatar
Kevin Funk committed
141
        auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange();
142
143
144
        // Note that the second condition is a workaround for seemingly wrong ranges that
        // were observed sometimes. In principle, such a range should be valid.
        if(!range.isValid() || (range.isEmpty() && range.start().line() == 0 && range.start().column() == 0)){
145
146
147
            continue;
        }

148
149
150
151
152
        if (range.start() < docRange.start()) {
            docRange.setStart(range.start());
        }
        if (range.end() > docRange.end()) {
            docRange.setEnd(range.end());
Kevin Funk's avatar
Kevin Funk committed
153
154
        }
    }
155
156
157
158
159
160
161
162
163
164
    if (docRange.isEmpty()) {
        // try to find a bigger range for the given location by using the token at the given location
        CXFile file = nullptr;
        unsigned line = 0;
        unsigned column = 0;
        clang_getExpansionLocation(location, &file, &line, &column, nullptr);
        // just skip ahead some characters, hoping that it's sufficient to encompass
        // a token we can use for building the range
        auto nextLocation = clang_getLocation(unit, file, line, column + 100);
        auto rangeToTokenize = clang_getRange(location, nextLocation);
165
166
167
        const ClangTokens tokens(unit, rangeToTokenize);
        if (tokens.size()) {
            docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange());
168
169
        }
    }
Kevin Funk's avatar
Kevin Funk committed
170
171
172

    setFixits(fixitsForDiagnostic(diagnostic));
    setFinalLocation(docRange);
173
    setSource(IProblem::SemanticAnalysis);
Kevin Funk's avatar
Kevin Funk committed
174

175
    QVector<IProblem::Ptr> diagnostics;
Kevin Funk's avatar
Kevin Funk committed
176
177
    auto childDiagnostics = clang_getChildDiagnostics(diagnostic);
    auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics);
178
    diagnostics.reserve(numChildDiagnostics);
Kevin Funk's avatar
Kevin Funk committed
179
180
    for (uint j = 0; j < numChildDiagnostics; ++j) {
        auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j);
181
        ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit));
Kevin Funk's avatar
Kevin Funk committed
182
        diagnostics << ProblemPointer(problem.data());
Kevin Funk's avatar
Kevin Funk committed
183
184
185
186
    }
    setDiagnostics(diagnostics);
}

Kevin Funk's avatar
Kevin Funk committed
187
IAssistant::Ptr ClangProblem::solutionAssistant() const
Kevin Funk's avatar
Kevin Funk committed
188
189
190
191
192
{
    if (allFixits().isEmpty()) {
        return {};
    }

Kevin Funk's avatar
Kevin Funk committed
193
    return IAssistant::Ptr(new ClangFixitAssistant(allFixits()));
Kevin Funk's avatar
Kevin Funk committed
194
195
196
197
198
199
200
201
202
203
204
205
}

ClangFixits ClangProblem::fixits() const
{
    return m_fixits;
}

void ClangProblem::setFixits(const ClangFixits& fixits)
{
    m_fixits = fixits;
}

206
ClangFixits ClangProblem::allFixits() const
Kevin Funk's avatar
Kevin Funk committed
207
{
208
209
    ClangFixits result;
    result << m_fixits;
Kevin Funk's avatar
Kevin Funk committed
210

211
212
    const auto& diagnostics = this->diagnostics();
    for (const IProblem::Ptr& diagnostic : diagnostics) {
Kevin Funk's avatar
Kevin Funk committed
213
        const Ptr problem(dynamic_cast<ClangProblem*>(diagnostic.data()));
Kevin Funk's avatar
Kevin Funk committed
214
        Q_ASSERT(problem);
215
        result << problem->allFixits();
Kevin Funk's avatar
Kevin Funk committed
216
217
218
219
    }
    return result;
}

220
ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits)
221
    : m_title(i18n("Fix-it Hints"))
222
    , m_fixits(fixits)
Kevin Funk's avatar
Kevin Funk committed
223
224
225
{
}

226
227
228
229
230
231
232
233
234
235
236
ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits)
    : m_title(title)
    , m_fixits(fixits)
{
}

QString ClangFixitAssistant::title() const
{
    return m_title;
}

Kevin Funk's avatar
Kevin Funk committed
237
238
239
240
void ClangFixitAssistant::createActions()
{
    KDevelop::IAssistant::createActions();

241
    for (const ClangFixit& fixit : qAsConst(m_fixits)) {
242
        addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit)));
Kevin Funk's avatar
Kevin Funk committed
243
244
245
    }
}

Kevin Funk's avatar
Kevin Funk committed
246
247
248
249
250
ClangFixits ClangFixitAssistant::fixits() const
{
    return m_fixits;
}

251
252
ClangFixitAction::ClangFixitAction(const ClangFixit& fixit)
    : m_fixit(fixit)
Kevin Funk's avatar
Kevin Funk committed
253
254
255
256
257
{
}

QString ClangFixitAction::description() const
{
258
259
260
261
    if (!m_fixit.description.isEmpty())
        return m_fixit.description;

    const auto range = m_fixit.range;
Kevin Funk's avatar
Kevin Funk committed
262
    if (range.start() == range.end()) {
263
        return i18n("Insert \"%1\" at line: %2, column: %3",
264
                    m_fixit.replacementText, range.start().line()+1, range.start().column()+1);
Kevin Funk's avatar
Kevin Funk committed
265
    } else if (range.start().line() == range.end().line()) {
266
267
        if (m_fixit.currentText.isEmpty()) {
            return i18n("Replace text at line: %1, column: %2 with: \"%3\"",
268
                    range.start().line()+1, range.start().column()+1, m_fixit.replacementText);
269
270
271
        } else
            return i18n("Replace \"%1\" with: \"%2\"",
                    m_fixit.currentText, m_fixit.replacementText);
272
273
    } else {
        return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"",
274
                    range.start().line()+1, range.start().column()+1, m_fixit.replacementText);
275
    }
Kevin Funk's avatar
Kevin Funk committed
276
277
278
279
280
281
282
283
}

void ClangFixitAction::execute()
{
    DocumentChangeSet changes;
    {
        DUChainReadLocker lock;

284
        DocumentChange change(m_fixit.range.document, m_fixit.range,
285
286
                    m_fixit.currentText, m_fixit.replacementText);
        change.m_ignoreOldText = !m_fixit.currentText.isEmpty();
Kevin Funk's avatar
Kevin Funk committed
287
288
289
290
291
292
293
        changes.addChange(change);
    }

    changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange);
    changes.applyAllChanges();
    emit executed(this);
}