1c45a66ecSJared Grubb //===--- ObjCPropertyAttributeOrderFixer.cpp -------------------*- C++--*-===// 2c45a66ecSJared Grubb // 3c45a66ecSJared Grubb // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4c45a66ecSJared Grubb // See https://llvm.org/LICENSE.txt for license information. 5c45a66ecSJared Grubb // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6c45a66ecSJared Grubb // 7c45a66ecSJared Grubb //===----------------------------------------------------------------------===// 8c45a66ecSJared Grubb /// 9c45a66ecSJared Grubb /// \file 10c45a66ecSJared Grubb /// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that 11c45a66ecSJared Grubb /// adjusts the order of attributes in an ObjC `@property(...)` declaration, 12c45a66ecSJared Grubb /// depending on the style. 13c45a66ecSJared Grubb /// 14c45a66ecSJared Grubb //===----------------------------------------------------------------------===// 15c45a66ecSJared Grubb 16c45a66ecSJared Grubb #include "ObjCPropertyAttributeOrderFixer.h" 17c45a66ecSJared Grubb 18b2082a98SOwen Pan #include <algorithm> 19b2082a98SOwen Pan 20c45a66ecSJared Grubb namespace clang { 21c45a66ecSJared Grubb namespace format { 22c45a66ecSJared Grubb 23c45a66ecSJared Grubb ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer( 24c45a66ecSJared Grubb const Environment &Env, const FormatStyle &Style) 25c45a66ecSJared Grubb : TokenAnalyzer(Env, Style) { 26c45a66ecSJared Grubb // Create an "order priority" map to use to sort properties. 27924f6ca1SOwen Pan unsigned Index = 0; 28c45a66ecSJared Grubb for (const auto &Property : Style.ObjCPropertyAttributeOrder) 29924f6ca1SOwen Pan SortOrderMap[Property] = Index++; 30c45a66ecSJared Grubb } 31c45a66ecSJared Grubb 32c45a66ecSJared Grubb struct ObjCPropertyEntry { 33924f6ca1SOwen Pan StringRef Attribute; // eg, `readwrite` 34924f6ca1SOwen Pan StringRef Value; // eg, the `foo` of the attribute `getter=foo` 35c45a66ecSJared Grubb }; 36c45a66ecSJared Grubb 37c45a66ecSJared Grubb void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes( 38c45a66ecSJared Grubb const SourceManager &SourceMgr, tooling::Replacements &Fixes, 39924f6ca1SOwen Pan const FormatToken *BeginTok, const FormatToken *EndTok) { 40c45a66ecSJared Grubb assert(BeginTok); 41c45a66ecSJared Grubb assert(EndTok); 42c45a66ecSJared Grubb assert(EndTok->Previous); 43c45a66ecSJared Grubb 44c45a66ecSJared Grubb // If there are zero or one tokens, nothing to do. 45c45a66ecSJared Grubb if (BeginTok == EndTok || BeginTok->Next == EndTok) 46c45a66ecSJared Grubb return; 47c45a66ecSJared Grubb 48924f6ca1SOwen Pan // Use a set to sort attributes and remove duplicates. 49924f6ca1SOwen Pan std::set<unsigned> Ordinals; 50924f6ca1SOwen Pan 51924f6ca1SOwen Pan // Create a "remapping index" on how to reorder the attributes. 52924f6ca1SOwen Pan SmallVector<int> Indices; 53924f6ca1SOwen Pan 54c45a66ecSJared Grubb // Collect the attributes. 55924f6ca1SOwen Pan SmallVector<ObjCPropertyEntry> PropertyAttributes; 56924f6ca1SOwen Pan bool HasDuplicates = false; 57924f6ca1SOwen Pan int Index = 0; 58c45a66ecSJared Grubb for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) { 59c45a66ecSJared Grubb assert(Tok); 60c45a66ecSJared Grubb if (Tok->is(tok::comma)) { 61c45a66ecSJared Grubb // Ignore the comma separators. 62c45a66ecSJared Grubb continue; 63c45a66ecSJared Grubb } 64c45a66ecSJared Grubb 65924f6ca1SOwen Pan // Most attributes look like identifiers, but `class` is a keyword. 66924f6ca1SOwen Pan if (!Tok->isOneOf(tok::identifier, tok::kw_class)) { 67c45a66ecSJared Grubb // If we hit any other kind of token, just bail. 68c45a66ecSJared Grubb return; 69c45a66ecSJared Grubb } 70c45a66ecSJared Grubb 71924f6ca1SOwen Pan const StringRef Attribute{Tok->TokenText}; 72924f6ca1SOwen Pan StringRef Value; 73c45a66ecSJared Grubb 74c45a66ecSJared Grubb // Also handle `getter=getFoo` attributes. 75c45a66ecSJared Grubb // (Note: no check needed against `EndTok`, since its type is not 76c45a66ecSJared Grubb // BinaryOperator or Identifier) 77c45a66ecSJared Grubb assert(Tok->Next); 78c45a66ecSJared Grubb if (Tok->Next->is(tok::equal)) { 79c45a66ecSJared Grubb Tok = Tok->Next; 80c45a66ecSJared Grubb assert(Tok->Next); 81c45a66ecSJared Grubb if (Tok->Next->isNot(tok::identifier)) { 82c45a66ecSJared Grubb // If we hit any other kind of token, just bail. It's unusual/illegal. 83c45a66ecSJared Grubb return; 84c45a66ecSJared Grubb } 85c45a66ecSJared Grubb Tok = Tok->Next; 86924f6ca1SOwen Pan Value = Tok->TokenText; 87c45a66ecSJared Grubb } 88c45a66ecSJared Grubb 89924f6ca1SOwen Pan // Sort the indices based on the priority stored in `SortOrderMap`. 90*19a2f178SKazu Hirata const auto Ordinal = 91*19a2f178SKazu Hirata SortOrderMap.try_emplace(Attribute, SortOrderMap.size()).first->second; 92924f6ca1SOwen Pan if (!Ordinals.insert(Ordinal).second) { 93924f6ca1SOwen Pan HasDuplicates = true; 94924f6ca1SOwen Pan continue; 95924f6ca1SOwen Pan } 96924f6ca1SOwen Pan 97924f6ca1SOwen Pan if (Ordinal >= Indices.size()) 98924f6ca1SOwen Pan Indices.resize(Ordinal + 1); 99924f6ca1SOwen Pan Indices[Ordinal] = Index++; 100924f6ca1SOwen Pan 101924f6ca1SOwen Pan // Memoize the attribute. 102924f6ca1SOwen Pan PropertyAttributes.push_back({Attribute, Value}); 103924f6ca1SOwen Pan } 104924f6ca1SOwen Pan 105924f6ca1SOwen Pan if (!HasDuplicates) { 106c45a66ecSJared Grubb // There's nothing to do unless there's more than one attribute. 107c45a66ecSJared Grubb if (PropertyAttributes.size() < 2) 108c45a66ecSJared Grubb return; 109c45a66ecSJared Grubb 110924f6ca1SOwen Pan int PrevIndex = -1; 111924f6ca1SOwen Pan bool IsSorted = true; 112924f6ca1SOwen Pan for (const auto Ordinal : Ordinals) { 113924f6ca1SOwen Pan const auto Index = Indices[Ordinal]; 114924f6ca1SOwen Pan if (Index < PrevIndex) { 115924f6ca1SOwen Pan IsSorted = false; 116924f6ca1SOwen Pan break; 117924f6ca1SOwen Pan } 118924f6ca1SOwen Pan assert(Index > PrevIndex); 119924f6ca1SOwen Pan PrevIndex = Index; 120924f6ca1SOwen Pan } 121c45a66ecSJared Grubb 122c45a66ecSJared Grubb // If the property order is already correct, then no fix-up is needed. 123924f6ca1SOwen Pan if (IsSorted) 124c45a66ecSJared Grubb return; 125924f6ca1SOwen Pan } 126c45a66ecSJared Grubb 127c45a66ecSJared Grubb // Generate the replacement text. 128c45a66ecSJared Grubb std::string NewText; 129924f6ca1SOwen Pan bool IsFirst = true; 130924f6ca1SOwen Pan for (const auto Ordinal : Ordinals) { 131924f6ca1SOwen Pan if (IsFirst) 132924f6ca1SOwen Pan IsFirst = false; 133924f6ca1SOwen Pan else 134924f6ca1SOwen Pan NewText += ", "; 135924f6ca1SOwen Pan 136924f6ca1SOwen Pan const auto &PropertyEntry = PropertyAttributes[Indices[Ordinal]]; 137c45a66ecSJared Grubb NewText += PropertyEntry.Attribute; 138c45a66ecSJared Grubb 139924f6ca1SOwen Pan if (const auto Value = PropertyEntry.Value; !Value.empty()) { 140924f6ca1SOwen Pan NewText += '='; 141924f6ca1SOwen Pan NewText += Value; 142c45a66ecSJared Grubb } 143c45a66ecSJared Grubb } 144c45a66ecSJared Grubb 145c45a66ecSJared Grubb auto Range = CharSourceRange::getCharRange( 146c45a66ecSJared Grubb BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc()); 147c45a66ecSJared Grubb auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); 148c45a66ecSJared Grubb auto Err = Fixes.add(Replacement); 149c45a66ecSJared Grubb if (Err) { 150c45a66ecSJared Grubb llvm::errs() << "Error while reodering ObjC property attributes : " 151c45a66ecSJared Grubb << llvm::toString(std::move(Err)) << "\n"; 152c45a66ecSJared Grubb } 153c45a66ecSJared Grubb } 154c45a66ecSJared Grubb 155c45a66ecSJared Grubb void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl( 156c45a66ecSJared Grubb const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, 157924f6ca1SOwen Pan tooling::Replacements &Fixes, const FormatToken *Tok) { 158c45a66ecSJared Grubb assert(Tok); 159c45a66ecSJared Grubb 160c45a66ecSJared Grubb // Expect `property` to be the very next token or else just bail early. 161c45a66ecSJared Grubb const FormatToken *const PropertyTok = Tok->Next; 162c45a66ecSJared Grubb if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property)) 163c45a66ecSJared Grubb return; 164c45a66ecSJared Grubb 165c45a66ecSJared Grubb // Expect the opening paren to be the next token or else just bail early. 166c45a66ecSJared Grubb const FormatToken *const LParenTok = PropertyTok->getNextNonComment(); 167c45a66ecSJared Grubb if (!LParenTok || LParenTok->isNot(tok::l_paren)) 168c45a66ecSJared Grubb return; 169c45a66ecSJared Grubb 170c45a66ecSJared Grubb // Get the matching right-paren, the bounds for property attributes. 171c45a66ecSJared Grubb const FormatToken *const RParenTok = LParenTok->MatchingParen; 172c45a66ecSJared Grubb if (!RParenTok) 173c45a66ecSJared Grubb return; 174c45a66ecSJared Grubb 175c45a66ecSJared Grubb sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok); 176c45a66ecSJared Grubb } 177c45a66ecSJared Grubb 178c45a66ecSJared Grubb std::pair<tooling::Replacements, unsigned> 179c45a66ecSJared Grubb ObjCPropertyAttributeOrderFixer::analyze( 180c45a66ecSJared Grubb TokenAnnotator & /*Annotator*/, 181c45a66ecSJared Grubb SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, 182c45a66ecSJared Grubb FormatTokenLexer &Tokens) { 183c45a66ecSJared Grubb tooling::Replacements Fixes; 184c45a66ecSJared Grubb const AdditionalKeywords &Keywords = Tokens.getKeywords(); 185c45a66ecSJared Grubb const SourceManager &SourceMgr = Env.getSourceManager(); 186c45a66ecSJared Grubb AffectedRangeMgr.computeAffectedLines(AnnotatedLines); 187c45a66ecSJared Grubb 188c45a66ecSJared Grubb for (AnnotatedLine *Line : AnnotatedLines) { 189c45a66ecSJared Grubb assert(Line); 190c45a66ecSJared Grubb if (!Line->Affected || Line->Type != LT_ObjCProperty) 191c45a66ecSJared Grubb continue; 192c45a66ecSJared Grubb FormatToken *First = Line->First; 193c45a66ecSJared Grubb assert(First); 194c45a66ecSJared Grubb if (First->Finalized) 195c45a66ecSJared Grubb continue; 196c45a66ecSJared Grubb 197c45a66ecSJared Grubb const auto *Last = Line->Last; 198c45a66ecSJared Grubb 199c45a66ecSJared Grubb for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) { 200c45a66ecSJared Grubb assert(Tok); 201c45a66ecSJared Grubb 202c45a66ecSJared Grubb // Skip until the `@` of a `@property` declaration. 203c45a66ecSJared Grubb if (Tok->isNot(TT_ObjCProperty)) 204c45a66ecSJared Grubb continue; 205c45a66ecSJared Grubb 206c45a66ecSJared Grubb analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok); 207c45a66ecSJared Grubb 208c45a66ecSJared Grubb // There are never two `@property` in a line (they are split 209c45a66ecSJared Grubb // by other passes), so this pass can break after just one. 210c45a66ecSJared Grubb break; 211c45a66ecSJared Grubb } 212c45a66ecSJared Grubb } 213c45a66ecSJared Grubb return {Fixes, 0}; 214c45a66ecSJared Grubb } 215c45a66ecSJared Grubb 216c45a66ecSJared Grubb } // namespace format 217c45a66ecSJared Grubb } // namespace clang 218