Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 41 additions & 33 deletions apps/api-documenter/src/documenters/YamlDocumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
ApiEnumMember,
ApiClass,
ApiInterface,
ApiParameterListMixin,
ApiMethod,
ApiMethodSignature,
ApiConstructor,
Expand Down Expand Up @@ -551,36 +550,10 @@ export class YamlDocumenter {

/**
* Calculate the DocFX "uid" for the ApiItem
* Example: node-core-library.JsonFile.load
* Example: `node-core-library!JsonFile#load`
*/
protected _getUid(apiItem: ApiItem): string {
let result: string = '';
for (const hierarchyItem of apiItem.getHierarchy()) {

// For overloaded methods, add a suffix such as "MyClass.myMethod_2".
let qualifiedName: string = hierarchyItem.displayName;
if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) {
if (hierarchyItem.overloadIndex > 1) {
// Subtract one for compatibility with earlier releases of API Documenter.
// (This will get revamped when we fix GitHub issue #1308)
qualifiedName += `_${hierarchyItem.overloadIndex - 1}`;
}
}

switch (hierarchyItem.kind) {
case ApiItemKind.Model:
case ApiItemKind.EntryPoint:
break;
case ApiItemKind.Package:
result += PackageName.getUnscopedName(hierarchyItem.displayName);
break;
default:
result += '.';
result += qualifiedName;
break;
}
}
return result;
return apiItem.canonicalReference.toString();
}

