Skip to content

Commit

Permalink
Add support for defining the kind sort order
Browse files Browse the repository at this point in the history
Closes #2109
  • Loading branch information
Gerrit0 committed Dec 11, 2022
1 parent 36c95c0 commit 5edf229
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Unreleased

### Features

- Add support for defining the kind sort order, #2109.

### Bug Fixes

- Normalize all file paths on Windows, #2113.
Expand Down
11 changes: 6 additions & 5 deletions src/lib/converter/plugins/GroupPlugin.ts
Expand Up @@ -8,7 +8,7 @@ import { ReflectionGroup } from "../../models/ReflectionGroup";
import { Component, ConverterComponent } from "../components";
import { Converter } from "../converter";
import type { Context } from "../context";
import { sortReflections, SortStrategy } from "../../utils/sort";
import { getSortFunction } from "../../utils/sort";
import { BindOption, removeIf } from "../../utils";
import { Comment } from "../../models";

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

/** @internal */
@BindOption("sort")
sortStrategies!: SortStrategy[];
sortFunction!: (reflections: DeclarationReflection[]) => void;

@BindOption("searchGroupBoosts")
boosts!: Record<string, number>;
Expand All @@ -52,6 +50,9 @@ export class GroupPlugin extends ConverterComponent {
*/
override initialize() {
this.listenTo(this.owner, {
[Converter.EVENT_RESOLVE_BEGIN]: () => {
this.sortFunction = getSortFunction(this.application.options);
},
[Converter.EVENT_RESOLVE]: this.onResolve,
[Converter.EVENT_RESOLVE_END]: this.onEndResolve,
});
Expand Down Expand Up @@ -104,7 +105,7 @@ export class GroupPlugin extends ConverterComponent {
reflection.children.length > 0 &&
!reflection.groups
) {
sortReflections(reflection.children, this.sortStrategies);
this.sortFunction(reflection.children);
reflection.groups = this.getReflectionGroups(reflection.children);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/lib/utils/enum.ts
Expand Up @@ -21,3 +21,10 @@ export function hasAllFlags(flags: number, check: number): boolean {
export function hasAnyFlag(flags: number, check: number): boolean {
return (flags & check) !== 0;
}

// Note: String enums are not handled.
export function getEnumKeys(Enum: Record<string, string | number>): string[] {
return Object.keys(Enum).filter((k) => {
return Enum[Enum[k]] === k;
});
}
2 changes: 1 addition & 1 deletion src/lib/utils/index.ts
Expand Up @@ -53,7 +53,7 @@ export type {
ManuallyValidatedOption,
} from "./options";
export { discoverPlugins, loadPlugins } from "./plugins";
export { sortReflections } from "./sort";
export { getSortFunction } from "./sort";
export type { SortStrategy } from "./sort";

export { EventHooks } from "./hooks";
Expand Down
1 change: 1 addition & 0 deletions src/lib/utils/options/declaration.ts
Expand Up @@ -133,6 +133,7 @@ export interface TypeDocOptionMap {
defaultCategory: string;
categoryOrder: string[];
sort: SortStrategy[];
kindSortOrder: Array<keyof typeof ReflectionKind>;
visibilityFilters: ManuallyValidatedOption<{
protected?: boolean;
private?: boolean;
Expand Down
27 changes: 24 additions & 3 deletions src/lib/utils/options/sources/typedoc.ts
Expand Up @@ -12,6 +12,7 @@ import { EntryPointStrategy } from "../../entry-point";
import { ReflectionKind } from "../../../models/reflections/kind";
import * as Validation from "../../validation";
import { blockTags, inlineTags, modifierTags } from "../tsdoc-defaults";
import { getEnumKeys } from "../../enum";

// For convenience, added in the same order as they are documented on the website.
export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
Expand Down Expand Up @@ -451,6 +452,28 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
}
},
});
options.addDeclaration({
name: "kindSortOrder",
help: "Specify the sort order for reflections when 'kind' is specified.",
type: ParameterType.Array,
defaultValue: [],
validate(value) {
const invalid = new Set(value);
const valid = getEnumKeys(ReflectionKind);
for (const v of valid) {
invalid.delete(v);
}

if (invalid.size !== 0) {
throw new Error(
`kindSortOrder may only specify known values, and invalid values were provided (${Array.from(
invalid
).join(", ")}). The valid kinds are:\n${valid.join(", ")}`
);
}
},
});

options.addDeclaration({
name: "visibilityFilters",
help: "Specify the default visibility for builtin filters and additional filters according to modifier tags.",
Expand Down Expand Up @@ -595,9 +618,7 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
type: ParameterType.Array,
validate(values) {
// this is good enough because the values of the ReflectionKind enum are all numbers
const validValues = Object.values(ReflectionKind).filter(
(v) => typeof v === "string"
);
const validValues = getEnumKeys(ReflectionKind);

for (const kind of values) {
if (!validValues.includes(kind)) {
Expand Down
109 changes: 63 additions & 46 deletions src/lib/utils/sort.ts
Expand Up @@ -6,6 +6,7 @@
import { ReflectionKind } from "../models/reflections/kind";
import type { DeclarationReflection } from "../models/reflections/declaration";
import { LiteralType } from "../models/types";
import type { Options } from "./options";

export const SORT_STRATEGIES = [
"source-order",
Expand All @@ -21,10 +22,43 @@ export const SORT_STRATEGIES = [

export type SortStrategy = typeof SORT_STRATEGIES[number];

const defaultKindSortOrder = [
ReflectionKind.Reference,
ReflectionKind.Project,
ReflectionKind.Module,
ReflectionKind.Namespace,
ReflectionKind.Enum,
ReflectionKind.EnumMember,
ReflectionKind.Class,
ReflectionKind.Interface,
ReflectionKind.TypeAlias,

ReflectionKind.Constructor,
ReflectionKind.Property,
ReflectionKind.Variable,
ReflectionKind.Function,
ReflectionKind.Accessor,
ReflectionKind.Method,
ReflectionKind.ObjectLiteral,

ReflectionKind.Parameter,
ReflectionKind.TypeParameter,
ReflectionKind.TypeLiteral,
ReflectionKind.CallSignature,
ReflectionKind.ConstructorSignature,
ReflectionKind.IndexSignature,
ReflectionKind.GetSignature,
ReflectionKind.SetSignature,
] as const;

// Return true if a < b
const sorts: Record<
SortStrategy,
(a: DeclarationReflection, b: DeclarationReflection) => boolean
(
a: DeclarationReflection,
b: DeclarationReflection,
data: { kindSortOrder: ReflectionKind[] }
) => boolean
> = {
"source-order"(a, b) {
const aSymbol = a.project.getSymbolFromReflection(a);
Expand Down Expand Up @@ -107,53 +141,36 @@ const sorts: Record<
"required-first"(a, b) {
return !a.flags.isOptional && b.flags.isOptional;
},
kind(a, b) {
const weights = [
ReflectionKind.Reference,
ReflectionKind.Project,
ReflectionKind.Module,
ReflectionKind.Namespace,
ReflectionKind.Enum,
ReflectionKind.EnumMember,
ReflectionKind.Class,
ReflectionKind.Interface,
ReflectionKind.TypeAlias,

ReflectionKind.Constructor,
ReflectionKind.Property,
ReflectionKind.Variable,
ReflectionKind.Function,
ReflectionKind.Accessor,
ReflectionKind.Method,
ReflectionKind.ObjectLiteral,

ReflectionKind.Parameter,
ReflectionKind.TypeParameter,
ReflectionKind.TypeLiteral,
ReflectionKind.CallSignature,
ReflectionKind.ConstructorSignature,
ReflectionKind.IndexSignature,
ReflectionKind.GetSignature,
ReflectionKind.SetSignature,
] as const;

return weights.indexOf(a.kind) < weights.indexOf(b.kind);
kind(a, b, { kindSortOrder }) {
return kindSortOrder.indexOf(a.kind) < kindSortOrder.indexOf(b.kind);
},
};

export function sortReflections(
reflections: DeclarationReflection[],
strategies: readonly SortStrategy[]
) {
reflections.sort((a, b) => {
for (const s of strategies) {
if (sorts[s](a, b)) {
return -1;
}
if (sorts[s](b, a)) {
return 1;
}
export function getSortFunction(opts: Options) {
const kindSortOrder = opts
.getValue("kindSortOrder")
.map((k) => ReflectionKind[k]);

for (const kind of defaultKindSortOrder) {
if (!kindSortOrder.includes(kind)) {
kindSortOrder.push(kind);
}
return 0;
});
}

const strategies = opts.getValue("sort");
const data = { kindSortOrder };

return function sortReflections(reflections: DeclarationReflection[]) {
reflections.sort((a, b) => {
for (const s of strategies) {
if (sorts[s](a, b, data)) {
return -1;
}
if (sorts[s](b, a, data)) {
return 1;
}
}
return 0;
});
};
}
11 changes: 11 additions & 0 deletions src/test/utils/enum.test.ts
@@ -0,0 +1,11 @@
import { ok } from "assert";
import { ReflectionKind } from "../../lib/models";
import { getEnumKeys } from "../../lib/utils/enum";

describe("Enum utils", () => {
it("Should be able to get enum keys", () => {
const keys = getEnumKeys(ReflectionKind);
ok(keys.includes("Project"));
ok(!keys.includes("SignatureContainer"));
});
});
13 changes: 12 additions & 1 deletion src/test/utils/sort.test.ts
Expand Up @@ -7,9 +7,20 @@ import {
ReflectionKind,
} from "../../lib/models";
import { resetReflectionID } from "../../lib/models/reflections/abstract";
import { sortReflections } from "../../lib/utils";
import { Logger, Options } from "../../lib/utils";
import { getSortFunction, SortStrategy } from "../../lib/utils/sort";

describe("Sort", () => {
function sortReflections(
arr: DeclarationReflection[],
strategies: SortStrategy[]
) {
const opts = new Options(new Logger());
opts.addDefaultDeclarations();
opts.setValue("sort", strategies);
getSortFunction(opts)(arr);
}

it("Should sort by name", () => {
const arr = [
new DeclarationReflection("a", ReflectionKind.TypeAlias),
Expand Down

0 comments on commit 5edf229

Please sign in to comment.