Skip to content

Commit f0ba28d

Browse files
committed
feat: sort-members-by option
1 parent 2b59269 commit f0ba28d

14 files changed

+1811
-52
lines changed

docs/mrdocs.schema.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,15 @@
437437
"title": "Sort assignment operators first",
438438
"type": "boolean"
439439
},
440+
"sort-members-by": {
441+
"default": "name",
442+
"description": "If `sort-members` is set to `true`, determine how members of a record or namespace are sorted. When set to `name`, members are sorted by name. When set to `location`, members are sorted by their primary location in the source code, considering the short name of the path and the location in the file.",
443+
"enum": [
444+
"name",
445+
"location"
446+
],
447+
"title": "Determine how members of a record or namespace are sorted"
448+
},
440449
"sort-members-conversion-last": {
441450
"default": true,
442451
"description": "When set to `true`, conversion operators are sorted last in the list of members of a record or namespace.",

src/lib/ConfigOptions.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,17 @@
282282
"type": "bool",
283283
"default": true
284284
},
285+
{
286+
"name": "sort-members-by",
287+
"brief": "Determine how members of a record or namespace are sorted",
288+
"details": "If `sort-members` is set to `true`, determine how members of a record or namespace are sorted. When set to `name`, members are sorted by name. When set to `location`, members are sorted by their primary location in the source code, considering the short name of the path and the location in the file.",
289+
"type": "enum",
290+
"values": [
291+
"name",
292+
"location"
293+
],
294+
"default": "name"
295+
},
285296
{
286297
"name": "sort-members-ctors-1st",
287298
"brief": "Sort constructors first",

src/lib/Metadata/Finalizers/SortMembersFinalizer.cpp

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ struct SymbolIDCompareFn
7575
Info const& lhs = *lhsPtr;
7676
Info const& rhs = *rhsPtr;
7777

78+
// Constructors come first
7879
std::optional<FunctionClass> const lhsClass = findFunctionClass(lhs);
7980
std::optional<FunctionClass> const rhsClass = findFunctionClass(rhs);
8081
if (corpus_.config->sortMembersCtors1St)
@@ -87,6 +88,7 @@ struct SymbolIDCompareFn
8788
}
8889
}
8990

91+
// Destructors come next
9092
if (corpus_.config->sortMembersDtors1St)
9193
{
9294
bool const lhsIsDtor = lhsClass && *lhsClass == FunctionClass::Destructor;
@@ -97,6 +99,7 @@ struct SymbolIDCompareFn
9799
}
98100
}
99101

102+
// Assignment operators come next
100103
std::optional<OperatorKind> const lhsOp = findOperatorKind(lhs);
101104
std::optional<OperatorKind> const rhsOp = findOperatorKind(rhs);
102105
if (corpus_.config->sortMembersAssignment1St)
@@ -109,6 +112,7 @@ struct SymbolIDCompareFn
109112
}
110113
}
111114

115+
// Relational operators come last
112116
if (corpus_.config->sortMembersRelationalLast)
113117
{
114118
bool const lhsIsRel = lhsOp && (
@@ -141,6 +145,7 @@ struct SymbolIDCompareFn
141145
}
142146
}
143147

148+
// Conversion operators come last
144149
if (corpus_.config->sortMembersConversionLast)
145150
{
146151
bool const lhsIsConvertion = lhsClass && *lhsClass == FunctionClass::Conversion;
@@ -207,11 +212,40 @@ struct SymbolIDCompareFn
207212
}
208213
}
209214

210-
if (auto const cmp = lhs.Name <=> rhs.Name; cmp != 0)
215+
// Special cases are handled, so use the configuration criteria
216+
switch (corpus_.config->sortMembersBy)
211217
{
212-
return std::is_lt(cmp);
218+
case clang::mrdocs::PublicSettings::SortSymbolBy::Name:
219+
if (auto const cmp = lhs.Name <=> rhs.Name; cmp != 0)
220+
{
221+
return std::is_lt(cmp);
222+
}
223+
break;
224+
case clang::mrdocs::PublicSettings::SortSymbolBy::Location:
225+
{
226+
// By location: short path, line, column
227+
auto const& lhsLoc = getPrimaryLocation(lhs);
228+
auto const& rhsLoc = getPrimaryLocation(rhs);
229+
if (auto const cmp = lhsLoc->ShortPath <=> rhsLoc->ShortPath;
230+
cmp != 0)
231+
{
232+
return std::is_lt(cmp);
233+
}
234+
if (auto const cmp = lhsLoc->LineNumber <=> rhsLoc->LineNumber;
235+
cmp != 0)
236+
{
237+
return std::is_lt(cmp);
238+
}
239+
break;
240+
}
241+
default:
242+
MRDOCS_UNREACHABLE();
213243
}
214244

245+
// In case of a tie, we use the internal criteria for that symbol type
246+
// to ensure a stable sort. For instance, in the case of functions,
247+
// we sort by name, then number of parameters, then parameter types,
248+
// and so on.
215249
return std::is_lt(CompareDerived(lhs, rhs));
216250
}
217251
};
@@ -267,78 +301,104 @@ sortMembers(RecordInterface& I)
267301
sortMembers(I.Private);
268302
}
269303

