Skip to content

Commit d1f1bfe

Browse files
committed
Cleanup _renderType, remove _linkToUidIfPossible and related members
1 parent f1fa61f commit d1f1bfe

4 files changed

Lines changed: 107 additions & 178 deletions

File tree

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

Lines changed: 81 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -60,32 +60,32 @@ import { CustomMarkdownEmitter} from '../markdown/CustomMarkdownEmitter';
6060

6161
const yamlApiSchema: JsonSchema = JsonSchema.fromFile(path.join(__dirname, '..', 'yaml', 'typescript.schema.json'));
6262

63+
interface IYamlReferences {
64+
references: IYamlReference[];
65+
typeNameToUid: Map<string, string>;
66+
}
67+
6368
/**
6469
* Writes documentation in the Universal Reference YAML file format, as defined by typescript.schema.json.
6570
*/
6671
export class YamlDocumenter {
6772
private readonly _apiModel: ApiModel;
6873
private readonly _markdownEmitter: CustomMarkdownEmitter;
6974

70-
// This is used by the _linkToUidIfPossible() workaround.
71-
// It stores a mapping from type name (e.g. "MyClass") to the corresponding ApiItem.
72-
// If the mapping would be ambiguous (e.g. "MyClass" is defined by multiple packages)
73-
// then it is excluded from the mapping. Also excluded are ApiItem objects (such as package
74-
// and function) which are not typically used as a data type.
75-
private _apiItemsByTypeName: Map<string, ApiItem>;
75+
private _apiItemsByCanonicalReference: Map<string, ApiItem>;
7676
private _knownTypeParameters: Set<string> | undefined;
77-
private _yamlReferences: IYamlReference[] | undefined;
77+
private _yamlReferences: IYamlReferences | undefined;
7878
private _uidTypeReferenceCounters: Map<string, number>;
7979

8080
private _outputFolder: string;
8181

8282
public constructor(apiModel: ApiModel) {
8383
this._apiModel = apiModel;
8484
this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel);
85-
this._apiItemsByTypeName = new Map<string, ApiItem>();
85+
this._apiItemsByCanonicalReference = new Map<string, ApiItem>();
8686
this._uidTypeReferenceCounters = new Map<string, number>();
8787

88-
this._initApiItemsByTypeName();
88+
this._initApiItems();
8989
}
9090

9191
/** @virtual */
@@ -171,11 +171,11 @@ export class YamlDocumenter {
171171
}
172172