/**
Expand Down Expand Up @@ -677,11 +650,46 @@ export class YamlDocumenter {

private _getYamlItemName(apiItem: ApiItem): string {
if (apiItem.parent && apiItem.parent.kind === ApiItemKind.Namespace) {
// For members a namespace, show the full name excluding the package part:
// Example: excel.Excel.Binding --> Excel.Binding
return this._getUid(apiItem).replace(/^[^.]+\./, '');
// If the immediate parent is a namespace, then add the namespaces to the name. For example:
//
// // Name: "N1"
// export namespace N1 {
// // Name: "N1.N2"
// export namespace N2 {
// // Name: "N1.N2.f(x,y)"
// export function f(x: string, y: string): string {
// return x + y;
// }
//
//
// // Name: "N1.N2.C"
// export class C {
// // Name: "member(x,y)" <===========
// public member(x: string, y: string): string {
// return x + y;
// }
// }
// }
// }
//
// In the above example, "member(x, y)" does not appear as "N1.N2.C.member(x,y)" because YamlDocumenter
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any concern with TOC collisions with class C { static x; x; }? Both would have the TOC display name of x with no disambiguation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does DocFX create TOC entries for class members? I didn't think it did.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure at the moment, but you may have to disambiguate other items in the TOC, such as merging a function or a variable and an interface with the same name. I had to workaround this for esfx: https://github.com/esfx/esfx/blob/bbc36650efa6a88c9d998b0dafb53e8421513898/scripts/docs/yamlDocumenter.js#L214-L216

// embeds this entry in the web page for "N1.N2.C", so the container is obvious. Whereas "N1.N2.f(x,y)"
// needs to be qualified because the DocFX template doesn't make pages for namespaces. Instead, they get
// flattened into the package's page.
Copy link
Copy Markdown
Collaborator Author

@octogonz octogonz Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rbuckton Does the DocFX template have support for namespaces yet? If so, then we could eliminate this awkward workaround and generate proper pages for them.

For an example of how this is currently getting rendered, take a look at: https://docs.microsoft.com/en-us/javascript/api/office?view=excel-js-preview#office-initialize

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it does not. We would have to submit a PR to dotnet/docfx for this change.

Copy link
Copy Markdown

@rbuckton rbuckton Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mentioned this before, but I have a workaround I use for https://github.com/esfx/esfx that involves changes to support namespaces properly:

Example: https://esfx.js.org/esfx/api/equatable/comparable_namespace.html#equatable_Comparable_Namespace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered opening a PR for DocFX itself? I think everyone who uses TypeScript namespaces would be greatly appreciative of this change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I planned to do so eventually, but not until my schedule has settled down.

const nameParts: string[] = [ Utilities.getConciseSignature(apiItem) ];

for (let current: ApiItem | undefined = apiItem.parent; current; current = current.parent) {
if (current.kind !== ApiItemKind.Namespace) {
break;
}

nameParts.unshift(current.displayName);
}

return nameParts.join('.');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the DeclarationReference already is namespace qualified, could you leverage that? I'm thinking of adding some additional utility methods/properties to the DeclarationReference API to make that even easier.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really a DeclarationReference. It's a title that is shown in the document, in a specific context.

I'm thinking of adding some additional utility methods/properties to the DeclarationReference API to make that even easier.

@rbuckton In order to integrate this into the TSDoc parser, we will need to integrate it into NodeParser, and the objects need to replace the earlier DocDeclarationReference AST node type.

Ideally we would do that in a way that eliminates the need for a standalone DeclarationReference API. We can follow up about this separately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, was just thinking about whether the DeclarationReference could be reused for this because it already supports the dot-qualified formatting, but I wouldn't worry about changing anything here.

Copy link
Copy Markdown

@rbuckton rbuckton Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought had been to add a few members to DeclRef and related objects:

  • isFullyQualified - true if the DeclRef has no missing components (:function, :constructor, :index, :call, :new, and :complex would also require an overload index), otherwise false
  • isReachable - true if the DeclRef contains no Local (~) navigations, otherwise false.
  • isStatic - true if the DeclRef contains only Static (.) navigations, otherwise false.
  • isInstance - true if the DeclRef contains any Instance (#) navigations, otherwise false.
  • namespaceQualifiedName - Returns the string value of the DeclRef's symbol excluding any meaning or overload index:
    • my-package:a.b.c:member -> a.b.c
    • my-package:a.b.c:class -> a.b.c
    • my-package:a.b.c:function(1) -> a.b.c
    • NOTE: I considered replacing # with .prototype. for this as well...
  • namespaceName - Returns the string value of the DeclRef's symbol excluding the outermost ComponentPaths that do not represent a namespace member, meaning, or overload index (i.e. my-package:a.b.c:member -> a.b).
    • NOTE: This would have been a good use case for enummember, as an enummember is kind of member-like and kind of a var-like export of its containing namespace...
    • my-package:a.b.c:member -> a
    • my-package:a.b.c:class -> a.b
    • my-package:a.b.c:var -> a.b
    • my-package:a.b.c:namespace -> a.b
  • classQualifiedName - Similar to namespaceQualifiedName, but only includes the containing class name for members:
    • my-package:a.b.c:member -> b.c
    • my-package:a.b#c:member -> b#c
    • my-package:a.b.c:var ->
    • my-package:a.b:constructor -> b
  • className - Gets the name of the containing class/interface/enum:
    • my-package:a.b.c:member -> b
    • my-package:a.b#c:member -> b
    • my-package:a.b.c:var ->
    • my-package:a.b:constructor -> b
  • name - Gets the name of the symbol:
    • my-package:a.b.c:member -> c
    • my-package:a.b.c:class -> c

Then you could have done something like:

const namespaceName = apiItem.canonicalReference.namespaceName
return (namespaceName ? namespaceName + '.' : '') + Utilities.getConciseSignature(apiItem);
// or, if `Utilities.getConciseSignature` is updated to return only the argument list...
return apiItem.canonicalReference.namespaceQualifiedName + Utilities.getConciseArgumentList(apiItem);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • my-package:a.b.c:member -> b.c
  • my-package:a.b#c:member -> b#c

It might be nice to have a standardized API for rendering names, especially if they may contain problematic characters like ECMAScript symbols, quoted strings, or overloads.

However, I'm unsure whether the DeclarationReference API should try to solve a problem that is really about documentation formatting. For example, although # and (2) are important operators in the {@link} notation, I'm not sure they will look nice on the documentation website. Utilities.getConciseSignature() returns (arg1,arg2) instead of the overload index because it's a better documentation experience, despite being a worse design for the UID requirements.

If we have my-package!myNamespace.MyClass#staticMember(2), the kinds of strings we'd want from a doc formatting API might be more like this:

  • myNamespace (namespace)
  • myNamespace.MyClass (class)
  • MyClass (class)
  • staticMember(arg1,arg2)
  • MyClass.staticMember(arg1,arg2) (static)

The api-extractor-model API might be a better place to implement this, since it has access to information like whether the parent is a namespace or not, and what are the argument names.

} else {
return Utilities.getConciseSignature(apiItem);
}
return Utilities.getConciseSignature(apiItem);
}

private _getYamlFilePath(apiItem: ApiItem): string {
Expand Down
58 changes: 29 additions & 29 deletions build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### YamlMime:UniversalReference
items:
- uid: api-documenter-test
- uid: api-documenter-test!
summary: |-
api-extractor-test-05

Expand All @@ -11,20 +11,20 @@ items:
- typeScript
type: package
children:
- api-documenter-test.DocBaseClass
- api-documenter-test.DocClass1
- api-documenter-test.DocEnum
- api-documenter-test.Generic
- api-documenter-test.globalFunction
- api-documenter-test.IDocInterface1
- api-documenter-test.IDocInterface2
- api-documenter-test.IDocInterface3
- api-documenter-test.IDocInterface4
- api-documenter-test.IDocInterface5
- api-documenter-test.IDocInterface6
- api-documenter-test.OuterNamespace.InnerNamespace.nestedFunction
- api-documenter-test.SystemEvent
- uid: api-documenter-test.globalFunction
- 'api-documenter-test!DocBaseClass:class'
- 'api-documenter-test!DocClass1:class'
- 'api-documenter-test!DocEnum:enum'
- 'api-documenter-test!Generic:class'
- 'api-documenter-test!globalFunction:function(1)'
- 'api-documenter-test!IDocInterface1:interface'
- 'api-documenter-test!IDocInterface2:interface'
- 'api-documenter-test!IDocInterface3:interface'
- 'api-documenter-test!IDocInterface4:interface'
- 'api-documenter-test!IDocInterface5:interface'
- 'api-documenter-test!IDocInterface6:interface'
- 'api-documenter-test!OuterNamespace.InnerNamespace.nestedFunction:function(1)'
- 'api-documenter-test!SystemEvent:class'
- uid: 'api-documenter-test!globalFunction:function(1)'
summary: An exported function
name: globalFunction(x)
fullName: globalFunction(x)
Expand All @@ -42,10 +42,10 @@ items:
description: ''
type:
- number
- uid: api-documenter-test.OuterNamespace.InnerNamespace.nestedFunction
- uid: 'api-documenter-test!OuterNamespace.InnerNamespace.nestedFunction:function(1)'
summary: A function inside a namespace
name: OuterNamespace.InnerNamespace.nestedFunction
fullName: OuterNamespace.InnerNamespace.nestedFunction
name: OuterNamespace.InnerNamespace.nestedFunction(x)
fullName: OuterNamespace.InnerNamespace.nestedFunction(x)
langs:
- typeScript
type: function
Expand All @@ -61,25 +61,25 @@ items:
type:
- number
references:
- uid: api-documenter-test.DocBaseClass
- uid: 'api-documenter-test!DocBaseClass:class'
name: DocBaseClass
- uid: api-documenter-test.DocClass1
- uid: 'api-documenter-test!DocClass1:class'
name: DocClass1
- uid: api-documenter-test.DocEnum
- uid: 'api-documenter-test!DocEnum:enum'
name: DocEnum
- uid: api-documenter-test.Generic
- uid: 'api-documenter-test!Generic:class'
name: Generic
- uid: api-documenter-test.IDocInterface1
- uid: 'api-documenter-test!IDocInterface1:interface'
name: IDocInterface1
- uid: api-documenter-test.IDocInterface2
- uid: 'api-documenter-test!IDocInterface2:interface'
name: IDocInterface2
- uid: api-documenter-test.IDocInterface3
- uid: 'api-documenter-test!IDocInterface3:interface'
name: IDocInterface3
- uid: api-documenter-test.IDocInterface4
- uid: 'api-documenter-test!IDocInterface4:interface'
name: IDocInterface4
- uid: api-documenter-test.IDocInterface5
- uid: 'api-documenter-test!IDocInterface5:interface'
name: IDocInterface5
- uid: api-documenter-test.IDocInterface6
- uid: 'api-documenter-test!IDocInterface6:interface'
name: IDocInterface6
- uid: api-documenter-test.SystemEvent
- uid: 'api-documenter-test!SystemEvent:class'
name: SystemEvent
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
### YamlMime:UniversalReference
items:
- uid: api-documenter-test.DocBaseClass
- uid: 'api-documenter-test!DocBaseClass:class'
summary: Example base class
name: DocBaseClass
fullName: DocBaseClass
langs:
- typeScript
type: class
package: api-documenter-test
package: api-documenter-test!
Comment thread
octogonz marked this conversation as resolved.
children:
- api-documenter-test.DocBaseClass.(constructor)
- api-documenter-test.DocBaseClass.(constructor)_1
- uid: api-documenter-test.DocBaseClass.(constructor)
- 'api-documenter-test!DocBaseClass:constructor(1)'
- 'api-documenter-test!DocBaseClass:constructor(2)'
- uid: 'api-documenter-test!DocBaseClass:constructor(1)'
summary: The simple constructor for `DocBaseClass`
name: (constructor)()
fullName: (constructor)()
Expand All @@ -20,7 +20,7 @@ items:
type: constructor
syntax:
content: constructor();
- uid: api-documenter-test.DocBaseClass.(constructor)_1
- uid: 'api-documenter-test!DocBaseClass:constructor(2)'
summary: The overloaded constructor for `DocBaseClass`
name: (constructor)(x)
fullName: (constructor)(x)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### YamlMime:UniversalReference
items:
- uid: api-documenter-test.DocClass1
- uid: 'api-documenter-test!DocClass1:class'
summary: This is an example class.
remarks: >-
These are some remarks.
Expand All @@ -14,22 +14,22 @@ items:
- typeScript
type: class
extends:
- api-documenter-test.DocBaseClass
- 'api-documenter-test!DocBaseClass:class'
implements:
- api-documenter-test.IDocInterface1
- api-documenter-test.IDocInterface2
package: api-documenter-test
- 'api-documenter-test!IDocInterface1:interface'
- 'api-documenter-test!IDocInterface2:interface'
package: api-documenter-test!
children:
- api-documenter-test.DocClass1.deprecatedExample
- api-documenter-test.DocClass1.exampleFunction
- api-documenter-test.DocClass1.exampleFunction_1
- api-documenter-test.DocClass1.interestingEdgeCases
- api-documenter-test.DocClass1.malformedEvent
- api-documenter-test.DocClass1.modifiedEvent
- api-documenter-test.DocClass1.regularProperty
- api-documenter-test.DocClass1.sumWithExample
- api-documenter-test.DocClass1.tableExample
- uid: api-documenter-test.DocClass1.deprecatedExample
- 'api-documenter-test!DocClass1#deprecatedExample:member(1)'
- 'api-documenter-test!DocClass1#exampleFunction:member(1)'
- 'api-documenter-test!DocClass1#exampleFunction:member(2)'
- 'api-documenter-test!DocClass1#interestingEdgeCases:member(1)'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though they can't collide, looking at this kind of makes me want to have :property, :accessor, :method, and :enummember, but this is probably fine.

Copy link
Copy Markdown
Collaborator Author

@octogonz octogonz Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want this to be the same notation that humans use when creating {@link} tags, though. Inexperienced developers may have trouble understanding the distinction between an :accessor versus a :property, and they may wonder why an :enummember is treated specially.

Most of the selector names are easy to learn because they correspond to the TypeScript keyword that introduces them (e.g. constructor, var, function, etc). Thus people can learn that :member is the catch-all for constructs that don't have a natural TypeScript keyword. (The :index and :call selectors are the only exceptions to this rule, but they are uncommon and somewhat special as they don't have an identifier.)

- 'api-documenter-test!DocClass1#malformedEvent:member'
- 'api-documenter-test!DocClass1#modifiedEvent:member'
- 'api-documenter-test!DocClass1#regularProperty:member'
- 'api-documenter-test!DocClass1.sumWithExample:member(1)'
- 'api-documenter-test!DocClass1#tableExample:member(1)'
- uid: 'api-documenter-test!DocClass1#deprecatedExample:member(1)'
deprecated:
content: Use `otherThing()` instead.
name: deprecatedExample()
Expand All @@ -43,7 +43,7 @@ items:
type:
- void
description: ''
- uid: api-documenter-test.DocClass1.exampleFunction
- uid: 'api-documenter-test!DocClass1#exampleFunction:member(1)'
summary: This is an overloaded function.
name: 'exampleFunction(a, b)'
fullName: 'exampleFunction(a, b)'
Expand All @@ -65,7 +65,7 @@ items:
description: the second string
type:
- string
- uid: api-documenter-test.DocClass1.exampleFunction_1
- uid: 'api-documenter-test!DocClass1#exampleFunction:member(2)'
summary: This is also an overloaded function.
name: exampleFunction(x)
fullName: exampleFunction(x)
Expand All @@ -83,7 +83,7 @@ items:
description: the number
type:
- number
- uid: api-documenter-test.DocClass1.interestingEdgeCases
- uid: 'api-documenter-test!DocClass1#interestingEdgeCases:member(1)'
summary: |-
Example: "<!-- -->{ \\<!-- -->"maxItemsToShow<!-- -->\\<!-- -->": 123 }<!-- -->"

Expand All @@ -99,7 +99,7 @@ items:
type:
- void
description: ''
- uid: api-documenter-test.DocClass1.malformedEvent
- uid: 'api-documenter-test!DocClass1#malformedEvent:member'
summary: This event should have been marked as readonly.
name: malformedEvent
fullName: malformedEvent
Expand All @@ -110,8 +110,8 @@ items:
content: 'malformedEvent: SystemEvent;'
return:
type:
- api-documenter-test.SystemEvent
- uid: api-documenter-test.DocClass1.modifiedEvent
- 'api-documenter-test!SystemEvent:class'
- uid: 'api-documenter-test!DocClass1#modifiedEvent:member'
summary: This event is fired whenever the object is modified.
name: modifiedEvent
fullName: modifiedEvent
Expand All @@ -122,8 +122,8 @@ items:
content: 'readonly modifiedEvent: SystemEvent;'
return:
type:
- api-documenter-test.SystemEvent
- uid: api-documenter-test.DocClass1.regularProperty
- 'api-documenter-test!SystemEvent:class'
- uid: 'api-documenter-test!DocClass1#regularProperty:member'
summary: This is a regular property that happens to use the SystemEvent type.
name: regularProperty
fullName: regularProperty
Expand All @@ -134,8 +134,8 @@ items:
content: 'regularProperty: SystemEvent;'
return:
type:
- api-documenter-test.SystemEvent
- uid: api-documenter-test.DocClass1.sumWithExample
- 'api-documenter-test!SystemEvent:class'
- uid: 'api-documenter-test!DocClass1.sumWithExample:member(1)'
summary: Returns the sum of two numbers.
remarks: This illustrates usage of the `@example` block tag.
name: 'sumWithExample(x, y)'
Expand All @@ -158,7 +158,7 @@ items:
description: the second number to add
type:
- number
- uid: api-documenter-test.DocClass1.tableExample
- uid: 'api-documenter-test!DocClass1#tableExample:member(1)'
summary: 'An example with tables:'
remarks: <table> <tr> <td>John</td> <td>Doe</td> </tr> </table>
name: tableExample()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
### YamlMime:UniversalReference
items:
- uid: api-documenter-test.DocEnum
- uid: 'api-documenter-test!DocEnum:enum'
summary: Docs for DocEnum
name: DocEnum
fullName: DocEnum
langs:
- typeScript
type: enum
package: api-documenter-test
package: api-documenter-test!
children:
- api-documenter-test.DocEnum.One
- api-documenter-test.DocEnum.Two
- api-documenter-test.DocEnum.Zero
- uid: api-documenter-test.DocEnum.One
- 'api-documenter-test!DocEnum.One:member'
- 'api-documenter-test!DocEnum.Two:member'
- 'api-documenter-test!DocEnum.Zero:member'
- uid: 'api-documenter-test!DocEnum.One:member'
summary: These are some docs for One
name: One
fullName: One
langs:
- typeScript
type: field
numericValue: '1'
- uid: api-documenter-test.DocEnum.Two
- uid: 'api-documenter-test!DocEnum.Two:member'
summary: These are some docs for Two
name: Two
fullName: Two
langs:
- typeScript
type: field
numericValue: '2'
- uid: api-documenter-test.DocEnum.Zero
- uid: 'api-documenter-test!DocEnum.Zero:member'
summary: These are some docs for Zero
name: Zero
fullName: Zero
Expand Down
Loading