Skip to content

Commit

Permalink
feat: Add support for sorting reflections based on user criteria
Browse files Browse the repository at this point in the history
Resolves #112
  • Loading branch information
Gerrit0 committed Jun 1, 2021
1 parent d5bb930 commit e125484
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 124 deletions.
5 changes: 3 additions & 2 deletions src/index.ts
Expand Up @@ -21,7 +21,7 @@ export {
TSConfigReader,
TypeDocReader,
ArgumentsReader,
} from "./lib/utils/options";
} from "./lib/utils";

export type {
OptionsReader,
Expand All @@ -37,7 +37,8 @@ export type {
MixedDeclarationOption,
MapDeclarationOption,
DeclarationOptionToOptionType,
} from "./lib/utils/options";
SortStrategy,
} from "./lib/utils";

export { JSONOutput } from "./lib/serialization";

Expand Down
9 changes: 4 additions & 5 deletions src/lib/application.ts
Expand Up @@ -141,11 +141,10 @@ export class Application extends ChildableComponent<
}
this.logger.level = this.options.getValue("logLevel");

let plugins = this.options.getValue("plugin");
if (plugins.length === 0) {
plugins = discoverNpmPlugins(this);
}
loadPlugins(this, this.options.getValue("plugin"));
const plugins = this.options.isSet("plugin")
? this.options.getValue("plugin")
: discoverNpmPlugins(this);
loadPlugins(this, plugins);

