Skip to content

Commit

Permalink
Category improvements (#938)
Browse files Browse the repository at this point in the history
* Improve categories to work at the group level or the container level, add category options for flexibility, add test for categories

* Make some changes for serialization and remove some unused code
  • Loading branch information
jonchardy authored and aciccarello committed Mar 19, 2019
1 parent cdf3acd commit 1816f75
Show file tree
Hide file tree
Showing 13 changed files with 1,551 additions and 102 deletions.
182 changes: 116 additions & 66 deletions src/lib/converter/plugins/CategoryPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Reflection, ContainerReflection } from '../../models/reflections/index';
import { Reflection, ContainerReflection, DeclarationReflection } from '../../models';
import { ReflectionCategory } from '../../models/ReflectionCategory';
import { SourceDirectory } from '../../models/sources/directory';
import { Component, ConverterComponent } from '../components';
import { Converter } from '../converter';
import { Context } from '../context';
import { GroupPlugin } from './GroupPlugin';
import { Option } from '../../utils/component';
import { ParameterType } from '../../utils/options/declaration';
import { Comment } from '../../models/comments/index';

/**
* A handler that sorts and categorizes the found reflections in the resolving phase.
Expand All @@ -13,19 +14,57 @@ import { GroupPlugin } from './GroupPlugin';
*/
@Component({name: 'category'})
export class CategoryPlugin extends ConverterComponent {
/**
* Define the sort order of categories. By default, sort alphabetically.
*/
@Option({
name: 'defaultCategory',
help: 'Specifies the default category for reflections without a category.',
type: ParameterType.String,
defaultValue: 'Other'
})
defaultCategory!: string;

@Option({
name: 'categoryOrder',
help: 'Specifies the order in which categories appear. * indicates the relative order for categories not in the list.',
type: ParameterType.Array
})
categoryOrder!: string[];

@Option({
name: 'categorizeByGroup',
help: 'Specifies whether categorization will be done at the group level.',
type: ParameterType.Boolean,
defaultValue: true
})
categorizeByGroup!: boolean;

// For use in static methods
static defaultCategory = 'Other';
static WEIGHTS: string[] = [];

/**
* Create a new CategoryPlugin instance.
*/
initialize() {
this.listenTo(this.owner, {
[Converter.EVENT_BEGIN]: this.onBegin,
[Converter.EVENT_RESOLVE]: this.onResolve,
[Converter.EVENT_RESOLVE_END]: this.onEndResolve
});
}, undefined, -200);
}

/**
* Triggered when the converter begins converting a project.
*
* @param context The context object describing the current state the converter is in.
*/
private onBegin(context: Context) {
// Set up static properties
if (this.defaultCategory) {
CategoryPlugin.defaultCategory = this.defaultCategory;
}
if (this.categoryOrder) {
CategoryPlugin.WEIGHTS = this.categoryOrder;
}
}

/**
Expand All @@ -36,13 +75,7 @@ export class CategoryPlugin extends ConverterComponent {
*/
private onResolve(context: Context, reflection: Reflection) {
if (reflection instanceof ContainerReflection) {
if (reflection.children && reflection.children.length > 0) {
reflection.children.sort(GroupPlugin.sortCallback);
reflection.categories = CategoryPlugin.getReflectionCategories(reflection.children);
}
if (reflection.categories && reflection.categories.length > 1) {
reflection.categories.sort(CategoryPlugin.sortCatCallback);
}
this.categorize(reflection);
}
}

