brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.9 KiB · c26fc21 Raw
199 lines · cpp
1//===-- TweakTesting.cpp ------------------------------------------------*-===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "TweakTesting.h"10 11#include "SourceCode.h"12#include "TestTU.h"13#include "refactor/Tweak.h"14#include "llvm/Support/Error.h"15#include "gmock/gmock.h"16#include "gtest/gtest.h"17#include <optional>18#include <string>19 20namespace clang {21namespace clangd {22namespace {23using Context = TweakTest::CodeContext;24 25std::pair<llvm::StringRef, llvm::StringRef> wrapping(Context Ctx) {26  switch (Ctx) {27  case TweakTest::File:28    return {"", ""};29  case TweakTest::Function:30    return {"void wrapperFunction(){\n", "\n}"};31  case TweakTest::Expression:32    return {"auto expressionWrapper(){return\n", "\n;}"};33  }34  llvm_unreachable("Unknown TweakTest::CodeContext enum");35}36 37std::string wrap(Context Ctx, llvm::StringRef Inner) {38  auto Wrapping = wrapping(Ctx);39  return (Wrapping.first + Inner + Wrapping.second).str();40}41 42llvm::StringRef unwrap(Context Ctx, llvm::StringRef Outer) {43  auto Wrapping = wrapping(Ctx);44  // Unwrap only if the code matches the expected wrapping.45  // Don't allow the begin/end wrapping to overlap!46  if (Outer.starts_with(Wrapping.first) && Outer.ends_with(Wrapping.second) &&47      Outer.size() >= Wrapping.first.size() + Wrapping.second.size())48    return Outer.drop_front(Wrapping.first.size())49        .drop_back(Wrapping.second.size());50  return Outer;51}52 53llvm::Annotations::Range rangeOrPoint(const llvm::Annotations &A) {54  if (A.points().size() != 0) {55    assert(A.ranges().size() == 0 &&56           "both a cursor point and a selection range were specified");57    return {A.point(), A.point()};58  }59  return A.range();60}61 62// Prepare and apply the specified tweak based on the selection in Input.63// Returns std::nullopt if and only if prepare() failed.64std::optional<llvm::Expected<Tweak::Effect>>65applyTweak(ParsedAST &AST, llvm::Annotations::Range Range, StringRef TweakID,66           const SymbolIndex *Index, llvm::vfs::FileSystem *FS) {67  std::optional<llvm::Expected<Tweak::Effect>> Result;68  SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Range.Begin,69                            Range.End, [&](SelectionTree ST) {70                              Tweak::Selection S(Index, AST, Range.Begin,71                                                 Range.End, std::move(ST), FS);72                              if (auto T = prepareTweak(TweakID, S, nullptr)) {73                                Result = (*T)->apply(S);74                                return true;75                              } else {76                                llvm::consumeError(T.takeError());77                                return false;78                              }79                            });80  return Result;81}82 83} // namespace84 85std::string TweakTest::apply(llvm::StringRef MarkedCode,86                             llvm::StringMap<std::string> *EditedFiles) const {87  std::string WrappedCode = wrap(Context, MarkedCode);88  llvm::Annotations Input(WrappedCode);89  TestTU TU;90  TU.Filename = std::string(FileName);91  TU.HeaderCode = Header;92  TU.AdditionalFiles = std::move(ExtraFiles);93  TU.Code = std::string(Input.code());94  TU.ExtraArgs = ExtraArgs;95  ParsedAST AST = TU.build();96 97  auto Result = applyTweak(98      AST, rangeOrPoint(Input), TweakID, Index.get(),99      &AST.getSourceManager().getFileManager().getVirtualFileSystem());100  if (!Result)101    return "unavailable";102  if (!*Result)103    return "fail: " + llvm::toString(Result->takeError());104  const auto &Effect = **Result;105  if ((*Result)->ShowMessage)106    return "message:\n" + *Effect.ShowMessage;107  if (Effect.ApplyEdits.empty())108    return "no effect";109 110  std::string EditedMainFile;111  for (auto &It : Effect.ApplyEdits) {112    auto NewText = It.second.apply();113    if (!NewText)114      return "bad edits: " + llvm::toString(NewText.takeError());115    llvm::StringRef Unwrapped = unwrap(Context, *NewText);116    if (It.first() == testPath(TU.Filename))117      EditedMainFile = std::string(Unwrapped);118    else {119      if (!EditedFiles)120        ADD_FAILURE() << "There were changes to additional files, but client "121                         "provided a nullptr for EditedFiles.";122      else123        EditedFiles->insert_or_assign(It.first(), Unwrapped.str());124    }125  }126  return EditedMainFile;127}128 129bool TweakTest::isAvailable(WrappedAST &AST,130                            llvm::Annotations::Range Range) const {131  // Adjust range for wrapping offset.132  Range.Begin += AST.second;133  Range.End += AST.second;134  auto Result = applyTweak(135      AST.first, Range, TweakID, Index.get(),136      &AST.first.getSourceManager().getFileManager().getVirtualFileSystem());137  // We only care if prepare() succeeded, but must handle Errors.138  if (Result && !*Result)139    consumeError(Result->takeError());140  return Result.has_value();141}142 143TweakTest::WrappedAST TweakTest::build(llvm::StringRef Code) const {144  TestTU TU;145  TU.Filename = std::string(FileName);146  TU.HeaderCode = Header;147  TU.Code = wrap(Context, Code);148  TU.ExtraArgs = ExtraArgs;149  TU.AdditionalFiles = std::move(ExtraFiles);150  return {TU.build(), wrapping(Context).first.size()};151}152 153std::string TweakTest::decorate(llvm::StringRef Code, unsigned Point) {154  return (Code.substr(0, Point) + "^" + Code.substr(Point)).str();155}156 157std::string TweakTest::decorate(llvm::StringRef Code,158                                llvm::Annotations::Range Range) {159  return (Code.substr(0, Range.Begin) + "[[" +160          Code.substr(Range.Begin, Range.End - Range.Begin) + "]]" +161          Code.substr(Range.End))162      .str();163}164 165// TODO: Reuse more code between TweakTest::apply() and166// TweakWorkspaceTest::apply().167TweakResult168TweakWorkspaceTest::apply(StringRef InvocationFile,169                          llvm::Annotations::Range InvocationRange) {170  auto AST = Workspace.openFile(InvocationFile);171  if (!AST) {172    ADD_FAILURE() << "No file '" << InvocationFile << "' in workspace";173    return TweakResult{"failed to setup"};174  }175 176  auto Index = Workspace.index();177  auto Result = applyTweak(178      *AST, InvocationRange, TweakID, Index.get(),179      &AST->getSourceManager().getFileManager().getVirtualFileSystem());180  if (!Result)181    return TweakResult{"unavailable"};182  if (!*Result)183    return TweakResult{"fail: " + llvm::toString(Result->takeError())};184  const auto &Effect = **Result;185  if ((*Result)->ShowMessage)186    return TweakResult{"message:\n" + *Effect.ShowMessage};187 188  TweakResult Retval{"success"};189  for (auto &It : Effect.ApplyEdits) {190    auto NewText = It.second.apply();191    if (!NewText)192      return TweakResult{"bad edits: " + llvm::toString(NewText.takeError())};193    Retval.EditedFiles.insert_or_assign(It.first(), *NewText);194  }195  return Retval;196}197} // namespace clangd198} // namespace clang199