xref: /llvm-project/clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp (revision 19a2f178da513399ad54c34dec94d616002d9686)
1c45a66ecSJared Grubb //===--- ObjCPropertyAttributeOrderFixer.cpp -------------------*- C++--*-===//
2c45a66ecSJared Grubb //
3c45a66ecSJared Grubb // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4c45a66ecSJared Grubb // See https://llvm.org/LICENSE.txt for license information.
5c45a66ecSJared Grubb // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6c45a66ecSJared Grubb //
7c45a66ecSJared Grubb //===----------------------------------------------------------------------===//
8c45a66ecSJared Grubb ///
9c45a66ecSJared Grubb /// \file
10c45a66ecSJared Grubb /// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
11c45a66ecSJared Grubb /// adjusts the order of attributes in an ObjC `@property(...)` declaration,
12c45a66ecSJared Grubb /// depending on the style.
13c45a66ecSJared Grubb ///
14c45a66ecSJared Grubb //===----------------------------------------------------------------------===//
15c45a66ecSJared Grubb 
16c45a66ecSJared Grubb #include "ObjCPropertyAttributeOrderFixer.h"
17c45a66ecSJared Grubb 
18b2082a98SOwen Pan #include <algorithm>
19b2082a98SOwen Pan 
20c45a66ecSJared Grubb namespace clang {
21c45a66ecSJared Grubb namespace format {
22c45a66ecSJared Grubb 
23c45a66ecSJared Grubb ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer(
24c45a66ecSJared Grubb     const Environment &Env, const FormatStyle &Style)
25c45a66ecSJared Grubb     : TokenAnalyzer(Env, Style) {
26c45a66ecSJared Grubb   // Create an "order priority" map to use to sort properties.
27924f6ca1SOwen Pan   unsigned Index = 0;
28c45a66ecSJared Grubb   for (const auto &Property : Style.ObjCPropertyAttributeOrder)
29924f6ca1SOwen Pan     SortOrderMap[Property] = Index++;
30c45a66ecSJared Grubb }
31c45a66ecSJared Grubb 
32c45a66ecSJared Grubb struct ObjCPropertyEntry {
33924f6ca1SOwen Pan   StringRef Attribute; // eg, `readwrite`
34924f6ca1SOwen Pan   StringRef Value;     // eg, the `foo` of the attribute `getter=foo`
35c45a66ecSJared Grubb };
36c45a66ecSJared Grubb 
37c45a66ecSJared Grubb void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes(
38c45a66ecSJared Grubb     const SourceManager &SourceMgr, tooling::Replacements &Fixes,
39924f6ca1SOwen Pan     const FormatToken *BeginTok, const FormatToken *EndTok) {
40c45a66ecSJared Grubb   assert(BeginTok);
41c45a66ecSJared Grubb   assert(EndTok);
42c45a66ecSJared Grubb   assert(EndTok->Previous);
43c45a66ecSJared Grubb 
44c45a66ecSJared Grubb   // If there are zero or one tokens, nothing to do.
45c45a66ecSJared Grubb   if (BeginTok == EndTok || BeginTok->Next == EndTok)
46c45a66ecSJared Grubb     return;
47c45a66ecSJared Grubb 
48924f6ca1SOwen Pan   // Use a set to sort attributes and remove duplicates.
49924f6ca1SOwen Pan   std::set<unsigned> Ordinals;
50924f6ca1SOwen Pan 
51924f6ca1SOwen Pan   // Create a "remapping index" on how to reorder the attributes.
52924f6ca1SOwen Pan   SmallVector<int> Indices;
53924f6ca1SOwen Pan 
54c45a66ecSJared Grubb   // Collect the attributes.
55924f6ca1SOwen Pan   SmallVector<ObjCPropertyEntry> PropertyAttributes;
56924f6ca1SOwen Pan   bool HasDuplicates = false;
57924f6ca1SOwen Pan   int Index = 0;
58c45a66ecSJared Grubb   for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) {
59c45a66ecSJared Grubb     assert(Tok);
60c45a66ecSJared Grubb     if (Tok->is(tok::comma)) {
61c45a66ecSJared Grubb       // Ignore the comma separators.
62c45a66ecSJared Grubb       continue;
63c45a66ecSJared Grubb     }
64c45a66ecSJared Grubb 
65924f6ca1SOwen Pan     // Most attributes look like identifiers, but `class` is a keyword.
66924f6ca1SOwen Pan     if (!Tok->isOneOf(tok::identifier, tok::kw_class)) {
67c45a66ecSJared Grubb       // If we hit any other kind of token, just bail.
68c45a66ecSJared Grubb       return;
69c45a66ecSJared Grubb     }
70c45a66ecSJared Grubb 
71924f6ca1SOwen Pan     const StringRef Attribute{Tok->TokenText};
72924f6ca1SOwen Pan     StringRef Value;
73c45a66ecSJared Grubb 
74c45a66ecSJared Grubb     // Also handle `getter=getFoo` attributes.
75c45a66ecSJared Grubb     // (Note: no check needed against `EndTok`, since its type is not
76c45a66ecSJared Grubb     // BinaryOperator or Identifier)
77c45a66ecSJared Grubb     assert(Tok->Next);
78c45a66ecSJared Grubb     if (Tok->Next->is(tok::equal)) {
79c45a66ecSJared Grubb       Tok = Tok->Next;
80c45a66ecSJared Grubb       assert(Tok->Next);
81c45a66ecSJared Grubb       if (Tok->Next->isNot(tok::identifier)) {
82c45a66ecSJared Grubb         // If we hit any other kind of token, just bail. It's unusual/illegal.
83c45a66ecSJared Grubb         return;
84c45a66ecSJared Grubb       }
85c45a66ecSJared Grubb       Tok = Tok->Next;
86924f6ca1SOwen Pan       Value = Tok->TokenText;
87c45a66ecSJared Grubb     }
88c45a66ecSJared Grubb 
89924f6ca1SOwen Pan     // Sort the indices based on the priority stored in `SortOrderMap`.
90*19a2f178SKazu Hirata     const auto Ordinal =
91*19a2f178SKazu Hirata         SortOrderMap.try_emplace(Attribute, SortOrderMap.size()).first->second;
92924f6ca1SOwen Pan     if (!Ordinals.insert(Ordinal).second) {
93924f6ca1SOwen Pan       HasDuplicates = true;
94924f6ca1SOwen Pan       continue;
95924f6ca1SOwen Pan     }
96924f6ca1SOwen Pan 
97924f6ca1SOwen Pan     if (Ordinal >= Indices.size())
98924f6ca1SOwen Pan       Indices.resize(Ordinal + 1);
99924f6ca1SOwen Pan     Indices[Ordinal] = Index++;
100924f6ca1SOwen Pan 
101924f6ca1SOwen Pan     // Memoize the attribute.
102924f6ca1SOwen Pan     PropertyAttributes.push_back({Attribute, Value});
103924f6ca1SOwen Pan   }
104924f6ca1SOwen Pan 
105924f6ca1SOwen Pan   if (!HasDuplicates) {
106c45a66ecSJared Grubb     // There's nothing to do unless there's more than one attribute.
107c45a66ecSJared Grubb     if (PropertyAttributes.size() < 2)
108c45a66ecSJared Grubb       return;
109c45a66ecSJared Grubb 
110924f6ca1SOwen Pan     int PrevIndex = -1;
111924f6ca1SOwen Pan     bool IsSorted = true;
112924f6ca1SOwen Pan     for (const auto Ordinal : Ordinals) {
113924f6ca1SOwen Pan       const auto Index = Indices[Ordinal];
114924f6ca1SOwen Pan       if (Index < PrevIndex) {
115924f6ca1SOwen Pan         IsSorted = false;
116924f6ca1SOwen Pan         break;
117924f6ca1SOwen Pan       }
118924f6ca1SOwen Pan       assert(Index > PrevIndex);
119924f6ca1SOwen Pan       PrevIndex = Index;
120924f6ca1SOwen Pan     }
121c45a66ecSJared Grubb 
122c45a66ecSJared Grubb     // If the property order is already correct, then no fix-up is needed.
123924f6ca1SOwen Pan     if (IsSorted)
124c45a66ecSJared Grubb       return;
125924f6ca1SOwen Pan   }
126c45a66ecSJared Grubb 
127c45a66ecSJared Grubb   // Generate the replacement text.
128c45a66ecSJared Grubb   std::string NewText;
129924f6ca1SOwen Pan   bool IsFirst = true;
130924f6ca1SOwen Pan   for (const auto Ordinal : Ordinals) {
131924f6ca1SOwen Pan     if (IsFirst)
132924f6ca1SOwen Pan       IsFirst = false;
133924f6ca1SOwen Pan     else
134924f6ca1SOwen Pan       NewText += ", ";
135924f6ca1SOwen Pan 
136924f6ca1SOwen Pan     const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]];
137c45a66ecSJared Grubb     NewText += PropertyEntry.Attribute;
138c45a66ecSJared Grubb 
139924f6ca1SOwen Pan     if (const auto Value = PropertyEntry.Value; !Value.empty()) {
140924f6ca1SOwen Pan       NewText += '=';
141924f6ca1SOwen Pan       NewText += Value;
142c45a66ecSJared Grubb     }
143c45a66ecSJared Grubb   }
144c45a66ecSJared Grubb 
145c45a66ecSJared Grubb   auto Range = CharSourceRange::getCharRange(
146c45a66ecSJared Grubb       BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
147c45a66ecSJared Grubb   auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
148c45a66ecSJared Grubb   auto Err = Fixes.add(Replacement);
149c45a66ecSJared Grubb   if (Err) {
150c45a66ecSJared Grubb     llvm::errs() << "Error while reodering ObjC property attributes : "
151c45a66ecSJared Grubb                  << llvm::toString(std::move(Err)) << "\n";
152c45a66ecSJared Grubb   }
153c45a66ecSJared Grubb }
154c45a66ecSJared Grubb 
155c45a66ecSJared Grubb void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
156c45a66ecSJared Grubb     const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,
157924f6ca1SOwen Pan     tooling::Replacements &Fixes, const FormatToken *Tok) {
158c45a66ecSJared Grubb   assert(Tok);
159c45a66ecSJared Grubb 
160c45a66ecSJared Grubb   // Expect `property` to be the very next token or else just bail early.
161c45a66ecSJared Grubb   const FormatToken *const PropertyTok = Tok->Next;
162c45a66ecSJared Grubb   if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property))
163c45a66ecSJared Grubb     return;
164c45a66ecSJared Grubb 
165c45a66ecSJared Grubb   // Expect the opening paren to be the next token or else just bail early.
166c45a66ecSJared Grubb   const FormatToken *const LParenTok = PropertyTok->getNextNonComment();
167c45a66ecSJared Grubb   if (!LParenTok || LParenTok->isNot(tok::l_paren))
168c45a66ecSJared Grubb     return;
169c45a66ecSJared Grubb 
170c45a66ecSJared Grubb   // Get the matching right-paren, the bounds for property attributes.
171c45a66ecSJared Grubb   const FormatToken *const RParenTok = LParenTok->MatchingParen;
172c45a66ecSJared Grubb   if (!RParenTok)
173c45a66ecSJared Grubb     return;
174c45a66ecSJared Grubb 
175c45a66ecSJared Grubb   sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok);
176c45a66ecSJared Grubb }
177c45a66ecSJared Grubb 
178c45a66ecSJared Grubb std::pair<tooling::Replacements, unsigned>
179c45a66ecSJared Grubb ObjCPropertyAttributeOrderFixer::analyze(
180c45a66ecSJared Grubb     TokenAnnotator & /*Annotator*/,
181c45a66ecSJared Grubb     SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
182c45a66ecSJared Grubb     FormatTokenLexer &Tokens) {
183c45a66ecSJared Grubb   tooling::Replacements Fixes;
184c45a66ecSJared Grubb   const AdditionalKeywords &Keywords = Tokens.getKeywords();
185c45a66ecSJared Grubb   const SourceManager &SourceMgr = Env.getSourceManager();
186c45a66ecSJared Grubb   AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
187c45a66ecSJared Grubb 
188c45a66ecSJared Grubb   for (AnnotatedLine *Line : AnnotatedLines) {
189c45a66ecSJared Grubb     assert(Line);
190c45a66ecSJared Grubb     if (!Line->Affected || Line->Type != LT_ObjCProperty)
191c45a66ecSJared Grubb       continue;
192c45a66ecSJared Grubb     FormatToken *First = Line->First;
193c45a66ecSJared Grubb     assert(First);
194c45a66ecSJared Grubb     if (First->Finalized)
195c45a66ecSJared Grubb       continue;
196c45a66ecSJared Grubb 
197c45a66ecSJared Grubb     const auto *Last = Line->Last;
198c45a66ecSJared Grubb 
199c45a66ecSJared Grubb     for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) {
200c45a66ecSJared Grubb       assert(Tok);
201c45a66ecSJared Grubb 
202c45a66ecSJared Grubb       // Skip until the `@` of a `@property` declaration.
203c45a66ecSJared Grubb       if (Tok->isNot(TT_ObjCProperty))
204c45a66ecSJared Grubb         continue;
205c45a66ecSJared Grubb 
206c45a66ecSJared Grubb       analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok);
207c45a66ecSJared Grubb 
208c45a66ecSJared Grubb       // There are never two `@property` in a line (they are split
209c45a66ecSJared Grubb       // by other passes), so this pass can break after just one.
210c45a66ecSJared Grubb       break;
211c45a66ecSJared Grubb     }
212c45a66ecSJared Grubb   }
213c45a66ecSJared Grubb   return {Fixes, 0};
214c45a66ecSJared Grubb }
215c45a66ecSJared Grubb 
216c45a66ecSJared Grubb } // namespace format
217c45a66ecSJared Grubb } // namespace clang
218