diff --git a/apps/api-documenter/src/documenters/YamlDocumenter.ts b/apps/api-documenter/src/documenters/YamlDocumenter.ts index 6fd3f21d7f1..eba9b181458 100644 --- a/apps/api-documenter/src/documenters/YamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/YamlDocumenter.ts @@ -34,7 +34,8 @@ import { ApiConstructor, ApiFunction, ApiReturnTypeMixin, - ApiTypeParameterListMixin + ApiTypeParameterListMixin, + Parameter } from '@microsoft/api-extractor-model'; import { @@ -52,6 +53,12 @@ import { CustomMarkdownEmitter} from '../markdown/CustomMarkdownEmitter'; const yamlApiSchema: JsonSchema = JsonSchema.fromFile(path.join(__dirname, '..', 'yaml', 'typescript.schema.json')); +interface IPrecomputeContext { + recordedPaths: Set; + apiItemNeedsDisambiguation: Map; + uidFragmentCache: Map; +} + /** * Writes documentation in the Universal Reference YAML file format, as defined by typescript.schema.json. */ @@ -65,6 +72,9 @@ export class YamlDocumenter { // then it is excluded from the mapping. Also excluded are ApiItem objects (such as package // and function) which are not typically used as a data type. private _apiItemsByTypeName: Map; + private _apiItemToFile: Map; + private _apiItemToUid: Map; + private _uidToApiItem: Map; private _outputFolder: string; @@ -72,7 +82,11 @@ export class YamlDocumenter { this._apiModel = apiModel; this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); this._apiItemsByTypeName = new Map(); + this._apiItemToUid = new Map(); + this._apiItemToFile = new Map(); + this._uidToApiItem = new Map(); + this._precompute(); this._initApiItemsByTypeName(); } @@ -191,6 +205,8 @@ export class YamlDocumenter { const tocFilePath: string = path.join(this._outputFolder, 'toc.yml'); console.log('Writing ' + tocFilePath); + + this._disambiguateTocItems(tocFile.items); this._writeYamlFile(tocFile, tocFilePath, '', undefined); } @@ -526,6 +542,53 @@ export class YamlDocumenter { return stringBuilder.toString().trim(); } + private _disambiguateTocItems(items: IYamlTocItem[]): void { + // Track the number of times a toc item occurs for the same name at this level. + const nameCollisions: Map = new Map(); + const recordedNames: Set = new Set(); + for (const item of items) { + if (item.items) { + this._disambiguateTocItems(item.items); + } + + recordedNames.add(item.name); + let collisions: IYamlTocItem[] | undefined = nameCollisions.get(item.name); + if (!collisions) { + nameCollisions.set(item.name, collisions = []); + } + collisions.push(item); + } + + // Disambiguate any collisions. + for (const [name, collisions] of nameCollisions) { + if (collisions.length === 1) { + continue; + } + + // find the highest precedence among collisions + for (const collision of collisions) { + // If a toc item doesn't have a uid, or the uid does not correlate to an ApiItem we know of, then it must not + // be renamed. + const apiItem: ApiItem | undefined = collision.uid ? this._uidToApiItem.get(collision.uid) : undefined; + if (!apiItem) { + continue; + } + + // Disambiguate the name by appending its kind. Provide further disambiguation if necessary, appending + // something like '_2', '_3', etc. if there is still a collision. + let candidateName: string; + let attempt: number = 0; + do { + candidateName = `${name} (${apiItem.kind})${attempt === 0 ? '' : ` (${attempt + 1})`}`; + attempt++; + } while (recordedNames.has(candidateName)); + + recordedNames.add(candidateName); + collision.name = candidateName; + } + } + } + private _writeYamlFile(dataObject: {}, filePath: string, yamlMimeType: string, schema: JsonSchema|undefined): void { @@ -554,33 +617,303 @@ export class YamlDocumenter { * 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}`; + if (!this._canHaveUid(apiItem)) { + return ''; + } + + const uid: string | undefined = this._apiItemToUid.get(apiItem); + if (uid === undefined) { + throw new InternalError(`Failed to precompute uid for ApiItem of kind '${apiItem.kind}'`); + } + + return uid; + } + + private _precompute(): void { + // In order to ensure uid and file generation and disambiguation is stable, we must precompute the uids for each + // item by walking the tree. We perform this computation in two phases: + // 1. Package, Namespace, Class, Interface, Enum, TypeAlias, Variable, EnumMember, Property, PropertySignature + // - These items only depend on the uids of their containers. + // 2. Function, Constructor, Method, MethodSignature + // - These items may have overloads and are disambiguated by the types of their parameters, which must + // already be pre-computed in the phase 1. + + const context: IPrecomputeContext = { + recordedPaths: new Set(), + apiItemNeedsDisambiguation: new Map(), + uidFragmentCache: new Map() + }; + + this._precomputeNonFunctionLikeRecursive(this._apiModel, '', 0, context); + this._precomputeFunctionLikeRecursive(this._apiModel, '', 0, context); + } + + private _precomputeNonFunctionLikeRecursive(apiItem: ApiItem, parentUid: string, packageLength: number, + context: IPrecomputeContext): void { + + const uid: string = this._precomputeUidAndPath(apiItem, parentUid, packageLength, context); + if (apiItem.kind === ApiItemKind.Package) { + packageLength = uid.length; + } + + for (const member of apiItem.members) { + if (!this._isFunctionLike(member)) { + this._precomputeNonFunctionLikeRecursive(member, uid, packageLength, context); + } + } + } + + private _precomputeFunctionLikeRecursive(apiItem: ApiItem, parentUid: string, packageLength: number, + context: IPrecomputeContext): void { + + if (this._isFunctionLike(apiItem)) { + this._precomputeUidAndPath(apiItem, parentUid, packageLength, context); + } else { + const uid: string | undefined = this._canHaveUid(apiItem) ? this._apiItemToUid.get(apiItem) : ''; + if (uid === undefined) { + throw new InternalError('Failed to precompute uid for item in first pass.'); + } + if (apiItem.kind === ApiItemKind.Package) { + packageLength = uid.length; + } + for (const member of apiItem.members) { + this._precomputeFunctionLikeRecursive(member, uid, packageLength, context); + } + } + } + + private _precomputeUidAndPath(apiItem: ApiItem, parentUid: string, packageLength: number, + context: IPrecomputeContext): string { + + if (this._canHaveUid(apiItem)) { + const itemUid: string = this._getUidFragment(apiItem, context); + + // Compute whether an ApiItem has a uid collision with any other ApiItems in the same scope. + let needsDisambiguation: boolean | undefined; + if (apiItem.parent) { + needsDisambiguation = context.apiItemNeedsDisambiguation.get(apiItem); + if (needsDisambiguation === undefined) { + const collisions: ApiItem[] = apiItem.parent.members + .filter(item => this._getUidFragment(item, context) === itemUid); + + if (collisions.length === 1) { + // If there is only one thing with this uid, the item does not need disambiguation. + needsDisambiguation = false; + context.apiItemNeedsDisambiguation.set(apiItem, false); + } else { + // Determine which collision has the highest precedence. + let highestPrecedence: number = -1; + for (const collision of collisions) { + highestPrecedence = Math.max(highestPrecedence, this._getDisambiguationPrecedence(collision)); + } + + // Store whether each collision needs disambiguation. + // + // A suffix will *not* be added if the item has the highest precedence among its siblings. The precedence + // is: + // 1. Classes, Functions, Enums, Variables + // 2. Interfaces, TypeAliases + // 3. Namespaces + // + // If a class, an interface, and a namespace all have the same uid ('MyService'), it will generate the + // following: + // - ApiClass: 'MyService' + // - ApiInterface: 'MyService:interface' + // - ApiNamespace: 'MyService:namespace' + // + // If an interface and a namespace both have the same uid ('MyService'), it will generate the following: + // - ApiInterface: 'MyService' + // - ApiNamespace: 'MyService:namespace' + for (const collision of collisions) { + const collisionNeedsDisambiguation: boolean = + this._getDisambiguationPrecedence(collision) < highestPrecedence; + + context.apiItemNeedsDisambiguation.set(apiItem, collisionNeedsDisambiguation); + if (collision === apiItem) { + needsDisambiguation = collisionNeedsDisambiguation; + } + } + } } } - switch (hierarchyItem.kind) { - case ApiItemKind.Model: - case ApiItemKind.EntryPoint: - break; + // Adds something like ':interface' to disambiguate declarations with the same name but different kinds. + let candidateUid: string = this._combineUids(parentUid, itemUid, '.'); + if (needsDisambiguation) { + candidateUid += ':' + apiItem.kind.toLowerCase(); + } + + const packagePath: string = packageLength ? this._safePath(candidateUid.slice(0, packageLength)) : ''; + const itemFile: string = this._safePath(packageLength ? candidateUid.slice(packageLength + 1) : candidateUid); + const candidateFile: string = path.join(packagePath, itemFile); + + // Provide further disambiguation if necessary, appending something like '_2', '_3', etc. + // The first item in gets the uid without a disambiguation suffix. + let attempt: number = 0; + let uid: string; + let file: string; + do { + uid = attempt === 0 ? candidateUid : `${candidateUid}_${attempt + 1}`; + file = attempt === 0 ? `${candidateFile}.yml` : `${candidateFile}_${attempt + 1}.yml`; + attempt++; + } while (this._uidToApiItem.has(uid) || context.recordedPaths.has(file)); + + // Record the mappings from uid and file to ApiItem. + this._apiItemToUid.set(apiItem, uid); + this._apiItemToFile.set(apiItem, file); + + // Prevent any other ApiItem from using this uid or file. + this._uidToApiItem.set(uid, apiItem); + context.recordedPaths.add(file); + return uid; + } else { + return parentUid; + } + } + + private _canHaveUid(apiItem: ApiItem): boolean { + switch (apiItem.kind) { + case ApiItemKind.Model: + case ApiItemKind.EntryPoint: + case ApiItemKind.CallSignature: + case ApiItemKind.ConstructSignature: + case ApiItemKind.IndexSignature: + return false; + } + return true; + } + + private _isFunctionLike(apiItem: ApiItem): boolean { + switch (apiItem.kind) { + case ApiItemKind.CallSignature: + case ApiItemKind.ConstructSignature: + case ApiItemKind.Function: + case ApiItemKind.Constructor: + case ApiItemKind.Method: + case ApiItemKind.MethodSignature: + return true; + } + return false; + } + + /** + * Gets the unscoped/non-namespaced portion of the uid for an ApiItem. + */ + private _getUidFragment(apiItem: ApiItem, context: IPrecomputeContext): string { + let fragment: string | undefined = context.uidFragmentCache.get(apiItem); + if (fragment === undefined) { + switch (apiItem.kind) { case ApiItemKind.Package: - result += PackageName.getUnscopedName(hierarchyItem.displayName); + fragment = PackageName.getUnscopedName(apiItem.displayName); break; - default: - result += '.'; - result += qualifiedName; + case ApiItemKind.Constructor: + fragment = `constructor${this._getSignatureUidSuffix(apiItem as ApiConstructor)}`; + break; + case ApiItemKind.Function: + case ApiItemKind.Method: + case ApiItemKind.MethodSignature: + fragment = `${apiItem.displayName}${this._getSignatureUidSuffix(apiItem as ApiParameterListMixin)}`; break; + case ApiItemKind.Class: + case ApiItemKind.Interface: + case ApiItemKind.TypeAlias: + case ApiItemKind.Namespace: + case ApiItemKind.Variable: + case ApiItemKind.Enum: + case ApiItemKind.EnumMember: + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + fragment = apiItem.displayName; + break; + default: + return ''; } + context.uidFragmentCache.set(apiItem, fragment); } - return result; + return fragment; + } + + /** + * Gets the precedence of an ApiItem for use when performing disambiguation. + * The highest precedence item often avoids needing a suffix. + */ + private _getDisambiguationPrecedence(apiItem: ApiItem): number { + switch (apiItem.kind) { + case ApiItemKind.Class: + case ApiItemKind.Enum: + // Classes and Enums both exist in the type-space and value-space and cannot merge with each other. + return 4; + case ApiItemKind.Interface: + case ApiItemKind.TypeAlias: + // Interfaces and TypeAliases both exist in type-space and cannot merge with each other. + return 3; + case ApiItemKind.Function: + case ApiItemKind.Variable: + // Functions and Variables both exist in value-space and cannot merge with each other. + return 2; + case ApiItemKind.Namespace: + // Namespaces merge with everything except variables. + return 1; + default: + // Anything else we will always disambiguate. + return 0; + } + } + + private _getSignatureUidSuffix(apiItem: ApiParameterListMixin): string { + // Generate a uid suffix do disambiguate function-like items by emulating the naming behavior of the .NET + // Metadata specification. + // + // Per the Generics section of the .NET Metadata specification: + // https://dotnet.github.io/docfx/spec/metadata_dotnet_spec.html#6-generics + // + // > The *ID* of a generic method uses postfix '``n', 'n' is the count of in method parameters, for example, + // > 'System.Tuple.Create``1(``0)'. + // + // Per the Methods section of the .NET Metadata specification: + // https://dotnet.github.io/docfx/spec/metadata_dotnet_spec.html#52-methods + // + // > The *ID* of a method is defined by its name, followed by the list of the *UIDs* of its parameter types. + // > When a method does not have parameter, its *ID* **MUST** end with parentheses. + + let result: string = ''; + + const typeParameterToIndex: Map = new Map(); + if (ApiTypeParameterListMixin.isBaseClassOf(apiItem) && apiItem.typeParameters.length) { + for (let i: number = 0; i < apiItem.typeParameters.length; i++) { + typeParameterToIndex.set(apiItem.typeParameters[i].name, i); + } + result += `\`\`${apiItem.typeParameters.length}`; + } + + result += '('; + for (let i: number = 0; i < apiItem.parameters.length; i++) { + if (i > 0) { + result += ','; + } + const parameter: Parameter = apiItem.parameters[i]; + if (!parameter.parameterTypeExcerpt.isEmpty) { + const typeParameterIndex: number | undefined = + typeParameterToIndex.get(parameter.parameterTypeExcerpt.text.trim()); + + if (typeParameterIndex !== undefined) { + result += `\`\`${typeParameterIndex}`; + } else { + result += this._linkToUidIfPossible(parameter.parameterTypeExcerpt.text); + } + } + } + + return result + ')'; + } + + private _combineUids(left: string, right: string, sep: string): string { + return left ? right ? `${left}${sep}${right}` : left : right; + } + + private _safePath(file: string): string { + // allow unicode word characters to support non-english language characters. + return file.toLowerCase().replace(/[^-.\w]/gu, () => '_'); } /** @@ -685,27 +1018,16 @@ export class YamlDocumenter { } private _getYamlFilePath(apiItem: ApiItem): string { - let result: string = ''; + if (!this._canHaveUid(apiItem)) { + return ''; + } - for (const current of apiItem.getHierarchy()) { - switch (current.kind) { - case ApiItemKind.Model: - case ApiItemKind.EntryPoint: - break; - case ApiItemKind.Package: - result += PackageName.getUnscopedName(current.displayName); - break; - default: - if (current.parent && current.parent.kind === ApiItemKind.EntryPoint) { - result += '/'; - } else { - result += '.'; - } - result += current.displayName; - break; - } + const file: string | undefined = this._apiItemToFile.get(apiItem); + if (file === undefined) { + throw new InternalError(`Failed to precompute path for ApiItem of kind '${apiItem.kind}'`); } - return path.join(this._outputFolder, result.toLowerCase() + '.yml'); + + return path.join(this._outputFolder, file); } private _deleteOldOutputFiles(): void { diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.json b/build-tests/api-documenter-test/etc/api-documenter-test.api.json index 431119195f5..8e2f6024213 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.json +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.json @@ -14,6 +14,113 @@ "canonicalReference": "", "name": "", "members": [ + { + "kind": "Class", + "canonicalReference": "(Collision:class)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class " + }, + { + "kind": "Reference", + "text": "Collision" + }, + { + "kind": "Content", + "text": " " + } + ], + "releaseTag": "Public", + "name": "Collision", + "members": [ + { + "kind": "Property", + "canonicalReference": "(a:instance)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Reference", + "text": "a" + }, + { + "kind": "Content", + "text": ": " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "a", + "propertyTypeTokenRange": { + "startIndex": 2, + "endIndex": 3 + }, + "isStatic": false + } + ], + "implementsTokenRanges": [] + }, + { + "kind": "Interface", + "canonicalReference": "(Collision:interface)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export interface " + }, + { + "kind": "Reference", + "text": "Collision" + }, + { + "kind": "Content", + "text": " " + } + ], + "releaseTag": "Public", + "name": "Collision", + "members": [ + { + "kind": "PropertySignature", + "canonicalReference": "b", + "docComment": "", + "excerptTokens": [ + { + "kind": "Reference", + "text": "b" + }, + { + "kind": "Content", + "text": ": " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "b", + "propertyTypeTokenRange": { + "startIndex": 2, + "endIndex": 3 + } + } + ], + "extendsTokenRanges": [] + }, { "kind": "Class", "canonicalReference": "(DocBaseClass:class)", diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index 2b8cc579bb9..1cfa36ebad0 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -4,6 +4,18 @@ ```ts +// @public (undocumented) +export class Collision { + // (undocumented) + a: number; +} + +// @public (undocumented) +export interface Collision { + // (undocumented) + b: number; +} + // @public export const constVariable: number; diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.a.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.a.md new file mode 100644 index 00000000000..508efd8a8dc --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.a.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [Collision](./api-documenter-test.collision.md) > [a](./api-documenter-test.collision.a.md) + +## Collision.a property + +Signature: + +```typescript +a: number; +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.b.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.b.md new file mode 100644 index 00000000000..0457059a280 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.b.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [Collision](./api-documenter-test.collision.md) > [b](./api-documenter-test.collision.b.md) + +## Collision.b property + +Signature: + +```typescript +b: number; +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.md new file mode 100644 index 00000000000..90cfa13b532 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.collision.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [Collision](./api-documenter-test.collision.md) + +## Collision interface + + +Signature: + +```typescript +export interface Collision +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [b](./api-documenter-test.collision.b.md) | number | | + diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md index 7947ac53ed5..b14f2594263 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md @@ -12,6 +12,7 @@ This project tests various documentation generation scenarios and doc comment sy | Class | Description | | --- | --- | +| [Collision](./api-documenter-test.collision.md) | | | [DocBaseClass](./api-documenter-test.docbaseclass.md) | Example base class | | [DocClass1](./api-documenter-test.docclass1.md) | This is an example class. | | [Generic](./api-documenter-test.generic.md) | Generic class. | @@ -33,6 +34,7 @@ This project tests various documentation generation scenarios and doc comment sy | Interface | Description | | --- | --- | +| [Collision](./api-documenter-test.collision.md) | | | [IDocInterface1](./api-documenter-test.idocinterface1.md) | | | [IDocInterface2](./api-documenter-test.idocinterface2.md) | | | [IDocInterface3](./api-documenter-test.idocinterface3.md) | Some less common TypeScript declaration kinds. | diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml index 288a11a8714..78cf37b4f9b 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml @@ -11,20 +11,22 @@ items: - typeScript type: package children: + - api-documenter-test.Collision + - 'api-documenter-test.Collision:interface' - api-documenter-test.DocBaseClass - api-documenter-test.DocClass1 - api-documenter-test.DocEnum - api-documenter-test.Generic - - api-documenter-test.globalFunction + - globalFunction(number) - 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.OuterNamespace.InnerNamespace.nestedFunction(number) - api-documenter-test.SystemEvent - - uid: api-documenter-test.globalFunction + - uid: globalFunction(number) summary: An exported function name: globalFunction(x) fullName: globalFunction(x) @@ -42,10 +44,10 @@ items: description: '' type: - number - - uid: api-documenter-test.OuterNamespace.InnerNamespace.nestedFunction + - uid: api-documenter-test.OuterNamespace.InnerNamespace.nestedFunction(number) summary: A function inside a namespace - name: OuterNamespace.InnerNamespace.nestedFunction - fullName: OuterNamespace.InnerNamespace.nestedFunction + name: OuterNamespace.InnerNamespace.nestedFunction(number) + fullName: OuterNamespace.InnerNamespace.nestedFunction(number) langs: - typeScript type: function @@ -61,6 +63,10 @@ items: type: - number references: + - uid: api-documenter-test.Collision + name: Collision + - uid: 'api-documenter-test.Collision:interface' + name: Collision - uid: api-documenter-test.DocBaseClass name: DocBaseClass - uid: api-documenter-test.DocClass1 diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision.yml new file mode 100644 index 00000000000..b35f7392e62 --- /dev/null +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision.yml @@ -0,0 +1,22 @@ +### YamlMime:UniversalReference +items: + - uid: api-documenter-test.Collision + name: Collision + fullName: Collision + langs: + - typeScript + type: class + package: api-documenter-test + children: + - api-documenter-test.Collision.a + - uid: api-documenter-test.Collision.a + name: a + fullName: a + langs: + - typeScript + type: property + syntax: + content: 'a: number;' + return: + type: + - number diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision_interface.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision_interface.yml new file mode 100644 index 00000000000..b042c6dd11f --- /dev/null +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/collision_interface.yml @@ -0,0 +1,22 @@ +### YamlMime:UniversalReference +items: + - uid: 'api-documenter-test.Collision:interface' + name: Collision + fullName: Collision + langs: + - typeScript + type: interface + package: api-documenter-test + children: + - 'api-documenter-test.Collision:interface.b' + - uid: 'api-documenter-test.Collision:interface.b' + name: b + fullName: b + langs: + - typeScript + type: property + syntax: + content: 'b: number;' + return: + type: + - number diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docbaseclass.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docbaseclass.yml index b2c0a2148db..43cebff962f 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docbaseclass.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docbaseclass.yml @@ -9,9 +9,9 @@ items: type: class package: api-documenter-test children: - - api-documenter-test.DocBaseClass.(constructor) - - api-documenter-test.DocBaseClass.(constructor)_1 - - uid: api-documenter-test.DocBaseClass.(constructor) + - api-documenter-test.DocBaseClass.constructor() + - api-documenter-test.DocBaseClass.constructor(number) + - uid: api-documenter-test.DocBaseClass.constructor() summary: The simple constructor for `DocBaseClass` name: (constructor)() fullName: (constructor)() @@ -20,7 +20,7 @@ items: type: constructor syntax: content: constructor(); - - uid: api-documenter-test.DocBaseClass.(constructor)_1 + - uid: api-documenter-test.DocBaseClass.constructor(number) summary: The overloaded constructor for `DocBaseClass` name: (constructor)(x) fullName: (constructor)(x) diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml index 8883547a1f0..b41acd80605 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml @@ -20,16 +20,16 @@ items: - api-documenter-test.IDocInterface2 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.deprecatedExample() + - 'api-documenter-test.DocClass1.exampleFunction(string,string)' + - api-documenter-test.DocClass1.exampleFunction(number) + - 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.sumWithExample(number,number)' + - api-documenter-test.DocClass1.tableExample() + - uid: api-documenter-test.DocClass1.deprecatedExample() deprecated: content: Use `otherThing()` instead. name: deprecatedExample() @@ -43,7 +43,7 @@ items: type: - void description: '' - - uid: api-documenter-test.DocClass1.exampleFunction + - uid: 'api-documenter-test.DocClass1.exampleFunction(string,string)' summary: This is an overloaded function. name: 'exampleFunction(a, b)' fullName: 'exampleFunction(a, b)' @@ -65,7 +65,7 @@ items: description: the second string type: - string - - uid: api-documenter-test.DocClass1.exampleFunction_1 + - uid: api-documenter-test.DocClass1.exampleFunction(number) summary: This is also an overloaded function. name: exampleFunction(x) fullName: exampleFunction(x) @@ -83,7 +83,7 @@ items: description: the number type: - number - - uid: api-documenter-test.DocClass1.interestingEdgeCases + - uid: api-documenter-test.DocClass1.interestingEdgeCases() summary: |- Example: "{ \\"maxItemsToShow\\": 123 }" @@ -135,7 +135,7 @@ items: return: type: - api-documenter-test.SystemEvent - - uid: api-documenter-test.DocClass1.sumWithExample + - uid: 'api-documenter-test.DocClass1.sumWithExample(number,number)' summary: Returns the sum of two numbers. remarks: This illustrates usage of the `@example` block tag. name: 'sumWithExample(x, y)' @@ -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() summary: 'An example with tables:' remarks:
John Doe
name: tableExample() diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface2.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface2.yml index 59cd704aac7..ac84dd4cb6f 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface2.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface2.yml @@ -10,8 +10,8 @@ items: - api-documenter-test.IDocInterface1 package: api-documenter-test children: - - api-documenter-test.IDocInterface2.deprecatedExample - - uid: api-documenter-test.IDocInterface2.deprecatedExample + - api-documenter-test.IDocInterface2.deprecatedExample() + - uid: api-documenter-test.IDocInterface2.deprecatedExample() deprecated: content: Use `otherThing()` instead. name: deprecatedExample() diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/systemevent.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/systemevent.yml index 7d20419769d..1df226dae0a 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/systemevent.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/systemevent.yml @@ -9,8 +9,8 @@ items: type: class package: api-documenter-test children: - - api-documenter-test.SystemEvent.addHandler - - uid: api-documenter-test.SystemEvent.addHandler + - api-documenter-test.SystemEvent.addHandler(() => void) + - uid: api-documenter-test.SystemEvent.addHandler(() => void) summary: Adds an handler for the event. name: addHandler(handler) fullName: addHandler(handler) diff --git a/build-tests/api-documenter-test/etc/yaml/toc.yml b/build-tests/api-documenter-test/etc/yaml/toc.yml index 98af9710fad..c555eb47143 100644 --- a/build-tests/api-documenter-test/etc/yaml/toc.yml +++ b/build-tests/api-documenter-test/etc/yaml/toc.yml @@ -39,6 +39,10 @@ items: items: - name: InjectedCustomItem uid: customUrl + - name: Collision (Class) + uid: api-documenter-test.Collision + - name: Collision (Interface) + uid: 'api-documenter-test.Collision:interface' - name: DocEnum uid: api-documenter-test.DocEnum - name: Generic diff --git a/build-tests/api-documenter-test/src/index.ts b/build-tests/api-documenter-test/src/index.ts index 78b2be798e8..70d80782a58 100644 --- a/build-tests/api-documenter-test/src/index.ts +++ b/build-tests/api-documenter-test/src/index.ts @@ -55,3 +55,13 @@ export namespace OuterNamespace { */ export let nestedVariable: boolean = false; } + +/** @public */ +export class Collision { + a = 1; +} + +/** @public */ +export interface Collision { + b: number; +} \ No newline at end of file diff --git a/common/changes/@microsoft/api-documenter/disambiguate_2019-06-11-01-03.json b/common/changes/@microsoft/api-documenter/disambiguate_2019-06-11-01-03.json new file mode 100644 index 00000000000..4cd1c282e15 --- /dev/null +++ b/common/changes/@microsoft/api-documenter/disambiguate_2019-06-11-01-03.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-documenter", + "comment": "Add logic to disambiguate duplicate UID, file, or toc entries", + "type": "minor" + } + ], + "packageName": "@microsoft/api-documenter", + "email": "ron.buckton@microsoft.com" +} \ No newline at end of file