Skip to content

Commit 1816f75

Browse files
jonchardyaciccarello
authored andcommitted
Category improvements (TypeStrong#938)
* 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
1 parent cdf3acd commit 1816f75

File tree

13 files changed

+1551
-102
lines changed

13 files changed

+1551
-102
lines changed

src/lib/converter/plugins/CategoryPlugin.ts

Lines changed: 116 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Reflection, ContainerReflection } from '../../models/reflections/index';
1+
import { Reflection, ContainerReflection, DeclarationReflection } from '../../models';
22
import { ReflectionCategory } from '../../models/ReflectionCategory';
3-
import { SourceDirectory } from '../../models/sources/directory';
43
import { Component, ConverterComponent } from '../components';
54
import { Converter } from '../converter';
65
import { Context } from '../context';
7-
import { GroupPlugin } from './GroupPlugin';
6+
import { Option } from '../../utils/component';
7+
import { ParameterType } from '../../utils/options/declaration';
8+
import { Comment } from '../../models/comments/index';
89

910
/**
1011
* A handler that sorts and categorizes the found reflections in the resolving phase.
@@ -13,19 +14,57 @@ import { GroupPlugin } from './GroupPlugin';
1314
*/
1415
@Component({name: 'category'})
1516
export class CategoryPlugin extends ConverterComponent {
16-
/**
17-
* Define the sort order of categories. By default, sort alphabetically.
18-
*/
17+
@Option({
18+
name: 'defaultCategory',
19+
help: 'Specifies the default category for reflections without a category.',
20+
type: ParameterType.String,
21+
defaultValue: 'Other'
22+
})
23+
defaultCategory!: string;
24+
25+
@Option({
26+
name: 'categoryOrder',
27+
help: 'Specifies the order in which categories appear. * indicates the relative order for categories not in the list.',
28+
type: ParameterType.Array
29+
})
30+
categoryOrder!: string[];
31+
32+
@Option({
33+
name: 'categorizeByGroup',
34+
help: 'Specifies whether categorization will be done at the group level.',
35+
type: ParameterType.Boolean,
36+
defaultValue: true
37+
})
38+
categorizeByGroup!: boolean;
39+
40+
// For use in static methods
41+
static defaultCategory = 'Other';
1942
static WEIGHTS: string[] = [];
2043

2144
/**
2245
* Create a new CategoryPlugin instance.
2346
*/
2447
initialize() {
2548
this.listenTo(this.owner, {
49+
[Converter.EVENT_BEGIN]: this.onBegin,
2650
[Converter.EVENT_RESOLVE]: this.onResolve,
2751
[Converter.EVENT_RESOLVE_END]: this.onEndResolve
28-
});
52+
}, undefined, -200);
53+
}
54+
55+
/**
56+
* Triggered when the converter begins converting a project.
57+
*
58+
* @param context The context object describing the current state the converter is in.
59+
*/
60+
private onBegin(context: Context) {
61+
// Set up static properties
62+
if (this.defaultCategory) {
63+
CategoryPlugin.defaultCategory = this.defaultCategory;
64+
}
65+
if (this.categoryOrder) {
66+
CategoryPlugin.WEIGHTS = this.categoryOrder;
67+
}
2968
}
3069

3170
/**
@@ -36,13 +75,7 @@ export class CategoryPlugin extends ConverterComponent {
3675
*/
3776
private onResolve(context: Context, reflection: Reflection) {
3877
if (reflection instanceof ContainerReflection) {
39-
if (reflection.children && reflection.children.length > 0) {
40-
reflection.children.sort(GroupPlugin.sortCallback);
41-
reflection.categories = CategoryPlugin.getReflectionCategories(reflection.children);
42-
}
43-
if (reflection.categories && reflection.categories.length > 1) {
44-
reflection.categories.sort(CategoryPlugin.sortCatCallback);
45-
}
78+
this.categorize(reflection);
4679
}
4780
}
4881

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

58-
for (let key in directory.directories) {
59-
if (!directory.directories.hasOwnProperty(key)) {
60-
continue;
61-
}
62-
walkDirectory(directory.directories[key]);
63-
}
92+
private categorize(obj: ContainerReflection) {
93+
if (this.categorizeByGroup) {
94+
this.groupCategorize(obj);
95+
} else {
96+
this.lumpCategorize(obj);
6497
}
98+
}
6599

