@@ -60,32 +60,32 @@ import { CustomMarkdownEmitter} from '../markdown/CustomMarkdownEmitter';
6060
6161const 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 */
6671export 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}
0 commit comments