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(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(tok::identifier, 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(tok::equal)) { 79 Tok = Tok->Next; 80 assert(Tok->Next); 81 if (Tok->Next->isNot(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(Attribute); 90 if (It == SortOrderMap.end()) 91 It = SortOrderMap.insert({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(Ordinal).second) { 96 HasDuplicates = true; 97 continue; 98 } 99 100 if (Ordinal >= Indices.size()) 101 Indices.resize(Ordinal + 1); 102 Indices[Ordinal] = Index++; 103 104 // Memoize the attribute. 105 PropertyAttributes.push_back({Attribute, 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 BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc()); 150 auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); 151 auto Err = Fixes.add(Replacement); 152 if (Err) { 153 llvm::errs() << "Error while reodering ObjC property attributes : " 154 << llvm::toString(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(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(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, LParenTok->Next, 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(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(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