/src/llvm-project/clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | : TokenAnalyzer(Env, Style) { |
26 | | // Create an "order priority" map to use to sort properties. |
27 | 0 | unsigned Index = 0; |
28 | 0 | for (const auto &Property : Style.ObjCPropertyAttributeOrder) |
29 | 0 | SortOrderMap[Property] = Index++; |
30 | 0 | } |
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 | 0 | const FormatToken *BeginTok, const FormatToken *EndTok) { |
40 | 0 | assert(BeginTok); |
41 | 0 | assert(EndTok); |
42 | 0 | assert(EndTok->Previous); |
43 | | |
44 | | // If there are zero or one tokens, nothing to do. |
45 | 0 | if (BeginTok == EndTok || BeginTok->Next == EndTok) |
46 | 0 | return; |
47 | | |
48 | | // Use a set to sort attributes and remove duplicates. |
49 | 0 | std::set<unsigned> Ordinals; |
50 | | |
51 | | // Create a "remapping index" on how to reorder the attributes. |
52 | 0 | SmallVector<int> Indices; |
53 | | |
54 | | // Collect the attributes. |
55 | 0 | SmallVector<ObjCPropertyEntry> PropertyAttributes; |
56 | 0 | bool HasDuplicates = false; |
57 | 0 | int Index = 0; |
58 | 0 | for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) { |
59 | 0 | assert(Tok); |
60 | 0 | if (Tok->is(tok::comma)) { |
61 | | // Ignore the comma separators. |
62 | 0 | continue; |
63 | 0 | } |
64 | | |
65 | | // Most attributes look like identifiers, but `class` is a keyword. |
66 | 0 | if (!Tok->isOneOf(tok::identifier, tok::kw_class)) { |
67 | | // If we hit any other kind of token, just bail. |
68 | 0 | return; |
69 | 0 | } |
70 | | |
71 | 0 | const StringRef Attribute{Tok->TokenText}; |
72 | 0 | 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 | 0 | assert(Tok->Next); |
78 | 0 | if (Tok->Next->is(tok::equal)) { |
79 | 0 | Tok = Tok->Next; |
80 | 0 | assert(Tok->Next); |
81 | 0 | if (Tok->Next->isNot(tok::identifier)) { |
82 | | // If we hit any other kind of token, just bail. It's unusual/illegal. |
83 | 0 | return; |
84 | 0 | } |
85 | 0 | Tok = Tok->Next; |
86 | 0 | Value = Tok->TokenText; |
87 | 0 | } |
88 | | |
89 | 0 | auto It = SortOrderMap.find(Attribute); |
90 | 0 | if (It == SortOrderMap.end()) |
91 | 0 | It = SortOrderMap.insert({Attribute, SortOrderMap.size()}).first; |
92 | | |
93 | | // Sort the indices based on the priority stored in `SortOrderMap`. |
94 | 0 | const auto Ordinal = It->second; |
95 | 0 | if (!Ordinals.insert(Ordinal).second) { |
96 | 0 | HasDuplicates = true; |
97 | 0 | continue; |
98 | 0 | } |
99 | | |
100 | 0 | if (Ordinal >= Indices.size()) |
101 | 0 | Indices.resize(Ordinal + 1); |
102 | 0 | Indices[Ordinal] = Index++; |
103 | | |
104 | | // Memoize the attribute. |
105 | 0 | PropertyAttributes.push_back({Attribute, Value}); |
106 | 0 | } |
107 | | |
108 | 0 | if (!HasDuplicates) { |
109 | | // There's nothing to do unless there's more than one attribute. |
110 | 0 | if (PropertyAttributes.size() < 2) |
111 | 0 | return; |
112 | | |
113 | 0 | int PrevIndex = -1; |
114 | 0 | bool IsSorted = true; |
115 | 0 | for (const auto Ordinal : Ordinals) { |
116 | 0 | const auto Index = Indices[Ordinal]; |
117 | 0 | if (Index < PrevIndex) { |
118 | 0 | IsSorted = false; |
119 | 0 | break; |
120 | 0 | } |
121 | 0 | assert(Index > PrevIndex); |
122 | 0 | PrevIndex = Index; |
123 | 0 | } |
124 | | |
125 | | // If the property order is already correct, then no fix-up is needed. |
126 | 0 | if (IsSorted) |
127 | 0 | return; |
128 | 0 | } |
129 | | |
130 | | // Generate the replacement text. |
131 | 0 | std::string NewText; |
132 | 0 | bool IsFirst = true; |
133 | 0 | for (const auto Ordinal : Ordinals) { |
134 | 0 | if (IsFirst) |
135 | 0 | IsFirst = false; |
136 | 0 | else |
137 | 0 | NewText += ", "; |
138 | |
|
139 | 0 | const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]]; |
140 | 0 | NewText += PropertyEntry.Attribute; |
141 | |
|
142 | 0 | if (const auto Value = PropertyEntry.Value; !Value.empty()) { |
143 | 0 | NewText += '='; |
144 | 0 | NewText += Value; |
145 | 0 | } |
146 | 0 | } |
147 | |
|
148 | 0 | auto Range = CharSourceRange::getCharRange( |
149 | 0 | BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc()); |
150 | 0 | auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); |
151 | 0 | auto Err = Fixes.add(Replacement); |
152 | 0 | if (Err) { |
153 | 0 | llvm::errs() << "Error while reodering ObjC property attributes : " |
154 | 0 | << llvm::toString(std::move(Err)) << "\n"; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl( |
159 | | const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, |
160 | 0 | tooling::Replacements &Fixes, const FormatToken *Tok) { |
161 | 0 | assert(Tok); |
162 | | |
163 | | // Expect `property` to be the very next token or else just bail early. |
164 | 0 | const FormatToken *const PropertyTok = Tok->Next; |
165 | 0 | if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property)) |
166 | 0 | return; |
167 | | |
168 | | // Expect the opening paren to be the next token or else just bail early. |
169 | 0 | const FormatToken *const LParenTok = PropertyTok->getNextNonComment(); |
170 | 0 | if (!LParenTok || LParenTok->isNot(tok::l_paren)) |
171 | 0 | return; |
172 | | |
173 | | // Get the matching right-paren, the bounds for property attributes. |
174 | 0 | const FormatToken *const RParenTok = LParenTok->MatchingParen; |
175 | 0 | if (!RParenTok) |
176 | 0 | return; |
177 | | |
178 | 0 | sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok); |
179 | 0 | } |
180 | | |
181 | | std::pair<tooling::Replacements, unsigned> |
182 | | ObjCPropertyAttributeOrderFixer::analyze( |
183 | | TokenAnnotator & /*Annotator*/, |
184 | | SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, |
185 | 0 | FormatTokenLexer &Tokens) { |
186 | 0 | tooling::Replacements Fixes; |
187 | 0 | const AdditionalKeywords &Keywords = Tokens.getKeywords(); |
188 | 0 | const SourceManager &SourceMgr = Env.getSourceManager(); |
189 | 0 | AffectedRangeMgr.computeAffectedLines(AnnotatedLines); |
190 | |
|
191 | 0 | for (AnnotatedLine *Line : AnnotatedLines) { |
192 | 0 | assert(Line); |
193 | 0 | if (!Line->Affected || Line->Type != LT_ObjCProperty) |
194 | 0 | continue; |
195 | 0 | FormatToken *First = Line->First; |
196 | 0 | assert(First); |
197 | 0 | if (First->Finalized) |
198 | 0 | continue; |
199 | | |
200 | 0 | const auto *Last = Line->Last; |
201 | |
|
202 | 0 | for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) { |
203 | 0 | assert(Tok); |
204 | | |
205 | | // Skip until the `@` of a `@property` declaration. |
206 | 0 | if (Tok->isNot(TT_ObjCProperty)) |
207 | 0 | continue; |
208 | | |
209 | 0 | 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 | 0 | break; |
214 | 0 | } |
215 | 0 | } |
216 | 0 | return {Fixes, 0}; |
217 | 0 | } |
218 | | |
219 | | } // namespace format |
220 | | } // namespace clang |