Skip to content

Commit 9d364c5

Browse files
Feature: Add a plugin that allows an ES6 module's name to be set, or merged to another module
Workaround for issue TypeStrong#98 Fixes TypeStrong#68 (parts of)
1 parent 1ccca03 commit 9d364c5

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
module td.converter
2+
{
3+
/**
4+
* This plugin allows an ES6 module to specify its TypeDoc name.
5+
* It also allows multiple ES6 modules to be merged together into a single TypeDoc module.
6+
*
7+
* @usage
8+
* At the top of an ES6 module, add a "dynamic module comment". Insert "@module typedocModuleName" to
9+
* specify that this ES6 module should be merged with module: "typedocModuleName".
10+
*
11+
* Similar to the [[DynamicModulePlugin]], ensure that there is a comment tag (even blank) for the
12+
* first symbol in the file.
13+
*
14+
* @example
15+
* ```
16+
*
17+
* /**
18+
* * @module newModuleName
19+
* */
20+
* /** for typedoc /
21+
* import {foo} from "../foo";
22+
* export let bar = "bar";
23+
* ```
24+
*
25+
* Also similar to [[DynamicModulePlugin]], if @preferred is found in a dynamic module comment, the comment
26+
* will be used as the module comment, and documentation will be generated from it (note: this plugin does not
27+
* attempt to count lengths of merged module comments in order to guess the best one)
28+
*/
29+
export class ModuleAnnotationPlugin extends ConverterPlugin
30+
{
31+
/** List of module reflections which are models to rename */
32+
private moduleRenames: ModuleRename[];
33+
34+
constructor(converter:Converter) {
35+
super(converter);
36+
converter.on(Converter.EVENT_BEGIN, this.onBegin, this);
37+
converter.on(Converter.EVENT_CREATE_DECLARATION, this.onDeclaration, this);
38+
converter.on(Converter.EVENT_RESOLVE_BEGIN, this.onBeginResolve, this);
39+
}
40+
41+
/**
42+
* Triggered when the converter begins converting a project.
43+
*
44+
* @param context The context object describing the current state the converter is in.
45+
*/
46+
private onBegin(context:Context) {
47+
this.moduleRenames = [];
48+
}
49+
50+
/**
51+
* Triggered when the converter has created a declaration reflection.
52+
*
53+
* @param context The context object describing the current state the converter is in.
54+
* @param reflection The reflection that is currently processed.
55+
* @param node The node that is currently processed if available.
56+
*/
57+
private onDeclaration(context:Context, reflection:models.Reflection, node?:ts.Node) {
58+
if (reflection.kindOf(models.ReflectionKind.ExternalModule)) {
59+
let comment = CommentPlugin.getComment(node);
60+
// Look for @module
61+
let match = /@module\s+(\w+)/.exec(comment);
62+
if (match) {
63+
// Look for @preferred
64+
let preferred = /@preferred/.exec(comment);
65+
// Set up a list of renames operations to perform when the resolve phase starts
66+
this.moduleRenames.push({
67+
renameTo: match[1],
68+
preferred: preferred != null,
69+
reflection: <models.ContainerReflection> reflection
70+
});
71+
}
72+
}
73+
}
74+
75+
76+
/**
77+
* Triggered when the converter begins resolving a project.
78+
*
79+
* @param context The context object describing the current state the converter is in.
80+
*/
81+
private onBeginResolve(context:Context) {
82+
let projRefs = context.project.reflections;
83+
let refsArray: models.Reflection[] = Object.keys(projRefs).reduce((m,k) => {m.push(projRefs[k]); return m;}, []);
84+
85+
// Process each rename
86+
this.moduleRenames.forEach(item => {
87+
let renaming = <models.ContainerReflection> item.reflection;
88+
// Find an existing module that already has the "rename to" name. Use it as the merge target.
89+
let mergeTarget = <models.ContainerReflection>
90+
refsArray.filter(ref => ref.kind === renaming.kind && ref.name === item.renameTo)[0];
91+
92+
// If there wasn't a merge target, just change the name of the current module and exit.
93+
if (!mergeTarget) {
94+
renaming.name = item.renameTo;
95+
return;
96+
}
97+
98+
// Since there is a merge target, relocate all the renaming module's children to the mergeTarget.
99+
let childrenOfRenamed = refsArray.filter(ref => ref.parent === renaming);
100+
childrenOfRenamed.forEach((ref: models.Reflection) => {
101+
// update links in both directions
102+
ref.parent = mergeTarget;
103+
mergeTarget.children.push(<any> ref)
104+
});
105+
106+
// If @preferred was found on the current item, update the mergeTarget's comment
107+
// with comment from the renaming module
108+
if (item.preferred)
109+
mergeTarget.comment = renaming.comment;
110+
111+
// Now that all the children have been relocated to the mergeTarget, delete the empty module
112+
// Make sure the module being renamed doesn't have children, or they will be deleted
113+
if (renaming.children)
114+
renaming.children.length = 0;
115+
CommentPlugin.removeReflection(context.project, renaming);
116+
117+
// Remove @module and @preferred from the comment, if found.
118+
CommentPlugin.removeTags(mergeTarget.comment, "module");
119+
CommentPlugin.removeTags(mergeTarget.comment, "preferred");
120+
});
121+
}
122+
}
123+
124+
125+
/**
126+
* Register this handler.
127+
*/
128+
Converter.registerPlugin('moduleAnnotation', ModuleAnnotationPlugin);
129+
130+
interface ModuleRename {
131+
renameTo: string;
132+
preferred: boolean;
133+
reflection: models.ContainerReflection;
134+
}
135+
}

0 commit comments

Comments
 (0)