Change the name of a function *and* modify a string literal argument using Clang's AST API?

473 views Asked by At

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));
    ```
0

There are 0 answers