216 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that11/// adjusts the order of attributes in an ObjC `@property(...)` declaration,12/// depending on the style.13///14//===----------------------------------------------------------------------===//15 16#include "ObjCPropertyAttributeOrderFixer.h"17 18namespace clang {19namespace format {20 21ObjCPropertyAttributeOrderFixer::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 30struct ObjCPropertyEntry {31 StringRef Attribute; // eg, `readwrite`32 StringRef Value; // eg, the `foo` of the attribute `getter=foo`33};34 35void 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->isNoneOf(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 not74 // 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 else132 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 153void 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 176std::pair<tooling::Replacements, unsigned>177ObjCPropertyAttributeOrderFixer::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 split207 // by other passes), so this pass can break after just one.208 break;209 }210 }211 return {Fixes, 0};212}213 214} // namespace format215} // namespace clang216