1 //===--- Tweak.cpp -----------------------------------------------*- C++-*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "Tweak.h"
9 #include "FeatureModule.h"
10 #include "SourceCode.h"
11 #include "index/Index.h"
12 #include "support/Logger.h"
13 #include "support/Path.h"
14 #include "llvm/ADT/Optional.h"
15 #include "llvm/ADT/STLExtras.h"
16 #include "llvm/ADT/StringMap.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/Support/Error.h"
19 #include "llvm/Support/Registry.h"
20 #include <functional>
21 #include <memory>
22 #include <utility>
23 #include <vector>
24 
25 LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::clangd::Tweak>)
26 
27 namespace clang {
28 namespace clangd {
29 
30 /// A handy typedef to save some typing.
31 typedef llvm::Registry<Tweak> TweakRegistry;
32 
33 namespace {
34 /// Asserts invariants on TweakRegistry. No-op with assertion disabled.
validateRegistry()35 void validateRegistry() {
36 #ifndef NDEBUG
37   llvm::StringSet<> Seen;
38   for (const auto &E : TweakRegistry::entries()) {
39     // REGISTER_TWEAK ensures E.getName() is equal to the tweak class name.
40     // We check that id() matches it.
41     assert(E.instantiate()->id() == E.getName() &&
42            "id should be equal to class name");
43     assert(Seen.try_emplace(E.getName()).second && "duplicate check id");
44   }
45 #endif
46 }
47 
48 std::vector<std::unique_ptr<Tweak>>
getAllTweaks(const FeatureModuleSet * Modules)49 getAllTweaks(const FeatureModuleSet *Modules) {
50   std::vector<std::unique_ptr<Tweak>> All;
51   for (const auto &E : TweakRegistry::entries())
52     All.emplace_back(E.instantiate());
53   if (Modules) {
54     for (auto &M : *Modules)
55       M.contributeTweaks(All);
56   }
57   return All;
58 }
59 } // namespace
60 
Selection(const SymbolIndex * Index,ParsedAST & AST,unsigned RangeBegin,unsigned RangeEnd,SelectionTree ASTSelection,llvm::vfs::FileSystem * FS)61 Tweak::Selection::Selection(const SymbolIndex *Index, ParsedAST &AST,
62                             unsigned RangeBegin, unsigned RangeEnd,
63                             SelectionTree ASTSelection,
64                             llvm::vfs::FileSystem *FS)
65     : Index(Index), AST(&AST), SelectionBegin(RangeBegin),
66       SelectionEnd(RangeEnd), ASTSelection(std::move(ASTSelection)), FS(FS) {
67   auto &SM = AST.getSourceManager();
68   Code = SM.getBufferData(SM.getMainFileID());
69   Cursor = SM.getComposedLoc(SM.getMainFileID(), RangeBegin);
70 }
71 
72 std::vector<std::unique_ptr<Tweak>>
prepareTweaks(const Tweak::Selection & S,llvm::function_ref<bool (const Tweak &)> Filter,const FeatureModuleSet * Modules)73 prepareTweaks(const Tweak::Selection &S,
74               llvm::function_ref<bool(const Tweak &)> Filter,
75               const FeatureModuleSet *Modules) {
76   validateRegistry();
77 
78   std::vector<std::unique_ptr<Tweak>> Available;
79   for (auto &T : getAllTweaks(Modules)) {
80     if (!Filter(*T) || !T->prepare(S))
81       continue;
82     Available.push_back(std::move(T));
83   }
84   // Ensure deterministic order of the results.
85   llvm::sort(Available,
86              [](const std::unique_ptr<Tweak> &L,
87                 const std::unique_ptr<Tweak> &R) { return L->id() < R->id(); });
88   return Available;
89 }
90 
91 llvm::Expected<std::unique_ptr<Tweak>>
prepareTweak(StringRef ID,const Tweak::Selection & S,const FeatureModuleSet * Modules)92 prepareTweak(StringRef ID, const Tweak::Selection &S,
93              const FeatureModuleSet *Modules) {
94   for (auto &T : getAllTweaks(Modules)) {
95     if (T->id() != ID)
96       continue;
97     if (!T->prepare(S))
98       return error("failed to prepare() tweak {0}", ID);
99     return std::move(T);
100   }
101   return error("tweak ID {0} is invalid", ID);
102 }
103 
104 llvm::Expected<std::pair<Path, Edit>>
fileEdit(const SourceManager & SM,FileID FID,tooling::Replacements Replacements)105 Tweak::Effect::fileEdit(const SourceManager &SM, FileID FID,
106                         tooling::Replacements Replacements) {
107   Edit Ed(SM.getBufferData(FID), std::move(Replacements));
108   if (auto FilePath = getCanonicalPath(SM.getFileEntryForID(FID), SM))
109     return std::make_pair(*FilePath, std::move(Ed));
110   return error("Failed to get absolute path for edited file: {0}",
111                SM.getFileEntryRefForID(FID)->getName());
112 }
113 
114 llvm::Expected<Tweak::Effect>
mainFileEdit(const SourceManager & SM,tooling::Replacements Replacements)115 Tweak::Effect::mainFileEdit(const SourceManager &SM,
116                             tooling::Replacements Replacements) {
117   auto PathAndEdit = fileEdit(SM, SM.getMainFileID(), std::move(Replacements));
118   if (!PathAndEdit)
119     return PathAndEdit.takeError();
120   Tweak::Effect E;
121   E.ApplyEdits.try_emplace(PathAndEdit->first, PathAndEdit->second);
122   return E;
123 }
124 
125 } // namespace clangd
126 } // namespace clang
127