| 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 | |