Skip to content

Commit 5edf229

Browse files
committed
Add support for defining the kind sort order
Closes #2109
1 parent 36c95c0 commit 5edf229

File tree

9 files changed

+129
-56
lines changed

9 files changed

+129
-56
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Unreleased
22

3+
### Features
4+
5+
- Add support for defining the kind sort order, #2109.
6+
37
### Bug Fixes
48

59
- Normalize all file paths on Windows, #2113.

src/lib/converter/plugins/GroupPlugin.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ReflectionGroup } from "../../models/ReflectionGroup";
88
import { Component, ConverterComponent } from "../components";
99
import { Converter } from "../converter";
1010
import type { Context } from "../context";
11-
import { sortReflections, SortStrategy } from "../../utils/sort";
11+
import { getSortFunction } from "../../utils/sort";
1212
import { BindOption, removeIf } from "../../utils";
1313
import { Comment } from "../../models";
1414

@@ -38,9 +38,7 @@ export class GroupPlugin extends ConverterComponent {
3838
[ReflectionKind.TypeAlias]: "Type Aliases",
3939
};
4040

41-
/** @internal */
42-
@BindOption("sort")
43-
sortStrategies!: SortStrategy[];
41+
sortFunction!: (reflections: DeclarationReflection[]) => void;
4442

