Coverage Report

Created: 2024-01-17 10:31

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