Index: clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clangd/refactor/tweaks/CMakeLists.txt +++ clangd/refactor/tweaks/CMakeLists.txt @@ -13,6 +13,7 @@ # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT SwapIfBranches.cpp + ClangTidy.cpp LINK_LIBS clangAST Index: clangd/refactor/tweaks/ClangTidy.cpp =================================================================== --- /dev/null +++ clangd/refactor/tweaks/ClangTidy.cpp @@ -0,0 +1,155 @@ +//===--- ClangTidy.cpp -------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "ClangdUnit.h" +#include "refactor/Tweak.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include "../clang-tidy/ClangTidyDiagnosticConsumer.h" +#include "../clang-tidy/modernize/UseUsingCheck.h" +#include "../clang-tidy/modernize/UseAutoCheck.h" + +namespace clang { +namespace clangd { +namespace { + +bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) { + return Loc.isValid() && M.isWrittenInMainFile(M.getFileLoc(Loc)); +} + +struct StoreClangTidyDiags : public DiagnosticConsumer { + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override { + assert(Info.hasSourceManager() && "Diagnostic must have SourceManager"); + if (Info.getNumFixItHints() > 0) + DiagsWithFixIt.push_back(Info); + } + std::vector DiagsWithFixIt; +}; + +/// An **experimental** interface for clang-tidy-based Tweaks, which is used to +/// prototype new tweaks (without reinventing the wheel). +/// The tweak will run a clang-tidy check over on an AST node under the cursor. +/// It is designed for simple refactoring-like clang-tidy checks. +/// +/// The tweak relies solely on the check's implementation, we don't have much +/// flexibility: +/// - only checks whose matcheres match exactly the selected node will work +/// - every check being added needs to be carefully reviewed to meet the +/// **fast** constraint of `prepare` +/// - no context-sensitive titles are supported +/// - checks don't see any preprocessor events +/// - no check options are supported yet +class ClangTidyTweak : public Tweak { +public: + ClangTidyTweak() + : DiagsEng(new clang::DiagnosticIDs(), new clang::DiagnosticOptions(), + &ClangTidyDiags, /*ShouldOwnClient=*/false) {} + + virtual std::unique_ptr + createCheck(tidy::ClangTidyContext *) = 0; + + bool prepare(const Selection &Inputs) override; + + Expected apply(const Selection &Inputs) override; + +protected: + StoreClangTidyDiags ClangTidyDiags; + DiagnosticsEngine DiagsEng; // Extend the lifetime of Diagnostics. +}; + +bool ClangTidyTweak::prepare(const Selection &Inputs) { + if (!Inputs.ASTSelection.commonAncestor()) + return false; + if (Inputs.AST.getASTContext().getDiagnostics().hasErrorOccurred()) + return false; + DiagsEng.setSourceManager(&Inputs.AST.getASTContext().getSourceManager()); + tidy::ClangTidyContext CTContext( + llvm::make_unique( + tidy::ClangTidyGlobalOptions(), + tidy::ClangTidyOptions::getDefaults())); + CTContext.setDiagnosticsEngine(&DiagsEng); + CTContext.setASTContext(&Inputs.AST.getASTContext()); + + auto Check = createCheck(&CTContext); + ast_matchers::MatchFinder CTFinder; + Check->registerMatchers(&CTFinder); + // Run the check on the AST node under the cursor. + CTFinder.match(Inputs.ASTSelection.commonAncestor()->ASTNode, + Inputs.AST.getASTContext()); + return !ClangTidyDiags.DiagsWithFixIt.empty(); +} + +Expected +ClangTidyTweak::apply(const Selection &Inputs) { + tooling::Replacements FixReplacements; + for (const auto &Diag : ClangTidyDiags.DiagsWithFixIt) { + for (auto &FixIt : Diag.getFixItHints()) { + if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), + Diag.getSourceManager())) + continue; + tooling::Replacement Fix(Diag.getSourceManager(), FixIt.RemoveRange, + FixIt.CodeToInsert); + + llvm::Error Err = FixReplacements.add(Fix); + if (Err) + return std::move(Err); + } + } + return FixReplacements; +} + +/// Convert typedef to using. +/// Before: +/// typedef int Foo; +/// ^^^^^^^ ^^^ +/// After: +/// using Foo = int; +class ConvertTypedef : public ClangTidyTweak { +public: + const char *id() const override final; + + std::unique_ptr + createCheck(tidy::ClangTidyContext *Context) override { + return llvm::make_unique( + "modernize-use-using", Context); + } + + std::string title() const override { return "Convert typedef to using"; } +}; + +/// Use auto. +/// Before: +/// [[std::vector::iterator I = my_container.begin();]] +/// After +/// auto I = my_container.begin(); +class UseAuto : public ClangTidyTweak { +public: + const char *id() const override final; + + std::unique_ptr + createCheck(tidy::ClangTidyContext *Context) override { + return llvm::make_unique( + "modernize-use-auto", Context); + } + + std::string title() const override { return "Covert type to auto"; } +}; + +REGISTER_TWEAK(ConvertTypedef); +REGISTER_TWEAK(UseAuto); + +} // namespace +} // namespace clangd +} // namespace clang Index: unittests/clangd/TweakTests.cpp =================================================================== --- unittests/clangd/TweakTests.cpp +++ unittests/clangd/TweakTests.cpp @@ -185,6 +185,23 @@ )cpp"); } +TEST(TweakTest, ConvertTypedef) { + llvm::StringLiteral ID = "ConvertTypedef"; + checkAvailable(ID, R"cpp(^t^y^p^e^d^e^f^ int ^F^o^o;)cpp"); + llvm::StringLiteral Input = "^typedef int Foo;"; + llvm::StringLiteral Output = "using Foo = int;"; + checkTransform(ID, Input, Output); +} + +TEST(TweakTest, UseAuto) { + llvm::StringLiteral ID = "UseAuto"; + llvm::StringLiteral Input = + "void f() { [[unsigned c = static_cast(1);]] }"; + llvm::StringLiteral Output = + "void f() { auto c = static_cast(1); }"; + checkTransform(ID, Input, Output); +} + } // namespace } // namespace clangd } // namespace clang