forked from OSchip/llvm-project
[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:
parent
0b44bb8d40
commit
018066d947
|
@ -22,6 +22,7 @@ add_clang_library(clangDaemonTweaks OBJECT
|
|||
ExtractFunction.cpp
|
||||
ExtractVariable.cpp
|
||||
ObjCLocalizeStringLiteral.cpp
|
||||
PopulateSwitch.cpp
|
||||
RawStringLiteral.cpp
|
||||
RemoveUsingNamespace.cpp
|
||||
SwapIfBranches.cpp
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue