Skip to content

Commit dc9ea2d

Browse files
committed
Add --namespaces parameter to opt-in to new namespace flattening behavior
1 parent 282d13c commit dc9ea2d

File tree

7 files changed

+106
-47
lines changed

7 files changed

+106
-47
lines changed

apps/api-documenter/src/cli/YamlAction.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ApiModel } from '@microsoft/api-extractor-model';
1414

1515
export class YamlAction extends BaseAction {
1616
private _officeParameter: CommandLineFlagParameter;
17+
private _namespacesParameter: CommandLineFlagParameter;
1718

1819
constructor(parser: ApiDocumenterCommandLine) {
1920
super({
@@ -32,14 +33,20 @@ export class YamlAction extends BaseAction {
3233
parameterLongName: '--office',
3334
description: `Enables some additional features specific to Office Add-ins`
3435
});
36+
this._namespacesParameter = this.defineFlagParameter({
37+
parameterLongName: '--namespaces',
38+
description: `Include documentation for namespaces and add them to the TOC.`
39+
+ ` This will also affect file layout as namespaced items will be nested`
40+
+ ` under a directory for the namespace instead of just within the package.`
41+
});
3542
}
3643

3744
protected onExecute(): Promise<void> { // override
3845
const apiModel: ApiModel = this.buildApiModel();
3946

4047
const yamlDocumenter: YamlDocumenter = this._officeParameter.value
41-
? new OfficeYamlDocumenter(apiModel, this.inputFolder)
42-
: new YamlDocumenter(apiModel);
48+
? new OfficeYamlDocumenter(apiModel, this.inputFolder, this._namespacesParameter.value)
49+
: new YamlDocumenter(apiModel, this._namespacesParameter.value);
4350

4451
yamlDocumenter.generateFiles(this.outputFolder);
4552
return Promise.resolve();

apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ExperimentalYamlDocumenter extends YamlDocumenter {
1919
private _catchAllPointer: IYamlTocItem;
2020

2121
public constructor(apiModel: ApiModel, documenterConfig: DocumenterConfig) {
22-
super(apiModel);
22+
super(apiModel, documenterConfig.configFile.documentNamespaces);
2323
this._config = documenterConfig.configFile.tableOfContents!;
2424

2525
this._tocPointerMap = {};
@@ -36,18 +36,25 @@ export class ExperimentalYamlDocumenter extends YamlDocumenter {
3636
private _buildTocItems2(apiItems: ReadonlyArray<ApiItem>): IYamlTocItem[] {
3737
const tocItems: IYamlTocItem[] = [];
3838
for (const apiItem of apiItems) {
39-
if (this._shouldEmbed(apiItem.kind)) {
40-
// Don't generate table of contents items for embedded definitions
41-
continue;
42-
}
39+
let tocItem: IYamlTocItem;
40+
if (apiItem.kind === ApiItemKind.Namespace && !this.documentNamespaces) {
41+
tocItem = {
42+
name: this._getTocItemName(apiItem)
43+
};
44+
} else {
45+
if (this._shouldEmbed(apiItem.kind)) {
46+
// Don't generate table of contents items for embedded definitions
47+
continue;
48+
}
4349

44-
const tocItem: IYamlTocItem = {
45-
name: this._getTocItemName(apiItem),
46-
uid: this._getUid(apiItem)
47-
};
50+
tocItem = {
51+
name: this._getTocItemName(apiItem),
52+
uid: this._getUid(apiItem)
53+
};
4854

49-
if (apiItem.kind !== ApiItemKind.Package) {
50-
this._filterItem(apiItem, tocItem);
55+
if (apiItem.kind !== ApiItemKind.Package) {
56+
this._filterItem(apiItem, tocItem);
57+
}
5158
}
5259

5360
tocItems.push(tocItem);

apps/api-documenter/src/documenters/IConfigFile.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ export interface IConfigFile {
7777
*/
7878
outputTarget: 'docfx' | 'markdown';
7979

80+
/**
81+
* Include documentation for namespaces and add them to the TOC.
82+
* This will also affect file layout as namespaced items will be nested
83+
* under a directory for the namespace instead of just within the package.
84+
*
85+
* This setting currently only affects the 'docfx' output target.
86+
*/
87+
documentNamespaces?: boolean;
88+
8089
/** {@inheritDoc IConfigPlugin} */
8190
plugins?: IConfigPlugin[];
8291

apps/api-documenter/src/documenters/OfficeYamlDocumenter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export class OfficeYamlDocumenter extends YamlDocumenter {
3939
'Word': '/office/dev/add-ins/reference/requirement-sets/word-api-requirement-sets'
4040
};
4141

42-
public constructor(apiModel: ApiModel, inputFolder: string) {
43-
super(apiModel);
42+
public constructor(apiModel: ApiModel, inputFolder: string, documentNamespaces?: boolean) {
43+
super(apiModel, documentNamespaces);
4444

4545
const snippetsFilePath: string = path.join(inputFolder, 'snippets.yaml');
4646

apps/api-documenter/src/documenters/YamlDocumenter.ts

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@ interface IYamlReferences {
7171
}
7272

7373
const enum FlattenMode {
74+
/** Include entries for nested namespaces and non-namespace children. */
7475
NestedNamespacesAndChildren,
76+
/** Include entries for nested namespaces only. */
7577
NestedNamespacesOnly,
76-
NoNamespaces
78+
/** Include entries for non-namespace immediate children. */
79+
ImmediateChildren,
80+
/** Include entries for nested non-namespace children. */
81+
NestedChildren
7782
}
7883

7984
interface INameOptions {
@@ -85,15 +90,17 @@ interface INameOptions {
8590
* Writes documentation in the Universal Reference YAML file format, as defined by typescript.schema.json.
8691
*/
8792
export class YamlDocumenter {
93+
protected readonly documentNamespaces: boolean;
8894
private readonly _apiModel: ApiModel;
8995
private readonly _markdownEmitter: CustomMarkdownEmitter;
9096

9197
private _apiItemsByCanonicalReference: Map<string, ApiItem>;
9298
private _yamlReferences: IYamlReferences | undefined;
9399
private _outputFolder: string;
94100

95-
public constructor(apiModel: ApiModel) {
101+
public constructor(apiModel: ApiModel, documentNamespaces: boolean = false) {
96102
this._apiModel = apiModel;
103+
this.documentNamespaces = documentNamespaces;
97104
this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel);
98105
this._apiItemsByCanonicalReference = new Map<string, ApiItem>();
99106

@@ -186,7 +193,7 @@ export class YamlDocumenter {
186193
this._recordYamlReference(
187194
this._ensureYamlReferences(),
188195
this._getUid(apiItem),
189-
this._getYamlItemName(apiItem, { includeSignature: true }),
196+
this._getYamlItemName(apiItem, { includeNamespace: !this.documentNamespaces, includeSignature: true }),
190197
this._getYamlItemName(apiItem, { includeNamespace: true, includeSignature: true }));
191198
}
192199
}
@@ -198,9 +205,11 @@ export class YamlDocumenter {
198205
const children: ApiItem[] = [];
199206
if (apiItem.kind === ApiItemKind.Package) {
200207
// Skip over the entry point, since it's not part of the documentation hierarchy
201-
this._flattenNamespaces(apiItem.members[0].members, children, FlattenMode.NestedNamespacesAndChildren);
208+
this._flattenNamespaces(apiItem.members[0].members, children,
209+
this.documentNamespaces ? FlattenMode.NestedNamespacesAndChildren : FlattenMode.NestedChildren);
202210
} else {
203-
this._flattenNamespaces(apiItem.members, children, FlattenMode.NoNamespaces);
211+
this._flattenNamespaces(apiItem.members, children,
212+
this.documentNamespaces ? FlattenMode.ImmediateChildren : FlattenMode.NestedChildren);
204213
}
205214
return children;
206215
}
@@ -215,23 +224,34 @@ export class YamlDocumenter {
215224
let hasNonNamespaceChildren: boolean = false;
216225
for (const item of items) {
217226
if (item.kind === ApiItemKind.Namespace) {
218-
if (mode !== FlattenMode.NoNamespaces) {
219-
// At any level, always include a nested namespace if it has non-namespace children, but do not include its
220-
// non-namespace children in the result.
221-
222-
// Record the offset at which the namespace is added in case we need to remove it later.
223-
const index: number = childrenOut.length;
224-
childrenOut.push(item);
225-
226-
if (!this._flattenNamespaces(item.members, childrenOut, FlattenMode.NestedNamespacesOnly)) {
227-
// This namespace had no non-namespace children, remove it.
228-
childrenOut.splice(index, 1);
229-
}
227+
switch (mode) {
228+
case FlattenMode.NestedChildren:
229+
// Include children of namespaces, but not the namespaces themselves. This matches existing legacy behavior.
230+
this._flattenNamespaces(item.members, childrenOut, FlattenMode.NestedChildren);
231+
break;
232+
case FlattenMode.NestedNamespacesOnly:
233+
case FlattenMode.NestedNamespacesAndChildren:
234+
// At any level, always include a nested namespace if it has non-namespace children, but do not include its
235+
// non-namespace children in the result.
236+
237+
// Record the offset at which the namespace is added in case we need to remove it later.
238+
const index: number = childrenOut.length;
239+
childrenOut.push(item);
240+
241+
if (!this._flattenNamespaces(item.members, childrenOut, FlattenMode.NestedNamespacesOnly)) {
242+
// This namespace had no non-namespace children, remove it.
243+
childrenOut.splice(index, 1);
244+
}
245+
break;
230246
}
231247
} else if (this._shouldInclude(item.kind)) {
232-
if (mode !== FlattenMode.NestedNamespacesOnly) {
233-
// At the top level, include non-namespace children as well.
234-
childrenOut.push(item);
248+
switch (mode) {
249+
case FlattenMode.NestedChildren:
250+
case FlattenMode.NestedNamespacesAndChildren:
251+
case FlattenMode.ImmediateChildren:
252+
// At the top level, include non-namespace children as well.
253+
childrenOut.push(item);
254+
break;
235255
}
236256
hasNonNamespaceChildren = true;
237257
}
@@ -266,15 +286,23 @@ export class YamlDocumenter {
266286
private _buildTocItems(apiItems: ReadonlyArray<ApiItem>): IYamlTocItem[] {
267287
const tocItems: IYamlTocItem[] = [];
268288
for (const apiItem of apiItems) {
269-
if (this._shouldEmbed(apiItem.kind)) {
270-
// Don't generate table of contents items for embedded definitions
271-
continue;
289+
let tocItem: IYamlTocItem;
290+
if (apiItem.kind === ApiItemKind.Namespace && !this.documentNamespaces) {
291+
tocItem = {
292+
name: this._getTocItemName(apiItem)
293+
};
294+
} else {
295+
if (this._shouldEmbed(apiItem.kind)) {
296+
// Don't generate table of contents items for embedded definitions
297+
continue;
298+
}
299+
300+
tocItem = {
301+
name: this._getTocItemName(apiItem),
302+
uid: this._getUid(apiItem)
303+
};
272304
}
273305

274-
const tocItem: IYamlTocItem = {
275-
name: this._getTocItemName(apiItem),
276-
uid: this._getUid(apiItem)
277-
};
278306
tocItems.push(tocItem);
279307

280308
const children: ApiItem[] = this._getLogicalChildren(apiItem);
@@ -308,8 +336,9 @@ export class YamlDocumenter {
308336
case ApiItemKind.Package:
309337
case ApiItemKind.Interface:
310338
case ApiItemKind.Enum:
311-
case ApiItemKind.Namespace:
312339
return false;
340+
case ApiItemKind.Namespace:
341+
return !this.documentNamespaces;
313342
}
314343
return true;
315344
}
@@ -366,13 +395,15 @@ export class YamlDocumenter {
366395
}
367396
}
368397

369-
yamlItem.name = this._getYamlItemName(apiItem, { includeSignature: true });
398+
yamlItem.name = this._getYamlItemName(apiItem, { includeSignature: true,
399+
includeNamespace: !this.documentNamespaces });
370400
yamlItem.fullName = this._getYamlItemName(apiItem, { includeSignature: true, includeNamespace: true });
371401
yamlItem.langs = [ 'typeScript' ];
372402

373403
// Add the namespace of the item if it is contained in one.
374404
// Do not add the namespace parent of a namespace as they are flattened in the documentation.
375-
if (apiItem.kind !== ApiItemKind.Namespace && apiItem.parent && apiItem.parent.kind === ApiItemKind.Namespace) {
405+
if (apiItem.kind !== ApiItemKind.Namespace && apiItem.parent && apiItem.parent.kind === ApiItemKind.Namespace &&
406+
this.documentNamespaces) {
376407
yamlItem.namespace = apiItem.parent.canonicalReference.toString();
377408
}
378409

apps/api-documenter/src/schemas/api-documenter.schema.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
]
1818
},
1919

20+
"documentNamespaces": {
21+
"description": "Include documentation for namespaces and add them to the TOC. This will also affect file layout as namespaced items will be nested under a directory for the namespace instead of just within the package. This setting currently only affects the 'docfx' output target.",
22+
"type": "boolean"
23+
},
24+
2025
"plugins": {
2126
"description": "Specifies plugin packages to be loaded",
2227
"type": "array"
@@ -26,7 +31,7 @@
2631
"description": "Configures how the table of contents is generated.",
2732
"type": "object",
2833
"additionalProperties": true
29-
},
34+
}
3035
},
3136

3237
"additionalProperties": false

build-tests/api-documenter-test/config/api-documenter.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-documenter.schema.json",
3-
3+
"documentNamespaces": true,
44
"tableOfContents": {
55
"tocConfig": {
66
"items": [

0 commit comments

Comments
 (0)