Index: clang-query/CMakeLists.txt =================================================================== --- clang-query/CMakeLists.txt +++ clang-query/CMakeLists.txt @@ -6,6 +6,9 @@ add_clang_library(clangQuery Query.cpp QueryParser.cpp + QueryReplace.cpp + QueryReplace.h + LINK_LIBS clangAST @@ -16,3 +19,4 @@ ) add_subdirectory(tool) +add_subdirectory(replace-tool) Index: clang-query/QueryReplace.h =================================================================== --- /dev/null +++ clang-query/QueryReplace.h @@ -0,0 +1,58 @@ +//===--- ReplaceSpec.h - clang-query-replace ------------------------*- C++ +//-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERYREPLACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERYREPLACE_H + +#include +#include +#include +#include "clang/AST/ASTConsumer.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorOr.h" + +namespace clang { +namespace query { +/// \brief Describes an AST query and replace operation. +struct QueryReplaceSpec { + static llvm::ErrorOr parseFromJSON(llvm::StringRef Spec); + /// \brief clang-query style matcher expression + std::string Query; + + /// \brief Identifier of the bound node to replace + std::string FromId; + + /// \brief Replacement text. %"identifier" will be substituted by the text of + /// an identifier. + std::string ToTemplate; +}; + +class QueryReplaceTool { +public: + QueryReplaceTool( + std::map &FileToReplaces); + void addOperation(QueryReplaceSpec &spec); + std::unique_ptr newASTConsumer(); + +private: + struct ReplaceOp { + ast_matchers::dynamic::DynTypedMatcher Matcher; + std::unique_ptr Callback; + }; + std::vector Operations; + + tooling::ASTMatchRefactorer Refactorer; +}; +} +} +#endif Index: clang-query/QueryReplace.cpp =================================================================== --- /dev/null +++ clang-query/QueryReplace.cpp @@ -0,0 +1,73 @@ +#include "QueryReplace.h" +#include "QueryParser.h" +#include "QuerySession.h" + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/YAMLTraits.h" + +using clang::query::QueryReplaceSpec; +using clang::ast_matchers::MatchFinder; +using clang::ast_type_traits::DynTypedNode; +using clang::tooling::ReplaceNodeWithTemplate; +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, clang::query::QueryReplaceSpec &Op) { + IO.mapRequired("Query", Op.Query); + IO.mapRequired("FromId", Op.FromId); + IO.mapRequired("ToTemplate", Op.ToTemplate); + } +}; +} +} + +namespace clang { +namespace query { + +llvm::ErrorOr QueryReplaceSpec::parseFromJSON( + StringRef Spec) { + llvm::yaml::Input Input(Spec); + QueryReplaceSpec Op; + Input >> Op; + if (Input.error()) return Input.error(); + return Op; +} + +QueryReplaceTool::QueryReplaceTool( + std::map &FileToReplaces) + : Refactorer(FileToReplaces) {} + +void QueryReplaceTool::addOperation(clang::query::QueryReplaceSpec &Spec) { + ast_matchers::dynamic::Diagnostics Diag; + // FIXME: Support query sessions (for interactive use). + auto Matcher = ast_matchers::dynamic::Parser::parseMatcherExpression( + Spec.Query, nullptr, nullptr, &Diag); + if (!Matcher) { + llvm::errs() << "Cannot parse Query:"; + Diag.printToStreamFull(llvm::errs()); + llvm::errs() << "\n"; + } + + Operations.emplace_back( + ReplaceOp{Matcher.getValue(), llvm::make_unique( + Spec.FromId, Spec.ToTemplate)}); + + Refactorer.addDynamicMatcher(Operations.back().Matcher, + Operations.back().Callback.get()); +} + +std::unique_ptr QueryReplaceTool::newASTConsumer() { + return Refactorer.newASTConsumer(); +} +} +} Index: clang-query/replace-tool/CMakeLists.txt =================================================================== --- /dev/null +++ clang-query/replace-tool/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query-replace ClangQueryReplace.cpp) +target_link_libraries(clang-query-replace + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangQuery + clangTooling + ) + +install(TARGETS clang-query-replace RUNTIME DESTINATION bin) \ No newline at end of file Index: clang-query/replace-tool/ClangQueryReplace.cpp =================================================================== --- /dev/null +++ clang-query/replace-tool/ClangQueryReplace.cpp @@ -0,0 +1,86 @@ +//===---- ClangQueryReplace.cpp - clang-query-replace tool ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool automatically modifies source files based on AST matchers. +// +// A given AST matcher is applied to the source, and a bound node in +// this matcher is replaced by the result of substituting the text +// corresponding to other bound nodes into a string template. +// +// For example: +// clang-query-replace --spec < +#include +#include + +using namespace clang; +using namespace clang::query; +using namespace llvm; + +using std::error_code; +using clang::tooling::CommonOptionsParser; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangQueryCategory("clang-query-replace options"); + +static cl::list Commands("spec", + cl::desc("YAML action specification"), + cl::value_desc("spec"), + cl::cat(ClangQueryCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + CommonOptionsParser OptionsParser(argc, argv, ClangQueryCategory); + if (Commands.empty()) { + llvm::errs() << "Must specify at least one --spec \n"; + return 1; + } + + tooling::RefactoringTool Refactor(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + QueryReplaceTool Tool(Refactor.getReplacements()); + for (auto &SpecYaml : Commands) { + llvm::ErrorOr Spec = + QueryReplaceSpec::parseFromJSON(SpecYaml); + if (error_code EC = Spec.getError()) { + llvm::errs() << " Error " << EC.message() + << " while parsing spec: " << SpecYaml << "\n"; + return 1; + } + Tool.addOperation(*Spec); + } + + return Refactor.runAndSave(tooling::newFrontendActionFactory(&Tool).get()); +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -46,6 +46,7 @@ clang-include-fixer clang-move clang-query + clang-query-replace clang-rename clang-reorder-fields clang-tidy @@ -56,11 +57,9 @@ # Unit tests ExtraToolsUnitTests ) - add_lit_testsuite(check-clang-tools "Running the Clang extra tools' regression tests" ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CLANG_TOOLS_TEST_DEPS} ARGS ${CLANG_TOOLS_TEST_EXTRA_ARGS} ) set_target_properties(check-clang-tools PROPERTIES FOLDER "Clang extra tools' tests") - Index: test/clang-query/replacement.c =================================================================== --- /dev/null +++ test/clang-query/replacement.c @@ -0,0 +1,6 @@ +// RUN: clang-query-replace --spec '{Query: "varDecl(hasName(\"version\"), +// hasInitializer(integerLiteral().bind(\"init\"))).bind(\"decl\")", FromId: +// "decl", ToTemplate: "string version = \"$$-${init}\""}' %s -- && FileCheck %s +// < %; +int version = 4; +// CHECK: string version = "$-4";