173173
if (this._yamlReferences) {
174-
if (this._yamlReferences.length > 0) {
174+
if (this._yamlReferences.references.length > 0) {
175175
if (newYamlFile.references) {
176-
newYamlFile.references = [...newYamlFile.references, ...this._yamlReferences];
176+
newYamlFile.references = [...newYamlFile.references, ...this._yamlReferences.references];
177177
} else {
178-
newYamlFile.references = this._yamlReferences;
178+
newYamlFile.references = this._yamlReferences.references;
179179
}
180180
}
181181
this._yamlReferences = undefined;
@@ -609,97 +609,39 @@ export class YamlDocumenter {
609609
}
610610

611611
/**
612-
* Initialize the _apiItemsByTypeName data structure.
612+
* Initialize the _apiItemsByCanonicalReference data structure.
613613
*/
614-
private _initApiItemsByTypeName(): void {
615-
// Collect the _apiItemsByTypeName table
616-
const ambiguousNames: Set<string> = new Set<string>();
617-
618-
this._initApiItemsByTypeNameRecursive(this._apiModel, ambiguousNames);
614+
private _initApiItems(): void {
615+
this._initApiItemsRecursive(this._apiModel);
619616

620-
// Remove the ambiguous matches
621-
for (const ambiguousName of ambiguousNames) {
622-
this._apiItemsByTypeName.delete(ambiguousName);
623-
}
624617
}
625618

626619
/**
627-
* Helper for _initApiItemsByTypeName()
620+
* Helper for _initApiItems()
628621
*/
629-
private _initApiItemsByTypeNameRecursive(apiItem: ApiItem, ambiguousNames: Set<string>): void {
630-
switch (apiItem.kind) {
631-
case ApiItemKind.Class:
632-
case ApiItemKind.Enum:
633-
case ApiItemKind.Interface:
634-
// Attempt to register both the fully qualified name and the short name
635-
const namesForType: string[] = [apiItem.displayName];
636-
637-
// Note that nameWithDot cannot conflict with apiItem.name (because apiItem.name
638-
// cannot contain a dot)
639-
const nameWithDot: string | undefined = this._getTypeNameWithDot(apiItem);
640-
if (nameWithDot) {
641-
namesForType.push(nameWithDot);
642-
}
643-
644-
// Register all names
645-
for (const typeName of namesForType) {
646-
if (ambiguousNames.has(typeName)) {
647-
break;
648-
}
649-
650-
if (this._apiItemsByTypeName.has(typeName)) {
651-
// We saw this name before, so it's an ambiguous match
652-
ambiguousNames.add(typeName);
653-
break;
654-
}
655-
656-
this._apiItemsByTypeName.set(typeName, apiItem);
657-
}
658-
659-
break;
622+
private _initApiItemsRecursive(apiItem: ApiItem): void {
623+
if (apiItem.canonicalReference && !apiItem.canonicalReference.isEmpty) {
624+
this._apiItemsByCanonicalReference.set(apiItem.canonicalReference.toString(), apiItem);
660625
}
661626

662627
// Recurse container members
663628
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
664629
for (const apiMember of apiItem.members) {
665-
this._initApiItemsByTypeNameRecursive(apiMember, ambiguousNames);
630+
this._initApiItemsRecursive(apiMember);
666631
}
667632
}
668633
}
669634

670-
private _ensureYamlReferences(): IYamlReference[] {
635+
private _ensureYamlReferences(): IYamlReferences {
671636
if (!this._yamlReferences) {
672-
this._yamlReferences = [];
637+
this._yamlReferences = {
638+
references: [],
639+
typeNameToUid: new Map()
640+
};
673641
}
674642
return this._yamlReferences;
675643
}
676644

677-
/**
678-
* This is a temporary workaround to enable limited autolinking of API item types
679-
* until the YAML file format is enhanced to support general hyperlinks.
680-
* @remarks
681-
* In the current version, fields such as IApiProperty.type allow either:
682-
* (1) a UID identifier such as "node-core-library.JsonFile" which will be rendered
683-
* as a hyperlink to that type name, or (2) a block of freeform text that must not
684-
* contain any Markdown links. The _substituteUidForSimpleType() function assumes
685-
* it is given #2 but substitutes #1 if the name can be matched to a ApiItem.
686-
*/
687-
private _linkToUidIfPossible(typeName: string): string | undefined {
688-
typeName = typeName.trim();
689-
// Do not look up the UID for a type parameter, as we could inadvertently
690-
// look up a different type with the same name.
691-
if (this._knownTypeParameters && this._knownTypeParameters.has(typeName)) {
692-
return typeName;
693-
}
694-
695-
// Note that typeName might be a _getTypeNameWithDot() name or it might be a simple class name
696-
const apiItem: ApiItem | undefined = this._apiItemsByTypeName.get(typeName);
697-
if (apiItem) {
698-
// Substitute the UID
699-
return this._getUid(apiItem);
700-
}
701-
}
702-
703645
private _renderType(contextUid: DeclarationReference, typeExcerpt: Excerpt): string {
704646
const excerptTokens: ExcerptToken[] = typeExcerpt.tokens.slice(
705647
typeExcerpt.tokenRange.startIndex,
@@ -719,76 +661,87 @@ export class YamlDocumenter {
719661
}
720662

721663
const typeName: string = typeExcerpt.text.trim();
664+
665+
// Record a reference to a type parameter as its name, so as not to resolve to a conflicting name
666+
if (this._knownTypeParameters && this._knownTypeParameters.has(typeName)) {
667+
return this._recordYamlReference(this._ensureYamlReferences(), typeName, typeName);
668+
}
669+
670+
// If there are no references to be used for a complex type, return the type name.
671+
if (!excerptTokens.some(tok => tok.kind === ExcerptTokenKind.Reference && !!tok.canonicalReference)) {
672+
return typeName;
673+
}
674+
675+
const yamlReferences: IYamlReferences = this._ensureYamlReferences();
676+
const existingUid: string | undefined = yamlReferences.typeNameToUid.get(typeName);
677+
678+
// If this type has already been referenced for the current file, return its uid.
679+
if (existingUid) {
680+
return existingUid;
681+
}
682+
683+
// If the excerpt consists of a single reference token, record the reference.
722684
if (excerptTokens.length === 1 &&
723685
excerptTokens[0].kind === ExcerptTokenKind.Reference &&
724686
excerptTokens[0].canonicalReference) {
725687
return this._recordYamlReference(
688+
yamlReferences,
726689
excerptTokens[0].canonicalReference.toString(),
727690
typeName
728691
);
729692
}
730693

731-
const typeNameAsUid: string | undefined = this._linkToUidIfPossible(typeName);
732-
if (typeNameAsUid !== undefined) {
733-
if (typeNameAsUid !== typeName) {
734-
return this._recordYamlReference(typeNameAsUid, typeName);
735-
}
736-
return typeNameAsUid;
737-
}
738-
739-
if (isEntityName(typeName)) {
740-
return typeName;
741-
}
742-
743-
// If there are no references to be used for a complex type, return the type name.
744-
if (!excerptTokens.some(tok => tok.kind === ExcerptTokenKind.Reference)) {
745-
return typeName;
746-
}
747-
694+
// Otherwise, the type is complex and consists of one or more reference tokens. Record a reference
695+
// and return its uid.
748696
const baseUid: string = contextUid
749697
.withMeaning(undefined)
750698
.withOverloadIndex(undefined)
751699
.toString();
700+
701+
// Keep track of the count for the base uid (without meaning or overload index) to ensure
702+
// that each complex type reference is unique.
752703
const counter: number = this._uidTypeReferenceCounters.get(baseUid) || 0;
704+
this._uidTypeReferenceCounters.set(baseUid, counter + 1);
705+
753706
const uid: string = contextUid
754707
.addNavigationStep(Navigation.Locals, `${counter}`)
755708
.withMeaning(Meaning.ComplexType)
709+
.withOverloadIndex(undefined)
756710
.toString();
757-
this._uidTypeReferenceCounters.set(baseUid, counter + 1);
758-
this._recordYamlReference(uid, typeName, excerptTokens);
759-
return uid;
711+
712+
return this._recordYamlReference(yamlReferences, uid, typeName, excerptTokens);
760713
}
761714

762-
private _recordYamlReference(uid: string, typeName: string, excerptTokens?: ExcerptToken[]): string {
763-
const yamlReferences: IYamlReference[] = this._ensureYamlReferences();
715+
private _recordYamlReference(yamlReferences: IYamlReferences, uid: string, typeName: string,
716+
excerptTokens?: ExcerptToken[]): string {
764717

765-
if (yamlReferences.some(ref => ref.uid === uid)) {
718+
if (yamlReferences.references.some(ref => ref.uid === uid)) {
766719
return uid;
767720
}
768721

769722
// Fill in the reference spec from the excerpt.
770-
const spec: IYamlReferenceSpec[] = [];
723+
const specs: IYamlReferenceSpec[] = [];
771724
if (excerptTokens) {
772725
for (const token of excerptTokens) {
773726
if (token.kind === ExcerptTokenKind.Reference) {
774-
let specUid: string | undefined = token.canonicalReference && token.canonicalReference.toString();
775-
const apiItem: ApiItem | undefined = this._apiItemsByTypeName.get(token.text);
776-
if (specUid === undefined) {
777-
if (apiItem) {
778-
specUid = this._getUid(apiItem);
779-
} else {
780-
specUid = token.text;
781-
}
727+
const spec: IYamlReferenceSpec = { };
728+
const specUid: string | undefined = token.canonicalReference && token.canonicalReference.toString();
729+
const apiItem: ApiItem | undefined = specUid ? this._apiItemsByCanonicalReference.get(specUid) : undefined;
730+
if (specUid) {
731+
spec.uid = specUid;
782732
}
783-
spec.push(
784-
{
785-
uid: specUid,
786-
name: token.text,
787-
fullName: apiItem ? apiItem.getScopedNameWithinPackage() : token.text
788-
}
789-
);
733+
spec.name = token.text;
734+
spec.fullName =
735+
apiItem ? apiItem.getScopedNameWithinPackage() :
736+
token.canonicalReference ? token.canonicalReference
737+
.withSource(undefined)
738+
.withMeaning(undefined)
739+
.withOverloadIndex(undefined)
740+
.toString() :
741+
token.text;
742+
specs.push(spec);
790743
} else {
791-
spec.push(
744+
specs.push(
792745
{
793746
name: token.text,
794747
fullName: token.text
@@ -799,31 +752,18 @@ export class YamlDocumenter {
799752
}
800753

801754
const yamlReference: IYamlReference = { uid };
802-
if (spec.length > 0) {
803-
yamlReference.name = spec.map(s => s.name).join('').trim();
804-
yamlReference.fullName = spec.map(s => s.fullName || s.name).join('').trim();
805-
yamlReference['spec.typeScript'] = spec;
755+
if (specs.length > 0) {
756+
yamlReference.name = specs.map(s => s.name).join('').trim();
757+
yamlReference.fullName = specs.map(s => s.fullName || s.name).join('').trim();
758+
yamlReference['spec.typeScript'] = specs;
806759
} else if (typeName !== uid) {
807760
yamlReference.name = typeName;
808761
}
809762

810-
yamlReferences.push(yamlReference);
763+
yamlReferences.references.push(yamlReference);
811764
return uid;
812765
}
813766

814-
/**
815-
* If the apiItem represents a scoped name such as "my-library#MyNamespace.MyClass",
816-
* this returns a string such as "MyNamespace.MyClass". If the result would not
817-
* have at least one dot in it, then undefined is returned.
818-
*/
819-
private _getTypeNameWithDot(apiItem: ApiItem): string | undefined {
820-
const result: string = apiItem.getScopedNameWithinPackage();
821-
if (result.indexOf('.') >= 0) {
822-
return result;
823-
}
824-
return undefined;
825-
}
826-
827767
private _getYamlItemName(apiItem: ApiItem): string {
828768
if (apiItem.parent && apiItem.parent.kind === ApiItemKind.Namespace) {
829769
// If the immediate parent is a namespace, then add the namespaces to the name. For example:
@@ -896,10 +836,4 @@ export class YamlDocumenter {
896836
console.log('Deleting old output from ' + this._outputFolder);
897837
FileSystem.ensureEmptyFolder(this._outputFolder);
898838
}
899-
}
900-
901-
const entityNameRegExp: RegExp = /^(?!\d)[\w$_]+(\.(?!\d)[\w$_]+)*$/;
902-
903-
function isEntityName(text: string): boolean {
904-
return entityNameRegExp.test(text);
905839
}

build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface4.yml

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ items:
2727
}) => boolean;
2828
return:
2929
type:
30-
- 'api-documenter-test!IDocInterface4#Context~0:complex'
30+
- |-
31+
({ children }: {
32+
children: string;
33+
}) => boolean
3134
- uid: 'api-documenter-test!IDocInterface4#generic:member'
3235
summary: make sure html entities are escaped in tables.
3336
name: generic
@@ -65,36 +68,6 @@ items:
6568
type:
6669
- string | number
6770
references:
68-
- uid: 'api-documenter-test!IDocInterface4#Context~0:complex'
69-
name: |-
70-
({ children }: {
71-
children: string;
72-
}) => boolean
73-
fullName: |-
74-
({ children }: {
75-
children: string;
76-
}) => boolean
77-
spec.typeScript:
78-
- name: '({ '
79-
fullName: '({ '
80-
- uid: children
81-
name: children
82-
fullName: children
83-
- name: |2-
84-
}: {
85-
86-
fullName: |2-
87-
}: {
88-
89-
- uid: children
90-
name: children
91-
fullName: children
92-
- name: |-
93-
: string;
94-
}) => boolean
95-
fullName: |-
96-
: string;
97-
}) => boolean
9871
- uid: 'api-documenter-test!IDocInterface4#generic~0:complex'
9972
name: Generic<number>
10073
fullName: Generic<number>

0 commit comments

Comments
 (0)