diff --git a/plugins/clang/duchain/clangproblem.cpp b/plugins/clang/duchain/clangproblem.cpp index 26bcb12498..ae764ef271 100644 --- a/plugins/clang/duchain/clangproblem.cpp +++ b/plugins/clang/duchain/clangproblem.cpp @@ -1,290 +1,291 @@ /* * Copyright 2014 Kevin Funk * * 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 . * */ #include "clangproblem.h" #include #include #include "util/clangtypes.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic, CXTranslationUnit unit) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); fixits.reserve(numFixits); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); QByteArray original = ClangUtils::getRawContents(unit, range); fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString(), QString::fromLocal8Bit(original)}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description + << ", currentText=" << fixit.currentText << "]"; return debug; } 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(diagnostic.data()); Q_ASSERT(clangDiagnostic); diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic)); } setDiagnostics(diagnostics); } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']')); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); // 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)){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } 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); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic, unit)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); diagnostics.reserve(numChildDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; const auto& diagnostics = this->diagnostics(); for (const IProblem::Ptr& diagnostic : diagnostics) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) : m_title(i18n("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : qAsConst(m_fixits)) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { if (m_fixit.currentText.isEmpty()) { return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } else return i18n("Replace \"%1\" with: \"%2\"", m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, m_fixit.currentText, m_fixit.replacementText); change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/plugins/clang/tests/test_problems.cpp b/plugins/clang/tests/test_problems.cpp index f5ba24afac..12693cd31d 100644 --- a/plugins/clang/tests/test_problems.cpp +++ b/plugins/clang/tests/test_problems.cpp @@ -1,506 +1,512 @@ /************************************************************************************* * Copyright (C) Kevin Funk * * * * 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) any later version. * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "test_problems.h" #include "../duchain/clangindex.h" #include "../duchain/clangproblem.h" #include "../duchain/parsesession.h" #include "../duchain/unknowndeclarationproblem.h" +#include "../duchain/clanghelpers.h" #include "../util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include Q_DECLARE_METATYPE(KDevelop::IProblem::Severity) using namespace KDevelop; namespace { const QString FileName = #ifdef Q_OS_WIN QStringLiteral("C:/tmp/stdin.cpp"); #else QStringLiteral("/tmp/stdin.cpp"); #endif QList parse(const QByteArray& code) { ClangIndex index; ClangParsingEnvironment environment; environment.setTranslationUnitUrl(IndexedString(FileName)); ParseSession session(ParseSessionData::Ptr(new ParseSessionData({UnsavedFile(FileName, {code})}, &index, environment))); return session.problemsForFile(session.mainFile()); } void compareFixitWithoutDescription(const ClangFixit& a, const ClangFixit& b) { QCOMPARE(a.replacementText, b.replacementText); QCOMPARE(a.range, b.range); QCOMPARE(a.currentText, b.currentText); } void compareFixitsWithoutDescription(const ClangFixits& a, const ClangFixits& b) { if (a.size() != b.size()) { qDebug() << "a:" << a; qDebug() << "b:" << b; } QCOMPARE(a.size(), b.size()); const int size = a.size(); for (int i = 0; i < size; ++i) { compareFixitWithoutDescription(a.at(i), b.at(i)); } } } QTEST_GUILESS_MAIN(TestProblems) void TestProblems::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); } void TestProblems::cleanupTestCase() { TestCore::shutdown(); } void TestProblems::testNoProblems() { const QByteArray code = "int main() {}"; auto problems = parse(code); QCOMPARE(problems.size(), 0); } void TestProblems::testBasicProblems() { // expected: // :1:13: error: expected ';' after class // class Foo {} // ^ // ; const QByteArray code = "class Foo {}"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 12)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 12)); } void TestProblems::testBasicRangeSupport() { // expected: // :1:17: warning: expression result unused [-Wunused-value] // int main() { (1 + 1); } // ~ ^ ~ const QByteArray code = "int main() { (1 + 1); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 14)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 19)); } void TestProblems::testChildDiagnostics() { // expected: // test.cpp:3:14: error: call to 'foo' is ambiguous // int main() { foo(0); } // ^~~ // test.cpp:1:6: note: candidate function // void foo(unsigned int); // ^ // test.cpp:2:6: note: candidate function // void foo(const char*); // ^ const QByteArray code = "void foo(unsigned int);\n" "void foo(const char*);\n" "int main() { foo(0); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(2, 13)); QCOMPARE(range.end(), KTextEditor::Cursor(2, 16)); QCOMPARE(problems[0]->diagnostics().size(), 2); IProblem::Ptr p1 = problems[0]->diagnostics()[0]; const ProblemPointer d1 = ProblemPointer(dynamic_cast(p1.data())); QCOMPARE(d1->url().str(), FileName); QCOMPARE(d1->rangeInCurrentRevision(), KTextEditor::Range(0, 5, 0, 8)); IProblem::Ptr p2 = problems[0]->diagnostics()[1]; const ProblemPointer d2 = ProblemPointer(dynamic_cast(p2.data())); QCOMPARE(d2->url().str(), FileName); QCOMPARE(d2->rangeInCurrentRevision(), KTextEditor::Range(1, 5, 1, 8)); } Q_DECLARE_METATYPE(QVector) /** * Provides a list of possible fixits: http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html */ void TestProblems::testFixits() { QFETCH(QString, code); QFETCH(int, problemsCount); QFETCH(QVector, fixits); auto problems = parse(code.toLatin1()); qDebug() << problems.last()->description(); QCOMPARE(problems.size(), problemsCount); const ClangProblem* p1 = dynamic_cast(problems[0].data()); QVERIFY(p1); auto* a1 = qobject_cast(p1->solutionAssistant().data()); QVERIFY(a1); QCOMPARE(p1->allFixits(), fixits); } void TestProblems::testFixits_data() { QTest::addColumn("code"); // input QTest::addColumn("problemsCount"); QTest::addColumn>("fixits"); // expected: // test -Wextra-tokens // /home/krf/test.cpp:2:8: warning: extra tokens at end of #endif directive [-Wextra-tokens] // #endif FOO // ^ // // - QTest::newRow("extra-tokens test") + QTest::newRow("extra-tokens") << "#ifdef FOO\n#endif FOO\n" << 1 << QVector{ ClangFixit{"//", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 7, 1, 7)), QString()} }; + const auto clangVersion9OrHigher = QVersionNumber::fromString(ClangHelpers::clangVersion()) >= QVersionNumber(9, 0, 0); // expected: // test.cpp:1:19: warning: empty parentheses interpreted as a function declaration [-Wvexing-parse] // int a(); // ^~ // test.cpp:1:19: note: replace parentheses with an initializer to declare a variable // int a(); // ^~ // = 0 - QTest::newRow("vexing-parse test") + QTest::newRow("vexing-parse") << "int main() { int a(); }\n" << 1 - << QVector{ ClangFixit{" = 0", DocumentRange(IndexedString(FileName), KTextEditor::Range(0, 18, 0, 20)), QString()} }; + << QVector{ ClangFixit{" = 0", DocumentRange(IndexedString(FileName), + KTextEditor::Range(0, 18, 0, 20)), QString(), + clangVersion9OrHigher ? QStringLiteral("()") : QString()} }; // expected: // test.cpp:2:21: error: no member named 'someVariablf' in 'C'; did you mean 'someVariable'? // int main() { C c; c.someVariablf = 1; } // ^~~~~~~~~~~~ // someVariable - QTest::newRow("spell-check test") + QTest::newRow("spell-check") << "class C{ int someVariable; };\n" "int main() { C c; c.someVariablf = 1; }\n" << 1 - << QVector{ ClangFixit{"someVariable", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 20, 1, 32)), QString()} }; + << QVector{ ClangFixit{"someVariable", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 20, 1, 32)), + QString(), clangVersion9OrHigher ? QStringLiteral("someVariablf") : QString()} }; } struct Replacement { QString string; QString replacement; }; using Replacements = QVector; ClangFixits resolveFilenames(const ClangFixits& fixits, const Replacements& replacements) { ClangFixits ret; for (const auto& fixit : fixits) { ClangFixit copy = fixit; for (const auto& replacement : replacements) { copy.replacementText.replace(replacement.string, replacement.replacement); copy.range.document = IndexedString(copy.range.document.str().replace(replacement.string, replacement.replacement)); } ret << copy; } return ret; } void TestProblems::testMissingInclude() { QFETCH(QString, includeFileContent); QFETCH(QString, workingFileContent); QFETCH(QString, dummyFileName); QFETCH(QVector, fixits); TestFile include(includeFileContent, QStringLiteral("h")); include.parse(TopDUContext::AllDeclarationsAndContexts); QScopedPointer dummyFile; if (!dummyFileName.isEmpty()) { dummyFile.reset(new QTemporaryFile(QDir::tempPath() + dummyFileName)); QVERIFY(dummyFile->open()); workingFileContent.replace(QLatin1String("dummyInclude"), dummyFile->fileName()); } TestFile workingFile(workingFileContent, QStringLiteral("cpp")); workingFile.parse(TopDUContext::AllDeclarationsAndContexts); QCOMPARE(include.url().toUrl().adjusted(QUrl::RemoveFilename), workingFile.url().toUrl().adjusted(QUrl::RemoveFilename)); QVERIFY(include.waitForParsed()); QVERIFY(workingFile.waitForParsed()); DUChainReadLocker lock; QVERIFY(include.topContext()); TopDUContext* includeTop = DUChainUtils::contentContextFromProxyContext(include.topContext().data()); QVERIFY(includeTop); QVERIFY(workingFile.topContext()); TopDUContext* top = DUChainUtils::contentContextFromProxyContext(workingFile.topContext()); QVERIFY(top); QCOMPARE(top->problems().size(), 1); auto problem = dynamic_cast(top->problems().first().data()); auto assistant = problem->solutionAssistant(); auto clangFixitAssistant = qobject_cast(assistant.data()); QVERIFY(clangFixitAssistant); auto resolvedFixits = resolveFilenames(fixits, { {"includeFile.h", include.url().toUrl().fileName()}, {"workingFile.h", workingFile.url().toUrl().fileName()} }); compareFixitsWithoutDescription(clangFixitAssistant->fixits(), resolvedFixits); } void TestProblems::testMissingInclude_data() { QTest::addColumn("includeFileContent"); QTest::addColumn("workingFileContent"); QTest::addColumn("dummyFileName"); QTest::addColumn>("fixits"); QTest::newRow("basic") << "class A {};\n" << "int main() { A a; }\n" << QString() << QVector{ ClangFixit{"class A;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; // cf. bug 375274 QTest::newRow("ignore-moc-at-end") << "class Foo {};\n" << "#include \nint main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/moc_fooXXXXXX.cpp" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(1, 0, 1, 0)), QString()} }; QTest::newRow("ignore-moc-at-end2") << "class Foo {};\n" << "int main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/fooXXXXXX.moc" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; } struct ExpectedTodo { QString description; KTextEditor::Cursor start; KTextEditor::Cursor end; }; using ExpectedTodos = QVector; Q_DECLARE_METATYPE(ExpectedTodos) void TestProblems::testTodoProblems() { QFETCH(QString, code); QFETCH(ExpectedTodos, expectedTodos); TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto problems = top->problems(); QCOMPARE(problems.size(), expectedTodos.size()); for (int i = 0; i < problems.size(); ++i) { auto problem = problems[i]; auto expectedTodo = expectedTodos[i]; QCOMPARE(problem->description(), expectedTodo.description); QCOMPARE(problem->finalLocation().start(), expectedTodo.start); QCOMPARE(problem->finalLocation().end(), expectedTodo.end); } } void TestProblems::testTodoProblems_data() { QTest::addColumn("code"); QTest::addColumn("expectedTodos"); // we have two problems here: // - we cannot search for comments without declarations, // that means: we can only search inside doxygen-style comments // possible fix: -fparse-all-comments -- however: libclang API is lacking here again. // Can only search through comments attached to a valid entity in the AST // - we cannot detect the correct location of the comment yet // see more comments inside TodoExtractor QTest::newRow("simple1") << "/** TODO: Something */\n/** notodo */\n" << ExpectedTodos{{"TODO: Something", {0, 4}, {0, 19}}}; QTest::newRow("simple2") << "/// FIXME: Something\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("mixed-content") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multi-line1") << "/**\n* foo\n*\n* FIXME: Something\n*/\n" << ExpectedTodos{{"FIXME: Something", {3, 2}, {3, 18}}}; QTest::newRow("multi-line2") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multiple-todos-line2") << "/**\n* FIXME: one\n*foo bar\n* FIXME: two */\n" << ExpectedTodos{ {"FIXME: one", {1, 2}, {1, 12}}, {"FIXME: two", {3, 2}, {3, 12}} }; QTest::newRow("todo-later-in-the-document") << "///foo\n\n///FIXME: bar\n" << ExpectedTodos{{"FIXME: bar", {2, 3}, {2, 13}}}; QTest::newRow("non-ascii-todo") << "/* TODO: 例えば */" << ExpectedTodos{{"TODO: 例えば", {0, 3}, {0, 12}}}; } void TestProblems::testProblemsForIncludedFiles() { TestFile header(QStringLiteral("#pragma once\n//TODO: header\n"), QStringLiteral("h")); TestFile file("#include \"" + header.url().str() + "\"\n//TODO: source\n", QStringLiteral("cpp")); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST | TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto context = DUChain::self()->chainForDocument(file.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: source")); QCOMPARE(context->problems()[0]->finalLocation().document, file.url()); context = DUChain::self()->chainForDocument(header.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: header")); QCOMPARE(context->problems()[0]->finalLocation().document, header.url()); } } using RangeList = QVector; void TestProblems::testRanges_data() { QTest::addColumn("code"); QTest::addColumn("ranges"); { // expected: // test.cpp:4:1: error: C++ requires a type specifier for all declarations // operator[](int){return string;} // ^ // // test.cpp:4:24: error: 'string' does not refer to a value // operator[](int){return string;} // ^ const QByteArray code = "struct string{};\nclass Test{\npublic:\noperator[](int){return string;}\n};"; QTest::newRow("operator") << code << RangeList{{3, 0, 3, 8}, {3, 23, 3, 29}}; } { const QByteArray code = "#include \"/some/file/that/does/not/exist.h\"\nint main() { return 0; }"; QTest::newRow("badInclude") << code << RangeList{{0, 9, 0, 43}}; } { const QByteArray code = "int main() const\n{ return 0; }"; QTest::newRow("badConst") << code << RangeList{{0, 11, 0, 16}}; } } void TestProblems::testRanges() { QFETCH(QByteArray, code); QFETCH(RangeList, ranges); const auto problems = parse(code); RangeList actualRanges; for (auto problem : problems) { actualRanges << problem->rangeInCurrentRevision(); } qDebug() << actualRanges << ranges; QCOMPARE(actualRanges, ranges); } void TestProblems::testSeverity() { QFETCH(QByteArray, code); QFETCH(IProblem::Severity, severity); const auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems.at(0)->severity(), severity); } void TestProblems::testSeverity_data() { QTest::addColumn("code"); QTest::addColumn("severity"); QTest::newRow("error") << QByteArray("class foo {}") << IProblem::Error; QTest::newRow("warning") << QByteArray("int main() { int foo = 1 / 0; return foo; }") << IProblem::Warning; QTest::newRow("hint-unused-variable") << QByteArray("int main() { int foo = 0; return 0; }") << IProblem::Hint; QTest::newRow("hint-unused-parameter") << QByteArray("int main(int argc, char**) { return 0; }") << IProblem::Hint; }