Created
March 19, 2016 02:01
-
-
Save ricejasonf/a62c22740888b3ee2eac to your computer and use it in GitHub Desktop.
Refactor Some CamelCase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This isn't exactly iron clad. Some stuff gets missed. For those I settled for manual changes.