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 // Sort the indices based on the priority stored in `SortOrderMap`. 90 const auto Ordinal = 91 SortOrderMap.try_emplace(Attribute, SortOrderMap.size()).first->second; 92 if (!Ordinals.insert(Ordinal).second) { 93 HasDuplicates = true; 94 continue; 95 } 96 97 if (Ordinal >= Indices.size()) 98 Indices.resize(Ordinal + 1); 99 Indices[Ordinal] = Index++; 100 101 // Memoize the attribute. 102 PropertyAttributes.push_back({Attribute, Value}); 103 } 104 105 if (!HasDuplicates) { 106 // There's nothing to do unless there's more than one attribute. 107 if (PropertyAttributes.size() < 2) 108 return; 109 110 int PrevIndex = -1; 111 bool IsSorted = true; 112 for (const auto Ordinal : Ordinals) { 113 const auto Index = Indices[Ordinal]; 114 if (Index < PrevIndex) { 115 IsSorted = false; 116 break; 117 } 118 assert(Index > PrevIndex); 119 PrevIndex = Index; 120 } 121 122 // If the property order is already correct, then no fix-up is needed. 123 if (IsSorted) 124 return; 125 } 126 127 // Generate the replacement text. 128 std::string NewText; 129 bool IsFirst = true; 130 for (const auto Ordinal : Ordinals) { 131 if (IsFirst) 132 IsFirst = false; 133 else 134 NewText += ", "; 135 136 const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]]; 137 NewText += PropertyEntry.Attribute; 138 139 if (const auto Value = PropertyEntry.Value; !Value.empty()) { 140 NewText += '='; 141 NewText += Value; 142 } 143 } 144 145 auto Range = CharSourceRange::getCharRange( 146 BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc()); 147 auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); 148 auto Err = Fixes.add(Replacement); 149 if (Err) { 150 llvm::errs() << "Error while reodering ObjC property attributes : " 151 << llvm::toString(std::move(Err)) << "\n"; 152 } 153 } 154 155 void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl( 156 const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, 157 tooling::Replacements &Fixes, const FormatToken *Tok) { 158 assert(Tok); 159 160 // Expect `property` to be the very next token or else just bail early. 161 const FormatToken *const PropertyTok = Tok->Next; 162 if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property)) 163 return; 164 165 // Expect the opening paren to be the next token or else just bail early. 166 const FormatToken *const LParenTok = PropertyTok->getNextNonComment(); 167 if (!LParenTok || LParenTok->isNot(tok::l_paren)) 168 return; 169 170 // Get the matching right-paren, the bounds for property attributes. 171 const FormatToken *const RParenTok = LParenTok->MatchingParen; 172 if (!RParenTok) 173 return; 174 175 sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok); 176 } 177 178 std::pair<tooling::Replacements, unsigned> 179 ObjCPropertyAttributeOrderFixer::analyze( 180 TokenAnnotator & /*Annotator*/, 181 SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, 182 FormatTokenLexer &Tokens) { 183 tooling::Replacements Fixes; 184 const AdditionalKeywords &Keywords = Tokens.getKeywords(); 185 const SourceManager &SourceMgr = Env.getSourceManager(); 186 AffectedRangeMgr.computeAffectedLines(AnnotatedLines); 187 188 for (AnnotatedLine *Line : AnnotatedLines) { 189 assert(Line); 190 if (!Line->Affected || Line->Type != LT_ObjCProperty) 191 continue; 192 FormatToken *First = Line->First; 193 assert(First); 194 if (First->Finalized) 195 continue; 196 197 const auto *Last = Line->Last; 198 199 for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) { 200 assert(Tok); 201 202 // Skip until the `@` of a `@property` declaration. 203 if (Tok->isNot(TT_ObjCProperty)) 204 continue; 205 206 analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok); 207 208 // There are never two `@property` in a line (they are split 209 // by other passes), so this pass can break after just one. 210 break; 211 } 212 } 213 return {Fixes, 0}; 214 } 215 216 } // namespace format 217 } // namespace clang 218