66-
const project = context.project;
67-
if (project.children && project.children.length > 0) {
68-
project.children.sort(GroupPlugin.sortCallback);
69-
project.categories = CategoryPlugin.getReflectionCategories(project.children);
70-
}
71-
if (project.categories && project.categories.length > 1) {
72-
project.categories.sort(CategoryPlugin.sortCatCallback);
100+
private groupCategorize(obj: ContainerReflection) {
101+
if (!obj.groups || obj.groups.length === 0) {
102+
return;
73103
}
74-
75-
walkDirectory(project.directory);
76-
project.files.forEach((file) => {
77-
file.categories = CategoryPlugin.getReflectionCategories(file.reflections);
104+
obj.groups.forEach((group) => {
105+
group.categories = CategoryPlugin.getReflectionCategories(group.children);
106+
if (group.categories && group.categories.length > 1) {
107+
group.categories.sort(CategoryPlugin.sortCatCallback);
108+
} else if (group.categories.length === 1 && group.categories[0].title === CategoryPlugin.defaultCategory) {
109+
// no categories if everything is uncategorized
110+
group.categories = undefined;
111+
}
78112
});
79113
}
80114

115+
private lumpCategorize(obj: ContainerReflection) {
116+
if (obj instanceof ContainerReflection) {
117+
if (obj.children && obj.children.length > 0) {
118+
obj.categories = CategoryPlugin.getReflectionCategories(obj.children);
119+
}
120+
if (obj.categories && obj.categories.length > 1) {
121+
obj.categories.sort(CategoryPlugin.sortCatCallback);
122+
}
123+
}
124+
}
125+
81126
/**
82127
* Create a categorized representation of the given list of reflections.
83128
*
@@ -86,23 +131,26 @@ export class CategoryPlugin extends ConverterComponent {
86131
*/
87132
static getReflectionCategories(reflections: Reflection[]): ReflectionCategory[] {
88133
const categories: ReflectionCategory[] = [];
134+
let defaultCat: ReflectionCategory | undefined;
89135
reflections.forEach((child) => {
90136
const childCat = CategoryPlugin.getCategory(child);
91137
if (childCat === '') {
92-
return;
93-
}
94-
for (let i = 0; i < categories.length; i++) {
95-
const category = categories[i];
96-
97-
if (category.title !== childCat) {
98-
continue;
138+
if (!defaultCat) {
139+
defaultCat = categories.find(category => category.title === CategoryPlugin.defaultCategory);
140+
if (!defaultCat) {
141+
defaultCat = new ReflectionCategory(CategoryPlugin.defaultCategory);
142+
categories.push(defaultCat);
143+
}
99144
}
100-
145+
defaultCat.children.push(child);
146+
return;
147+
}
148+
let category = categories.find(cat => cat.title === childCat);
149+
if (category) {
101150
category.children.push(child);
102151
return;
103152
}
104-
105-
const category = new ReflectionCategory(childCat);
153+
category = new ReflectionCategory(childCat);
106154
category.children.push(child);
107155
categories.push(category);
108156
});
@@ -116,29 +164,31 @@ export class CategoryPlugin extends ConverterComponent {
116164
* @returns The category the reflection belongs to
117165
*/
118166
static getCategory(reflection: Reflection): string {
119-
if (reflection.comment) {
120-
const tags = reflection.comment.tags;
167+
function extractCategoryTag(comment: Comment) {
168+
const tags = comment.tags;
121169
if (tags) {
122170
for (let i = 0; i < tags.length; i++) {
123171
if (tags[i].tagName === 'category') {
124172
let tag = tags[i].text;
125-
return (tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase()).trim();
173+
return tag.trim();
126174
}
127175
}
128176
}
177+
return '';
129178
}
130-
return '';
131-
}
132179

133-
/**
134-
* Callback used to sort reflections by name.
135-
*
136-
* @param a The left reflection to sort.
137-
* @param b The right reflection to sort.
138-
* @returns The sorting weight.
139-
*/
140-
static sortCallback(a: Reflection, b: Reflection): number {
141-
return a.name > b.name ? 1 : -1;
180+
let category = '';
181+
if (reflection.comment) {
182+
category = extractCategoryTag(reflection.comment);
183+
} else if (reflection instanceof DeclarationReflection && reflection.signatures) {
184+
// If a reflection has signatures, use the first category tag amongst them
185+
reflection.signatures.forEach(sig => {
186+
if (sig.comment && category === '') {
187+
category = extractCategoryTag(sig.comment);
188+
}
189+
});
190+
}
191+
return category;
142192
}
143193

144194
/**
@@ -149,16 +199,16 @@ export class CategoryPlugin extends ConverterComponent {
149199
* @returns The sorting weight.
150200
*/
151201
static sortCatCallback(a: ReflectionCategory, b: ReflectionCategory): number {
152-
const aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
153-
const bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
154-
if (aWeight < 0 && bWeight < 0) {
155-
return a.title > b.title ? 1 : -1;
156-
}
157-
if (aWeight < 0) {
158-
return 1;
202+
let aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
203+
let bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
204+
if (aWeight === -1 || bWeight === -1) {
205+
let asteriskIndex = CategoryPlugin.WEIGHTS.indexOf('*');
206+
if (asteriskIndex === -1) { asteriskIndex = CategoryPlugin.WEIGHTS.length; }
207+
if (aWeight === -1) { aWeight = asteriskIndex; }
208+
if (bWeight === -1) { bWeight = asteriskIndex; }
159209
}
160-
if (bWeight < 0) {
161-
return -1;
210+
if (aWeight === bWeight) {
211+
return a.title > b.title ? 1 : -1;
162212
}
163213
return aWeight - bWeight;
164214
}

src/lib/models/ReflectionGroup.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Reflection, ReflectionKind } from './reflections/abstract';
2+
import { ReflectionCategory } from './ReflectionCategory';
23

34
/**
45
* A group of reflections. All reflections in a group are of the same kind.
@@ -62,6 +63,11 @@ export class ReflectionGroup {
6263
*/
6364
someChildrenAreExported?: boolean;
6465

66+
/**
67+
* Categories contained within this group.
68+
*/
69+
categories?: ReflectionCategory[];
70+
6571
/**
6672
* Create a new ReflectionGroup instance.
6773
*
@@ -106,6 +112,15 @@ export class ReflectionGroup {
106112
result['children'] = children;
107113
}
108114

115+
if (this.categories) {
116+
const categories: any[] = [];
117+
this.categories.forEach((category) => {
118+
categories.push(category.toObject());
119+
});
120+
121+
result['categories'] = categories;
122+
}
123+
109124
return result;
110125
}
111126
}

src/lib/models/reflections/project.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { SourceFile, SourceDirectory } from '../sources/index';
22
import { Reflection, ReflectionKind } from './abstract';
33
import { ContainerReflection } from './container';
4-
import { ReflectionCategory } from '../ReflectionCategory';
54

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

30-
/**
31-
* All reflections categorized.
32-
*/
33-
categories?: ReflectionCategory[];
34-
3529
/**
3630
* The name of the project.
3731
*
@@ -123,25 +117,4 @@ export class ProjectReflection extends ContainerReflection {
123117

124118
return undefined;
125119
}
126-
127-
/**
128-
* Return a raw object representation of this reflection.
129-
* @deprecated Use serializers instead
130-
*/
131-
toObject(): any {
132-
const result = super.toObject();
133-
134-
if (this.categories) {
135-
const categories: any[] = [];
136-
this.categories.forEach((category) => {
137-
categories.push(category.toObject());
138-
});
139-
140-
if (categories.length > 0) {
141-
result['categories'] = categories;
142-
}
143-
}
144-
145-
return result;
146-
}
147120
}

src/lib/models/sources/directory.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Reflection } from '../reflections/abstract';
2-
import { ReflectionCategory } from '../ReflectionCategory';
32
import { ReflectionGroup } from '../ReflectionGroup';
43
import { SourceFile } from './file';
54

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

2423
groups?: ReflectionGroup[];
2524

26-
categories?: ReflectionCategory[];
27-
2825
/**
2926
* A list of all files in this directory.
3027
*/

src/lib/models/sources/file.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as Path from 'path';
22

33
import { Reflection } from '../reflections/abstract';
4-
import { ReflectionCategory } from '../ReflectionCategory';
54
import { ReflectionGroup } from '../ReflectionGroup';
65
import { SourceDirectory } from './directory';
76

@@ -81,11 +80,6 @@ export class SourceFile {
8180
*/
8281
groups?: ReflectionGroup[];
8382

84-
/**
85-
* A categorized list of the reflections declared in this file.
86-
*/
87-
categories?: ReflectionCategory[];
88-
8983
/**
9084
* Create a new SourceFile instance.
9185
*

0 commit comments

Comments
 (0)