4543
@BindOption("searchGroupBoosts")
4644
boosts!: Record<string, number>;
@@ -52,6 +50,9 @@ export class GroupPlugin extends ConverterComponent {
5250
*/
5351
override initialize() {
5452
this.listenTo(this.owner, {
53+
[Converter.EVENT_RESOLVE_BEGIN]: () => {
54+
this.sortFunction = getSortFunction(this.application.options);
55+
},
5556
[Converter.EVENT_RESOLVE]: this.onResolve,
5657
[Converter.EVENT_RESOLVE_END]: this.onEndResolve,
5758
});
@@ -104,7 +105,7 @@ export class GroupPlugin extends ConverterComponent {
104105
reflection.children.length > 0 &&
105106
!reflection.groups
106107
) {
107-
sortReflections(reflection.children, this.sortStrategies);
108+
this.sortFunction(reflection.children);
108109
reflection.groups = this.getReflectionGroups(reflection.children);
109110
}
110111
}

src/lib/utils/enum.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ export function hasAllFlags(flags: number, check: number): boolean {
2121
export function hasAnyFlag(flags: number, check: number): boolean {
2222
return (flags & check) !== 0;
2323
}
24+
25+
// Note: String enums are not handled.
26+
export function getEnumKeys(Enum: Record<string, string | number>): string[] {
27+
return Object.keys(Enum).filter((k) => {
28+
return Enum[Enum[k]] === k;
29+
});
30+
}

src/lib/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export type {
5353
ManuallyValidatedOption,
5454
} from "./options";
5555
export { discoverPlugins, loadPlugins } from "./plugins";
56-
export { sortReflections } from "./sort";
56+
export { getSortFunction } from "./sort";
5757
export type { SortStrategy } from "./sort";
5858

5959
export { EventHooks } from "./hooks";

src/lib/utils/options/declaration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export interface TypeDocOptionMap {
133133
defaultCategory: string;
134134
categoryOrder: string[];
135135
sort: SortStrategy[];
136+
kindSortOrder: Array<keyof typeof ReflectionKind>;
136137
visibilityFilters: ManuallyValidatedOption<{
137138
protected?: boolean;
138139
private?: boolean;

src/lib/utils/options/sources/typedoc.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { EntryPointStrategy } from "../../entry-point";
1212
import { ReflectionKind } from "../../../models/reflections/kind";
1313
import * as Validation from "../../validation";
1414
import { blockTags, inlineTags, modifierTags } from "../tsdoc-defaults";
15+
import { getEnumKeys } from "../../enum";
1516

1617
// For convenience, added in the same order as they are documented on the website.
1718
export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
@@ -451,6 +452,28 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
451452
}
452453
},
453454
});
455+
options.addDeclaration({
456+
name: "kindSortOrder",
457+
help: "Specify the sort order for reflections when 'kind' is specified.",
458+
type: ParameterType.Array,
459+
defaultValue: [],
460+
validate(value) {
461+
const invalid = new Set(value);
462+
const valid = getEnumKeys(ReflectionKind);
463+
for (const v of valid) {
464+
invalid.delete(v);
465+
}
466+
467+
if (invalid.size !== 0) {
468+
throw new Error(
469+
`kindSortOrder may only specify known values, and invalid values were provided (${Array.from(
470+
invalid
471+
).join(", ")}). The valid kinds are:\n${valid.join(", ")}`
472+
);
473+
}
474+
},
475+
});
476+
454477
options.addDeclaration({
455478
name: "visibilityFilters",
456479
help: "Specify the default visibility for builtin filters and additional filters according to modifier tags.",
@@ -595,9 +618,7 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
595618
type: ParameterType.Array,
596619
validate(values) {
597620
// this is good enough because the values of the ReflectionKind enum are all numbers
598-
const validValues = Object.values(ReflectionKind).filter(
599-
(v) => typeof v === "string"
600-
);
621+
const validValues = getEnumKeys(ReflectionKind);
601622

602623
for (const kind of values) {
603624
if (!validValues.includes(kind)) {

src/lib/utils/sort.ts

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { ReflectionKind } from "../models/reflections/kind";
77
import type { DeclarationReflection } from "../models/reflections/declaration";
88
import { LiteralType } from "../models/types";
9+
import type { Options } from "./options";
910

1011
export const SORT_STRATEGIES = [
1112
"source-order",
@@ -21,10 +22,43 @@ export const SORT_STRATEGIES = [
2122

2223
export type SortStrategy = typeof SORT_STRATEGIES[number];
2324

25+
const defaultKindSortOrder = [
26+
ReflectionKind.Reference,
27+
ReflectionKind.Project,
28+
ReflectionKind.Module,
29+
ReflectionKind.Namespace,
30+
ReflectionKind.Enum,
31+
ReflectionKind.EnumMember,
32+
ReflectionKind.Class,
33+
ReflectionKind.Interface,
34+
ReflectionKind.TypeAlias,
35+
36+
ReflectionKind.Constructor,
37+
ReflectionKind.Property,
38+
ReflectionKind.Variable,
39+
ReflectionKind.Function,
40+
ReflectionKind.Accessor,
41+
ReflectionKind.Method,
42+
ReflectionKind.ObjectLiteral,
43+
44+
ReflectionKind.Parameter,
45+
ReflectionKind.TypeParameter,
46+
ReflectionKind.TypeLiteral,
47+
ReflectionKind.CallSignature,
48+
ReflectionKind.ConstructorSignature,
49+
ReflectionKind.IndexSignature,
50+
ReflectionKind.GetSignature,
51+
ReflectionKind.SetSignature,
52+
] as const;
53+
2454
// Return true if a < b
2555
const sorts: Record<
2656
SortStrategy,
27-
(a: DeclarationReflection, b: DeclarationReflection) => boolean
57+
(
58+
a: DeclarationReflection,
59+
b: DeclarationReflection,
60+
data: { kindSortOrder: ReflectionKind[] }
61+
) => boolean
2862
> = {
2963
"source-order"(a, b) {
3064
const aSymbol = a.project.getSymbolFromReflection(a);
@@ -107,53 +141,36 @@ const sorts: Record<
107141
"required-first"(a, b) {
108142
return !a.flags.isOptional && b.flags.isOptional;
109143
},
110-
kind(a, b) {
111-
const weights = [
112-
ReflectionKind.Reference,
113-
ReflectionKind.Project,
114-
ReflectionKind.Module,
115-
ReflectionKind.Namespace,
116-
ReflectionKind.Enum,
117-
ReflectionKind.EnumMember,
118-
ReflectionKind.Class,
119-
ReflectionKind.Interface,
120-
ReflectionKind.TypeAlias,
121-
122-
ReflectionKind.Constructor,
123-
ReflectionKind.Property,
124-
ReflectionKind.Variable,
125-
ReflectionKind.Function,
126-
ReflectionKind.Accessor,
127-
ReflectionKind.Method,
128-
ReflectionKind.ObjectLiteral,
129-
130-
ReflectionKind.Parameter,
131-
ReflectionKind.TypeParameter,
132-
ReflectionKind.TypeLiteral,
133-
ReflectionKind.CallSignature,
134-
ReflectionKind.ConstructorSignature,
135-
ReflectionKind.IndexSignature,
136-
ReflectionKind.GetSignature,
137-
ReflectionKind.SetSignature,
138-
] as const;
139-
140-
return weights.indexOf(a.kind) < weights.indexOf(b.kind);
144+
kind(a, b, { kindSortOrder }) {
145+
return kindSortOrder.indexOf(a.kind) < kindSortOrder.indexOf(b.kind);
141146
},
142147
};
143148

144-
export function sortReflections(
145-
reflections: DeclarationReflection[],
146-
strategies: readonly SortStrategy[]
147-
) {
148-
reflections.sort((a, b) => {
149-
for (const s of strategies) {
150-
if (sorts[s](a, b)) {
151-
return -1;
152-
}
153-
if (sorts[s](b, a)) {
154-
return 1;
155-
}
149+
export function getSortFunction(opts: Options) {
150+
const kindSortOrder = opts
151+
.getValue("kindSortOrder")
152+
.map((k) => ReflectionKind[k]);
153+
154+
for (const kind of defaultKindSortOrder) {
155+
if (!kindSortOrder.includes(kind)) {
156+
kindSortOrder.push(kind);
156157
}
157-
return 0;
158-
});
158+
}
159+
160+
const strategies = opts.getValue("sort");
161+
const data = { kindSortOrder };
162+
163+
return function sortReflections(reflections: DeclarationReflection[]) {
164+
reflections.sort((a, b) => {
165+
for (const s of strategies) {
166+
if (sorts[s](a, b, data)) {
167+
return -1;
168+
}
169+
if (sorts[s](b, a, data)) {
170+
return 1;
171+
}
172+
}
173+
return 0;
174+
});
175+
};
159176
}

src/test/utils/enum.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ok } from "assert";
2+
import { ReflectionKind } from "../../lib/models";
3+
import { getEnumKeys } from "../../lib/utils/enum";
4+
5+
describe("Enum utils", () => {
6+
it("Should be able to get enum keys", () => {
7+
const keys = getEnumKeys(ReflectionKind);
8+
ok(keys.includes("Project"));
9+
ok(!keys.includes("SignatureContainer"));
10+
});
11+
});

src/test/utils/sort.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@ import {
77
ReflectionKind,
88
} from "../../lib/models";
99
import { resetReflectionID } from "../../lib/models/reflections/abstract";
10-
import { sortReflections } from "../../lib/utils";
10+
import { Logger, Options } from "../../lib/utils";
11+
import { getSortFunction, SortStrategy } from "../../lib/utils/sort";
1112

1213
describe("Sort", () => {
14+
function sortReflections(
15+
arr: DeclarationReflection[],
16+
strategies: SortStrategy[]
17+
) {
18+
const opts = new Options(new Logger());
19+
opts.addDefaultDeclarations();
20+
opts.setValue("sort", strategies);
21+
getSortFunction(opts)(arr);
22+
}
23+
1324
it("Should sort by name", () => {
1425
const arr = [
1526
new DeclarationReflection("a", ReflectionKind.TypeAlias),

0 commit comments

Comments
 (0)