Skip to content

Commit 4e76e44

Browse files
authored
Merge pull request microsoft#363 from Microsoft/pgonzal/yaml-documenter4
api-documenter now correctly supports function parameters and property types
2 parents 3411f6a + fec4bf7 commit 4e76e44

File tree

4 files changed

+118
-9
lines changed

4 files changed

+118
-9
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/node-core-library",
5+
"comment": "Improve error reporting for JsonFile.validateNoUndefinedMembers()",
6+
"type": "patch"
7+
}
8+
],
9+
"packageName": "@microsoft/node-core-library",
10+
"email": "pgonzal@users.noreply.github.com"
11+
}

libraries/api-documenter/src/DocItemSet.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ export class DocItem {
6969
case 'class':
7070
case 'interface':
7171
this.kind = this.apiItem.kind === 'class' ? DocItemKind.Class : DocItemKind.Interface;
72-
for (const memberName of Object.keys(this.apiItem.members)) {
73-
const child: ApiItem = this.apiItem.members[memberName];
74-
this.children.push(new DocItem(child, memberName, this.docItemSet, this));
72+
if (this.apiItem.members) {
73+
for (const memberName of Object.keys(this.apiItem.members)) {
74+
const child: ApiItem = this.apiItem.members[memberName];
75+
this.children.push(new DocItem(child, memberName, this.docItemSet, this));
76+
}
7577
}
7678
break;
7779

@@ -89,9 +91,11 @@ export class DocItem {
8991
break;
9092
case 'enum':
9193
this.kind = DocItemKind.Enum;
92-
for (const memberName of Object.keys(this.apiItem.values)) {
93-
const child: ApiItem = this.apiItem.values[memberName];
94-
this.children.push(new DocItem(child, memberName, this.docItemSet, this));
94+
if (this.apiItem.values) {
95+
for (const memberName of Object.keys(this.apiItem.values)) {
96+
const child: ApiItem = this.apiItem.values[memberName];
97+
this.children.push(new DocItem(child, memberName, this.docItemSet, this));
98+
}
9599
}
96100
break;
97101
case 'enum value':

libraries/api-documenter/src/yaml/YamlGenerator.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ import { JsonFile, JsonSchema } from '@microsoft/node-core-library';
88
import {
99
MarkupElement,
1010
IDocElement,
11+
IApiMethod,
12+
IApiParameter,
13+
IApiProperty,
1114
IApiEnumMember
1215
} from '@microsoft/api-extractor';
1316

1417
import { DocItemSet, DocItem, DocItemKind, IDocItemSetResolveResult } from '../DocItemSet';
1518
import {
1619
IYamlFile,
17-
IYamlItem
20+
IYamlItem,
21+
IYamlSyntax,
22+
IYamlParameter
1823
} from './IYamlFile';
1924
import { RenderingHelpers } from '../RenderingHelpers';
2025
import { MarkupBuilder } from '../MarkupBuilder';
@@ -143,12 +148,14 @@ export class YamlGenerator {
143148
break;
144149
case DocItemKind.Method:
145150
yamlItem.type = 'method';
151+
this._populateYamlMethod(yamlItem, docItem);
146152
break;
147153
case DocItemKind.Constructor:
148154
yamlItem.type = 'constructor';
149155
break;
150156
case DocItemKind.Property:
151157
yamlItem.type = 'property';
158+
this._populateYamlProperty(yamlItem, docItem);
152159
break;
153160
case DocItemKind.Function:
154161
// Unimplemented
@@ -164,6 +171,55 @@ export class YamlGenerator {
164171
return yamlItem as IYamlItem;
165172
}
166173

174+
private _populateYamlMethod(yamlItem: Partial<IYamlItem>, docItem: DocItem): void {
175+
const apiMethod: IApiMethod = docItem.apiItem as IApiMethod;
176+
yamlItem.name = RenderingHelpers.getConciseSignature(docItem.name, apiMethod);
177+
178+
const syntax: IYamlSyntax = {
179+
content: apiMethod.signature
180+
};
181+
yamlItem.syntax = syntax;
182+
183+
if (apiMethod.returnValue) {
184+
syntax.return = {
185+
type: [ apiMethod.returnValue.type ],
186+
description: this._renderMarkdownFromDocElement(apiMethod.returnValue.description, docItem)
187+
};
188+
}
189+
190+
const parameters: IYamlParameter[] = [];
191+
for (const parameterName of Object.keys(apiMethod.parameters)) {
192+
const apiParameter: IApiParameter = apiMethod.parameters[parameterName];
193+
parameters.push(
194+
{
195+
id: parameterName,
196+
description: this._renderMarkdownFromDocElement(apiParameter.description, docItem),
197+
type: [ apiParameter.type || '' ]
198+
} as IYamlParameter
199+
);
200+
}
201+
202+
if (parameters.length) {
203+
syntax.parameters = parameters;
204+
}
205+
206+
}
207+
208+
private _populateYamlProperty(yamlItem: Partial<IYamlItem>, docItem: DocItem): void {
209+
const apiProperty: IApiProperty = docItem.apiItem as IApiProperty;
210+
211+
const syntax: IYamlSyntax = {
212+
content: docItem.name + ': ' + apiProperty.type + ';' // TODO
213+
};
214+
yamlItem.syntax = syntax;
215+
216+
if (apiProperty.type) {
217+
syntax.return = {
218+
type: [ apiProperty.type ]
219+
};
220+
}
221+
}
222+
167223
private _renderMarkdownFromDocElement(docElements: IDocElement[] | undefined, containingDocItem: DocItem): string {
168224
return this._renderMarkdown(MarkupBuilder.renderDocElements(docElements || []), containingDocItem);
169225
}

libraries/node-core-library/src/JsonFile.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,57 @@ export class JsonFile {
146146
*/
147147
// tslint:disable-next-line:no-any
148148
public static validateNoUndefinedMembers(jsonObject: Object): void {
149+
return JsonFile._validateNoUndefinedMembers(jsonObject, []);
150+
}
151+
152+
// Private implementation of validateNoUndefinedMembers()
153+
private static _validateNoUndefinedMembers(jsonObject: Object, keyPath: string[]): void {
149154
if (!jsonObject) {
150155
return;
151156
}
152157
if (typeof jsonObject === 'object') {
153158
for (const key of Object.keys(jsonObject)) {
159+
keyPath.push(key);
160+
154161
// tslint:disable-next-line:no-any
155162
const value: any = jsonObject[key];
156163
if (value === undefined) {
157-
throw new Error(`The key "${key}" is undefined`);
164+
const fullPath: string = JsonFile._formatKeyPath(keyPath);
165+
throw new Error(`The value for ${fullPath} is undefined`);
166+
}
167+
168+
JsonFile._validateNoUndefinedMembers(value, keyPath);
169+
keyPath.pop();
170+
}
171+
}
172+
}
173+
174+
// Given this input: ['items', '4', 'syntax', 'parameters', 'string "with" symbols", 'type']
175+
// Return this string: items[4].syntax.parameters["string \"with\" symbols"].type
176+
private static _formatKeyPath(keyPath: string[]): string {
177+
let result: string = '';
178+
179+
for (const key of keyPath) {
180+
if (/^[0-9]+$/.test(key)) {
181+
// It's an integer, so display like this: parent[123]
182+
result += `[${key}]`;
183+
} else if (/^[a-z_][a-z_0-9]*$/i.test(key)) {
184+
// It's an alphanumeric identifier, so display like this: parent.name
185+
if (result) {
186+
result += '.';
158187
}
159-
JsonFile.validateNoUndefinedMembers(value);
188+
result += `${key}`;
189+
} else {
190+
// It's a freeform string, so display like this: parent["A path: \"C:\\file\""]
191+
192+
// Convert this: A path: "C:\file"
193+
// To this: A path: \"C:\\file\"
194+
const escapedKey: string = key.replace(/[\\]/g, '\\\\') // escape backslashes
195+
.replace(/["]/g, '\\'); // escape quotes
196+
result += `["${escapedKey}"]`;
160197
}
161198
}
199+
return result;
162200
}
163201

164202
/**

0 commit comments

Comments
 (0)