270-
void
271-
SortMembersFinalizer::
272-
sortNamespaceMembers(std::vector<SymbolID>& ids)
304+
namespace {
305+
template <class T>
306+
constexpr
307+
auto
308+
toDerivedView(std::vector<SymbolID> const& ids, CorpusImpl& c)
273309
{
274-
for (SymbolID const& id: ids)
275-
{
276-
auto infoPtr = corpus_.find(id);
277-
MRDOCS_CHECK_OR_CONTINUE(infoPtr);
278-
auto* ns = infoPtr->asNamespacePtr();
279-
MRDOCS_CHECK_OR_CONTINUE(ns);
280-
operator()(*ns);
281-
}
310+
return ids |
311+
std::views::transform([&c](SymbolID const& id) {
312+
return c.find(id);
313+
}) |
314+
std::views::filter([](Info* infoPtr) {
315+
return infoPtr != nullptr;
316+
}) |
317+
std::views::transform([](Info* infoPtr) -> T* {
318+
return dynamic_cast<T*>(infoPtr);
319+
}) |
320+
std::views::filter([](T* ptr) {
321+
return ptr != nullptr;
322+
}) |
323+
std::views::transform([](T* ptr) -> T& {
324+
return *ptr;
325+
});
326+
}
282327
}
283328

284329
void
285330
SortMembersFinalizer::
286-
sortRecordMembers(std::vector<SymbolID>& ids)
331+
operator()(NamespaceInfo& I)
287332
{
288-
for (SymbolID const& id: ids)
333+
// Sort members of all tranches
334+
sortMembers(I.Members);
335+
336+
// Recursively sort members of child namespaces, records, and overloads
337+
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Members.Records, corpus_))
289338
{
290-
auto infoPtr = corpus_.find(id);
291-
MRDOCS_CHECK_OR_CONTINUE(infoPtr);
292-
auto* record = infoPtr->asRecordPtr();
293-
MRDOCS_CHECK_OR_CONTINUE(record);
294-
operator()(*record);
339+
operator()(RI);
295340
}
296-
}
297-
298-
void
299-
SortMembersFinalizer::
300-
sortOverloadMembers(std::vector<SymbolID>& ids)
301-
{
302-
for (SymbolID const& id: ids)
341+
for (NamespaceInfo& RI: toDerivedView<NamespaceInfo>(I.Members.Namespaces, corpus_))
303342
{
304-
auto infoPtr = corpus_.find(id);
305-
MRDOCS_CHECK_OR_CONTINUE(infoPtr);
306-
auto* overloads = infoPtr->asOverloadsPtr();
307-
MRDOCS_CHECK_OR_CONTINUE(overloads);
308-
operator()(*overloads);
343+
operator()(RI);
344+
}
345+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Members.Functions, corpus_))
346+
{
347+
operator()(RI);
309348
}
310-
}
311-
312-
void
313-
SortMembersFinalizer::
314-
operator()(NamespaceInfo& I)
315-
{
316-
sortMembers(I.Members);
317-
sortRecordMembers(I.Members.Records);
318-
sortNamespaceMembers(I.Members.Namespaces);
319-
sortOverloadMembers(I.Members.Functions);
320349
}
321350

322351
void
323352
SortMembersFinalizer::
324353
operator()(RecordInfo& I)
325354
{
355+
// Sort members of all tranches
326356
sortMembers(I.Interface);
327-
sortRecordMembers(I.Interface.Public.Records);
328-
sortRecordMembers(I.Interface.Protected.Records);
329-
sortRecordMembers(I.Interface.Private.Records);
330-
sortOverloadMembers(I.Interface.Public.Functions);
331-
sortOverloadMembers(I.Interface.Protected.Functions);
332-
sortOverloadMembers(I.Interface.Private.Functions);
333-
sortOverloadMembers(I.Interface.Public.StaticFunctions);
334-
sortOverloadMembers(I.Interface.Protected.StaticFunctions);
335-
sortOverloadMembers(I.Interface.Private.StaticFunctions);
357+
358+
// Recursively sort members of child records and overloads
359+
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Interface.Public.Records, corpus_))
360+
{
361+
operator()(RI);
362+
}
363+
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Interface.Protected.Records, corpus_))
364+
{
365+
operator()(RI);
366+
}
367+
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Interface.Private.Records, corpus_))
368+
{
369+
operator()(RI);
370+
}
371+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Public.Functions, corpus_))
372+
{
373+
operator()(RI);
374+
}
375+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Protected.Functions, corpus_))
376+
{
377+
operator()(RI);
378+
}
379+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Private.Functions, corpus_))
380+
{
381+
operator()(RI);
382+
}
383+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Public.StaticFunctions, corpus_))
384+
{
385+
operator()(RI);
386+
}
387+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Protected.StaticFunctions, corpus_))
388+
{
389+
operator()(RI);
390+
}
391+
for (OverloadsInfo& RI: toDerivedView<OverloadsInfo>(I.Interface.Private.StaticFunctions, corpus_))
392+
{
393+
operator()(RI);
394+
}
336395
}
337396

338397
void
339398
SortMembersFinalizer::
340399
operator()(OverloadsInfo& I)
341400
{
401+
// Sort the member functions
342402
sortMembers(I.Members);
343403
}
344404

0 commit comments

Comments
 (0)