Skip to content

Instantly share code, notes, and snippets.

@ricejasonf
Created March 19, 2016 02:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ricejasonf/a62c22740888b3ee2eac to your computer and use it in GitHub Desktop.
Save ricejasonf/a62c22740888b3ee2eac to your computer and use it in GitHub Desktop.
Refactor Some CamelCase
set(LLVM_LINK_COMPONENTS support)
add_clang_executable(my_refactor
my_refactor.cpp
)
target_link_libraries(my_refactor
clangTooling
clangBasic
clangASTMatchers
)
install(TARGETS my_refactor RUNTIME DESTINATION bin)
//
// Copyright Jason Rice 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/TextDiagnostic.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <regex>
#include <unordered_set>
namespace tooling = clang::tooling;
namespace cl = llvm::cl;
static auto option_category = cl::OptionCategory("Refactor");
static cl::opt<bool> overwrite_option("overwrite",
cl::desc("Actually overwrite files in place."),
cl::init(false),
cl::cat(option_category)
);
static cl::opt<bool> queit_option("queit",
cl::desc("Suppress overly chatty output."),
cl::init(false),
cl::cat(option_category)
);
bool match_string(char const* name, char const* regex)
{
return (std::regex_match(name, std::regex(regex, std::regex::ECMAScript)));
}
bool isSourceLocationInProjectFile(clang::SourceManager const& sm, clang::SourceLocation const& sl)
{
auto file_entry = sm.getFileEntryForID(sm.getFileID(sl));
if (!file_entry) return false;
if (sm.isInSystemHeader(sl)) return false;
if (match_string(file_entry->getName(), ".*catch.hpp")) return false;
if (!match_string(file_entry->getName(), ".*nbdl/.*")) return false;
return true;
}
bool isCanonicalName(clang::TypeLoc type_loc)
{
clang::QualType type = type_loc.getType();
if (clang::TemplateSpecializationType::classof(type.getTypePtr()))
{
return true;
}
return type.isCanonical();
}
// this is a convoluted solution
class is_callable_visitor : public clang::RecursiveASTVisitor<is_callable_visitor>
{
bool& is_callable;
clang::TagDecl* record;
public:
is_callable_visitor(bool& is_callable, clang::TagDecl* record)
: is_callable(is_callable)
, record(record)
{
is_callable = false;
}
bool VisitCXXMethodDecl(clang::CXXMethodDecl const* fdecl)
{
if (fdecl->getParent() != record) return true;
if (fdecl->getOverloadedOperator() == clang::OO_Call)
{
is_callable = true;
return false;
}
return true;
}
};
class ast_visitor : public clang::RecursiveASTVisitor<ast_visitor>
{
clang::Rewriter& rewriter;
std::unordered_set<unsigned> changed_locations;
void emitDiagnosticNote(char const* note, clang::SourceRange const& sr)
{
auto& sm = rewriter.getSourceMgr();
clang::TextDiagnostic td(
llvm::errs(),
rewriter.getLangOpts(),
&sm.getDiagnostics().getDiagnosticOptions()
);
td.emitDiagnostic(
sr.getBegin(),
clang::DiagnosticsEngine::Note,
note,
clang::CharSourceRange::getTokenRange(sr),
llvm::None,
&sm
);
}
bool isPseudoConcept(clang::Decl const* decl)
{
auto sl = decl->getLocStart();
auto& sm = rewriter.getSourceMgr();
auto file_entry = sm.getFileEntryForID(sm.getFileID(sl));
if (!file_entry) return false;
if (match_string(file_entry->getName(), ".*concept.*")) return true;
return false;
}
bool isInterestingUseOfTypeAlias(clang::Type const* type)
{
if (clang::TemplateSpecializationType::classof(type))
{
auto* s = type->getAs<clang::TemplateSpecializationType>();
auto* tmp_decl = s->getTemplateName().getAsTemplateDecl();
// if (s->isTypeAlias() && tmp_decl)
if (tmp_decl)
{
return tmp_decl->getDeclContext()->isNamespace();
}
}
return false;
}
bool isCallableRecord(clang::QualType const& qual_type)
{
auto* record = qual_type.getTypePtr()->getAsTagDecl();
return isCallableRecord(record);
}
bool isCallableRecord(clang::TagDecl* record)
{
if (!record) return false;
bool result = false;
is_callable_visitor visitor(result, record);
visitor.TraverseDecl(record);
return result;
}
bool renameToken(clang::SourceLocation loc, bool is_callable_record = false)
{
// prevent dup changes that make for weird effects
if (changed_locations.count(loc.getRawEncoding()))
return true;
changed_locations.insert(loc.getRawEncoding());
auto lang_opts = rewriter.getLangOpts();
auto& sm = rewriter.getSourceMgr();
auto expansion_loc = sm.getExpansionLoc(loc);
if (expansion_loc.isInvalid()) return false;
if (!rewriter.isRewritable(expansion_loc)) return false;
clang::Token raw;
clang::Lexer::getRawToken(loc, raw, sm, lang_opts);
std::string token = clang::Lexer::getSpelling(raw, sm, lang_opts);
std::string new_token = std::regex_replace(token, std::regex("([a-z])([A-Z])"), "$1_$2");
std::transform(new_token.begin(), new_token.end(), new_token.begin(), ::tolower);
// if location is a record check to see if it is callable
// and add `_fn` to the end
if (is_callable_record && !match_string(new_token.c_str(), ".*_fn"))
{
new_token += "_fn";
}
if (new_token == "null")
new_token = "nothing";
if (new_token == "void")
{
return true;
}
if (token == new_token)
{
return true;
}
rewriter.ReplaceText(loc, token.length(), new_token);
if (!queit_option.getValue())
{
llvm::errs() << "RENAME: " << token << " to " << new_token;
llvm::errs() << "\n";
emitDiagnosticNote("renamed token", loc);
}
return true;
}
bool reason(char const* r)
{
llvm::errs() << r << "\n";
return true;
}
public:
ast_visitor(clang::Rewriter& r)
: rewriter(r)
{ }
bool VisitTypeLoc(clang::TypeLoc t)
{
if (changed_locations.count(t.getLocStart().getRawEncoding()))
return true;
auto& sm = rewriter.getSourceMgr();
if (!isSourceLocationInProjectFile(sm, t.getLocStart())) return true;//reason("NOT_IN_PROJECT");
clang::QualType type = t.getType();
if (clang::TagDecl* tag_decl = type->getAsTagDecl())
{
if (auto* cxx_decl = type->getAsCXXRecordDecl())
{
if (cxx_decl->getKind() == clang::Decl::TemplateTemplateParm)
{
return true;
}
if (cxx_decl->getKind() == clang::Decl::ClassTemplateSpecialization)
{
if (auto *t_decl = static_cast<clang::ClassTemplateSpecializationDecl*>(cxx_decl)
->getSpecializedTemplate())
{
if (isPseudoConcept(t_decl)) return true;
if (!isSourceLocationInProjectFile(sm, t_decl->getLocStart())) return true;
}
}
}
if (!isSourceLocationInProjectFile(sm, tag_decl->getLocStart())) return true;
if (isPseudoConcept(tag_decl)) return true;
}
else if (clang::TemplateSpecializationType::classof(type.getTypePtr()))
{
if (auto* tmp_decl = type->getAs<clang::TemplateSpecializationType>()
->getTemplateName().getAsTemplateDecl())
{
if (auto* tmpd_decl = tmp_decl->getTemplatedDecl())
{
if (isPseudoConcept(tmpd_decl)) return true;
if (!isSourceLocationInProjectFile(sm, tmpd_decl->getLocStart())) return true;
}
}
}
if (
!isCanonicalName(t)
&& !isInterestingUseOfTypeAlias(type.getTypePtr())
) return true;//reason("NOT_CANONICAL_NAME");
auto sr = t.getSourceRange();
if (sr.isInvalid()) return reason("INVALID_TYPE_LOC_SOURCE_RANGE");
// emitDiagnosticNote("Found TypeLoc", t.getSourceRange());
renameToken(t.getLocStart(), isCallableRecord(type));
return true;
}
bool VisitTagDecl(clang::TagDecl* decl)
{
auto& sm = rewriter.getSourceMgr();
if (!isSourceLocationInProjectFile(sm, decl->getLocStart()))
return true;
if (isPseudoConcept(decl)) return true;
if (decl->getKind() == clang::Decl::ClassTemplateSpecialization)
{
if (auto *t_decl = static_cast<clang::ClassTemplateSpecializationDecl*>(decl)
->getSpecializedTemplate())
{
if (isPseudoConcept(t_decl)) return true;
if (!isSourceLocationInProjectFile(sm, t_decl->getLocStart())) return true;
}
}
// emitDiagnosticNote("Found TagDecl", decl->getSourceRange());
renameToken(decl->getLocation(), isCallableRecord(decl));
return true;
}
bool VisitTypeAliasDecl(clang::TypeAliasDecl const* decl)
{
auto& sm = rewriter.getSourceMgr();
if (!isSourceLocationInProjectFile(sm, decl->getLocStart()))
return true;
if (decl->getDeclContext()->isNamespace())
{
// emitDiagnosticNote("Found TypeAliasDecl", decl->getSourceRange());
renameToken(decl->getLocation());
}
return true;
}
bool VisitFunctionDecl(clang::FunctionDecl* decl)
{
auto& sm = rewriter.getSourceMgr();
if (decl->isOverloadedOperator())
return true;
if (decl->getKind() == clang::Decl::CXXDestructor)
return true;
if (!isSourceLocationInProjectFile(sm, decl->getLocStart()))
return true;
// emitDiagnosticNote("Found TagDecl", decl->getSourceRange());
renameToken(decl->getLocation());
return true;
}
bool VisitVarDecl(clang::VarDecl* decl)
{
auto& sm = rewriter.getSourceMgr();
if (!isSourceLocationInProjectFile(sm, decl->getLocStart()))
return true;
if (decl->getName().size() == 0)
return true;
// emitDiagnosticNote("Found VarDecl", decl->getSourceRange());
renameToken(decl->getLocation());
return true;
}
bool VisitDeclRefExpr(clang::DeclRefExpr* expr)
{
auto& sm = rewriter.getSourceMgr();
if (!isSourceLocationInProjectFile(sm, expr->getLocStart()))
return true;
auto* value_decl = expr->getDecl();
if (!value_decl
||!isSourceLocationInProjectFile(sm, value_decl->getLocStart())
) return true;
if (auto* function_decl = value_decl->getAsFunction())
{
if (function_decl->isOverloadedOperator()) return true;
}
auto name_info = expr->getNameInfo();
if (name_info.getName().getAsString().size() == 0)
return true;
// emitDiagnosticNote("Found DeclRefExpr", expr->getSourceRange());
renameToken(name_info.getLoc());
return true;
}
};
class ast_consumer : public clang::ASTConsumer
{
ast_visitor visitor;
public:
ast_consumer(clang::Rewriter& r)
: visitor(r)
{ }
bool HandleTopLevelDecl(clang::DeclGroupRef d) override
{
for (auto i : d)
{
visitor.TraverseDecl(i);
// i->dump();
}
return true;
}
};
class front_end_action : public clang::ASTFrontendAction
{
clang::Rewriter rewriter;
public:
front_end_action() { }
~front_end_action()
{
if (overwrite_option.getValue())
{
llvm::errs() << "Overwriting files...\n";
rewriter.overwriteChangedFiles();
llvm::errs() << "Overwrite complete.\n";
}
}
void EndSourceFileAction() override
{
}
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance& ci, llvm::StringRef file) override
{
rewriter.setSourceMgr(ci.getSourceManager(), ci.getLangOpts());
return llvm::make_unique<ast_consumer>(rewriter);
}
};
int main(int argc, const char** argv)
{
tooling::CommonOptionsParser opts(argc, argv, option_category);
tooling::ClangTool tool(opts.getCompilations(), opts.getSourcePathList());
return tool.run(tooling::newFrontendActionFactory<front_end_action>().get());
}
@ricejasonf
Copy link
Author

This isn't exactly iron clad. Some stuff gets missed. For those I settled for manual changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment