基于Clang libTooling的条件语句改写:保留注释问题求助
问题分析
你的核心问题是:clang::IfStmt::getCond()返回的AST节点的SourceRange仅覆盖条件表达式本身,不包含括号内表达式前后的注释(这些注释属于AST节点的"trivia",不在Cond节点的源码范围内)。直接替换Cond节点的文本,会导致注释被留在/*BEGIN*/之前或/*END*/之后,不符合预期。
解决方案
要实现需求,你需要:
- 定位到条件语句括号内的完整源码范围(从左括号
(之后到右括号)之前,包含所有注释、空白和表达式); - 提取该范围内的全部文本,包裹
/*BEGIN*/和/*END*/后替换整个范围。
具体实现步骤
1. 工具函数:定位括号内的完整范围
先实现一个通用函数,用于从各类条件语句中获取括号内的源码范围:
#include "clang/Lex/Lexer.h" using namespace clang; std::optional<std::pair<SourceRange, std::string>> getCondParenContent(const Stmt *S, const ASTContext &Ctx) { const SourceManager &SM = Ctx.getSourceManager(); const LangOptions &LO = Ctx.getLangOpts(); // 定位到对应语句的左括号位置 SourceLocation StartLoc; if (const auto *IS = dyn_cast<IfStmt>(S)) { StartLoc = IS->getLParenLoc(); } else if (const auto *WS = dyn_cast<WhileStmt>(S)) { StartLoc = WS->getLParenLoc(); } else if (const auto *FS = dyn_cast<ForStmt>(S)) { StartLoc = FS->getLParenLoc(); } else if (const auto *DS = dyn_cast<DoStmt>(S)) { // DoStmt的条件在while后,需定位到while的左括号 StartLoc = DS->getWhileLoc(); StartLoc = Lexer::findNextToken(StartLoc, SM, LO)->getLocation(); } if (StartLoc.isInvalid()) return std::nullopt; // 找到匹配的右括号 SourceLocation EndLoc; if (!Lexer::findMatchingParen(StartLoc, EndLoc, SM, LO)) { return std::nullopt; } // 调整范围:左括号之后到右括号之前 SourceRange ParenRange( StartLoc.getLocWithOffset(1), EndLoc.getLocWithOffset(-1) ); // 提取范围内的原始文本(包含注释、空白) StringRef Content = Lexer::getSourceText(CharSourceRange::getCharRange(ParenRange), SM, LO); if (Content.empty()) return std::nullopt; return std::make_pair(ParenRange, Content.str()); }
2. 修改条件语句的Visitor函数
用上述函数替换原有的getSourceFromStmt逻辑,直接替换括号内的全部内容:
bool VisitIfStmt(const IfStmt *IS) const { if (auto ParenData = getCondParenContent(IS, mContext)) { auto [ParenRange, Content] = *ParenData; std::string NewContent = std::format("/*BEGIN*/{}/*END*/", Content); mRewriter.ReplaceText(ParenRange, NewContent); } return true; } // 同理实现WhileStmt的处理 bool VisitWhileStmt(const WhileStmt *WS) const { if (auto ParenData = getCondParenContent(WS, mContext)) { auto [ParenRange, Content] = *ParenData; std::string NewContent = std::format("/*BEGIN*/{}/*END*/", Content); mRewriter.ReplaceText(ParenRange, NewContent); } return true; } // ForStmt需兼容空条件的情况(如for(;;)) bool VisitForStmt(const ForStmt *FS) const { if (auto ParenData = getCondParenContent(FS, mContext)) { auto [ParenRange, Content] = *ParenData; std::string NewContent = std::format("/*BEGIN*/{}/*END*/", Content); mRewriter.ReplaceText(ParenRange, NewContent); } return true; } // DoStmt的处理逻辑 bool VisitDoStmt(const DoStmt *DS) const { if (auto ParenData = getCondParenContent(DS, mContext)) { auto [ParenRange, Content] = *ParenData; std::string NewContent = std::format("/*BEGIN*/{}/*END*/", Content); mRewriter.ReplaceText(ParenRange, NewContent); } return true; }
3. 关键细节说明
- 括号匹配:利用
Lexer::findMatchingParen精准处理嵌套括号,避免范围定位错误; - 原始文本提取:
Lexer::getSourceText获取的是括号内的原始源码,包含所有注释、空白,不会丢失宏展开前的内容; - DoStmt特殊处理:DoStmt的条件位于
while关键字之后,需额外定位到while的左括号位置。
测试验证
用你的测试输入代码,修改后的工具会输出符合预期的结果:
#define TRUE 1 #define FALSE 0 void foo() { // Leading Macro test if (/*BEGIN*/TRUE == 1/*END*/) { } // Leading Comment, trailing Macro if (/*BEGIN*//*COMMENT*/0 == FALSE/*END*/) { } // trailing Comment after Macro if (/*BEGIN*/0 == TRUE/*COMMENT*//*END*/) { } // Leading comment if (/*BEGIN*//*COMMENT*/2 < t/*END*/) { } // Trailing comment if (/*BEGIN*/t < 2 /*COMMENT*//*END*/) { } }
内容的提问来源于stack exchange,提问作者johnco3
相关产品推荐
相关产品推荐

