xref: /llvm-project/clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp (revision 19a2f178da513399ad54c34dec94d616002d9686)
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     : 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 
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     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     // Sort the indices based on the priority stored in `SortOrderMap`.
90     const auto Ordinal =
91         SortOrderMap.try_emplace(Attribute, SortOrderMap.size()).first->second;
92     if (!Ordinals.insert(Ordinal).second) {
93       HasDuplicates = true;
94       continue;
95     }
96 
97     if (Ordinal >= Indices.size())
98       Indices.resize(Ordinal + 1);
99     Indices[Ordinal] = Index++;
100 
101     // Memoize the attribute.
102     PropertyAttributes.push_back({Attribute, Value});
103   }
104 
105   if (!HasDuplicates) {
106     // There's nothing to do unless there's more than one attribute.
107     if (PropertyAttributes.size() < 2)
108       return;
109 
110     int PrevIndex = -1;
111     bool IsSorted = true;
112     for (const auto Ordinal : Ordinals) {
113       const auto Index = Indices[Ordinal];
114       if (Index < PrevIndex) {
115         IsSorted = false;
116         break;
117       }
118       assert(Index > PrevIndex);
119       PrevIndex = Index;
120     }
121 
122     // If the property order is already correct, then no fix-up is needed.
123     if (IsSorted)
124       return;
125   }
126 
127   // Generate the replacement text.
128   std::string NewText;
129   bool IsFirst = true;
130   for (const auto Ordinal : Ordinals) {
131     if (IsFirst)
132       IsFirst = false;
133     else
134       NewText += ", ";
135 
136     const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]];
137     NewText += PropertyEntry.Attribute;
138 
139     if (const auto Value = PropertyEntry.Value; !Value.empty()) {
140       NewText += '=';
141       NewText += Value;
142     }
143   }
144 
145   auto Range = CharSourceRange::getCharRange(
146       BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
147   auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
148   auto Err = Fixes.add(Replacement);
149   if (Err) {
150     llvm::errs() << "Error while reodering ObjC property attributes : "
151                  << llvm::toString(std::move(Err)) << "\n";
152   }
153 }
154 
155 void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
156     const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,
157     tooling::Replacements &Fixes, const FormatToken *Tok) {
158   assert(Tok);
159 
160   // Expect `property` to be the very next token or else just bail early.
161   const FormatToken *const PropertyTok = Tok->Next;
162   if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property))
163     return;
164 
165   // Expect the opening paren to be the next token or else just bail early.
166   const FormatToken *const LParenTok = PropertyTok->getNextNonComment();
167   if (!LParenTok || LParenTok->isNot(tok::l_paren))
168     return;
169 
170   // Get the matching right-paren, the bounds for property attributes.
171   const FormatToken *const RParenTok = LParenTok->MatchingParen;
172   if (!RParenTok)
173     return;
174 
175   sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok);
176 }
177 
178 std::pair<tooling::Replacements, unsigned>
179 ObjCPropertyAttributeOrderFixer::analyze(
180     TokenAnnotator & /*Annotator*/,
181     SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
182     FormatTokenLexer &Tokens) {
183   tooling::Replacements Fixes;
184   const AdditionalKeywords &Keywords = Tokens.getKeywords();
185   const SourceManager &SourceMgr = Env.getSourceManager();
186   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
187 
188   for (AnnotatedLine *Line : AnnotatedLines) {
189     assert(Line);
190     if (!Line->Affected || Line->Type != LT_ObjCProperty)
191       continue;
192     FormatToken *First = Line->First;
193     assert(First);
194     if (First->Finalized)
195       continue;
196 
197     const auto *Last = Line->Last;
198 
199     for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) {
200       assert(Tok);
201 
202       // Skip until the `@` of a `@property` declaration.
203       if (Tok->isNot(TT_ObjCProperty))
204         continue;
205 
206       analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok);
207 
208       // There are never two `@property` in a line (they are split
209       // by other passes), so this pass can break after just one.
210       break;
211     }
212   }
213   return {Fixes, 0};
214 }
215 
216 } // namespace format
217 } // namespace clang
218