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 
ObjCPropertyAttributeOrderFixer(const Environment & Env,const FormatStyle & Style)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 
sortPropertyAttributes(const SourceManager & SourceMgr,tooling::Replacements & Fixes,const FormatToken * BeginTok,const FormatToken * EndTok)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     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 
analyzeObjCPropertyDecl(const SourceManager & SourceMgr,const AdditionalKeywords & Keywords,tooling::Replacements & Fixes,const FormatToken * Tok)158 void 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 
181 std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)182 ObjCPropertyAttributeOrderFixer::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