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