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