I'm trying to write a clang-tidy rule that will change the name of a function and modify a string literal parameter. The goal is to auto-port from tinyFormat to libfmt.
I've got what I think is a good matcher here:
finder->addMatcher(
callExpr(
callee(functionDecl(hasName("::tiny::Format"))),
optionally(hasArgument(
0,
ignoringImpCasts(
declRefExpr(
to(varDecl(hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(
cxxRecordDecl(isDerivedFrom(
"::std::basic_ostream")))))))))
.bind("streamArg")))),
anyOf(hasArgument(0, stringLiteral().bind("fmtLiteral")),
hasArgument(1, stringLiteral().bind("fmtLiteral"))))
.bind("call"),
this);
The trouble is with writing the replacement rule. If I create 1 fixit to replace the name, and a separate replacement to change the string literal (from %s style to {} style), they wind up stomping on one another. In attempting to write a single long replacement string I've come up against a really bizzare issue though: It appears nigh on impossible to reliably get the last character of a string literal. Presently I have this, but it chokes if the literal is in a macro (like say, an ASSERT_ALWAYS). I've also tried using findLocationAfterToken with no luck.
fmtDiag << clang::FixItHint::CreateReplacement(
callExpr->getSourceRange(),
(llvm::Twine(streamArg ? "fmt::print(" : "fmt::format(") // use fmt::print if we're printing to a stream
+ (streamArg ? clang::tooling::fixit::getText(
streamArg->getSourceRange(), *result.Context)
+ ", "
: StringRef())
+ llvm::Twine(TransformFormatString(fmtLiteral->getString()))
+ clang::tooling::fixit::getText(
SourceRange(
// Note(wgray): I have no idea why this is required, but just
// using fmtLiteral->getEndLoc() doesn't work. This magic
// incantation was taken from
// bugprone/NotNullTerminatedResultCheck.cpp
Lexer::getLocForEndOfToken(
fmtLiteral->getEndLoc(),
0,
*result.SourceManager,
result.Context->getLangOpts()),
endLoc,
callExpr->getEndLoc()),
*result.Context));
```