1 //===--- ObjCPropertyAttributeOrderFixer.cpp -------------------*- C++--*-===//
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
7 //===----------------------------------------------------------------------===//
10 /// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
11 /// adjusts the order of attributes in an ObjC `@property(...)` declaration,
12 /// depending on the style.
14 //===----------------------------------------------------------------------===//
16 #include "ObjCPropertyAttributeOrderFixer.h"
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.
28 for (const auto &Property
: Style
.ObjCPropertyAttributeOrder
)
29 SortOrderMap
[Property
] = Index
++;
32 struct ObjCPropertyEntry
{
33 StringRef Attribute
; // eg, `readwrite`
34 StringRef Value
; // eg, the `foo` of the attribute `getter=foo`
37 void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes(
38 const SourceManager
&SourceMgr
, tooling::Replacements
&Fixes
,
39 const FormatToken
*BeginTok
, const FormatToken
*EndTok
) {
42 assert(EndTok
->Previous
);
44 // If there are zero or one tokens, nothing to do.
45 if (BeginTok
== EndTok
|| BeginTok
->Next
== EndTok
)
48 // Use a set to sort attributes and remove duplicates.
49 std::set
<unsigned> Ordinals
;
51 // Create a "remapping index" on how to reorder the attributes.
52 SmallVector
<int> Indices
;
54 // Collect the attributes.
55 SmallVector
<ObjCPropertyEntry
> PropertyAttributes
;
56 bool HasDuplicates
= false;
58 for (auto Tok
= BeginTok
; Tok
!= EndTok
; Tok
= Tok
->Next
) {
60 if (Tok
->is(tok::comma
)) {
61 // Ignore the comma separators.
65 // Most attributes look like identifiers, but `class` is a keyword.
66 if (!Tok
->isOneOf(tok::identifier
, tok::kw_class
)) {
67 // If we hit any other kind of token, just bail.
71 const StringRef Attribute
{Tok
->TokenText
};
74 // Also handle `getter=getFoo` attributes.
75 // (Note: no check needed against `EndTok`, since its type is not
76 // BinaryOperator or Identifier)
78 if (Tok
->Next
->is(tok::equal
)) {
81 if (Tok
->Next
->isNot(tok::identifier
)) {
82 // If we hit any other kind of token, just bail. It's unusual/illegal.
86 Value
= Tok
->TokenText
;
89 auto It
= SortOrderMap
.find(Attribute
);
90 if (It
== SortOrderMap
.end())
91 It
= SortOrderMap
.insert({Attribute
, SortOrderMap
.size()}).first
;
93 // Sort the indices based on the priority stored in `SortOrderMap`.
94 const auto Ordinal
= It
->second
;
95 if (!Ordinals
.insert(Ordinal
).second
) {
100 if (Ordinal
>= Indices
.size())
101 Indices
.resize(Ordinal
+ 1);
102 Indices
[Ordinal
] = Index
++;
104 // Memoize the attribute.
105 PropertyAttributes
.push_back({Attribute
, Value
});
108 if (!HasDuplicates
) {
109 // There's nothing to do unless there's more than one attribute.
110 if (PropertyAttributes
.size() < 2)
114 bool IsSorted
= true;
115 for (const auto Ordinal
: Ordinals
) {
116 const auto Index
= Indices
[Ordinal
];
117 if (Index
< PrevIndex
) {
121 assert(Index
> PrevIndex
);
125 // If the property order is already correct, then no fix-up is needed.
130 // Generate the replacement text.
133 for (const auto Ordinal
: Ordinals
) {
139 const auto &PropertyEntry
= PropertyAttributes
[Indices
[Ordinal
]];
140 NewText
+= PropertyEntry
.Attribute
;
142 if (const auto Value
= PropertyEntry
.Value
; !Value
.empty()) {
148 auto Range
= CharSourceRange::getCharRange(
149 BeginTok
->getStartOfNonWhitespace(), EndTok
->Previous
->Tok
.getEndLoc());
150 auto Replacement
= tooling::Replacement(SourceMgr
, Range
, NewText
);
151 auto Err
= Fixes
.add(Replacement
);
153 llvm::errs() << "Error while reodering ObjC property attributes : "
154 << llvm::toString(std::move(Err
)) << "\n";
158 void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
159 const SourceManager
&SourceMgr
, const AdditionalKeywords
&Keywords
,
160 tooling::Replacements
&Fixes
, const FormatToken
*Tok
) {
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(Keywords
.kw_property
))
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(tok::l_paren
))
173 // Get the matching right-paren, the bounds for property attributes.
174 const FormatToken
*const RParenTok
= LParenTok
->MatchingParen
;
178 sortPropertyAttributes(SourceMgr
, Fixes
, LParenTok
->Next
, RParenTok
);
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(AnnotatedLines
);
191 for (AnnotatedLine
*Line
: AnnotatedLines
) {
193 if (!Line
->Affected
|| Line
->Type
!= LT_ObjCProperty
)
195 FormatToken
*First
= Line
->First
;
197 if (First
->Finalized
)
200 const auto *Last
= Line
->Last
;
202 for (const auto *Tok
= First
; Tok
!= Last
; Tok
= Tok
->Next
) {
205 // Skip until the `@` of a `@property` declaration.
206 if (Tok
->isNot(TT_ObjCProperty
))
209 analyzeObjCPropertyDecl(SourceMgr
, Keywords
, Fixes
, Tok
);
211 // There are never two `@property` in a line (they are split
212 // by other passes), so this pass can break after just one.
219 } // namespace format