mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 02:46:06 +00:00
218 lines
6.6 KiB
C++
218 lines
6.6 KiB
C++
//===--- 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
|