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(Kind: 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(K1: tok::identifier, K2: 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(Kind: tok::equal)) {
79 Tok = Tok->Next;
80 assert(Tok->Next);
81 if (Tok->Next->isNot(Kind: 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(Key: Attribute);
90 if (It == SortOrderMap.end())
91 It = SortOrderMap.insert(KV: {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(x: Ordinal).second) {
96 HasDuplicates = true;
97 continue;
98 }
99
100 if (Ordinal >= Indices.size())
101 Indices.resize(N: Ordinal + 1);
102 Indices[Ordinal] = Index++;
103
104 // Memoize the attribute.
105 PropertyAttributes.push_back(Elt: {.Attribute: Attribute, .Value: 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 B: BeginTok->getStartOfNonWhitespace(), E: EndTok->Previous->Tok.getEndLoc());
150 auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
151 auto Err = Fixes.add(R: Replacement);
152 if (Err) {
153 llvm::errs() << "Error while reodering ObjC property attributes : "
154 << llvm::toString(E: 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(Kind: 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(Kind: 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, BeginTok: LParenTok->Next, EndTok: 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(Lines&: 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(Kind: 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