Skip to content

Commit c45a66e

Browse files
jaredgrubbowenca
authored andcommitted
[clang-format] ObjCPropertyAttributeOrder to sort ObjC property attributes
Add a style option to specify the order that property attributes should appear in ObjC property declarations (property attributes are things like `nonatomic, strong, nullable`). Closes #71323. Differential Revision: https://reviews.llvm.org/D150083
1 parent ccfc2d6 commit c45a66e

File tree

9 files changed

+740
-0
lines changed

9 files changed

+740
-0
lines changed

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4211,6 +4211,32 @@ the configuration (without a prefix: ``Auto``).
42114211
}]
42124212
}
42134213
4214+
.. _ObjCPropertyAttributeOrder:
4215+
4216+
**ObjCPropertyAttributeOrder** (``List of Strings``) :versionbadge:`clang-format 18` :ref:`<ObjCPropertyAttributeOrder>`
4217+
The order in which ObjC property attributes should appear.
4218+
4219+
Attributes in code will be sorted in the order specified. Any attributes
4220+
encountered that are not mentioned in this array will be sorted last, in
4221+
stable order. Comments between attributes will leave the attributes
4222+
untouched.
4223+
4224+
.. warning::
4225+
4226+
Using this option could lead to incorrect code formatting due to
4227+
clang-format's lack of complete semantic information. As such, extra
4228+
care should be taken to review code changes made by this option.
4229+
4230+
.. code-block:: yaml
4231+
4232+
ObjCPropertyAttributeOrder: [
4233+
class, direct,
4234+
atomic, nonatomic,
4235+
assign, retain, strong, copy, weak, unsafe_unretained,
4236+
readonly, readwrite, getter, setter,
4237+
nullable, nonnull, null_resettable, null_unspecified
4238+
]
4239+
42144240
.. _ObjCSpaceAfterProperty:
42154241

42164242
**ObjCSpaceAfterProperty** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`<ObjCSpaceAfterProperty>`

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,8 @@ clang-format
984984
- Add ``AllowShortCompoundRequirementOnASingleLine`` option.
985985
- Change ``BreakAfterAttributes`` from ``Never`` to ``Leave`` in LLVM style.
986986
- Add ``BreakAdjacentStringLiterals`` option.
987+
- Add ``ObjCPropertyAttributeOrder`` which can be used to sort ObjC property
988+
attributes (like ``nonatomic, strong, nullable``).
987989

988990
libclang
989991
--------

