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(Kind: 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(K1: tok::identifier, K2: 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(Kind: tok::equal)) { |
79 | Tok = Tok->Next; |
80 | assert(Tok->Next); |
81 | if (Tok->Next->isNot(Kind: 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(Key: Attribute); |
90 | if (It == SortOrderMap.end()) |
91 | It = SortOrderMap.insert(KV: {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(x: Ordinal).second) { |
96 | HasDuplicates = true; |
97 | continue; |
98 | } |
99 | |
100 | if (Ordinal >= Indices.size()) |
101 | Indices.resize(N: Ordinal + 1); |
102 | Indices[Ordinal] = Index++; |
103 | |
104 | // Memoize the attribute. |
105 | PropertyAttributes.push_back(Elt: {.Attribute: Attribute, .Value: 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 | B: BeginTok->getStartOfNonWhitespace(), E: EndTok->Previous->Tok.getEndLoc()); |
150 | auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); |
151 | auto Err = Fixes.add(R: Replacement); |
152 | if (Err) { |
153 | llvm::errs() << "Error while reodering ObjC property attributes : " |
154 | << llvm::toString(E: std::move(Err)) << "\n" ; |
155 | } |
156 | } |
157 | |
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(Kind: 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(Kind: 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, BeginTok: LParenTok->Next, EndTok: RParenTok); |
179 | } |
180 | |
181 | std::pair<tooling::Replacements, unsigned> |
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(Lines&: 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(Kind: 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 | |