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