this.options.reset();
for (const [key, val] of Object.entries(options)) {
Expand Down
64 changes: 5 additions & 59 deletions src/lib/converter/plugins/GroupPlugin.ts
Expand Up @@ -9,6 +9,7 @@ import { SourceDirectory } from "../../models/sources/directory";
import { Component, ConverterComponent } from "../components";
import { Converter } from "../converter";
import { Context } from "../context";
import { sortReflections } from "../../utils/sort";

/**
* A handler that sorts and groups the found reflections in the resolving phase.
Expand All @@ -17,38 +18,6 @@ import { Context } from "../context";
*/
@Component({ name: "group" })
export class GroupPlugin extends ConverterComponent {
/**
* Define the sort order of reflections.
*/
static WEIGHTS = [
ReflectionKind.Project,
ReflectionKind.Module,
ReflectionKind.Namespace,
ReflectionKind.Enum,
ReflectionKind.EnumMember,
ReflectionKind.Class,
ReflectionKind.Interface,
ReflectionKind.TypeAlias,

ReflectionKind.Constructor,
ReflectionKind.Event,
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,
];

/**
* Define the singular name of individual reflection kinds.
*/
Expand Down Expand Up @@ -123,7 +92,10 @@ export class GroupPlugin extends ConverterComponent {
reflection.children.length > 0 &&
!reflection.groups
) {
reflection.children.sort(GroupPlugin.sortCallback);
sortReflections(
reflection.children,
this.application.options.getValue("sort")
);
reflection.groups = GroupPlugin.getReflectionGroups(
reflection.children
);
Expand Down Expand Up @@ -234,30 +206,4 @@ export class GroupPlugin extends ConverterComponent {
return this.getKindString(kind) + "s";
}
}

/**
* Callback used to sort reflections by weight defined by ´GroupPlugin.WEIGHTS´ and name.
*
* @param a The left reflection to sort.
* @param b The right reflection to sort.
* @returns The sorting weight.
*/
static sortCallback(a: Reflection, b: Reflection): number {
const aWeight = GroupPlugin.WEIGHTS.indexOf(a.kind);
const bWeight = GroupPlugin.WEIGHTS.indexOf(b.kind);
if (aWeight === bWeight) {
if (a.flags.isStatic && !b.flags.isStatic) {
return 1;
}
if (!a.flags.isStatic && b.flags.isStatic) {
return -1;
}
if (a.name === b.name) {
return 0;
}
return a.name > b.name ? 1 : -1;
} else {
return aWeight - bWeight;
}
}
}
10 changes: 10 additions & 0 deletions src/lib/models/reflections/abstract.ts
@@ -1,3 +1,4 @@
import { ok } from "assert";
import { SourceReference } from "../sources/file";
import { Type } from "../types/index";
import { Comment } from "../comments/comment";
Expand Down Expand Up @@ -366,6 +367,15 @@ export abstract class Reflection {
*/
parent?: Reflection;

get project(): ProjectReflection {
if (this.isProject()) return this;
ok(
this.parent,
"Tried to get the project on a reflection not in a project"
);
return this.parent.project;
}

/**
* The parsed documentation comment attached to this reflection.
*/
Expand Down
29 changes: 28 additions & 1 deletion src/lib/utils/index.ts
@@ -1,6 +1,30 @@
export type { IfInternal, NeverIfInternal } from "./general";

export { Options, ParameterType, ParameterHint, BindOption } from "./options";
export {
Options,
ParameterType,
ParameterHint,
BindOption,
TSConfigReader,
TypeDocReader,
ArgumentsReader,
} from "./options";
export type {
OptionsReader,
TypeDocOptions,
TypeDocOptionMap,
KeyToDeclaration,
DeclarationOption,
DeclarationOptionBase,
StringDeclarationOption,
NumberDeclarationOption,
BooleanDeclarationOption,
ArrayDeclarationOption,
MixedDeclarationOption,
MapDeclarationOption,
DeclarationOptionToOptionType,
} from "./options";

export {
insertPrioritySorted,
removeIfPresent,
Expand All @@ -23,3 +47,6 @@ export {
} from "./fs";
export { Logger, LogLevel, ConsoleLogger, CallbackLogger } from "./loggers";
export { loadPlugins, discoverNpmPlugins } from "./plugins";

export { sortReflections } from "./sort";
export type { SortStrategy } from "./sort";
2 changes: 2 additions & 0 deletions src/lib/utils/options/declaration.ts
@@ -1,5 +1,6 @@
import { Theme as ShikiTheme } from "shiki";
import { LogLevel } from "../loggers";
import { SortStrategy } from "../sort";

/**
* An interface describing all TypeDoc specific options. Generated from a
Expand Down Expand Up @@ -66,6 +67,7 @@ export interface TypeDocOptionMap {
defaultCategory: string;
categoryOrder: string[];
categorizeByGroup: boolean;
sort: SortStrategy[];
gitRevision: string;
gitRemote: string;
gaID: string;
Expand Down
25 changes: 25 additions & 0 deletions src/lib/utils/options/sources/typedoc.ts
Expand Up @@ -2,6 +2,7 @@ import { Options } from "..";
import { LogLevel } from "../../loggers";
import { ParameterType, ParameterHint } from "../declaration";
import { BUNDLED_THEMES } from "shiki";
import { SORT_STRATEGIES } from "../../sort";

export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
options.addDeclaration({
Expand Down Expand Up @@ -188,6 +189,30 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
type: ParameterType.Boolean,
defaultValue: true,
});
options.addDeclaration({
name: "sort",
help: "Specify the sort strategy for documented values",
type: ParameterType.Array,
defaultValue: ["kind", "instance-first", "alphabetical"],
validate(value) {
const invalid = new Set(value);
for (const v of SORT_STRATEGIES) {
invalid.delete(v);
}

if (invalid.size !== 0) {
throw new Error(
`sort may only specify known values, and invalid values were provided (${Array.from(
invalid
).join(
", "
)}). The valid sort strategies are:\n${SORT_STRATEGIES.join(
", "
)}`
);
}
},
});
options.addDeclaration({
name: "gitRevision",
help:
Expand Down
154 changes: 154 additions & 0 deletions src/lib/utils/sort.ts
@@ -0,0 +1,154 @@
/**
* Module which handles sorting reflections according to a user specified strategy.
* @module
*/

import { DeclarationReflection, ReflectionKind } from "../models";

export const SORT_STRATEGIES = [
"source-order",
"alphabetical",
"enum-value-ascending",
"enum-value-descending",
"static-first",
"instance-first",
"visibility",
"required-first",
"kind",
] as const;

export type SortStrategy = typeof SORT_STRATEGIES[number];

// Return true if a < b
const sorts: Record<
SortStrategy,
(a: DeclarationReflection, b: DeclarationReflection) => boolean
> = {
"source-order"(a, b) {
const aSymbol = a.project.getSymbolFromReflection(a);
const bSymbol = b.project.getSymbolFromReflection(b);

// This is going to be somewhat ambiguous. No way around that. Treat the first
// declaration of a symbol as its ordering declaration.
const aDecl = aSymbol?.getDeclarations()?.[0];
const bDecl = bSymbol?.getDeclarations()?.[0];

if (aDecl && bDecl) {
const aFile = aDecl.getSourceFile().fileName;
const bFile = bDecl.getSourceFile().fileName;
if (aFile < bFile) {
return true;
}
if (aFile == bFile && aDecl.pos < bDecl.pos) {
return true;
}

return false;
}

// Someone is doing something weird. Fail to re-order. This *might* be a bug in TD
// but it could also be TS having some exported symbol without a declaration.
return false;
},
alphabetical(a, b) {
return a.name < b.name;
},
"enum-value-ascending"(a, b) {
if (
a.kind == ReflectionKind.EnumMember &&
b.kind == ReflectionKind.EnumMember
) {
return (
parseFloat(a.defaultValue ?? "0") <
parseFloat(b.defaultValue ?? "0")
);
}
return false;
},
"enum-value-descending"(a, b) {
if (
a.kind == ReflectionKind.EnumMember &&
b.kind == ReflectionKind.EnumMember
) {
return (
parseFloat(b.defaultValue ?? "0") <
parseFloat(a.defaultValue ?? "0")
);
}
return false;
},
"static-first"(a, b) {
return a.flags.isStatic && !b.flags.isStatic;
},
"instance-first"(a, b) {
return !a.flags.isStatic && b.flags.isStatic;
},
visibility(a, b) {
// Note: flags.isPublic may not be set on public members. It will only be set
// if the user explicitly marks members as public. Therefore, we can't use it
// here to get a reliable sort order.
if (a.flags.isPrivate) {
return false; // Not sorted before anything
}
if (a.flags.isProtected) {
return b.flags.isPrivate; // Sorted before privates
}
if (b.flags.isPrivate || b.flags.isProtected) {
return true; // We are public, sort before b if b is less visible
}
return false;
},
"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.Event,
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);
},
};

export function sortReflections(
strategies: DeclarationReflection[],
strats: readonly SortStrategy[]
) {
strategies.sort((a, b) => {
for (const s of strats) {
if (sorts[s](a, b)) {
return -1;
}
if (sorts[s](b, a)) {
return 1;
}
}
return 0;
});
}

0 comments on commit e125484

Please sign in to comment.