From ff46f1c4204a65416af3bbeacc9ac036c9b7365f Mon Sep 17 00:00:00 2001 From: Nicolas Arnaud-Cormos Date: Thu, 20 Jun 2024 19:19:52 +0200 Subject: [PATCH] fix: Fix message map extraction when namespaces are used The query test for MESSAGE_MAP either at top-level or in a namespace --- src/core/cppdocument.cpp | 76 +++--- .../TutorialDialogWithNamespace.cpp | 217 ++++++++++++++++++ tests/tst_cppdocument_treesitter.cpp | 11 + 3 files changed, 268 insertions(+), 36 deletions(-) create mode 100644 test_data/tst_cppdocument/message_map/TutorialDialogWithNamespace.cpp diff --git a/src/core/cppdocument.cpp b/src/core/cppdocument.cpp index 76d58695..56fadc90 100644 --- a/src/core/cppdocument.cpp +++ b/src/core/cppdocument.cpp @@ -624,44 +624,48 @@ MessageMap CppDocument::mfcExtractMessageMap(const QString &className /* = ""*/) auto checkClassName = className.isEmpty() ? "" : QString("(#eq? @class \"%1\")").arg(className); // clang-format off - auto queryString = QString(R"EOF( - (translation_unit - ; Assumption: the MESSAGE_MAP is always top-level - - ; Group to make sure the nodes are actually siblings - ( - - ; Search for BEGIN_MESSAGE_MAP - (expression_statement - (call_expression - function: (identifier) @begin_ident - (#eq? @begin_ident "BEGIN_MESSAGE_MAP") - arguments: (argument_list - (identifier) @class - %1 ; If a class name is given, check if the captured class name matches - (identifier) @superclass)) @begin) - - ; Followed by one or more entries - [ - (expression_statement - (call_expression - function: (identifier) @message-name - arguments: (argument_list - [(_)* @parameter ","]* - (#exclude! @parameter comment)) - ))@message - (_) - ]* - - ; Ending with END_MESSAGE_MAP - (expression_statement - (call_expression - function: (identifier) @end_ident - (#eq? @end_ident "END_MESSAGE_MAP")) @end) - ) + const auto messageMapQueryString = QString(R"EOF( + ; Search for BEGIN_MESSAGE_MAP + (expression_statement + (call_expression + function: (identifier) @begin_ident + (#eq? @begin_ident "BEGIN_MESSAGE_MAP") + arguments: (argument_list + (identifier) @class + %1 ; If a class name is given, check if the captured class name matches + (identifier) @superclass)) @begin) + + ; Followed by one or more entries + [ + (expression_statement + (call_expression + function: (identifier) @message-name + arguments: (argument_list + [(_)* @parameter ","]* + (#exclude! @parameter comment)) + ))@message + (_) + ]* + + ; Ending with END_MESSAGE_MAP + (expression_statement + (call_expression + function: (identifier) @end_ident + (#eq? @end_ident "END_MESSAGE_MAP")) @end) + )EOF").arg(checkClassName); + // clang-format on + // clang-format off + // Assumption: the MESSAGE_MAP is either top-level or in a namespace + // Parenthesis (around %1) are used to make sure nodes are siblings + const auto queryString = QString(R"EOF( + (translation_unit + [ + (namespace_definition (_ ( %1 ) ) ) + ( %1 ) + ] ) - )EOF").arg(checkClassName); + )EOF").arg(messageMapQueryString); // clang-format on // We assume there is at most one MessageMap per file. diff --git a/test_data/tst_cppdocument/message_map/TutorialDialogWithNamespace.cpp b/test_data/tst_cppdocument/message_map/TutorialDialogWithNamespace.cpp new file mode 100644 index 00000000..9ba03f05 --- /dev/null +++ b/test_data/tst_cppdocument/message_map/TutorialDialogWithNamespace.cpp @@ -0,0 +1,217 @@ +#include "stdafx.h" +#include "TutorialApp.h" +#include "TutorialDlg.h" + +// DEBUG_NEW macro allows MFC applications to determine memory leak locations in debug builds +#ifdef _DEBUG + #define new DEBUG_NEW +#endif + +namespace TestNamespace { + +CTutorialDlg::CTutorialDlg(CWnd* pParent) +: CDialog(CTutorialDlg::IDD, pParent) +, m_EchoText(L"") +, m_HSliderEcho(L"") +, m_VSliderEcho(L"") +, m_MouseEcho(L"") +, m_TimerEcho(L"") +, m_TimerCtrlSliders(TRUE) +, m_OkCount(0) +, m_Seconds(0) +{ + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CTutorialDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Text(pDX, IDC_ECHO_AREA, m_EchoText); + DDX_Text(pDX, IDC_H_SLIDER_ECHO, m_HSliderEcho); + DDX_Text(pDX, IDC_V_SLIDER_ECHO, m_VSliderEcho); + DDX_Control(pDX, IDC_V_SLIDER_BAR, m_VSliderBar); + DDX_Control(pDX, IDC_H_SLIDER_BAR, m_HSliderBar); + DDX_Text(pDX, IDC_MOUSEECHO, m_MouseEcho); + DDX_Text(pDX, IDC_TIMERECHO, m_TimerEcho); + DDX_Check(pDX, IDC_TIMER_CONTROL_SLIDERS, m_TimerCtrlSliders); +} + +BEGIN_MESSAGE_MAP(CTutorialDlg, CDialog) + ON_WM_PAINT() + ON_WM_HSCROLL() + ON_WM_VSCROLL() + ON_WM_TIMER() + ON_WM_LBUTTONDOWN() + ON_WM_MOUSEMOVE() + ON_WM_RBUTTONDOWN() + ON_BN_CLICKED(ID_BTN_ADD, OnBnClickedBtnAdd) + ON_BN_CLICKED(IDC_TIMER_CONTROL_SLIDERS, OnBnClickedTimerControlSliders) +END_MESSAGE_MAP() + +// This is called when the dialog is first created and shown. +// It is a good spot to initialize member variables. +BOOL CTutorialDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // Add extra initialization here. + // We want to initialize the slider bars + m_VSliderBar.SetRange(0, 100, TRUE); + m_VSliderBar.SetPos(50); + m_VSliderEcho.Format(L"%d", 50); + + m_HSliderBar.SetRange(0, 10, TRUE); + m_HSliderBar.SetPos(5); + m_HSliderEcho.Format(L"%d", 5); + + // Initialize the timer to go off every 1000 milliseconds (every second) + // when timer "goes-off", our OnTimer() event handler function will be + // called and it is upto us to decide what we want to do. + SetTimer(0, 1000, NULL); + + UpdateData(false); + + return TRUE; // return TRUE unless you set the focus to a control +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. +void CTutorialDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +void CTutorialDlg::OnBnClickedBtnAdd() +{ + m_OkCount++; + m_EchoText.Format(L"%d", m_OkCount); + + // Notice, without UpdateData() status area will _NOT_ be updated. + UpdateData(FALSE); +} + + +void CTutorialDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + // We should check to make sure we know which slider bar is generating the events + if (pScrollBar == (CScrollBar *) &m_HSliderBar) + { + int value = m_HSliderBar.GetPos(); + m_HSliderEcho.Format(L"%d", value); + UpdateData(false); + } + else + CDialog::OnHScroll(nSBCode, nPos, pScrollBar); +} + +void CTutorialDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + // We should check to make sure we know which slider bar is generating the events + if (pScrollBar == (CScrollBar *) &m_VSliderBar) + { + int value = m_VSliderBar.GetPos(); + m_VSliderEcho.Format(L"%d", value); + UpdateData(false); + } + else + CDialog::OnVScroll(nSBCode, nPos, pScrollBar); +} + +void CTutorialDlg::OnTimer(UINT_PTR nIDEvent) +{ + m_Seconds++; + + if (m_TimerCtrlSliders) + { + // Get ready to decrease the sliders ... + int hvalue = m_HSliderBar.GetPos(); + if (hvalue > 0) + { + m_HSliderBar.SetPos(hvalue-1); + m_HSliderEcho.Format(L"%d", hvalue-1); + } + + int vvalue = m_VSliderBar.GetPos(); + if (vvalue > 0) + { + m_VSliderBar.SetPos(vvalue-1); + m_VSliderEcho.Format(L"%d", vvalue-1); + } + + if ( (hvalue==0) && (vvalue==0) ) + m_TimerCtrlSliders = false; + } + + m_TimerEcho.Format(L"%d: Seconds have passed", m_Seconds); + UpdateData(false); +} + +void CTutorialDlg::OnLButtonDown(UINT nFlags, CPoint point) +{ + CString prefix; + if(nFlags & MK_CONTROL) + prefix = L"[CTRL]"; + if(nFlags & MK_SHIFT) + prefix+= L"[SHIFT]"; + m_MouseEcho.Format(L"%sLeft mouse down at %d,%d", prefix, point.x, point.y); + UpdateData(false); +} + +void CTutorialDlg::OnMouseMove(UINT nFlags, CPoint point) +{ + CString prefix; + if(nFlags & MK_CONTROL) + prefix = L"[CTRL]"; + if(nFlags & MK_SHIFT) + prefix+= L"[SHIFT]"; + m_MouseEcho.Format(L"%sMouse move at %d,%d", prefix, point.x, point.y); + UpdateData(false); +} + +void CTutorialDlg::OnRButtonDown(UINT nFlags, CPoint point) +{ + CString prefix; + if(nFlags & MK_CONTROL) + prefix = L"[CTRL]"; + if(nFlags & MK_SHIFT) + prefix+= L"[SHIFT]"; + m_MouseEcho.Format(L"%sRight mouse down at %d,%d", prefix, point.x, point.y); + UpdateData(false); +} + +void CTutorialDlg::OnBnClickedTimerControlSliders() +{ + UpdateData(true); + // This will fill all UI-connected variables with whatever + // value that is showing on the UI control objects. + // + // In this case, we care most about the value for m_TimerCtrlSliders +} + +} // namespace TestNamespace diff --git a/tests/tst_cppdocument_treesitter.cpp b/tests/tst_cppdocument_treesitter.cpp index 8bca6504..c429d8a3 100644 --- a/tests/tst_cppdocument_treesitter.cpp +++ b/tests/tst_cppdocument_treesitter.cpp @@ -178,6 +178,17 @@ private slots: existingMessageMap(cppdocument); } + void mfcExtractMessageMapWithNamespace() + { + Core::KnutCore core; + auto project = Core::Project::instance(); + project->setRoot(Test::testDataPath() + "/tst_cppdocument/message_map"); + + auto cppdocument = + qobject_cast(Core::Project::instance()->get("TutorialDialogWithNamespace.cpp")); + existingMessageMap(cppdocument); + } + void deleteMessageMap() { Test::FileTester file(Test::testDataPath() + "/tst_cppdocument/message_map/TutorialDlg.cpp");