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(Kind: 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(K1: tok::identifier, K2: 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(Kind: tok::equal)) { |
77 | Tok = Tok->Next; |
78 | assert(Tok->Next); |
79 | if (Tok->Next->isNot(Kind: 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(Key: Attribute, Args: SortOrderMap.size()).first->second; |
90 | if (!Ordinals.insert(x: Ordinal).second) { |
91 | HasDuplicates = true; |
92 | continue; |
93 | } |
94 | |
95 | if (Ordinal >= Indices.size()) |
96 | Indices.resize(N: Ordinal + 1); |
97 | Indices[Ordinal] = Index++; |
98 | |
99 | // Memoize the attribute. |
100 | PropertyAttributes.push_back(Elt: {.Attribute: Attribute, .Value: 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 | B: BeginTok->getStartOfNonWhitespace(), E: EndTok->Previous->Tok.getEndLoc()); |
145 | auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); |
146 | auto Err = Fixes.add(R: Replacement); |
147 | if (Err) { |
148 | llvm::errs() << "Error while reodering ObjC property attributes : " |
149 | << llvm::toString(E: 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(Kind: 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(Kind: 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, BeginTok: LParenTok->Next, EndTok: 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(Lines&: 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(Kind: 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 | |