Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Skip the AFX_EXT_CLASS macro in C++ parsing
Browse files Browse the repository at this point in the history
We can use TreeSitters includedRanges feature to only parse the
specified ranges.
This allows us to exclude certain macros like the AFX_EXT_CLASS macro
which was causing issues previously.
LeonMatthesKDAB committed Jul 17, 2024
1 parent ce3088c commit acb23f3
Showing 10 changed files with 118 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/API/knut/cppdocument.md
Original file line number Diff line number Diff line change
@@ -298,6 +298,7 @@ The returned QueryMatch instances contain the following captures:
- `declaration`: The full declaration of the method
- `function`: The function declaration, without the return type
- `name`: The name of the function
- `return-type`: The return type of the function without any reference/pointer specifiers (i.e. `&`/`*`)
#### <a name="queryMethodDefinition"></a>array&lt;[QueryMatch](../knut/querymatch.md)> **queryMethodDefinition**(string scope, string methodName)
6 changes: 6 additions & 0 deletions src/core/codedocument.cpp
Original file line number Diff line number Diff line change
@@ -848,4 +848,10 @@ AstNode CodeDocument::astNodeAt(int pos)
return {};
}

QList<treesitter::Range> CodeDocument::includedRanges() const
{
// An empty list tells the parser to include the entire document.
return {};
}

} // namespace Core
3 changes: 3 additions & 0 deletions src/core/codedocument.h
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
#include "querymatch.h"
#include "symbol.h"
#include "textdocument.h"
#include "treesitter/parser.h"
#include "treesitter/query.h"

#include <functional>
@@ -79,6 +80,8 @@ class CodeDocument : public TextDocument

Q_INVOKABLE Core::AstNode astNodeAt(int pos);

virtual QList<treesitter::Range> includedRanges() const;

public slots:
void selectSymbol(const QString &name, int options = NoFindFlags);

