1*5f757f3fSDimitry Andric //===--- ObjCPropertyAttributeOrderFixer.cpp -------------------*- C++--*-===//
2*5f757f3fSDimitry Andric //
3*5f757f3fSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*5f757f3fSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*5f757f3fSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*5f757f3fSDimitry Andric //
7*5f757f3fSDimitry Andric //===----------------------------------------------------------------------===//
8*5f757f3fSDimitry Andric ///
9*5f757f3fSDimitry Andric /// \file
10*5f757f3fSDimitry Andric /// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
11*5f757f3fSDimitry Andric /// adjusts the order of attributes in an ObjC `@property(...)` declaration,
12*5f757f3fSDimitry Andric /// depending on the style.
13*5f757f3fSDimitry Andric ///
14*5f757f3fSDimitry Andric //===----------------------------------------------------------------------===//
15*5f757f3fSDimitry Andric
16*5f757f3fSDimitry Andric #include "ObjCPropertyAttributeOrderFixer.h"
17*5f757f3fSDimitry Andric
18*5f757f3fSDimitry Andric #include <algorithm>
19*5f757f3fSDimitry Andric
20*5f757f3fSDimitry Andric namespace clang {
21*5f757f3fSDimitry Andric namespace format {
22*5f757f3fSDimitry Andric
ObjCPropertyAttributeOrderFixer(const Environment & Env,const FormatStyle & Style)23*5f757f3fSDimitry Andric ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer(
24*5f757f3fSDimitry Andric const Environment &Env, const FormatStyle &Style)
25*5f757f3fSDimitry Andric : TokenAnalyzer(Env, Style) {
26*5f757f3fSDimitry Andric // Create an "order priority" map to use to sort properties.
27*5f757f3fSDimitry Andric unsigned Index = 0;
28*5f757f3fSDimitry Andric for (const auto &Property : Style.ObjCPropertyAttributeOrder)
29*5f757f3fSDimitry Andric SortOrderMap[Property] = Index++;
30*5f757f3fSDimitry Andric }
31*5f757f3fSDimitry Andric
32*5f757f3fSDimitry Andric struct ObjCPropertyEntry {
33*5f757f3fSDimitry Andric StringRef Attribute; // eg, `readwrite`
34*5f757f3fSDimitry Andric StringRef Value; // eg, the `foo` of the attribute `getter=foo`
35*5f757f3fSDimitry Andric };
36*5f757f3fSDimitry Andric
sortPropertyAttributes(const SourceManager & SourceMgr,tooling::Replacements & Fixes,const FormatToken * BeginTok,const FormatToken * EndTok)37*5f757f3fSDimitry Andric void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes(
38*5f757f3fSDimitry Andric const SourceManager &SourceMgr, tooling::Replacements &Fixes,
39*5f757f3fSDimitry Andric const FormatToken *BeginTok, const FormatToken *EndTok) {
40*5f757f3fSDimitry Andric assert(BeginTok);
41*5f757f3fSDimitry Andric assert(EndTok);
42*5f757f3fSDimitry Andric assert(EndTok->Previous);
43*5f757f3fSDimitry Andric
44*5f757f3fSDimitry Andric // If there are zero or one tokens, nothing to do.
45*5f757f3fSDimitry Andric if (BeginTok == EndTok || BeginTok->Next == EndTok)
46*5f757f3fSDimitry Andric return;
47*5f757f3fSDimitry Andric
48*5f757f3fSDimitry Andric // Use a set to sort attributes and remove duplicates.
49*5f757f3fSDimitry Andric std::set<unsigned> Ordinals;
50*5f757f3fSDimitry Andric
51*5f757f3fSDimitry Andric // Create a "remapping index" on how to reorder the attributes.
52*5f757f3fSDimitry Andric SmallVector<int> Indices;
53*5f757f3fSDimitry Andric
54*5f757f3fSDimitry Andric // Collect the attributes.
55*5f757f3fSDimitry Andric SmallVector<ObjCPropertyEntry> PropertyAttributes;
56*5f757f3fSDimitry Andric bool HasDuplicates = false;
57*5f757f3fSDimitry Andric int Index = 0;
58*5f757f3fSDimitry Andric for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) {
59*5f757f3fSDimitry Andric assert(Tok);
60*5f757f3fSDimitry Andric if (Tok->is(tok::comma)) {
61*5f757f3fSDimitry Andric // Ignore the comma separators.
62*5f757f3fSDimitry Andric continue;
63*5f757f3fSDimitry Andric }
64*5f757f3fSDimitry Andric
65*5f757f3fSDimitry Andric // Most attributes look like identifiers, but `class` is a keyword.
66*5f757f3fSDimitry Andric if (!Tok->isOneOf(tok::identifier, tok::kw_class)) {
67*5f757f3fSDimitry Andric // If we hit any other kind of token, just bail.
68*5f757f3fSDimitry Andric return;
69*5f757f3fSDimitry Andric }
70*5f757f3fSDimitry Andric
71*5f757f3fSDimitry Andric const StringRef Attribute{Tok->TokenText};
72*5f757f3fSDimitry Andric StringRef Value;
73*5f757f3fSDimitry Andric
74*5f757f3fSDimitry Andric // Also handle `getter=getFoo` attributes.
75*5f757f3fSDimitry Andric // (Note: no check needed against `EndTok`, since its type is not
76*5f757f3fSDimitry Andric // BinaryOperator or Identifier)
77*5f757f3fSDimitry Andric assert(Tok->Next);
78*5f757f3fSDimitry Andric if (Tok->Next->is(tok::equal)) {
79*5f757f3fSDimitry Andric Tok = Tok->Next;
80*5f757f3fSDimitry Andric assert(Tok->Next);
81*5f757f3fSDimitry Andric if (Tok->Next->isNot(tok::identifier)) {
82*5f757f3fSDimitry Andric // If we hit any other kind of token, just bail. It's unusual/illegal.
83*5f757f3fSDimitry Andric return;
84*5f757f3fSDimitry Andric }
85*5f757f3fSDimitry Andric Tok = Tok->Next;
86*5f757f3fSDimitry Andric Value = Tok->TokenText;
87*5f757f3fSDimitry Andric }
88*5f757f3fSDimitry Andric
89*5f757f3fSDimitry Andric auto It = SortOrderMap.find(Attribute);
90*5f757f3fSDimitry Andric if (It == SortOrderMap.end())
91*5f757f3fSDimitry Andric It = SortOrderMap.insert({Attribute, SortOrderMap.size()}).first;
92*5f757f3fSDimitry Andric
93*5f757f3fSDimitry Andric // Sort the indices based on the priority stored in `SortOrderMap`.
94*5f757f3fSDimitry Andric const auto Ordinal = It->second;
95*5f757f3fSDimitry Andric if (!Ordinals.insert(Ordinal).second) {
96*5f757f3fSDimitry Andric HasDuplicates = true;
97*5f757f3fSDimitry Andric continue;
98*5f757f3fSDimitry Andric }
99*5f757f3fSDimitry Andric
100*5f757f3fSDimitry Andric if (Ordinal >= Indices.size())
101*5f757f3fSDimitry Andric Indices.resize(Ordinal + 1);
102*5f757f3fSDimitry Andric Indices[Ordinal] = Index++;
103*5f757f3fSDimitry Andric
104*5f757f3fSDimitry Andric // Memoize the attribute.
105*5f757f3fSDimitry Andric PropertyAttributes.push_back({Attribute, Value});
106*5f757f3fSDimitry Andric }
107*5f757f3fSDimitry Andric
108*5f757f3fSDimitry Andric if (!HasDuplicates) {
109*5f757f3fSDimitry Andric // There's nothing to do unless there's more than one attribute.
110*5f757f3fSDimitry Andric if (PropertyAttributes.size() < 2)
111*5f757f3fSDimitry Andric return;
112*5f757f3fSDimitry Andric
113*5f757f3fSDimitry Andric int PrevIndex = -1;
114*5f757f3fSDimitry Andric bool IsSorted = true;
115*5f757f3fSDimitry Andric for (const auto Ordinal : Ordinals) {
116*5f757f3fSDimitry Andric const auto Index = Indices[Ordinal];
117*5f757f3fSDimitry Andric if (Index < PrevIndex) {
118*5f757f3fSDimitry Andric IsSorted = false;
119*5f757f3fSDimitry Andric break;
120*5f757f3fSDimitry Andric }
121*5f757f3fSDimitry Andric assert(Index > PrevIndex);
122*5f757f3fSDimitry Andric PrevIndex = Index;
123*5f757f3fSDimitry Andric }
124*5f757f3fSDimitry Andric
125*5f757f3fSDimitry Andric // If the property order is already correct, then no fix-up is needed.
126*5f757f3fSDimitry Andric if (IsSorted)
127*5f757f3fSDimitry Andric return;
128*5f757f3fSDimitry Andric }
129*5f757f3fSDimitry Andric
130*5f757f3fSDimitry Andric // Generate the replacement text.
131*5f757f3fSDimitry Andric std::string NewText;
132*5f757f3fSDimitry Andric bool IsFirst = true;
133*5f757f3fSDimitry Andric for (const auto Ordinal : Ordinals) {
134*5f757f3fSDimitry Andric if (IsFirst)
135*5f757f3fSDimitry Andric IsFirst = false;
136*5f757f3fSDimitry Andric else
137*5f757f3fSDimitry Andric NewText += ", ";
138*5f757f3fSDimitry Andric
139*5f757f3fSDimitry Andric const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]];
140*5f757f3fSDimitry Andric NewText += PropertyEntry.Attribute;
141*5f757f3fSDimitry Andric
142*5f757f3fSDimitry Andric if (const auto Value = PropertyEntry.Value; !Value.empty()) {
143*5f757f3fSDimitry Andric NewText += '=';
144*5f757f3fSDimitry Andric NewText += Value;
145*5f757f3fSDimitry Andric }
146*5f757f3fSDimitry Andric }
147*5f757f3fSDimitry Andric
148*5f757f3fSDimitry Andric auto Range = CharSourceRange::getCharRange(
149*5f757f3fSDimitry Andric BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
150*5f757f3fSDimitry Andric auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
151*5f757f3fSDimitry Andric auto Err = Fixes.add(Replacement);
152*5f757f3fSDimitry Andric if (Err) {
153*5f757f3fSDimitry Andric llvm::errs() << "Error while reodering ObjC property attributes : "
154*5f757f3fSDimitry Andric << llvm::toString(std::move(Err)) << "\n";
155*5f757f3fSDimitry Andric }
156*5f757f3fSDimitry Andric }
157*5f757f3fSDimitry Andric
analyzeObjCPropertyDecl(const SourceManager & SourceMgr,const AdditionalKeywords & Keywords,tooling::Replacements & Fixes,const FormatToken * Tok)158*5f757f3fSDimitry Andric void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
159*5f757f3fSDimitry Andric const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,
160*5f757f3fSDimitry Andric tooling::Replacements &Fixes, const FormatToken *Tok) {
161*5f757f3fSDimitry Andric assert(Tok);
162*5f757f3fSDimitry Andric
163*5f757f3fSDimitry Andric // Expect `property` to be the very next token or else just bail early.
164*5f757f3fSDimitry Andric const FormatToken *const PropertyTok = Tok->Next;
165*5f757f3fSDimitry Andric if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property))
166*5f757f3fSDimitry Andric return;
167*5f757f3fSDimitry Andric
168*5f757f3fSDimitry Andric // Expect the opening paren to be the next token or else just bail early.
169*5f757f3fSDimitry Andric const FormatToken *const LParenTok = PropertyTok->getNextNonComment();
170*5f757f3fSDimitry Andric if (!LParenTok || LParenTok->isNot(tok::l_paren))
171*5f757f3fSDimitry Andric return;
172*5f757f3fSDimitry Andric
173*5f757f3fSDimitry Andric // Get the matching right-paren, the bounds for property attributes.
174*5f757f3fSDimitry Andric const FormatToken *const RParenTok = LParenTok->MatchingParen;
175*5f757f3fSDimitry Andric if (!RParenTok)
176*5f757f3fSDimitry Andric return;
177*5f757f3fSDimitry Andric
178*5f757f3fSDimitry Andric sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok);
179*5f757f3fSDimitry Andric }
180*5f757f3fSDimitry Andric
181*5f757f3fSDimitry Andric std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &,SmallVectorImpl<AnnotatedLine * > & AnnotatedLines,FormatTokenLexer & Tokens)182*5f757f3fSDimitry Andric ObjCPropertyAttributeOrderFixer::analyze(
183*5f757f3fSDimitry Andric TokenAnnotator & /*Annotator*/,
184*5f757f3fSDimitry Andric SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
185*5f757f3fSDimitry Andric FormatTokenLexer &Tokens) {
186*5f757f3fSDimitry Andric tooling::Replacements Fixes;
187*5f757f3fSDimitry Andric const AdditionalKeywords &Keywords = Tokens.getKeywords();
188*5f757f3fSDimitry Andric const SourceManager &SourceMgr = Env.getSourceManager();
189*5f757f3fSDimitry Andric AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
190*5f757f3fSDimitry Andric
191*5f757f3fSDimitry Andric for (AnnotatedLine *Line : AnnotatedLines) {
192*5f757f3fSDimitry Andric assert(Line);
193*5f757f3fSDimitry Andric if (!Line->Affected || Line->Type != LT_ObjCProperty)
194*5f757f3fSDimitry Andric continue;
195*5f757f3fSDimitry Andric FormatToken *First = Line->First;
196*5f757f3fSDimitry Andric assert(First);
197*5f757f3fSDimitry Andric if (First->Finalized)
198*5f757f3fSDimitry Andric continue;
199*5f757f3fSDimitry Andric
200*5f757f3fSDimitry Andric const auto *Last = Line->Last;
201*5f757f3fSDimitry Andric
202*5f757f3fSDimitry Andric for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) {
203*5f757f3fSDimitry Andric assert(Tok);
204*5f757f3fSDimitry Andric
205*5f757f3fSDimitry Andric // Skip until the `@` of a `@property` declaration.
206*5f757f3fSDimitry Andric if (Tok->isNot(TT_ObjCProperty))
207*5f757f3fSDimitry Andric continue;
208*5f757f3fSDimitry Andric
209*5f757f3fSDimitry Andric analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok);
210*5f757f3fSDimitry Andric
211*5f757f3fSDimitry Andric // There are never two `@property` in a line (they are split
212*5f757f3fSDimitry Andric // by other passes), so this pass can break after just one.
213*5f757f3fSDimitry Andric break;
214*5f757f3fSDimitry Andric }
215*5f757f3fSDimitry Andric }
216*5f757f3fSDimitry Andric return {Fixes, 0};
217*5f757f3fSDimitry Andric }
218*5f757f3fSDimitry Andric
219*5f757f3fSDimitry Andric } // namespace format
220*5f757f3fSDimitry Andric } // namespace clang
221