|
9 | 9 | #include "RawStringLiteralCheck.h" |
10 | 10 | #include "clang/AST/ASTContext.h" |
11 | 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | +#include "clang/Basic/LangOptions.h" |
| 13 | +#include "clang/Basic/SourceManager.h" |
12 | 14 | #include "clang/Lex/Lexer.h" |
| 15 | +#include "llvm/ADT/StringRef.h" |
| 16 | +#include <optional> |
13 | 17 |
|
14 | 18 | using namespace clang::ast_matchers; |
15 | 19 |
|
@@ -66,20 +70,6 @@ bool containsDelimiter(StringRef Bytes, const std::string &Delimiter) { |
66 | 70 | : (")" + Delimiter + R"(")")) != StringRef::npos; |
67 | 71 | } |
68 | 72 |
|
69 | | -std::string asRawStringLiteral(const StringLiteral *Literal, |
70 | | - const std::string &DelimiterStem) { |
71 | | - const StringRef Bytes = Literal->getBytes(); |
72 | | - std::string Delimiter; |
73 | | - for (int I = 0; containsDelimiter(Bytes, Delimiter); ++I) { |
74 | | - Delimiter = (I == 0) ? DelimiterStem : DelimiterStem + std::to_string(I); |
75 | | - } |
76 | | - |
77 | | - if (Delimiter.empty()) |
78 | | - return (R"(R"()" + Bytes + R"lit()")lit").str(); |
79 | | - |
80 | | - return (R"(R")" + Delimiter + "(" + Bytes + ")" + Delimiter + R"(")").str(); |
81 | | -} |
82 | | - |
83 | 73 | } // namespace |
84 | 74 |
|
85 | 75 | RawStringLiteralCheck::RawStringLiteralCheck(StringRef Name, |
@@ -119,30 +109,73 @@ void RawStringLiteralCheck::registerMatchers(MatchFinder *Finder) { |
119 | 109 | stringLiteral(unless(hasParent(predefinedExpr()))).bind("lit"), this); |
120 | 110 | } |
121 | 111 |
|
| 112 | +static std::optional<StringRef> |
| 113 | +createUserDefinedSuffix(const StringLiteral *Literal, const SourceManager &SM, |
| 114 | + const LangOptions &LangOpts) { |
| 115 | + const CharSourceRange TokenRange = |
| 116 | + CharSourceRange::getTokenRange(Literal->getSourceRange()); |
| 117 | + Token T; |
| 118 | + if (Lexer::getRawToken(Literal->getBeginLoc(), T, SM, LangOpts)) |
| 119 | + return std::nullopt; |
| 120 | + const CharSourceRange CharRange = |
| 121 | + Lexer::makeFileCharRange(TokenRange, SM, LangOpts); |
| 122 | + if (T.hasUDSuffix()) { |
| 123 | + StringRef Text = Lexer::getSourceText(CharRange, SM, LangOpts); |
| 124 | + const size_t UDSuffixPos = Text.find_last_of('"'); |
| 125 | + if (UDSuffixPos == StringRef::npos) |
| 126 | + return std::nullopt; |
| 127 | + return Text.slice(UDSuffixPos + 1, Text.size()); |
| 128 | + } |
| 129 | + return std::nullopt; |
| 130 | +} |
| 131 | + |
| 132 | +static std::string createRawStringLiteral(const StringLiteral *Literal, |
| 133 | + const std::string &DelimiterStem, |
| 134 | + const SourceManager &SM, |
| 135 | + const LangOptions &LangOpts) { |
| 136 | + const StringRef Bytes = Literal->getBytes(); |
| 137 | + std::string Delimiter; |
| 138 | + for (int I = 0; containsDelimiter(Bytes, Delimiter); ++I) { |
| 139 | + Delimiter = (I == 0) ? DelimiterStem : DelimiterStem + std::to_string(I); |
| 140 | + } |
| 141 | + |
| 142 | + std::optional<StringRef> UserDefinedSuffix = |
| 143 | + createUserDefinedSuffix(Literal, SM, LangOpts); |
| 144 | + |
| 145 | + if (Delimiter.empty()) |
| 146 | + return (R"(R"()" + Bytes + R"lit()")lit" + UserDefinedSuffix.value_or("")) |
| 147 | + .str(); |
| 148 | + |
| 149 | + return (R"(R")" + Delimiter + "(" + Bytes + ")" + Delimiter + R"(")" + |
| 150 | + UserDefinedSuffix.value_or("")) |
| 151 | + .str(); |
| 152 | +} |
| 153 | + |
| 154 | +static bool compareStringLength(StringRef Replacement, |
| 155 | + const StringLiteral *Literal, |
| 156 | + const SourceManager &SM, |
| 157 | + const LangOptions &LangOpts) { |
| 158 | + return Replacement.size() <= |
| 159 | + Lexer::MeasureTokenLength(Literal->getBeginLoc(), SM, LangOpts); |
| 160 | +} |
| 161 | + |
122 | 162 | void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) { |
123 | 163 | const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("lit"); |
124 | 164 | if (Literal->getBeginLoc().isMacroID()) |
125 | 165 | return; |
126 | | - |
| 166 | + const SourceManager &SM = *Result.SourceManager; |
| 167 | + const LangOptions &LangOpts = getLangOpts(); |
127 | 168 | if (containsEscapedCharacters(Result, Literal, DisallowedChars)) { |
128 | | - std::string Replacement = asRawStringLiteral(Literal, DelimiterStem); |
| 169 | + const std::string Replacement = |
| 170 | + createRawStringLiteral(Literal, DelimiterStem, SM, LangOpts); |
129 | 171 | if (ReplaceShorterLiterals || |
130 | | - Replacement.length() <= |
131 | | - Lexer::MeasureTokenLength(Literal->getBeginLoc(), |
132 | | - *Result.SourceManager, getLangOpts())) |
133 | | - replaceWithRawStringLiteral(Result, Literal, Replacement); |
| 172 | + compareStringLength(Replacement, Literal, SM, LangOpts)) { |
| 173 | + diag(Literal->getBeginLoc(), |
| 174 | + "escaped string literal can be written as a raw string literal") |
| 175 | + << FixItHint::CreateReplacement(Literal->getSourceRange(), |
| 176 | + Replacement); |
| 177 | + } |
134 | 178 | } |
135 | 179 | } |
136 | 180 |
|
137 | | -void RawStringLiteralCheck::replaceWithRawStringLiteral( |
138 | | - const MatchFinder::MatchResult &Result, const StringLiteral *Literal, |
139 | | - StringRef Replacement) { |
140 | | - CharSourceRange CharRange = Lexer::makeFileCharRange( |
141 | | - CharSourceRange::getTokenRange(Literal->getSourceRange()), |
142 | | - *Result.SourceManager, getLangOpts()); |
143 | | - diag(Literal->getBeginLoc(), |
144 | | - "escaped string literal can be written as a raw string literal") |
145 | | - << FixItHint::CreateReplacement(CharRange, Replacement); |
146 | | -} |
147 | | - |
148 | 181 | } // namespace clang::tidy::modernize |
0 commit comments