Expand All @@ -52,32 +85,44 @@ export class CategoryPlugin extends ConverterComponent {
* @param context The context object describing the current state the converter is in.
*/
private onEndResolve(context: Context) {
function walkDirectory(directory: SourceDirectory) {
directory.categories = CategoryPlugin.getReflectionCategories(directory.getAllReflections());
const project = context.project;
this.categorize(project);
}

for (let key in directory.directories) {
if (!directory.directories.hasOwnProperty(key)) {
continue;
}
walkDirectory(directory.directories[key]);
}
private categorize(obj: ContainerReflection) {
if (this.categorizeByGroup) {
this.groupCategorize(obj);
} else {
this.lumpCategorize(obj);
}
}

const project = context.project;
if (project.children && project.children.length > 0) {
project.children.sort(GroupPlugin.sortCallback);
project.categories = CategoryPlugin.getReflectionCategories(project.children);
}
if (project.categories && project.categories.length > 1) {
project.categories.sort(CategoryPlugin.sortCatCallback);
private groupCategorize(obj: ContainerReflection) {
if (!obj.groups || obj.groups.length === 0) {
return;
}

walkDirectory(project.directory);
project.files.forEach((file) => {
file.categories = CategoryPlugin.getReflectionCategories(file.reflections);
obj.groups.forEach((group) => {
group.categories = CategoryPlugin.getReflectionCategories(group.children);
if (group.categories && group.categories.length > 1) {
group.categories.sort(CategoryPlugin.sortCatCallback);
} else if (group.categories.length === 1 && group.categories[0].title === CategoryPlugin.defaultCategory) {
// no categories if everything is uncategorized
group.categories = undefined;
}
});
}

private lumpCategorize(obj: ContainerReflection) {
if (obj instanceof ContainerReflection) {
if (obj.children && obj.children.length > 0) {
obj.categories = CategoryPlugin.getReflectionCategories(obj.children);
}
if (obj.categories && obj.categories.length > 1) {
obj.categories.sort(CategoryPlugin.sortCatCallback);
}
}
}

/**
* Create a categorized representation of the given list of reflections.
*
Expand All @@ -86,23 +131,26 @@ export class CategoryPlugin extends ConverterComponent {
*/
static getReflectionCategories(reflections: Reflection[]): ReflectionCategory[] {
const categories: ReflectionCategory[] = [];
let defaultCat: ReflectionCategory | undefined;
reflections.forEach((child) => {
const childCat = CategoryPlugin.getCategory(child);
if (childCat === '') {
return;
}
for (let i = 0; i < categories.length; i++) {
const category = categories[i];

if (category.title !== childCat) {
continue;
if (!defaultCat) {
defaultCat = categories.find(category => category.title === CategoryPlugin.defaultCategory);
if (!defaultCat) {
defaultCat = new ReflectionCategory(CategoryPlugin.defaultCategory);
categories.push(defaultCat);
}
}

defaultCat.children.push(child);
return;
}
let category = categories.find(cat => cat.title === childCat);
if (category) {
category.children.push(child);
return;
}

const category = new ReflectionCategory(childCat);
category = new ReflectionCategory(childCat);
category.children.push(child);
categories.push(category);
});
Expand All @@ -116,29 +164,31 @@ export class CategoryPlugin extends ConverterComponent {
* @returns The category the reflection belongs to
*/
static getCategory(reflection: Reflection): string {
if (reflection.comment) {
const tags = reflection.comment.tags;
function extractCategoryTag(comment: Comment) {
const tags = comment.tags;
if (tags) {
for (let i = 0; i < tags.length; i++) {
if (tags[i].tagName === 'category') {
let tag = tags[i].text;
return (tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase()).trim();
return tag.trim();
}
}
}
return '';
}
return '';
}

/**
* Callback used to sort reflections by 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 {
return a.name > b.name ? 1 : -1;
let category = '';
if (reflection.comment) {
category = extractCategoryTag(reflection.comment);
} else if (reflection instanceof DeclarationReflection && reflection.signatures) {
// If a reflection has signatures, use the first category tag amongst them
reflection.signatures.forEach(sig => {
if (sig.comment && category === '') {
category = extractCategoryTag(sig.comment);
}
});
}
return category;
}

/**
Expand All @@ -149,16 +199,16 @@ export class CategoryPlugin extends ConverterComponent {
* @returns The sorting weight.
*/
static sortCatCallback(a: ReflectionCategory, b: ReflectionCategory): number {
const aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
const bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
if (aWeight < 0 && bWeight < 0) {
return a.title > b.title ? 1 : -1;
}
if (aWeight < 0) {
return 1;
let aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
let bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
if (aWeight === -1 || bWeight === -1) {
let asteriskIndex = CategoryPlugin.WEIGHTS.indexOf('*');
if (asteriskIndex === -1) { asteriskIndex = CategoryPlugin.WEIGHTS.length; }
if (aWeight === -1) { aWeight = asteriskIndex; }
if (bWeight === -1) { bWeight = asteriskIndex; }
}
if (bWeight < 0) {
return -1;
if (aWeight === bWeight) {
return a.title > b.title ? 1 : -1;
}
return aWeight - bWeight;
}
Expand Down
15 changes: 15 additions & 0 deletions src/lib/models/ReflectionGroup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Reflection, ReflectionKind } from './reflections/abstract';
import { ReflectionCategory } from './ReflectionCategory';

/**
* A group of reflections. All reflections in a group are of the same kind.
Expand Down Expand Up @@ -62,6 +63,11 @@ export class ReflectionGroup {
*/
someChildrenAreExported?: boolean;

/**
* Categories contained within this group.
*/
categories?: ReflectionCategory[];

/**
* Create a new ReflectionGroup instance.
*
Expand Down Expand Up @@ -106,6 +112,15 @@ export class ReflectionGroup {
result['children'] = children;
}

if (this.categories) {
const categories: any[] = [];
this.categories.forEach((category) => {
categories.push(category.toObject());
});

result['categories'] = categories;
}

return result;
}
}
27 changes: 0 additions & 27 deletions src/lib/models/reflections/project.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SourceFile, SourceDirectory } from '../sources/index';
import { Reflection, ReflectionKind } from './abstract';
import { ContainerReflection } from './container';
import { ReflectionCategory } from '../ReflectionCategory';

/**
* A reflection that represents the root of the project.
Expand All @@ -27,11 +26,6 @@ export class ProjectReflection extends ContainerReflection {
*/
files: SourceFile[] = [];

/**
* All reflections categorized.
*/
categories?: ReflectionCategory[];

/**
* The name of the project.
*
Expand Down Expand Up @@ -123,25 +117,4 @@ export class ProjectReflection extends ContainerReflection {

return undefined;
}

/**
* Return a raw object representation of this reflection.
* @deprecated Use serializers instead
*/
toObject(): any {
const result = super.toObject();

if (this.categories) {
const categories: any[] = [];
this.categories.forEach((category) => {
categories.push(category.toObject());
});

if (categories.length > 0) {
result['categories'] = categories;
}
}

return result;
}
}
3 changes: 0 additions & 3 deletions src/lib/models/sources/directory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Reflection } from '../reflections/abstract';
import { ReflectionCategory } from '../ReflectionCategory';
import { ReflectionGroup } from '../ReflectionGroup';
import { SourceFile } from './file';

Expand All @@ -23,8 +22,6 @@ export class SourceDirectory {

groups?: ReflectionGroup[];

categories?: ReflectionCategory[];

/**
* A list of all files in this directory.
*/
Expand Down
6 changes: 0 additions & 6 deletions src/lib/models/sources/file.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Path from 'path';

import { Reflection } from '../reflections/abstract';
import { ReflectionCategory } from '../ReflectionCategory';
import { ReflectionGroup } from '../ReflectionGroup';
import { SourceDirectory } from './directory';

Expand Down Expand Up @@ -81,11 +80,6 @@ export class SourceFile {
*/
groups?: ReflectionGroup[];

/**
* A categorized list of the reflections declared in this file.
*/
categories?: ReflectionCategory[];

/**
* Create a new SourceFile instance.
*
Expand Down
Loading

0 comments on commit 1816f75

Please sign in to comment.