@@ -575,7 +575,7 @@ export function hoistRequestAnnotations (
575
575
request : model . Request , jsDocs : JSDoc [ ] , mappings : Record < string , model . Endpoint > , response : model . TypeName | null
576
576
) : void {
577
577
const knownRequestAnnotations = [
578
- 'since' , ' rest_spec_name', 'stability' , 'visibility' , ' behavior', 'class_serializer' , 'index_privileges' , 'cluster_privileges' , 'doc_id'
578
+ 'rest_spec_name' , 'behavior' , 'class_serializer' , 'index_privileges' , 'cluster_privileges' , 'doc_id' , 'availability '
579
579
]
580
580
// in most of the cases the jsDocs comes in a single block,
581
581
// but it can happen that the user defines multiple single line jsDoc.
@@ -602,19 +602,6 @@ export function hoistRequestAnnotations (
602
602
setTags ( jsDocs , request , tags , knownRequestAnnotations , ( tags , tag , value ) => {
603
603
if ( tag . endsWith ( '_serializer' ) ) {
604
604
} else if ( tag === 'rest_spec_name' ) {
605
- } else if ( tag === 'visibility' ) {
606
- if ( endpoint . visibility !== null && endpoint . visibility !== undefined ) {
607
- assert ( jsDocs , endpoint . visibility === value ,
608
- `Request ${ request . name . name } visibility on annotation ${ value } does not match spec: ${ endpoint . visibility ?? '' } ` )
609
- }
610
- endpoint . visibility = model . Visibility [ value ]
611
- } else if ( tag === 'stability' ) {
612
- assert ( jsDocs , endpoint . stability === value ,
613
- `Request ${ request . name . name } stability on annotation ${ value } does not match spec: ${ endpoint . stability ?? '' } ` )
614
- endpoint . stability = model . Stability [ value ]
615
- } else if ( tag === 'since' ) {
616
- assert ( jsDocs , semver . valid ( value ) , `Request ${ request . name . name } 's @since is not valid semver: ${ value } ` )
617
- endpoint . since = value
618
605
} else if ( tag === 'index_privileges' ) {
619
606
const privileges = [
620
607
'all' , 'auto_configure' , 'create' , 'create_doc' , 'create_index' , 'delete' , 'delete_index' , 'index' ,
@@ -648,6 +635,33 @@ export function hoistRequestAnnotations (
648
635
const docUrl = docIds . find ( entry => entry [ 0 ] === value . trim ( ) )
649
636
assert ( jsDocs , docUrl != null , `The @doc_id '${ value . trim ( ) } ' is not present in _doc_ids/table.csv` )
650
637
endpoint . docUrl = docUrl [ 1 ]
638
+ } else if ( tag === 'availability' ) {
639
+ // The @availability jsTag is different than most because it allows
640
+ // multiple values within the same docstring, hence needing to parse
641
+ // the values again in order to preserve multiple values.
642
+ const jsDocsMulti = parseJsDocTagsAllowDuplicates ( jsDocs )
643
+ const availabilities = parseAvailabilityTags ( jsDocs , jsDocsMulti . availability )
644
+
645
+ // Apply the availabilities to the Endpoint.
646
+ for ( const [ availabilityName , availabilityValue ] of Object . entries ( availabilities ) ) {
647
+ endpoint . availability [ availabilityName ] = availabilityValue
648
+
649
+ // Backfilling deprecated fields on an endpoint.
650
+ if ( availabilityName === 'stack' ) {
651
+ if ( availabilityValue . since !== undefined ) {
652
+ endpoint . since = availabilityValue . since
653
+ }
654
+ if ( availabilityValue . stability !== undefined ) {
655
+ endpoint . stability = availabilityValue . stability
656
+ }
657
+ if ( availabilityValue . visibility !== undefined ) {
658
+ endpoint . visibility = availabilityValue . visibility
659
+ }
660
+ if ( availabilityValue . featureFlag !== undefined ) {
661
+ endpoint . featureFlag = availabilityValue . featureFlag
662
+ }
663
+ }
664
+ }
651
665
} else {
652
666
assert ( jsDocs , false , `Unhandled tag: '${ tag } ' with value: '${ value } ' on request ${ request . name . name } ` )
653
667
}
@@ -899,7 +913,7 @@ export function getNameSpace (node: Node): string {
899
913
900
914
function cleanPath ( path : string ) : string {
901
915
path = dirname ( path )
902
- . replace ( / .* [ / \\ ] s p e c i f i c a t i o n [ / \\ ] ? / , '' )
916
+ . replace ( / .* [ / \\ ] s p e c i f i c a t i o n [ ^ / \\ ] * [ / \\ ] ? / , '' )
903
917
. replace ( / [ / \\ ] / g, '.' )
904
918
if ( path === '' ) path = '_builtins'
905
919
return path
@@ -966,6 +980,25 @@ export function parseJsDocTags (jsDoc: JSDoc[]): Record<string, string> {
966
980
return mapped
967
981
}
968
982
983
+ /**
984
+ * Given a JSDoc definition, return a mapping from a tag name to all its values.
985
+ * This function is similar to the above parseJsDocTags() function except
986
+ * it allows for multiple annotations with the same name.
987
+ */
988
+ export function parseJsDocTagsAllowDuplicates ( jsDoc : JSDoc [ ] ) : Record < string , string [ ] > {
989
+ const mapped = { }
990
+ jsDoc . forEach ( ( elem : JSDoc ) => {
991
+ elem . getTags ( ) . forEach ( ( tag ) => {
992
+ const tagName = tag . getTagName ( )
993
+ if ( mapped [ tagName ] === undefined ) {
994
+ mapped [ tagName ] = [ ]
995
+ }
996
+ mapped [ tagName ] . push ( tag . getComment ( ) ?? '' )
997
+ } )
998
+ } )
999
+ return mapped
1000
+ }
1001
+
969
1002
/**
970
1003
* Given a JSDoc definition, it returns the Variants is present.
971
1004
* It also validates the variants syntax.
@@ -1023,6 +1056,60 @@ export function parseVariantNameTag (jsDoc: JSDoc[]): string | undefined {
1023
1056
return name . replace ( / ' / g, '' )
1024
1057
}
1025
1058
1059
+ /**
1060
+ * Parses the '@availability' JS tags into known values.
1061
+ */
1062
+ export function parseAvailabilityTags ( node : Node | Node [ ] , values : string [ ] ) : model . Availability {
1063
+ return values . reduce ( ( result , value , index , array ) => {
1064
+ // Ensure that there is actually a name for this availability definition.
1065
+ assert ( node , value . split ( ' ' ) . length >= 1 , 'The @availability tag must include a name (either stack or serverless)' )
1066
+ const [ availabilityName , ...values ] = value . split ( ' ' )
1067
+
1068
+ // Since we're using reduce() we need to check that there's no duplicates.
1069
+ assert ( node , ! ( availabilityName in result ) , `Duplicate @availability tag: '${ availabilityName } '` )
1070
+
1071
+ // Enforce only known availability names.
1072
+ assert ( node , availabilityName === 'stack' || availabilityName === 'serverless' , 'The @availablility <name> value must either be stack or serverless' )
1073
+
1074
+ // Now we can parse all the key-values and load them into variables
1075
+ // for easier access below.
1076
+ const validKeys = [ 'stability' , 'visibility' , 'since' , 'feature_flag' ]
1077
+ const parsedKeyValues = parseKeyValues ( node , values , ...validKeys )
1078
+ const visibility = parsedKeyValues . visibility
1079
+ const stability = parsedKeyValues . stability
1080
+ const since = parsedKeyValues . since
1081
+ const featureFlag = parsedKeyValues . feature_flag
1082
+
1083
+ // Remove the 'feature_flag' name used in the annotations
1084
+ // in favor of 'featureFlag' as used in the metamodel.
1085
+ delete parsedKeyValues . feature_flag
1086
+
1087
+ // Lastly we go through all the fields and validate them.
1088
+ if ( visibility !== undefined ) {
1089
+ parsedKeyValues . visibility = model . Visibility [ visibility ]
1090
+ assert ( node , parsedKeyValues . visibility !== undefined , `visibility is not valid: ${ visibility } ` )
1091
+ if ( visibility === model . Visibility . feature_flag ) {
1092
+ assert ( node , featureFlag !== undefined , '\'feature_flag\' must be defined if visibility is \'feature_flag\'' )
1093
+ }
1094
+ }
1095
+ if ( stability !== undefined ) {
1096
+ parsedKeyValues . stability = model . Stability [ stability ]
1097
+ assert ( node , parsedKeyValues . stability !== undefined , `stability is not valid: ${ stability } ` )
1098
+ }
1099
+ if ( since !== undefined ) {
1100
+ assert ( node , semver . valid ( since ) , `'since' is not valid semver: ${ since } ` )
1101
+ }
1102
+ if ( featureFlag !== undefined ) {
1103
+ assert ( node , visibility === 'feature_flag' , '\'visibility\' must be \'feature_flag\' if a feature flag is defined' )
1104
+ parsedKeyValues . featureFlag = featureFlag
1105
+ }
1106
+
1107
+ // Add the computed set of fields to the result.
1108
+ result [ availabilityName ] = parsedKeyValues
1109
+ return result
1110
+ } , { } )
1111
+ }
1112
+
1026
1113
/**
1027
1114
* Parses a list of comma-separated values as an array. Values can optionally be enclosed with single
1028
1115
* or double quotes.
0 commit comments