1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
//===--- ObjCPropertyAttributeOrderFixer.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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
/// adjusts the order of attributes in an ObjC `@property(...)` declaration,
/// depending on the style.
///
//===----------------------------------------------------------------------===//
#include "ObjCPropertyAttributeOrderFixer.h"
#include <algorithm>
namespace clang {
namespace format {
ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer(
const Environment &Env, const FormatStyle &Style)
: TokenAnalyzer(Env, Style) {
// Create an "order priority" map to use to sort properties.
unsigned Index = 0;
for (const auto &Property : Style.ObjCPropertyAttributeOrder)
SortOrderMap[Property] = Index++;
}
struct ObjCPropertyEntry {
StringRef Attribute; // eg, `readwrite`
StringRef Value; // eg, the `foo` of the attribute `getter=foo`
};
void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes(
const SourceManager &SourceMgr, tooling::Replacements &Fixes,
const FormatToken *BeginTok, const FormatToken *EndTok) {
assert(BeginTok);
assert(EndTok);
assert(EndTok->Previous);
// If there are zero or one tokens, nothing to do.
if (BeginTok == EndTok || BeginTok->Next == EndTok)
return;
// Use a set to sort attributes and remove duplicates.
std::set<unsigned> Ordinals;
// Create a "remapping index" on how to reorder the attributes.
SmallVector<int> Indices;
// Collect the attributes.
SmallVector<ObjCPropertyEntry> PropertyAttributes;
bool HasDuplicates = false;
int Index = 0;
for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) {
assert(Tok);
if (Tok->is(tok::comma)) {
// Ignore the comma separators.
continue;
}
// Most attributes look like identifiers, but `class` is a keyword.
if (!Tok->isOneOf(tok::identifier, tok::kw_class)) {
// If we hit any other kind of token, just bail.
return;
}
const StringRef Attribute{Tok->TokenText};
StringRef Value;
// Also handle `getter=getFoo` attributes.
// (Note: no check needed against `EndTok`, since its type is not
// BinaryOperator or Identifier)
assert(Tok->Next);
if (Tok->Next->is(tok::equal)) {
Tok = Tok->Next;
assert(Tok->Next);
if (Tok->Next->isNot(tok::identifier)) {
// If we hit any other kind of token, just bail. It's unusual/illegal.
return;
}
Tok = Tok->Next;
Value = Tok->TokenText;
}
auto It = SortOrderMap.find(Attribute);
if (It == SortOrderMap.end())
It = SortOrderMap.insert({Attribute, SortOrderMap.size()}).first;
// Sort the indices based on the priority stored in `SortOrderMap`.
const auto Ordinal = It->second;
if (!Ordinals.insert(Ordinal).second) {
HasDuplicates = true;
continue;
}
if (Ordinal >= Indices.size())
Indices.resize(Ordinal + 1);
Indices[Ordinal] = Index++;
// Memoize the attribute.
PropertyAttributes.push_back({Attribute, Value});
}
if (!HasDuplicates) {
// There's nothing to do unless there's more than one attribute.
if (PropertyAttributes.size() < 2)
return;
int PrevIndex = -1;
bool IsSorted = true;
for (const auto Ordinal : Ordinals) {
const auto Index = Indices[Ordinal];
if (Index < PrevIndex) {
IsSorted = false;
break;
}
assert(Index > PrevIndex);
PrevIndex = Index;
}
// If the property order is already correct, then no fix-up is needed.
if (IsSorted)
return;
}
// Generate the replacement text.
std::string NewText;
bool IsFirst = true;
for (const auto Ordinal : Ordinals) {
if (IsFirst)
IsFirst = false;
else
NewText += ", ";
const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]];
NewText += PropertyEntry.Attribute;
if (const auto Value = PropertyEntry.Value; !Value.empty()) {
NewText += '=';
NewText += Value;
}
}
auto Range = CharSourceRange::getCharRange(
BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
auto Err = Fixes.add(Replacement);
if (Err) {
llvm::errs() << "Error while reodering ObjC property attributes : "
<< llvm::toString(std::move(Err)) << "\n";
}
}
void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,
tooling::Replacements &Fixes, const FormatToken *Tok) {
assert(Tok);
// Expect `property` to be the very next token or else just bail early.
const FormatToken *const PropertyTok = Tok->Next;
if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property))
return;
// Expect the opening paren to be the next token or else just bail early.
const FormatToken *const LParenTok = PropertyTok->getNextNonComment();
if (!LParenTok || LParenTok->isNot(tok::l_paren))
return;
// Get the matching right-paren, the bounds for property attributes.
const FormatToken *const RParenTok = LParenTok->MatchingParen;
if (!RParenTok)
return;
sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok);
}
std::pair<tooling::Replacements, unsigned>
ObjCPropertyAttributeOrderFixer::analyze(
TokenAnnotator & /*Annotator*/,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) {
tooling::Replacements Fixes;
const AdditionalKeywords &Keywords = Tokens.getKeywords();
const SourceManager &SourceMgr = Env.getSourceManager();
AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
for (AnnotatedLine *Line : AnnotatedLines) {
assert(Line);
if (!Line->Affected || Line->Type != LT_ObjCProperty)
continue;
FormatToken *First = Line->First;
assert(First);
if (First->Finalized)
continue;
const auto *Last = Line->Last;
for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) {
assert(Tok);
// Skip until the `@` of a `@property` declaration.
if (Tok->isNot(TT_ObjCProperty))
continue;
analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok);
// There are never two `@property` in a line (they are split
// by other passes), so this pass can break after just one.
break;
}
}
return {Fixes, 0};
}
} // namespace format
} // namespace clang
|