xref: /freebsd/contrib/llvm-project/clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp (revision e64bea71c21eb42e97aa615188ba91f6cce0d36d)
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 namespace clang {
19 namespace format {
20 
21 ObjCPropertyAttributeOrderFixer::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 
30 struct ObjCPropertyEntry {
31   StringRef Attribute; // eg, `readwrite`
32   StringRef Value;     // eg, the `foo` of the attribute `getter=foo`
33 };
34 
35 void 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(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(tok::identifier, 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(tok::equal)) {
77       Tok = Tok->Next;
78       assert(Tok->Next);
79       if (Tok->Next->isNot(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(Attribute, SortOrderMap.size()).first->second;
90     if (!Ordinals.insert(Ordinal).second) {
91       HasDuplicates = true;
92       continue;
93     }
94 
95     if (Ordinal >= Indices.size())
96       Indices.resize(Ordinal + 1);
97     Indices[Ordinal] = Index++;
98 
99     // Memoize the attribute.
100     PropertyAttributes.push_back({Attribute, 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       BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
145   auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
146   auto Err = Fixes.add(Replacement);
147   if (Err) {
148     llvm::errs() << "Error while reodering ObjC property attributes : "
149                  << llvm::toString(std::move(Err)) << "\n";
150   }
151 }
152 
153 void 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(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(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, LParenTok->Next, RParenTok);
174 }
175 
176 std::pair<tooling::Replacements, unsigned>
177 ObjCPropertyAttributeOrderFixer::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(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(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