clang/include/clang/Format/Format.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3264,6 +3264,29 @@ struct FormatStyle {
32643264
/// \version 11
32653265
bool ObjCBreakBeforeNestedBlockParam;
32663266

3267+
/// The order in which ObjC property attributes should appear.
3268+
///
3269+
/// Attributes in code will be sorted in the order specified. Any attributes
3270+
/// encountered that are not mentioned in this array will be sorted last, in
3271+
/// stable order. Comments between attributes will leave the attributes
3272+
/// untouched.
3273+
/// \warning
3274+
/// Using this option could lead to incorrect code formatting due to
3275+
/// clang-format's lack of complete semantic information. As such, extra
3276+
/// care should be taken to review code changes made by this option.
3277+
/// \endwarning
3278+
/// \code{.yaml}
3279+
/// ObjCPropertyAttributeOrder: [
3280+
/// class, direct,
3281+
/// atomic, nonatomic,
3282+
/// assign, retain, strong, copy, weak, unsafe_unretained,
3283+
/// readonly, readwrite, getter, setter,
3284+
/// nullable, nonnull, null_resettable, null_unspecified
3285+
/// ]
3286+
/// \endcode
3287+
/// \version 18
3288+
std::vector<std::string> ObjCPropertyAttributeOrder;
3289+
32673290
/// Add a space after ``@property`` in Objective-C, i.e. use
32683291
/// ``@property (readonly)`` instead of ``@property(readonly)``.
32693292
/// \version 3.7
@@ -4821,6 +4844,7 @@ struct FormatStyle {
48214844
ObjCBlockIndentWidth == R.ObjCBlockIndentWidth &&
48224845
ObjCBreakBeforeNestedBlockParam ==
48234846
R.ObjCBreakBeforeNestedBlockParam &&
4847+
ObjCPropertyAttributeOrder == R.ObjCPropertyAttributeOrder &&
48244848
ObjCSpaceAfterProperty == R.ObjCSpaceAfterProperty &&
48254849
ObjCSpaceBeforeProtocolList == R.ObjCSpaceBeforeProtocolList &&
48264850
PackConstructorInitializers == R.PackConstructorInitializers &&

clang/lib/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_clang_library(clangFormat
1212
MacroCallReconstructor.cpp
1313
MacroExpander.cpp
1414
NamespaceEndCommentsFixer.cpp
15+
ObjCPropertyAttributeOrderFixer.cpp
1516
QualifierAlignmentFixer.cpp
1617
SortJavaScriptImports.cpp
1718
TokenAnalyzer.cpp

clang/lib/Format/Format.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "FormatTokenLexer.h"
2323
#include "IntegerLiteralSeparatorFixer.h"
2424
#include "NamespaceEndCommentsFixer.h"
25+
#include "ObjCPropertyAttributeOrderFixer.h"
2526
#include "QualifierAlignmentFixer.h"
2627
#include "SortJavaScriptImports.h"
2728
#include "TokenAnalyzer.h"
@@ -1039,6 +1040,8 @@ template <> struct MappingTraits<FormatStyle> {
10391040
IO.mapOptional("ObjCBlockIndentWidth", Style.ObjCBlockIndentWidth);
10401041
IO.mapOptional("ObjCBreakBeforeNestedBlockParam",
10411042
Style.ObjCBreakBeforeNestedBlockParam);
1043+
IO.mapOptional("ObjCPropertyAttributeOrder",
1044+
Style.ObjCPropertyAttributeOrder);
10421045
IO.mapOptional("ObjCSpaceAfterProperty", Style.ObjCSpaceAfterProperty);
10431046
IO.mapOptional("ObjCSpaceBeforeProtocolList",
10441047
Style.ObjCSpaceBeforeProtocolList);
@@ -3711,6 +3714,13 @@ reformat(const FormatStyle &Style, StringRef Code,
37113714
});
37123715
}
37133716

3717+
if (Style.Language == FormatStyle::LK_ObjC &&
3718+
!Style.ObjCPropertyAttributeOrder.empty()) {
3719+
Passes.emplace_back([&](const Environment &Env) {
3720+
return ObjCPropertyAttributeOrderFixer(Env, Expanded).process();
3721+
});
3722+
}
3723+
37143724
if (Style.isJavaScript() &&
37153725
Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) {
37163726
Passes.emplace_back([&](const Environment &Env) {
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file implements ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
11+
/// adjusts the order of attributes in an ObjC `@property(...)` declaration,
12+
/// depending on the style.
13+
///
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "ObjCPropertyAttributeOrderFixer.h"
17+
18+
#include "llvm/ADT/Sequence.h"
19+
20+
#include <algorithm>
21+
22+
namespace clang {
23+
namespace format {
24+
25+
ObjCPropertyAttributeOrderFixer::ObjCPropertyAttributeOrderFixer(
26+
const Environment &Env, const FormatStyle &Style)
27+
: TokenAnalyzer(Env, Style) {
28+
29+
// Create an "order priority" map to use to sort properties.
30+
unsigned index = 0;
31+
for (const auto &Property : Style.ObjCPropertyAttributeOrder)
32+
SortOrderMap[Property] = index++;
33+
}
34+
35+
struct ObjCPropertyEntry {
36+
StringRef Attribute; // eg, "readwrite"
37+
StringRef Value; // eg, the "foo" of the attribute "getter=foo"
38+
};
39+
40+
static bool isObjCPropertyAttribute(const FormatToken *Tok) {
41+
// Most attributes look like identifiers, but `class` is a keyword.
42+
return Tok->isOneOf(tok::identifier, tok::kw_class);
43+
}
44+
45+
void ObjCPropertyAttributeOrderFixer::sortPropertyAttributes(
46+
const SourceManager &SourceMgr, tooling::Replacements &Fixes,
47+
const FormatToken *BeginTok, const FormatToken *EndTok) const {
48+
assert(BeginTok);
49+
assert(EndTok);
50+
assert(EndTok->Previous);
51+
52+
// If there are zero or one tokens, nothing to do.
53+
if (BeginTok == EndTok || BeginTok->Next == EndTok)
54+
return;
55+
56+
// Collect the attributes.
57+
SmallVector<ObjCPropertyEntry, 8> PropertyAttributes;
58+
for (auto Tok = BeginTok; Tok != EndTok; Tok = Tok->Next) {
59+
assert(Tok);
60+
if (Tok->is(tok::comma)) {
61+
// Ignore the comma separators.
62+
continue;
63+
}
64+
65+
if (!isObjCPropertyAttribute(Tok)) {
66+
// If we hit any other kind of token, just bail.
67+
return;
68+
}
69+
70+
// Memoize the attribute. (Note that 'class' is a legal attribute!)
71+
PropertyAttributes.push_back({Tok->TokenText, StringRef{}});
72+
73+
// Also handle `getter=getFoo` attributes.
74+
// (Note: no check needed against `EndTok`, since its type is not
75+
// BinaryOperator or Identifier)
76+
assert(Tok->Next);
77+
if (Tok->Next->is(tok::equal)) {
78+
Tok = Tok->Next;
79+
assert(Tok->Next);
80+
if (Tok->Next->isNot(tok::identifier)) {
81+
// If we hit any other kind of token, just bail. It's unusual/illegal.
82+
return;
83+
}
84+
Tok = Tok->Next;
85+
PropertyAttributes.back().Value = Tok->TokenText;
86+
}
87+
}
88+
89+
// There's nothing to do unless there's more than one attribute.
90+
if (PropertyAttributes.size() < 2)
91+
return;
92+
93+
// Create a "remapping index" on how to reorder the attributes.
94+
SmallVector<unsigned, 8> Indices =
95+
llvm::to_vector<8>(llvm::seq<unsigned>(0, PropertyAttributes.size()));
96+
97+
// Sort the indices based on the priority stored in 'SortOrderMap'; use Max
98+
// for missing values.
99+
const auto SortOrderMax = Style.ObjCPropertyAttributeOrder.size();
100+
auto SortIndex = [&](const StringRef &Needle) -> unsigned {
101+
auto I = SortOrderMap.find(Needle);
102+
return (I == SortOrderMap.end()) ? SortOrderMax : I->getValue();
103+
};
104+
llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) {
105+
return SortIndex(PropertyAttributes[LHSI].Attribute) <
106+
SortIndex(PropertyAttributes[RHSI].Attribute);
107+
});
108+
109+
// If the property order is already correct, then no fix-up is needed.
110+
if (llvm::is_sorted(Indices))
111+
return;
112+
113+
// Generate the replacement text.
114+
std::string NewText;
115+
const auto AppendAttribute = [&](const ObjCPropertyEntry &PropertyEntry) {
116+
NewText += PropertyEntry.Attribute;
117+
118+
if (!PropertyEntry.Value.empty()) {
119+
NewText += "=";
120+
NewText += PropertyEntry.Value;
121+
}
122+
};
123+
124+
AppendAttribute(PropertyAttributes[Indices[0]]);
125+
for (unsigned Index : llvm::drop_begin(Indices)) {
126+
NewText += ", ";
127+
AppendAttribute(PropertyAttributes[Index]);
128+
}
129+
130+
auto Range = CharSourceRange::getCharRange(
131+
BeginTok->getStartOfNonWhitespace(), EndTok->Previous->Tok.getEndLoc());
132+
auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);
133+
auto Err = Fixes.add(Replacement);
134+
if (Err) {
135+
llvm::errs() << "Error while reodering ObjC property attributes : "
136+
<< llvm::toString(std::move(Err)) << "\n";
137+
}
138+
}
139+
140+
void ObjCPropertyAttributeOrderFixer::analyzeObjCPropertyDecl(
141+
const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,
142+
tooling::Replacements &Fixes, const FormatToken *Tok) const {
143+
assert(Tok);
144+
145+
// Expect `property` to be the very next token or else just bail early.
146+
const FormatToken *const PropertyTok = Tok->Next;
147+
if (!PropertyTok || PropertyTok->isNot(Keywords.kw_property))
148+
return;
149+
150+
// Expect the opening paren to be the next token or else just bail early.
151+
const FormatToken *const LParenTok = PropertyTok->getNextNonComment();
152+
if (!LParenTok || LParenTok->isNot(tok::l_paren))
153+
return;
154+
155+
// Get the matching right-paren, the bounds for property attributes.
156+
const FormatToken *const RParenTok = LParenTok->MatchingParen;
157+
if (!RParenTok)
158+
return;
159+
160+
sortPropertyAttributes(SourceMgr, Fixes, LParenTok->Next, RParenTok);
161+
}
162+
163+
std::pair<tooling::Replacements, unsigned>
164+
ObjCPropertyAttributeOrderFixer::analyze(
165+
TokenAnnotator & /*Annotator*/,
166+
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
167+
FormatTokenLexer &Tokens) {
168+
tooling::Replacements Fixes;
169+
const AdditionalKeywords &Keywords = Tokens.getKeywords();
170+
const SourceManager &SourceMgr = Env.getSourceManager();
171+
AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
172+
173+
for (AnnotatedLine *Line : AnnotatedLines) {
174+
assert(Line);
175+
if (!Line->Affected || Line->Type != LT_ObjCProperty)
176+
continue;
177+
FormatToken *First = Line->First;
178+
assert(First);
179+
if (First->Finalized)
180+
continue;
181+
182+
const auto *Last = Line->Last;
183+
184+
for (const auto *Tok = First; Tok != Last; Tok = Tok->Next) {
185+
assert(Tok);
186+
187+
// Skip until the `@` of a `@property` declaration.
188+
if (Tok->isNot(TT_ObjCProperty))
189+
continue;
190+
191+
analyzeObjCPropertyDecl(SourceMgr, Keywords, Fixes, Tok);
192+
193+
// There are never two `@property` in a line (they are split
194+
// by other passes), so this pass can break after just one.
195+
break;
196+
}
197+
}
198+
return {Fixes, 0};
199+
}
200+
201+
} // namespace format
202+
} // namespace clang
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===--- ObjCPropertyAttributeOrderFixer.h ------------------------------*- C++
2+
//-*-===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
///
10+
/// \file
11+
/// This file declares ObjCPropertyAttributeOrderFixer, a TokenAnalyzer that
12+
/// adjusts the order of attributes in an ObjC `@property(...)` declaration,
13+
/// depending on the style.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef LLVM_CLANG_LIB_FORMAT_OBJCPROPERTYATTRIBUTEORDERFIXER_H
18+
#define LLVM_CLANG_LIB_FORMAT_OBJCPROPERTYATTRIBUTEORDERFIXER_H
19+
20+
#include "TokenAnalyzer.h"
21+
22+
namespace clang {
23+
namespace format {
24+
25+
class ObjCPropertyAttributeOrderFixer : public TokenAnalyzer {
26+
llvm::StringMap<unsigned> SortOrderMap;
27+
28+
void analyzeObjCPropertyDecl(const SourceManager &SourceMgr,
29+
const AdditionalKeywords &Keywords,
30+
tooling::Replacements &Fixes,
31+
const FormatToken *Tok) const;
32+
33+
void sortPropertyAttributes(const SourceManager &SourceMgr,
34+
tooling::Replacements &Fixes,
35+
const FormatToken *BeginTok,
36+
const FormatToken *EndTok) const;
37+
38+
std::pair<tooling::Replacements, unsigned>
39+
analyze(TokenAnnotator &Annotator,
40+
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
41+
FormatTokenLexer &Tokens) override;
42+
43+
public:
44+
ObjCPropertyAttributeOrderFixer(const Environment &Env,
45+
const FormatStyle &Style);
46+
};
47+
48+
} // end namespace format
49+
} // end namespace clang
50+
51+
#endif

clang/unittests/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ add_clang_unittest(FormatTests
2828
MacroCallReconstructorTest.cpp
2929
MacroExpanderTest.cpp
3030
NamespaceEndCommentsFixerTest.cpp
31+
ObjCPropertyAttributeOrderFixerTest.cpp
3132
QualifierFixerTest.cpp
3233
SortImportsTestJS.cpp
3334
SortImportsTestJava.cpp

0 commit comments

Comments
 (0)