| //===--- Tweak.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 "Tweak.h" |
| #include "FeatureModule.h" |
| #include "SourceCode.h" |
| #include "index/Index.h" |
| #include "support/Logger.h" |
| #include "support/Path.h" |
| #include "llvm/ADT/None.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/Registry.h" |
| #include <functional> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::clangd::Tweak>) |
| |
| namespace clang { |
| namespace clangd { |
| |
| /// A handy typedef to save some typing. |
| typedef llvm::Registry<Tweak> TweakRegistry; |
| |
| namespace { |
| /// Asserts invariants on TweakRegistry. No-op with assertion disabled. |
| void validateRegistry() { |
| #ifndef NDEBUG |
| llvm::StringSet<> Seen; |
| for (const auto &E : TweakRegistry::entries()) { |
| // REGISTER_TWEAK ensures E.getName() is equal to the tweak class name. |
| // We check that id() matches it. |
| assert(E.instantiate()->id() == E.getName() && |
| "id should be equal to class name"); |
| assert(Seen.try_emplace(E.getName()).second && "duplicate check id"); |
| } |
| #endif |
| } |
| |
| std::vector<std::unique_ptr<Tweak>> |
| getAllTweaks(const FeatureModuleSet *Modules) { |
| std::vector<std::unique_ptr<Tweak>> All; |
| for (const auto &E : TweakRegistry::entries()) |
| All.emplace_back(E.instantiate()); |
| if (Modules) { |
| for (auto &M : *Modules) |
| M.contributeTweaks(All); |
| } |
| return All; |
| } |
| } // namespace |
| |
| Tweak::Selection::Selection(const SymbolIndex *Index, ParsedAST &AST, |
| unsigned RangeBegin, unsigned RangeEnd, |
| SelectionTree ASTSelection, |
| llvm::vfs::FileSystem *FS) |
| : Index(Index), AST(&AST), SelectionBegin(RangeBegin), |
| SelectionEnd(RangeEnd), ASTSelection(std::move(ASTSelection)), FS(FS) { |
| auto &SM = AST.getSourceManager(); |
| Code = SM.getBufferData(SM.getMainFileID()); |
| Cursor = SM.getComposedLoc(SM.getMainFileID(), RangeBegin); |
| } |
| |
| std::vector<std::unique_ptr<Tweak>> |
| prepareTweaks(const Tweak::Selection &S, |
| llvm::function_ref<bool(const Tweak &)> Filter, |
| const FeatureModuleSet *Modules) { |
| validateRegistry(); |
| |
| std::vector<std::unique_ptr<Tweak>> Available; |
| for (auto &T : getAllTweaks(Modules)) { |
| if (!Filter(*T) || !T->prepare(S)) |
| continue; |
| Available.push_back(std::move(T)); |
| } |
| // Ensure deterministic order of the results. |
| llvm::sort(Available, |
| [](const std::unique_ptr<Tweak> &L, |
| const std::unique_ptr<Tweak> &R) { return L->id() < R->id(); }); |
| return Available; |
| } |
| |
| llvm::Expected<std::unique_ptr<Tweak>> |
| prepareTweak(StringRef ID, const Tweak::Selection &S, |
| const FeatureModuleSet *Modules) { |
| for (auto &T : getAllTweaks(Modules)) { |
| if (T->id() != ID) |
| continue; |
| if (!T->prepare(S)) |
| return error("failed to prepare() tweak {0}", ID); |
| return std::move(T); |
| } |
| return error("tweak ID {0} is invalid", ID); |
| } |
| |
| llvm::Expected<std::pair<Path, Edit>> |
| Tweak::Effect::fileEdit(const SourceManager &SM, FileID FID, |
| tooling::Replacements Replacements) { |
| Edit Ed(SM.getBufferData(FID), std::move(Replacements)); |
| if (auto FilePath = getCanonicalPath(SM.getFileEntryForID(FID), SM)) |
| return std::make_pair(*FilePath, std::move(Ed)); |
| return error("Failed to get absolute path for edited file: {0}", |
| SM.getFileEntryForID(FID)->getName()); |
| } |
| |
| llvm::Expected<Tweak::Effect> |
| Tweak::Effect::mainFileEdit(const SourceManager &SM, |
| tooling::Replacements Replacements) { |
| auto PathAndEdit = fileEdit(SM, SM.getMainFileID(), std::move(Replacements)); |
| if (!PathAndEdit) |
| return PathAndEdit.takeError(); |
| Tweak::Effect E; |
| E.ApplyEdits.try_emplace(PathAndEdit->first, PathAndEdit->second); |
| return E; |
| } |
| |
| } // namespace clangd |
| } // namespace clang |