Commit 184993a1 authored by Sergey Kalinichev's avatar Sergey Kalinichev

Automatically change member access operator.

This automatically changes '.' to '->' and vice-versa, based on
LibClang diagnostics.

Sadly, Clang doesn't provide the diagnostic when '.' used instead of '->',
so in that case automatic replacement doesn't work.

REVIEW: 124735
parent 53ec147c
......@@ -21,6 +21,9 @@
#include "context.h"
#include <interfaces/icore.h>
#include <interfaces/idocumentcontroller.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/ducontext.h>
#include <language/duchain/topducontext.h>
......@@ -41,6 +44,7 @@
#include "../util/clangdebug.h"
#include "../util/clangtypes.h"
#include "../duchain/clangdiagnosticevaluator.h"
#include "../duchain/parsesession.h"
#include "../duchain/navigationwidget.h"
#include "../clangsettings/clangsettingsmanager.h"
......@@ -613,8 +617,52 @@ private:
bool m_enabled;
};
struct MemberAccessReplacer : public QObject
{
Q_OBJECT
public:
enum Type {
None,
DotToArrow,
ArrowToDot
};
public slots:
void replaceCurrentAccess(MemberAccessReplacer::Type type)
{
if (auto document = ICore::self()->documentController()->activeDocument()) {
if (auto textDocument = document->textDocument()) {
auto activeView = document->activeTextView();
if (!activeView) {
return;
}
auto cursor = activeView->cursorPosition();
QString oldAccess, newAccess;
if (type == DotToArrow) {
oldAccess = QStringLiteral("->");
newAccess = QStringLiteral(".");
} else {
oldAccess = QStringLiteral(".");
newAccess = QStringLiteral("->");
}
auto oldRange = KTextEditor::Range(cursor - KTextEditor::Cursor(0, oldAccess.length()), cursor);
if (oldRange.start().column() >= 0 && textDocument->text(oldRange) == oldAccess) {
textDocument->replaceText(oldRange, newAccess);
}
}
}
}
};
static MemberAccessReplacer s_memberAccessReplacer;
}
Q_DECLARE_METATYPE(MemberAccessReplacer::Type)
ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& context,
const ParseSessionData::Ptr& sessionData,
const QUrl& url,
......@@ -625,6 +673,7 @@ ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& c
, m_results(nullptr, clang_disposeCodeCompleteResults)
, m_parseSessionData(sessionData)
{
qRegisterMetaType<MemberAccessReplacer::Type>();
const QByteArray file = url.toLocalFile().toUtf8();
ParseSession session(m_parseSessionData);
{
......@@ -646,6 +695,27 @@ ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& c
return;
}
auto numDiagnostics = clang_codeCompleteGetNumDiagnostics(m_results.get());
for (uint i = 0; i < numDiagnostics; i++) {
auto diagnostic = clang_codeCompleteGetDiagnostic(m_results.get(), i);
auto diagnosticType = ClangDiagnosticEvaluator::diagnosticType(diagnostic);
clang_disposeDiagnostic(diagnostic);
if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithArrowProblem || diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) {
MemberAccessReplacer::Type replacementType;
if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) {
replacementType = MemberAccessReplacer::DotToArrow;
} else {
replacementType = MemberAccessReplacer::ArrowToDot;
}
QMetaObject::invokeMethod(&s_memberAccessReplacer, "replaceCurrentAccess", Qt::QueuedConnection,
Q_ARG(MemberAccessReplacer::Type, replacementType));
m_valid = false;
return;
}
}
auto addMacros = ClangSettingsManager::self()->codeCompletionSettings().macros;
if (!addMacros) {
m_filters |= NoMacros;
......@@ -970,3 +1040,5 @@ void ClangCodeCompletionContext::setFilters(const ClangCodeCompletionContext::Co
{
m_filters = filters;
}
#include "context.moc"
......@@ -62,12 +62,57 @@ bool isIncludeFileNotFound(CXDiagnostic diagnostic)
return ClangString(clang_getDiagnosticSpelling(diagnostic)).toString().endsWith(QLatin1String("file not found"));
}
bool isReplaceWithDotProblem(CXDiagnostic diagnostic)
{
// TODO: The diagnostic message depends on LibClang version.
const QList<QString> diagnosticMessages {QStringLiteral("did you mean to use '.'?"), QStringLiteral("maybe you meant to use '.'?")};
QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString();
for (const auto& diagnStr : diagnosticMessages) {
if (description.endsWith(diagnStr)) {
return true;
}
}
return false;
}
bool isReplaceWithArrowProblem(CXDiagnostic diagnostic)
{
// TODO: The diagnostic message depends on LibClang version.
const QList<QString> diagnosticMessages {QStringLiteral("did you mean to use '->'?"), QStringLiteral("maybe you meant to use '->'?")};
QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString();
for (const auto& diagnStr : diagnosticMessages) {
if (description.endsWith(diagnStr)) {
return true;
}
}
return false;
}
}
ClangDiagnosticEvaluator::DiagnosticType ClangDiagnosticEvaluator::diagnosticType(CXDiagnostic diagnostic)
{
if (isDeclarationProblem(diagnostic)) {
return UnknownDeclarationProblem;
} else if (isIncludeFileNotFound(diagnostic)) {
return IncludeFileNotFoundProblem;
} else if (isReplaceWithDotProblem(diagnostic)) {
return ReplaceWithDotProblem;
} else if (isReplaceWithArrowProblem(diagnostic)) {
return ReplaceWithArrowProblem;
}
return Unknown;
}
ClangProblem* ClangDiagnosticEvaluator::createProblem(CXDiagnostic diagnostic, CXTranslationUnit unit)
{
if (isDeclarationProblem(diagnostic)) {
return new UnknownDeclarationProblem(diagnostic, unit);
return new class UnknownDeclarationProblem(diagnostic, unit);
} else if(isIncludeFileNotFound(diagnostic)){
return new MissingIncludePathProblem(diagnostic, unit);
}
......
......@@ -30,6 +30,20 @@ class ClangProblem;
namespace ClangDiagnosticEvaluator {
KDEVCLANGDUCHAIN_EXPORT ClangProblem* createProblem(CXDiagnostic diagnostic, CXTranslationUnit unit);
enum DiagnosticType {
Unknown,
UnknownDeclarationProblem,
IncludeFileNotFoundProblem,
ReplaceWithDotProblem,
ReplaceWithArrowProblem
};
/**
* @return Type of @p diagnostic
* @sa DiagnosticType
*/
KDEVCLANGDUCHAIN_EXPORT DiagnosticType diagnosticType(CXDiagnostic diagnostic);
}
#endif // CLANGDIAGNOSTICEVALUATOR_H
......@@ -30,6 +30,8 @@
#include "duchain/parsesession.h"
#include "util/clangtypes.h"
#include <interfaces/idocumentcontroller.h>
#include <language/codecompletion/codecompletiontesthelper.h>
#include <language/duchain/types/functiontype.h>
......@@ -101,8 +103,8 @@ void TestCodeCompletion::initTestCase()
{
QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n"));
QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport"));
AutoTestShell::init();
TestCore::initialize(Core::NoUi);
AutoTestShell::init({QStringLiteral("kdevclangsupport")});
TestCore::initialize();
ClangSettingsManager::self()->m_enableTesting = true;
}
......@@ -196,6 +198,46 @@ void executeCompletionPriorityTest(const QString& code, const CompletionPriority
}
}
void executeMemberAccessReplacerTest(const QString& code, const CompletionItems& expectedCompletionItems,
const ClangCodeCompletionContext::ContextFilters& filters = ClangCodeCompletionContext::ContextFilters(
ClangCodeCompletionContext::NoBuiltins |
ClangCodeCompletionContext::NoMacros))
{
TestFile file(code, "cpp");
auto document = ICore::self()->documentController()->openDocument(file.url().toUrl());
QVERIFY(document);
ICore::self()->documentController()->activateDocument(document);
auto view = ICore::self()->documentController()->activeTextDocumentView();
Q_ASSERT(view);
view->setCursorPosition(expectedCompletionItems.position);
QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST));
DUChainReadLocker lock;
auto top = file.topContext();
QVERIFY(top);
const ParseSessionData::Ptr sessionData(dynamic_cast<ParseSessionData*>(top->ast().data()));
QVERIFY(sessionData);
DUContextPointer topPtr(top);
lock.unlock();
auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString());
QApplication::processEvents();
document->close(KDevelop::IDocument::Silent);
// The previous ClangCodeCompletionContext call should replace member access.
context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString());
context->setFilters(filters);
lock.lock();
auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer<ClangCodeCompletionContext>(context));
tester.names.sort();
QEXPECT_FAIL("replace dot to arrow", "Clang doesn't provide the diagnostic when '.' used instead of '->'", Continue);
QCOMPARE(tester.names, expectedCompletionItems.completions);
}
using IncludeTester = CodeCompletionItemTester<IncludePathCompletionContext>;
QExplicitlySharedDataPointer<IncludePathCompletionContext> executeIncludePathCompletion(TestFile* file, const KTextEditor::Cursor& position)
......@@ -413,6 +455,34 @@ void TestCodeCompletion::testClangCodeCompletion_data()
}};
}
void TestCodeCompletion::testReplaceMemberAccess()
{
QFETCH(QString, code);
QFETCH(CompletionItems, expectedItems);
executeMemberAccessReplacerTest(code, expectedItems);
}
void TestCodeCompletion::testReplaceMemberAccess_data()
{
QTest::addColumn<QString>("code");
QTest::addColumn<CompletionItems>("expectedItems");
QTest::newRow("replace arrow to dot")
<< "struct Struct { void function(); };"
"int main() { Struct s; \ns-> "
<< CompletionItems{{1, 3}, {
"function"
}};
QTest::newRow("replace dot to arrow")
<< "struct Struct { void function(); };"
"int main() { Struct* s; \ns. "
<< CompletionItems{{1, 2}, {
"function"
}};
}
void TestCodeCompletion::testVirtualOverride()
{
QFETCH(QString, code);
......
......@@ -46,6 +46,8 @@ private slots:
void testInvalidCompletions_data();
void testCompletionPriority();
void testCompletionPriority_data();
void testReplaceMemberAccess();
void testReplaceMemberAccess_data();
void testOverloadedFunctions();
void testVariableScope();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment