[clangd] Add a tweak for filling in enumerators of a switch statement.

Add a tweak that populates an empty switch statement of an enumeration type with all of the enumerators of that type.

Before:
```
enum Color { RED, GREEN, BLUE };
void f(Color color) {
  switch (color) {}
}
```

After:
```
enum Color { RED, GREEN, BLUE };
void f(Color color) {
  switch (color) {
  case RED:
  case GREEN:
  case BLUE:
    break;
  }
}
```

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D88383
This commit is contained in:
Tadeo Kondrak 2020-09-28 13:18:24 +02:00 committed by Sam McCall
parent 0b44bb8d40
commit 018066d947
3 changed files with 237 additions and 0 deletions

View File

@ -22,6 +22,7 @@ add_clang_library(clangDaemonTweaks OBJECT
ExtractFunction.cpp
ExtractVariable.cpp
ObjCLocalizeStringLiteral.cpp
PopulateSwitch.cpp
RawStringLiteral.cpp
RemoveUsingNamespace.cpp
SwapIfBranches.cpp

View File

@ -0,0 +1,146 @@
//===--- PopulateSwitch.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
//
//===----------------------------------------------------------------------===//
//
// Tweak that populates an empty switch statement of an enumeration type with
// all of the enumerators of that type.
//
// Before:
// enum Color { RED, GREEN, BLUE };
//
// void f(Color color) {
// switch (color) {}
// }
//
// After:
// enum Color { RED, GREEN, BLUE };
//
// void f(Color color) {
// switch (color) {
// case RED:
// case GREEN:
// case BLUE:
// break;
// }
// }
//
//===----------------------------------------------------------------------===//
#include "AST.h"
#include "Selection.h"
#include "refactor/Tweak.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Core/Replacement.h"
#include <string>
namespace clang {
namespace clangd {
namespace {
class PopulateSwitch : public Tweak {
const char *id() const override;
bool prepare(const Selection &Sel) override;
Expected<Effect> apply(const Selection &Sel) override;
std::string title() const override { return "Populate switch"; }
Intent intent() const override { return Refactor; }
private:
ASTContext *ASTCtx = nullptr;
const DeclContext *DeclCtx = nullptr;
const SwitchStmt *Switch = nullptr;
const CompoundStmt *Body = nullptr;
const EnumDecl *EnumD = nullptr;
};
REGISTER_TWEAK(PopulateSwitch)
bool PopulateSwitch::prepare(const Selection &Sel) {
ASTCtx = &Sel.AST->getASTContext();
const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor();
if (!CA)
return false;
const Stmt *CAStmt = CA->ASTNode.get<Stmt>();
if (!CAStmt)
return false;
// Go up a level if we see a compound statement.
// switch (value) {}
// ^^
if (isa<CompoundStmt>(CAStmt)) {
CA = CA->Parent;
if (!CA)
return false;
CAStmt = CA->ASTNode.get<Stmt>();
if (!CAStmt)
return false;
}
DeclCtx = &CA->getDeclContext();
Switch = dyn_cast<SwitchStmt>(CAStmt);
if (!Switch)
return false;
Body = dyn_cast<CompoundStmt>(Switch->getBody());
if (!Body)
return false;
// Since we currently always insert all enumerators, don't suggest this tweak
// if the body is not empty.
if (!Body->body_empty())
return false;
const Expr *Cond = Switch->getCond();
if (!Cond)
return false;
// Ignore implicit casts, since enums implicitly cast to integer types.
Cond = Cond->IgnoreParenImpCasts();
const EnumType *EnumT = Cond->getType()->getAsAdjusted<EnumType>();
if (!EnumT)
return false;
EnumD = EnumT->getDecl();
if (!EnumD)
return false;
// If there aren't any enumerators, there's nothing to insert.
if (EnumD->enumerator_begin() == EnumD->enumerator_end())
return false;
return true;
}
Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
const SourceManager &SM = ASTCtx->getSourceManager();
SourceLocation Loc = Body->getRBracLoc();
std::string Text;
for (EnumConstantDecl *Enumerator : EnumD->enumerators()) {
Text += "case ";
Text += getQualification(*ASTCtx, DeclCtx, Loc, EnumD);
if (EnumD->isScoped()) {
Text += EnumD->getName();
Text += "::";
}
Text += Enumerator->getName();
Text += ":";
}
Text += "break;";
return Effect::mainFileEdit(
SM, tooling::Replacements(tooling::Replacement(SM, Loc, 0, Text)));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@ -2813,6 +2813,96 @@ public:
}
}
TWEAK_TEST(PopulateSwitch);
TEST_F(PopulateSwitchTest, Test) {
struct Case {
CodeContext Context;
llvm::StringRef TestSource;
llvm::StringRef ExpectedSource;
};
Case Cases[]{
{
// No enumerators
Function,
R""(enum Enum {}; ^switch ((Enum)0) {})"",
"unavailable",
},
{
// Existing enumerators in switch
Function,
R""(enum Enum {A}; ^switch ((Enum)0) {case A:break;})"",
"unavailable",
},
{
// Body not CompoundStmt
Function,
R""(enum Enum {A}; ^switch (A);)"",
"unavailable",
},
{
// Selection on switch token
Function,
R""(enum Enum {A}; ^switch (A) {})"",
R""(enum Enum {A}; switch (A) {case A:break;})"",
},
{
// Selection on switch condition
Function,
R""(enum Enum {A}; switch (^A) {})"",
R""(enum Enum {A}; switch (A) {case A:break;})"",
},
{
// Selection in switch body
Function,
R""(enum Enum {A}; switch (A) {^})"",
R""(enum Enum {A}; switch (A) {case A:break;})"",
},
{
// Scoped enumeration
Function,
R""(enum class Enum {A}; ^switch (Enum::A) {})"",
R""(enum class Enum {A}; switch (Enum::A) {case Enum::A:break;})"",
},
{
// Scoped enumeration with multiple enumerators
Function,
R""(enum class Enum {A,B}; ^switch (Enum::A) {})"",
R""(enum class Enum {A,B}; )""
R""(switch (Enum::A) {case Enum::A:case Enum::B:break;})"",
},
{
// Scoped enumerations in namespace
File,
R""(
namespace ns { enum class Enum {A}; }
void function() { ^switch (ns::Enum::A) {} }
)"",
R""(
namespace ns { enum class Enum {A}; }
void function() { switch (ns::Enum::A) {case ns::Enum::A:break;} }
)"",
},
{
// Unscoped enumerations in namespace
File,
R""(
namespace ns { enum Enum {A}; }
void function() { ^switch (ns::A) {} }
)"",
R""(
namespace ns { enum Enum {A}; }
void function() { switch (ns::A) {case ns::A:break;} }
)"",
},
};
for (const auto &Case : Cases) {
Context = Case.Context;
EXPECT_EQ(apply(Case.TestSource), Case.ExpectedSource);
}
}
} // namespace
} // namespace clangd
} // namespace clang