blob: c91d6251425eab508c3c196c66ba77774f9fabdb [file] [log] [blame]
//===--- 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;
}
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<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