File indexing completed on 2024-05-12 04:39:15
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 "test_clangutils.h" 0008 0009 #include "../util/clangutils.h" 0010 #include "../util/clangtypes.h" 0011 #include "../util/clangdebug.h" 0012 0013 #include <language/editor/documentrange.h> 0014 #include <tests/testcore.h> 0015 #include <tests/testhelpers.h> 0016 #include <tests/autotestshell.h> 0017 0018 #include <clang-c/Index.h> 0019 0020 #include <QTemporaryFile> 0021 0022 #include <QDebug> 0023 #include <QTest> 0024 0025 #include <memory> 0026 0027 QTEST_MAIN(TestClangUtils) 0028 0029 using namespace KDevelop; 0030 0031 namespace { 0032 0033 struct CursorCollectorVisitor 0034 { 0035 QVector<CXCursor> cursors; 0036 0037 static CXChildVisitResult visit(CXCursor cursor, CXCursor /*parent*/, CXClientData d) 0038 { 0039 auto* thisPtr = static_cast<CursorCollectorVisitor*>(d); 0040 thisPtr->cursors << cursor; 0041 0042 return CXChildVisit_Recurse; 0043 }; 0044 }; 0045 0046 CXSourceRange toCXRange(CXTranslationUnit unit, const DocumentRange& range) 0047 { 0048 auto file = clang_getFile(unit, QString::fromUtf8(range.document.c_str(), range.document.length()).toUtf8()); 0049 auto begin = clang_getLocation(unit, file, range.start().line()+1, range.start().column()+1); 0050 auto end = clang_getLocation(unit, file, range.end().line()+1, range.end().column()+1); 0051 return clang_getRange(begin, end); 0052 } 0053 0054 struct DisposeTranslationUnit { 0055 void operator()(CXTranslationUnit unit) { clang_disposeTranslationUnit(unit); } 0056 }; 0057 using TranslationUnit = std::unique_ptr<CXTranslationUnitImpl, DisposeTranslationUnit>; 0058 0059 TranslationUnit parse(const QByteArray& code, QString* fileName = nullptr) 0060 { 0061 QTemporaryFile tempFile; 0062 QVERIFY_RETURN(tempFile.open(), {}); 0063 tempFile.write(code); 0064 tempFile.flush(); 0065 0066 if (fileName) { 0067 *fileName = tempFile.fileName(); 0068 } 0069 0070 std::unique_ptr<void, void(*)(CXIndex)> index(clang_createIndex(1, 1), clang_disposeIndex); 0071 const QVector<const char*> args = {"-std=c++11", "-xc++", "-Wall", "-nostdinc", "-nostdinc++"}; 0072 auto unit = TranslationUnit(clang_parseTranslationUnit(index.get(), qPrintable(tempFile.fileName()), args.data(), 0073 args.size(), nullptr, 0, CXTranslationUnit_None)); 0074 QVERIFY_RETURN(unit, {}); 0075 return unit; 0076 } 0077 0078 TranslationUnit runVisitor(const QByteArray& code, CXCursorVisitor visitor, CXClientData data) 0079 { 0080 auto unit = parse(code); 0081 const auto startCursor = clang_getTranslationUnitCursor(unit.get()); 0082 QVERIFY_RETURN(!clang_Cursor_isNull(startCursor), {}); 0083 QVERIFY_RETURN(clang_visitChildren(startCursor, visitor, data) == 0, {}); 0084 return unit; 0085 } 0086 0087 template <typename Visitor> 0088 TranslationUnit runVisitor(const QByteArray& code, Visitor& visitor) 0089 { 0090 return runVisitor(code, &Visitor::visit, &visitor); 0091 } 0092 } 0093 0094 void TestClangUtils::initTestCase() 0095 { 0096 QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); 0097 AutoTestShell::init(); 0098 TestCore::initialize(Core::NoUi); 0099 } 0100 0101 void TestClangUtils::cleanupTestCase() 0102 { 0103 TestCore::shutdown(); 0104 } 0105 0106 void TestClangUtils::testGetScope() 0107 { 0108 QFETCH(QByteArray, code); 0109 QFETCH(int, cursorIndex); 0110 QFETCH(QString, expectedScope); 0111 0112 CursorCollectorVisitor visitor; 0113 auto unit = runVisitor(code, visitor); 0114 QVERIFY(cursorIndex < visitor.cursors.size()); 0115 const auto cursor = visitor.cursors[cursorIndex]; 0116 clangDebug() << "Found decl:" << clang_getCursorSpelling(cursor) << "| range:" << ClangRange(clang_getCursorExtent(cursor)).toRange(); 0117 const QString scope = ClangUtils::getScope(cursor); 0118 QCOMPARE(scope, expectedScope); 0119 0120 QVERIFY(!visitor.cursors.isEmpty()); 0121 } 0122 0123 void TestClangUtils::testGetScope_data() 0124 { 0125 QTest::addColumn<QByteArray>("code"); 0126 QTest::addColumn<int>("cursorIndex"); 0127 QTest::addColumn<QString>("expectedScope"); 0128 0129 QTest::newRow("func-decl-inside-ns") 0130 << QByteArray("namespace ns1 { void foo(); } ns1::foo() {}") 0131 << 2 0132 << "ns1"; 0133 QTest::newRow("fwd-decl-inside-ns") 0134 << QByteArray("namespace ns1 { struct foo; } struct ns1::foo {};") 0135 << 2 0136 << "ns1"; 0137 QTest::newRow("fwd-decl-inside-ns-inside-ns") 0138 << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};") 0139 << 3 0140 << "ns1::ns2"; 0141 QTest::newRow("fwd-decl-inside-ns-inside-ns") 0142 << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};") 0143 << 3 0144 << "ns1::ns2"; 0145 QTest::newRow("using-namespace") 0146 << QByteArray("namespace ns1 { struct klass { struct foo; }; } using namespace ns1; struct klass::foo {};") 0147 << 5 0148 << "ns1::klass"; 0149 QTest::newRow("fwd-decl-def-inside-namespace") 0150 << QByteArray("namespace ns1 { struct klass { struct foo; }; } namespace ns1 { struct klass::foo {}; }") 0151 << 4 0152 << "klass"; 0153 } 0154 0155 void TestClangUtils::testTemplateArgumentTypes() 0156 { 0157 QFETCH(QByteArray, code); 0158 QFETCH(int, cursorIndex); 0159 QFETCH(QStringList, expectedTypes); 0160 0161 CursorCollectorVisitor visitor; 0162 auto unit = runVisitor(code, visitor); 0163 QVERIFY(cursorIndex < visitor.cursors.size()); 0164 const auto cursor = visitor.cursors[cursorIndex]; 0165 const QStringList types = ClangUtils::templateArgumentTypes(cursor); 0166 0167 QCOMPARE(types, expectedTypes); 0168 } 0169 0170 void TestClangUtils::testTemplateArgumentTypes_data() 0171 { 0172 QTest::addColumn<QByteArray>("code"); 0173 QTest::addColumn<int>("cursorIndex"); 0174 QTest::addColumn<QStringList>("expectedTypes"); 0175 0176 QTest::newRow("template-spec") 0177 << QByteArray( 0178 "template<typename T>\n" 0179 "struct is_void\n" 0180 "{ };\n" 0181 "template<>\n" 0182 "struct is_void<void>\n" 0183 "{ };\n") 0184 << 2 0185 << QStringList({"void"}); 0186 0187 // Partially specialize one 0188 QTest::newRow("template-partial-spec") 0189 << QByteArray( 0190 "template<typename T, unsigned N>\n" 0191 "struct is_void\n" 0192 "{ };\n" 0193 "template<typename T>\n" 0194 "struct is_void<T, 3>\n" 0195 "{ };\n") 0196 << 3 0197 << QStringList({"type-parameter-0-0", ""}); 0198 0199 // Fully specialize 3 args 0200 QTest::newRow("template-full-spec") 0201 << QByteArray( 0202 "template<typename T, typename R, unsigned N>\n" 0203 "struct is_void\n" 0204 "{ };\n" 0205 "template<>\n" 0206 "struct is_void<unsigned, void, 5>\n" 0207 "{ };\n") 0208 << 4 0209 << QStringList({"unsigned int", "void", ""}); 0210 } 0211 0212 void TestClangUtils::testGetRawContents() 0213 { 0214 QFETCH(QByteArray, code); 0215 QFETCH(KTextEditor::Range, range); 0216 QFETCH(QString, expectedContents); 0217 0218 QString fileName; 0219 auto unit = parse(code, &fileName); 0220 0221 DocumentRange documentRange{IndexedString(fileName), range}; 0222 auto cxRange = toCXRange(unit.get(), documentRange); 0223 const QString contents = ClangUtils::getRawContents(unit.get(), cxRange); 0224 QCOMPARE(contents, expectedContents); 0225 } 0226 0227 void TestClangUtils::testGetRawContents_data() 0228 { 0229 QTest::addColumn<QByteArray>("code"); 0230 QTest::addColumn<KTextEditor::Range>("range"); 0231 QTest::addColumn<QString>("expectedContents"); 0232 0233 QTest::newRow("complete") 0234 << QByteArray("void; int foo = 42; void;") 0235 << KTextEditor::Range(0, 6, 0, 19) 0236 << "int foo = 42;"; 0237 QTest::newRow("cut-off-at-start") 0238 << QByteArray("void; int foo = 42; void;") 0239 << KTextEditor::Range(0, 7, 0, 19) 0240 << "nt foo = 42;"; 0241 QTest::newRow("cut-off-at-end") 0242 << QByteArray("void; int foo = 42; void;") 0243 << KTextEditor::Range(0, 6, 0, 17) 0244 << "int foo = 4"; 0245 QTest::newRow("whitespace") 0246 << QByteArray("void; int ; void;") 0247 << KTextEditor::Range(0, 5, 0, 18) 0248 << " int "; 0249 QTest::newRow("empty-range") 0250 << QByteArray("void;") 0251 << KTextEditor::Range(0, 0, 0, 0) 0252 << ""; 0253 } 0254 0255 void TestClangUtils::testRangeForIncludePathSpec() 0256 { 0257 QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include <vector>"), KTextEditor::Range(0, 10, 0, 16)); 0258 QCOMPARE(ClangUtils::rangeForIncludePathSpec("# include <vector>"), KTextEditor::Range(0, 11, 0, 17)); 0259 QCOMPARE(ClangUtils::rangeForIncludePathSpec("#\t include <vector>"), KTextEditor::Range(0, 12, 0, 18)); 0260 QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include <foo\\\".h>"), KTextEditor::Range(0, 10, 0, 17)); 0261 QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo\\\".h\""), KTextEditor::Range(0, 10, 0, 17)); 0262 QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo<>.h\""), KTextEditor::Range(0, 10, 0, 17)); 0263 } 0264 0265 void TestClangUtils::testGetCursorSignature() 0266 { 0267 QFETCH(QByteArray, code); 0268 QFETCH(QString, expectedSignature); 0269 0270 QString fileName; 0271 auto unit = parse(code, &fileName); 0272 CXCursor functionCursor = clang_getNullCursor(); 0273 const auto startCursor = clang_getTranslationUnitCursor(unit.get()); 0274 ClangUtils::visitChildren(startCursor, [&](CXCursor cursor, CXCursor){ 0275 switch (clang_getCursorKind(cursor)) 0276 { 0277 case CXCursor_FunctionDecl: 0278 case CXCursor_CXXMethod: 0279 case CXCursor_FunctionTemplate: 0280 functionCursor = cursor; 0281 return CXChildVisit_Break; 0282 default: 0283 return CXChildVisit_Recurse; 0284 } 0285 }); 0286 QVERIFY2(!clang_Cursor_isNull(functionCursor), "function not found"); 0287 auto scope = ClangUtils::getScope(functionCursor, startCursor); 0288 auto signature = ClangUtils::getCursorSignature(functionCursor, scope, {}); 0289 QCOMPARE(signature, expectedSignature); 0290 } 0291 0292 void TestClangUtils::testGetCursorSignature_data() 0293 { 0294 QTest::addColumn<QByteArray>("code"); 0295 QTest::addColumn<QString>("expectedSignature"); 0296 0297 QTest::newRow("global-less") 0298 << QByteArray("class klass {}; bool operator < (const klass&, const klass&);") 0299 << "bool operator<(const klass&, const klass&)"; 0300 QTest::newRow("member-less") 0301 << QByteArray("class klass { bool operator < (const klass&); };") 0302 << "bool klass::operator<(const klass&)"; 0303 QTest::newRow("template-member-less") 0304 << QByteArray("class klass { template<typename T> bool operator < (const T&); };") 0305 << "bool klass::operator<(const T&)"; 0306 } 0307 0308 #include "moc_test_clangutils.cpp"