| //===--- ObjCPropertyAttributeOrderFixer.cpp -------------------*- C++--*-===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 | /// | 
 | /// \file | 
 | /// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that | 
 | /// adjusts the order of attributes in an ObjC `@property(...)` declaration, | 
 | /// depending on the style. | 
 | /// | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "ObjCPropertyAttributeOrderFixer.h" | 
 |  | 
 | #include <algorithm> | 
 |  | 
 | namespace clang { | 
 | namespace format { | 
 |  | 
 | ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer( | 
 |     const Environment &Env, const FormatStyle &Style) | 
 |     : TokenAnalyzer(Env, Style) { | 
 |   // Create an "order priority" map to use to sort properties. | 
 |   unsigned Index = 0; | 
 |   for (const auto &Property : Style.ObjCPropertyAttributeOrder) | 
 |     SortOrderMap[Property] = Index++; | 
 | } | 
 |  | 
 | struct ObjCPropertyEntry { | 
 |   StringRef Attribute; // eg, `readwrite` | 
 |   StringRef Value;     // eg, the `foo` of the attribute `getter=foo` | 
 | }; | 
 |  | 
 | void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes( | 
 |     const SourceManager &SourceMgr, tooling::Replacements &Fixes, | 
 |     const FormatToken *BeginTok, const FormatToken *EndTok) { | 
 |   assert(BeginTok); | 
 |   assert(EndTok); | 
 |   assert(EndTok->Previous); | 
 |  | 
 |   // If there are zero or one tokens, nothing to do. | 
 |   if (BeginTok == EndTok || BeginTok->Next == EndTok) | 
 |     return; | 
 |  | 
 |   // Use a set to sort attributes and remove duplicates. | 
 |   std::set<unsigned> Ordinals; | 
 |  | 
 |   // Create a "remapping index" on how to reorder the attributes. | 
 |   SmallVector<int> Indices; | 
 |  | 
 |   // Collect the attributes. | 
 |   SmallVector<ObjCPropertyEntry> PropertyAttributes; | 
 |   bool HasDuplicates = false; | 
 |   int Index = 0; | 
 |   for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) { | 
 |     assert(Tok); | 
 |     if (Tok->is(tok::comma)) { | 
 |       // Ignore the comma separators. | 
 |       continue; | 
 |     } | 
 |  | 
 |     // Most attributes look like identifiers, but `class` is a keyword. | 
 |     if (!Tok->isOneOf(tok::identifier, tok::kw_class)) { | 
 |       // If we hit any other kind of token, just bail. | 
 |       return; | 
 |     } | 
 |  | 
 |     const StringRef Attribute{Tok->TokenText}; | 
 |     StringRef Value; | 
 |  | 
 |     // Also handle `getter=getFoo` attributes. | 
 |     // (Note: no check needed against `EndTok`, since its type is not | 
 |     // BinaryOperator or Identifier) | 
 |     assert(Tok->Next); | 
 |     if (Tok->Next->is(tok::equal)) { | 
 |       Tok = Tok->Next; | 
 |       assert(Tok->Next); | 
 |       if (Tok->Next->isNot(tok::identifier)) { | 
 |         // If we hit any other kind of token, just bail. It's unusual/illegal. | 
 |         return; | 
 |       } | 
 |       Tok = Tok->Next; | 
 |       Value = Tok->TokenText; | 
 |     } | 
 |  | 
 |     // Sort the indices based on the priority stored in `SortOrderMap`. | 
 |     const auto Ordinal = | 
 |         SortOrderMap.try_emplace(Attribute, SortOrderMap.size()).first->second; | 
 |     if (!Ordinals.insert(Ordinal).second) { | 
 |       HasDuplicates = true; | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (Ordinal >= Indices.size()) | 
 |       Indices.resize(Ordinal + 1); | 
 |     Indices[Ordinal] = Index++; | 
 |  | 
 |     // Memoize the attribute. | 
 |     PropertyAttributes.push_back({Attribute, Value}); | 
 |   } | 
 |  | 
 |   if (!HasDuplicates) { | 
 |     // There's nothing to do unless there's more than one attribute. | 
 |     if (PropertyAttributes.size() < 2) | 
 |       return; | 
 |  | 
 |     int PrevIndex = -1; | 
 |     bool IsSorted = true; | 
 |     for (const auto Ordinal : Ordinals) { | 
 |       const auto Index = Indices[Ordinal]; | 
 |       if (Index < PrevIndex) { | 
 |         IsSorted = false; | 
 |         break; | 
 |       } | 
 |       assert(Index > PrevIndex); | 
 |       PrevIndex = Index; | 
 |     } | 
 |  | 
 |     // If the property order is already correct, then no fix-up is needed. | 
 |     if (IsSorted) | 
 |       return; | 
 |   } | 
 |  | 
 |   // Generate the replacement text. | 
 |   std::string NewText; | 
 |   bool IsFirst = true; | 
 |   for (const auto Ordinal : Ordinals) { | 
 |     if (IsFirst) | 
 |       IsFirst = false; | 
 |     else | 
 |       NewText += ", "; | 
 |  | 
 |     const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]]; | 
 |     NewText += PropertyEntry.Attribute; | 
 |  | 
 |     if (const auto Value = PropertyEntry.Value; !Value.empty()) { | 
 |       NewText += '='; | 
 |       NewText += Value; | 
 |     } | 
 |   } | 
 |  | 
 |   auto Range = CharSourceRange::getCharRange( | 
 |       BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc()); | 
 |   auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); | 
 |   auto Err = Fixes.add(Replacement); | 
 |   if (Err) { | 
 |     llvm::errs() << "Error while reodering ObjC property attributes : " | 
 |                  << llvm::toString(std::move(Err)) << "\n"; | 
 |   } | 
 | } | 
 |  | 
 | void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl( | 
 |     const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, | 
 |     tooling::Replacements &Fixes, const FormatToken *Tok) { | 
 |   assert(Tok); | 
 |  | 
 |   // Expect `property` to be the very next token or else just bail early. | 
 |   const FormatToken *const PropertyTok = Tok->Next; | 
 |   if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property)) | 
 |     return; | 
 |  | 
 |   // Expect the opening paren to be the next token or else just bail early. | 
 |   const FormatToken *const LParenTok = PropertyTok->getNextNonComment(); | 
 |   if (!LParenTok || LParenTok->isNot(tok::l_paren)) | 
 |     return; | 
 |  | 
 |   // Get the matching right-paren, the bounds for property attributes. | 
 |   const FormatToken *const RParenTok = LParenTok->MatchingParen; | 
 |   if (!RParenTok) | 
 |     return; | 
 |  | 
 |   sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok); | 
 | } | 
 |  | 
 | std::pair<tooling::Replacements, unsigned> | 
 | ObjCPropertyAttributeOrderFixer::analyze( | 
 |     TokenAnnotator & /*Annotator*/, | 
 |     SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, | 
 |     FormatTokenLexer &Tokens) { | 
 |   tooling::Replacements Fixes; | 
 |   const AdditionalKeywords &Keywords = Tokens.getKeywords(); | 
 |   const SourceManager &SourceMgr = Env.getSourceManager(); | 
 |   AffectedRangeMgr.computeAffectedLines(AnnotatedLines); | 
 |  | 
 |   for (AnnotatedLine *Line : AnnotatedLines) { | 
 |     assert(Line); | 
 |     if (!Line->Affected || Line->Type != LT_ObjCProperty) | 
 |       continue; | 
 |     FormatToken *First = Line->First; | 
 |     assert(First); | 
 |     if (First->Finalized) | 
 |       continue; | 
 |  | 
 |     const auto *Last = Line->Last; | 
 |  | 
 |     for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) { | 
 |       assert(Tok); | 
 |  | 
 |       // Skip until the `@` of a `@property` declaration. | 
 |       if (Tok->isNot(TT_ObjCProperty)) | 
 |         continue; | 
 |  | 
 |       analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok); | 
 |  | 
 |       // There are never two `@property` in a line (they are split | 
 |       // by other passes), so this pass can break after just one. | 
 |       break; | 
 |     } | 
 |   } | 
 |   return {Fixes, 0}; | 
 | } | 
 |  | 
 | } // namespace format | 
 | } // namespace clang |