//===--- 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 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 Ordinals; // Create a "remapping index" on how to reorder the attributes. SmallVector Indices; // Collect the attributes. SmallVector 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; } auto It = SortOrderMap.find(Attribute); if (It == SortOrderMap.end()) It = SortOrderMap.insert({Attribute, SortOrderMap.size()}).first; // Sort the indices based on the priority stored in `SortOrderMap`. const auto Ordinal = It->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 ObjCPropertyAttributeOrderFixer::analyze( TokenAnnotator & /*Annotator*/, SmallVectorImpl &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