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