File indexing completed on 2024-05-12 04:39:12
0001 /* 0002 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "clangproblem.h" 0008 #include <interfaces/idocumentcontroller.h> 0009 #include <interfaces/icore.h> 0010 0011 #include "util/clangtypes.h" 0012 #include "util/clangdebug.h" 0013 #include "util/clangutils.h" 0014 0015 #include <language/duchain/duchainlock.h> 0016 #include <language/codegen/documentchangeset.h> 0017 0018 #include <KLocalizedString> 0019 0020 using namespace KDevelop; 0021 0022 namespace { 0023 0024 IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) 0025 { 0026 switch (severity) { 0027 case CXDiagnostic_Fatal: 0028 case CXDiagnostic_Error: 0029 return IProblem::Error; 0030 case CXDiagnostic_Warning: 0031 if (optionName.startsWith(QLatin1String("-Wunused-"))) { 0032 return IProblem::Hint; 0033 } 0034 return IProblem::Warning; 0035 default: 0036 return IProblem::Hint; 0037 } 0038 } 0039 0040 /** 0041 * Clang diagnostic messages always start with a lowercase character 0042 * 0043 * @return Prettified version, starting with uppercase character 0044 */ 0045 inline QString prettyDiagnosticSpelling(const QString& str) 0046 { 0047 QString ret = str; 0048 if (ret.isEmpty()) { 0049 return {}; 0050 } 0051 ret[0] = ret[0].toUpper(); 0052 return ret; 0053 } 0054 0055 ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic, CXTranslationUnit unit) 0056 { 0057 ClangFixits fixits; 0058 auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); 0059 fixits.reserve(numFixits); 0060 for (uint i = 0; i < numFixits; ++i) { 0061 CXSourceRange range; 0062 const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); 0063 const auto original = ClangUtils::getRawContents(unit, range); 0064 fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString(), original}; 0065 } 0066 return fixits; 0067 } 0068 0069 } 0070 0071 QDebug operator<<(QDebug debug, const ClangFixit& fixit) 0072 { 0073 debug.nospace() << "ClangFixit[" 0074 << "replacementText=" << fixit.replacementText 0075 << ", range=" << fixit.range 0076 << ", description=" << fixit.description 0077 << ", currentText=" << fixit.currentText 0078 << "]"; 0079 return debug; 0080 } 0081 0082 ClangProblem::ClangProblem() = default; 0083 0084 ClangProblem::ClangProblem(const ClangProblem& other) 0085 : Problem(), 0086 m_fixits(other.m_fixits) 0087 { 0088 setSource(other.source()); 0089 setFinalLocation(other.finalLocation()); 0090 setFinalLocationMode(other.finalLocationMode()); 0091 setDescription(other.description()); 0092 setExplanation(other.explanation()); 0093 setSeverity(other.severity()); 0094 0095 auto diagnostics = other.diagnostics(); 0096 for (auto& diagnostic : diagnostics) { 0097 auto* clangDiagnostic = dynamic_cast<ClangProblem*>(diagnostic.data()); 0098 Q_ASSERT(clangDiagnostic); 0099 diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic)); 0100 } 0101 setDiagnostics(diagnostics); 0102 } 0103 0104 ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) 0105 { 0106 const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); 0107 0108 auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); 0109 setSeverity(severity); 0110 0111 QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); 0112 if (!diagnosticOption.isEmpty()) { 0113 description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']')); 0114 } 0115 setDescription(prettyDiagnosticSpelling(description)); 0116 0117 ClangLocation location(clang_getDiagnosticLocation(diagnostic)); 0118 CXFile diagnosticFile; 0119 clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); 0120 const ClangString fileName(clang_getFileName(diagnosticFile)); 0121 DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); 0122 const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); 0123 for (uint i = 0; i < numRanges; ++i) { 0124 auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); 0125 // Note that the second condition is a workaround for seemingly wrong ranges that 0126 // were observed sometimes. In principle, such a range should be valid. 0127 if(!range.isValid() || (range.isEmpty() && range.start().line() == 0 && range.start().column() == 0)){ 0128 continue; 0129 } 0130 0131 if (range.start() < docRange.start()) { 0132 docRange.setStart(range.start()); 0133 } 0134 if (range.end() > docRange.end()) { 0135 docRange.setEnd(range.end()); 0136 } 0137 } 0138 if (docRange.isEmpty()) { 0139 // try to find a bigger range for the given location by using the token at the given location 0140 CXFile file = nullptr; 0141 unsigned line = 0; 0142 unsigned column = 0; 0143 clang_getExpansionLocation(location, &file, &line, &column, nullptr); 0144 // just skip ahead some characters, hoping that it's sufficient to encompass 0145 // a token we can use for building the range 0146 auto nextLocation = clang_getLocation(unit, file, line, column + 100); 0147 auto rangeToTokenize = clang_getRange(location, nextLocation); 0148 const ClangTokens tokens(unit, rangeToTokenize); 0149 if (tokens.size()) { 0150 docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); 0151 } 0152 } 0153 0154 setFixits(fixitsForDiagnostic(diagnostic, unit)); 0155 setFinalLocation(docRange); 0156 setSource(IProblem::SemanticAnalysis); 0157 0158 QVector<IProblem::Ptr> diagnostics; 0159 auto childDiagnostics = clang_getChildDiagnostics(diagnostic); 0160 auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); 0161 diagnostics.reserve(numChildDiagnostics); 0162 for (uint j = 0; j < numChildDiagnostics; ++j) { 0163 auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); 0164 ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); 0165 diagnostics << ProblemPointer(problem.data()); 0166 } 0167 setDiagnostics(diagnostics); 0168 } 0169 0170 IAssistant::Ptr ClangProblem::solutionAssistant() const 0171 { 0172 if (allFixits().isEmpty()) { 0173 return {}; 0174 } 0175 0176 return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); 0177 } 0178 0179 ClangFixits ClangProblem::fixits() const 0180 { 0181 return m_fixits; 0182 } 0183 0184 void ClangProblem::setFixits(const ClangFixits& fixits) 0185 { 0186 m_fixits = fixits; 0187 } 0188 0189 ClangFixits ClangProblem::allFixits() const 0190 { 0191 ClangFixits result; 0192 result << m_fixits; 0193 0194 const auto& diagnostics = this->diagnostics(); 0195 for (const IProblem::Ptr& diagnostic : diagnostics) { 0196 const Ptr problem(dynamic_cast<ClangProblem*>(diagnostic.data())); 0197 Q_ASSERT(problem); 0198 result << problem->allFixits(); 0199 } 0200 return result; 0201 } 0202 0203 ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) 0204 : m_title(i18n("Fix-it Hints")) 0205 , m_fixits(fixits) 0206 { 0207 } 0208 0209 ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) 0210 : m_title(title) 0211 , m_fixits(fixits) 0212 { 0213 } 0214 0215 QString ClangFixitAssistant::title() const 0216 { 0217 return m_title; 0218 } 0219 0220 void ClangFixitAssistant::createActions() 0221 { 0222 KDevelop::IAssistant::createActions(); 0223 0224 for (const ClangFixit& fixit : qAsConst(m_fixits)) { 0225 addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); 0226 } 0227 } 0228 0229 ClangFixits ClangFixitAssistant::fixits() const 0230 { 0231 return m_fixits; 0232 } 0233 0234 ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) 0235 : m_fixit(fixit) 0236 { 0237 } 0238 0239 QString ClangFixitAction::description() const 0240 { 0241 if (!m_fixit.description.isEmpty()) 0242 return m_fixit.description; 0243 0244 const auto range = m_fixit.range; 0245 if (range.start() == range.end()) { 0246 return i18n("Insert \"%1\" at line: %2, column: %3", 0247 m_fixit.replacementText, range.start().line()+1, range.start().column()+1); 0248 } else if (range.start().line() == range.end().line()) { 0249 if (m_fixit.currentText.isEmpty()) { 0250 return i18n("Replace text at line: %1, column: %2 with: \"%3\"", 0251 range.start().line()+1, range.start().column()+1, m_fixit.replacementText); 0252 } else 0253 return i18n("Replace \"%1\" with: \"%2\"", 0254 m_fixit.currentText, m_fixit.replacementText); 0255 } else { 0256 return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", 0257 range.start().line()+1, range.start().column()+1, m_fixit.replacementText); 0258 } 0259 } 0260 0261 void ClangFixitAction::execute() 0262 { 0263 DocumentChangeSet changes; 0264 { 0265 DUChainReadLocker lock; 0266 0267 DocumentChange change(m_fixit.range.document, m_fixit.range, 0268 m_fixit.currentText, m_fixit.replacementText); 0269 change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); 0270 changes.addChange(change); 0271 } 0272 0273 changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); 0274 changes.applyAllChanges(); 0275 emit executed(this); 0276 } 0277 0278 #include "moc_clangproblem.cpp"