Skip to content

Commit 43d0b1c

Browse files
committed
[clangd] Reject renames to non-identifier characters
Differential Revision: https://reviews.llvm.org/D98424
1 parent a92693d commit 43d0b1c

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

clang-tools-extra/clangd/refactor/Rename.cpp

+28-4
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@
2222
#include "clang/AST/DeclTemplate.h"
2323
#include "clang/AST/ParentMapContext.h"
2424
#include "clang/AST/Stmt.h"
25+
#include "clang/Basic/CharInfo.h"
2526
#include "clang/Basic/LLVM.h"
2627
#include "clang/Basic/SourceLocation.h"
2728
#include "clang/Tooling/Syntax/Tokens.h"
2829
#include "llvm/ADT/None.h"
2930
#include "llvm/ADT/STLExtras.h"
31+
#include "llvm/ADT/StringExtras.h"
3032
#include "llvm/Support/Casting.h"
3133
#include "llvm/Support/Error.h"
3234
#include "llvm/Support/FormatVariadic.h"
35+
#include "llvm/Support/JSON.h"
3336
#include <algorithm>
3437

3538
namespace clang {
@@ -178,8 +181,7 @@ enum class ReasonToReject {
178181
UnsupportedSymbol,
179182
AmbiguousSymbol,
180183

181-
// name validation.
182-
RenameToKeywords,
184+
// name validation. FIXME: reconcile with InvalidName
183185
SameName,
184186
};
185187

@@ -241,8 +243,6 @@ llvm::Error makeError(ReasonToReject Reason) {
241243
return "symbol is not a supported kind (e.g. namespace, macro)";
242244
case ReasonToReject::AmbiguousSymbol:
243245
return "there are multiple symbols at the given location";
244-
case ReasonToReject::RenameToKeywords:
245-
return "the chosen name is a keyword";
246246
case ReasonToReject::SameName:
247247
return "new name is the same as the old name";
248248
}
@@ -437,6 +437,7 @@ struct InvalidName {
437437
enum Kind {
438438
Keywords,
439439
Conflict,
440+
BadIdentifier,
440441
};
441442
Kind K;
442443
std::string Details;
@@ -447,6 +448,8 @@ std::string toString(InvalidName::Kind K) {
447448
return "Keywords";
448449
case InvalidName::Conflict:
449450
return "Conflict";
451+
case InvalidName::BadIdentifier:
452+
return "BadIdentifier";
450453
}
451454
llvm_unreachable("unhandled InvalidName kind");
452455
}
@@ -459,12 +462,31 @@ llvm::Error makeError(InvalidName Reason) {
459462
Reason.Details);
460463
case InvalidName::Conflict:
461464
return llvm::formatv("conflict with the symbol in {0}", Reason.Details);
465+
case InvalidName::BadIdentifier:
466+
return llvm::formatv("the chosen name \"{0}\" is not a valid identifier",
467+
Reason.Details);
462468
}
463469
llvm_unreachable("unhandled InvalidName kind");
464470
};
465471
return error("invalid name: {0}", Message(Reason));
466472
}
467473

474+
static bool mayBeValidIdentifier(llvm::StringRef Ident) {
475+
assert(llvm::json::isUTF8(Ident));
476+
if (Ident.empty())
477+
return false;
478+
// We don't check all the rules for non-ascii characters (most are allowed).
479+
bool AllowDollar = true; // lenient
480+
if (llvm::isASCII(Ident.front()) &&
481+
!isIdentifierHead(Ident.front(), AllowDollar))
482+
return false;
483+
for (char C : Ident) {
484+
if (llvm::isASCII(C) && !isIdentifierBody(C, AllowDollar))
485+
return false;
486+
}
487+
return true;
488+
}
489+
468490
// Check if we can rename the given RenameDecl into NewName.
469491
// Return details if the rename would produce a conflict.
470492
llvm::Optional<InvalidName> checkName(const NamedDecl &RenameDecl,
@@ -476,6 +498,8 @@ llvm::Optional<InvalidName> checkName(const NamedDecl &RenameDecl,
476498
llvm::Optional<InvalidName> Result;
477499
if (isKeyword(NewName, ASTCtx.getLangOpts()))
478500
Result = InvalidName{InvalidName::Keywords, NewName.str()};
501+
else if (!mayBeValidIdentifier(NewName))
502+
Result = InvalidName{InvalidName::BadIdentifier, NewName.str()};
479503
else {
480504
// Name conflict detection.
481505
// Function conflicts are subtle (overloading), so ignore them.

clang-tools-extra/clangd/unittests/RenameTests.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,21 @@ TEST(RenameTest, PrepareRename) {
12401240
testing::HasSubstr("keyword"));
12411241
EXPECT_THAT(Tracer.takeMetric("rename_name_invalid", "Keywords"),
12421242
ElementsAre(1));
1243+
1244+
for (std::string BadIdent : {"foo!bar", "123foo", "😀@"}) {
1245+
Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
1246+
/*NewName=*/BadIdent, {});
1247+
EXPECT_FALSE(Results);
1248+
EXPECT_THAT(llvm::toString(Results.takeError()),
1249+
testing::HasSubstr("identifier"));
1250+
EXPECT_THAT(Tracer.takeMetric("rename_name_invalid", "BadIdentifier"),
1251+
ElementsAre(1));
1252+
}
1253+
for (std::string GoodIdent : {"fooBar", "__foo$", "😀"}) {
1254+
Results = runPrepareRename(Server, FooCCPath, FooCC.point(),
1255+
/*NewName=*/GoodIdent, {});
1256+
EXPECT_TRUE(bool(Results));
1257+
}
12431258
}
12441259

12451260
TEST(CrossFileRenameTests, DirtyBuffer) {

0 commit comments

Comments
 (0)