7 changes: 6 additions & 1 deletion src/core/codedocument_p.cpp
Original file line number Diff line number Diff line change
@@ -48,7 +48,12 @@ treesitter::Parser &TreeSitterHelper::parser()
std::optional<treesitter::Tree> &TreeSitterHelper::syntaxTree()
{
if (!m_tree) {
m_tree = parser().parseString(m_document->text());
auto &parser = this->parser();
if (!parser.setIncludedRanges(m_document->includedRanges())) {
spdlog::warn("TreeSitterHelper::syntaxTree: Unable to set the included ranges on the treesitter parser!");
parser.setIncludedRanges({});
}
m_tree = parser.parseString(m_document->text());
if (!m_tree) {
spdlog::warn("CodeDocument::syntaxTree: Failed to parse document {}!", m_document->fileName());
}
62 changes: 62 additions & 0 deletions src/core/cppdocument.cpp
Original file line number Diff line number Diff line change
@@ -692,6 +692,7 @@ MessageMap CppDocument::mfcExtractMessageMap(const QString &className /* = ""*/)
* - `declaration`: The full declaration of the method
* - `function`: The function declaration, without the return type
* - `name`: The name of the function
* - `return-type`: The return type of the function without any reference/pointer specifiers (i.e. `&`/`*`)
*/
Core::QueryMatchList CppDocument::queryMethodDeclaration(const QString &className, const QString &functionName)
{
@@ -1545,4 +1546,65 @@ QStringList CppDocument::primitiveTypes() const
return Utils::cppPrimitiveTypes();
}

QList<treesitter::Range> CppDocument::includedRanges() const
{
QList<QString> macros {"AFX_EXT_CLASS"};
QList<treesitter::Range> ranges;
treesitter::Point lastPoint {0, 0};
uint32_t lastByte = 0;

auto document = textEdit()->document();

QRegularExpression regex(macros.join("|"));
for (auto block = document->firstBlock(); block.isValid(); block = block.next()) {
QRegularExpressionMatch match;
auto searchFrom = 0;
auto index = block.text().indexOf(regex, searchFrom, &match);

// Run this in a loop to support multiple macros on the same line.
while (index != -1) {
// We need to construct a range from the end of the last match to the start of the current match.
//
// Note that the ranges have an inclusive start and an exclusive end..
//
// Also Note that the column seems to be in bytes, not characters.
// This is why we multiply by sizeof(QChar) to get the correct column.
// At least that's what the TreeSitterInspector shows us.
auto endPoint = treesitter::Point {.row = static_cast<uint32_t>(block.blockNumber()),
.column = static_cast<uint32_t>(index * sizeof(QChar))};
ranges.push_back({.start_point = lastPoint,
.end_point = endPoint,
.start_byte = lastByte,
// No need to add - 1 here, the ranges are exclusive at the end.
.end_byte = static_cast<uint32_t>((block.position() + index) * sizeof(QChar))});

auto matchLength = match.capturedLength();
lastByte = static_cast<uint32_t>((block.position() + index + matchLength) * sizeof(QChar));
lastPoint = {.row = static_cast<uint32_t>(block.blockNumber()),
.column = static_cast<uint32_t>((index + matchLength) * sizeof(QChar))};
if (lastPoint.column == static_cast<uint32_t>(block.length())) {
++lastPoint.row;
lastPoint.column = 0;
}

searchFrom = index + matchLength;
index = block.text().indexOf(regex, searchFrom, &match);
}
}

if (!ranges.isEmpty()) {
// Add the last range, up to the end of the document, but only if we have another range.
// Leaving the ranges empty will parse the entire document, so that's easiest.
auto endPoint =
treesitter::Point {.row = static_cast<uint32_t>(document->blockCount() - 1),
.column = static_cast<uint32_t>(document->lastBlock().length() * sizeof(QChar))};
ranges.push_back({.start_point = lastPoint,
.end_point = endPoint,
.start_byte = lastByte,
.end_byte = static_cast<uint32_t>(document->characterCount() * sizeof(QChar))});
}

return ranges;
}

} // namespace Core
2 changes: 2 additions & 0 deletions src/core/cppdocument.h
Original file line number Diff line number Diff line change
@@ -60,6 +60,8 @@ class CppDocument : public CodeDocument
bool changeBaseClass(CppDocument *header, CppDocument *source, const QString &className,
const QString &newClassBaseName);

QList<treesitter::Range> includedRanges() const override;

public slots:
Core::CppDocument *openHeaderSource();

1 change: 1 addition & 0 deletions src/gui/treesitterinspector.cpp
Original file line number Diff line number Diff line change
@@ -182,6 +182,7 @@ void TreeSitterInspector::changeText()
Core::LoggerDisabler disableLogging;
text = m_document->text();
}
m_parser.setIncludedRanges(m_document->includedRanges());
auto tree = m_parser.parseString(text);
if (tree.has_value()) {
m_treemodel.setTree(std::move(tree.value()), makePredicates(), ui->enableUnnamed->isChecked());
6 changes: 4 additions & 2 deletions src/gui/treesittertreemodel.cpp
Original file line number Diff line number Diff line change
@@ -74,11 +74,13 @@ QVariant TreeSitterTreeModel::TreeNode::data(int column) const
return QString("%1: %2").arg(fieldName, m_node.type());
}
case 1:
return QString("[%1:%2] - [%3:%4]")
return QString("[%1:%2](%3) - [%4:%5](%6)")
.arg(m_node.startPoint().row)
.arg(m_node.startPoint().column)
.arg(m_node.startPosition())
.arg(m_node.endPoint().row)
.arg(m_node.endPoint().column);
.arg(m_node.endPoint().column)
.arg(m_node.endPosition());
default:
break;
}
10 changes: 10 additions & 0 deletions test_data/tst_cppdocument/treesitterExcludesMacros/AFX_EXT_CLASS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

class AFX_EXT_CLASS TestClass : public AFX_EXT_CLASSBase{
publicAFX_EXT_CLASS:
void testMethod();

private:
int AFX_EXT_CLASS m_count;
};
AFX_EXT_CLASS
23 changes: 23 additions & 0 deletions tests/tst_cppdocument_treesitter.cpp
Original file line number Diff line number Diff line change
@@ -358,6 +358,29 @@ private slots:
QVERIFY(headerFile.compare());
}
}

void excludeMacros()
{
Test::testCppDocument("tst_cppdocument/treesitterExcludesMacros", "AFX_EXT_CLASS.h", [](auto *document) {
auto match = document->queryClassDefinition("TestClass");
QVERIFY(!match.isEmpty());
QCOMPARE(match.get("name").text(), "TestClass");
QCOMPARE(match.get("base").text(), "Base");

match = document->queryMember("TestClass", "m_count");
QVERIFY(!match.isEmpty());
QCOMPARE(match.get("name").text(), "m_count");
QCOMPARE(match.get("type").text(), "int");
QCOMPARE(match.get("member").text(), "int AFX_EXT_CLASS m_count;");

auto matches = document->queryMethodDeclaration("TestClass", "testMethod");
QCOMPARE(matches.length(), 1);
match = matches.front();
QVERIFY(!match.isEmpty());
QCOMPARE(match.get("name").text(), "testMethod");
QCOMPARE(match.get("return-type").text(), "void");
});
}
};

QTEST_MAIN(TestCppDocumentTreeSitter)

0 comments on commit acb23f3

Please sign in to comment.