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