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