@@ -200,6 +200,165 @@ export class RubyGenerator extends Generator {
200200 return ! ! ( type ?. isInterfaceType ( ) && type ?. isDataType ( ) ) ;
201201 }
202202
203+ /**
204+ * Extract the raw `spec.Docs` from either shape we hold: jsii-reflect
205+ * objects wrap it under `.spec.docs`; plain spec objects (enum members,
206+ * initializer parameters) carry `.docs` directly.
207+ */
208+ private rawDocs ( obj : any ) : spec . Docs | undefined {
209+ return obj ?. spec ?. docs ?? obj ?. docs ;
210+ }
211+
212+ /**
213+ * Render a jsii type reference as a YARD type string for `@param` /
214+ * `@return` tags.
215+ */
216+ private rubyDocType ( ref : spec . TypeReference | undefined ) : string {
217+ if ( ! ref ) {
218+ return 'Object' ;
219+ }
220+ if ( spec . isPrimitiveTypeReference ( ref ) ) {
221+ switch ( ref . primitive ) {
222+ case spec . PrimitiveType . String :
223+ return 'String' ;
224+ case spec . PrimitiveType . Number :
225+ return 'Numeric' ;
226+ case spec . PrimitiveType . Boolean :
227+ return 'Boolean' ;
228+ case spec . PrimitiveType . Date :
229+ return 'DateTime' ;
230+ case spec . PrimitiveType . Json :
231+ return 'Hash' ;
232+ default :
233+ return 'Object' ;
234+ }
235+ }
236+ if ( spec . isNamedTypeReference ( ref ) ) {
237+ return this . rubyFullTypeName ( ref . fqn ) ;
238+ }
239+ if ( spec . isCollectionTypeReference ( ref ) ) {
240+ const elem = this . rubyDocType ( ref . collection . elementtype ) ;
241+ return ref . collection . kind === spec . CollectionKind . Array
242+ ? `Array<${ elem } >`
243+ : `Hash{String => ${ elem } }` ;
244+ }
245+ if ( spec . isUnionTypeReference ( ref ) ) {
246+ return ref . union . types . map ( ( t ) => this . rubyDocType ( t ) ) . join ( ', ' ) ;
247+ }
248+ return 'Object' ;
249+ }
250+
251+ /**
252+ * Emit a block of text as `#`-prefixed comment lines.
253+ */
254+ private emitDocLines ( text : string ) : void {
255+ for ( const line of text . split ( '\n' ) ) {
256+ const trimmed = line . trimEnd ( ) ;
257+ this . code . line ( trimmed === '' ? '#' : `# ${ trimmed } ` ) ;
258+ }
259+ }
260+
261+ /**
262+ * Collapse text for interpolation into a single-line YARD tag. Doc text
263+ * (e.g. `docs.returns`) may contain newlines, which would leak subsequent
264+ * lines out of the comment and into generated code.
265+ */
266+ private inlineDoc ( text : string ) : string {
267+ return text
268+ . split ( '\n' )
269+ . map ( ( l ) => l . trim ( ) )
270+ . filter ( ( l ) => l !== '' )
271+ . join ( ' ' ) ;
272+ }
273+
274+ /**
275+ * Emit a YARD documentation comment from jsii docs: summary and remarks
276+ * as free text, followed by `@param` / `@return` / `@deprecated` /
277+ * `@see` / `@example` tags as applicable. Silently emits nothing when
278+ * there are no docs and no tags to write.
279+ */
280+ private emitDocs (
281+ docsSource : any ,
282+ opts : {
283+ /** Parameters (raw spec or reflect) to render as @param tags. */
284+ params ?: any [ ] ;
285+ /** The method's raw `returns` OptionalValue; pass with isMethod. */
286+ returns ?: any ;
287+ /** Emit `@return [void]` when a method declares no return type. */
288+ isMethod ?: boolean ;
289+ /** Property getter: emit an @return of the property's type. */
290+ propertyType ?: any ;
291+ propertyOptional ?: boolean ;
292+ } = { } ,
293+ ) : void {
294+ const docs : spec . Docs = this . rawDocs ( docsSource ) ?? { } ;
295+ const tags : string [ ] = [ ] ;
296+
297+ for ( const p of opts . params ?? [ ] ) {
298+ const pDocs : spec . Docs = this . rawDocs ( p ) ?? { } ;
299+ const baseType = this . rubyDocType ( this . typeRefSpec ( p . type ) ) ;
300+ const rendered = p . variadic
301+ ? `Array<${ baseType } >`
302+ : `${ baseType } ${ p . optional ? ', nil' : '' } ` ;
303+ const summary = pDocs . summary ? ` ${ this . inlineDoc ( pDocs . summary ) } ` : '' ;
304+ tags . push ( `# @param ${ this . rubyName ( p . name ) } [${ rendered } ]${ summary } ` ) ;
305+ }
306+
307+ if ( opts . returns ?. type ) {
308+ const t = this . rubyDocType ( this . typeRefSpec ( opts . returns . type ) ) ;
309+ const optional = opts . returns . optional ? ', nil' : '' ;
310+ const text = docs . returns ? ` ${ this . inlineDoc ( docs . returns ) } ` : '' ;
311+ tags . push ( `# @return [${ t } ${ optional } ]${ text } ` ) ;
312+ } else if ( opts . isMethod ) {
313+ tags . push ( '# @return [void]' ) ;
314+ } else if ( opts . propertyType ) {
315+ const t = this . rubyDocType ( this . typeRefSpec ( opts . propertyType ) ) ;
316+ tags . push ( `# @return [${ t } ${ opts . propertyOptional ? ', nil' : '' } ]` ) ;
317+ }
318+
319+ if ( docs . default !== undefined ) {
320+ tags . push ( `# @note Default: ${ this . inlineDoc ( docs . default ) } ` ) ;
321+ }
322+ if ( docs . deprecated !== undefined ) {
323+ const reason =
324+ typeof docs . deprecated === 'string'
325+ ? ` ${ this . inlineDoc ( docs . deprecated ) } `
326+ : '' ;
327+ tags . push ( `# @deprecated${ reason } ` ) ;
328+ }
329+ if ( docs . see ) {
330+ tags . push ( `# @see ${ this . inlineDoc ( docs . see ) } ` ) ;
331+ }
332+
333+ const exampleLines = docs . example
334+ ? [ '# @example' , ...docs . example . split ( '\n' ) . map ( ( l ) => `# ${ l . trimEnd ( ) } ` . trimEnd ( ) ) ]
335+ : [ ] ;
336+
337+ const hasText = ! ! ( docs . summary || docs . remarks ) ;
338+ if ( ! hasText && tags . length === 0 && exampleLines . length === 0 ) {
339+ return ;
340+ }
341+
342+ if ( docs . summary ) {
343+ this . emitDocLines ( docs . summary ) ;
344+ }
345+ if ( docs . remarks ) {
346+ this . code . line ( '#' ) ;
347+ this . emitDocLines ( docs . remarks ) ;
348+ }
349+ if ( tags . length > 0 || exampleLines . length > 0 ) {
350+ if ( hasText ) {
351+ this . code . line ( '#' ) ;
352+ }
353+ for ( const tag of tags ) {
354+ this . code . line ( tag ) ;
355+ }
356+ for ( const line of exampleLines ) {
357+ this . code . line ( line ) ;
358+ }
359+ }
360+ }
361+
203362 public async save ( outdir : string , tarball : string , legalese : Legalese ) {
204363 const assembly = this . reflectAssembly ;
205364
@@ -366,8 +525,10 @@ export class RubyGenerator extends Generator {
366525 ( m : any ) => this . rubyConstName ( m . name ) ,
367526 typeSpec . fqn ,
368527 ) ;
528+ this . emitDocs ( typeSpec ) ;
369529 this . code . open ( `module ${ prefix } ${ this . rubyModuleName ( typeSpec . name ) } ` ) ;
370530 for ( const member of resolvedMembers ) {
531+ this . emitDocs ( member ) ;
371532 this . code . line (
372533 `${ this . rubyConstName ( member . name ) } = Jsii::Enum.new("${ rubyDq ( typeSpec . fqn ) } ", "${ rubyDq ( member . name ) } ")` ,
373534 ) ;
@@ -410,6 +571,7 @@ export class RubyGenerator extends Generator {
410571 ? ' < Jsii::Struct'
411572 : '' ;
412573
574+ this . emitDocs ( typeSpec ) ;
413575 this . code . open ( `${ kind } ${ prefix } ${ rubyName } ${ baseString } ` ) ;
414576
415577 if ( ! typeSpec . datatype ) {
@@ -438,6 +600,9 @@ export class RubyGenerator extends Generator {
438600 } )
439601 . join ( ', ' ) ;
440602
603+ // Struct members double as constructor keyword arguments — document
604+ // them as @params (each carries its own summary/type/optionality).
605+ this . emitDocs ( undefined , { params : props } ) ;
441606 this . code . open ( `def initialize(${ initArgs } )` ) ;
442607 for ( const prop of props ) {
443608 const rubyName = this . rubyName ( prop . name ) ;
@@ -455,6 +620,10 @@ export class RubyGenerator extends Generator {
455620 this . code . line ( '' ) ;
456621
457622 for ( const prop of props ) {
623+ this . emitDocs ( prop , {
624+ propertyType : prop . type ,
625+ propertyOptional : prop . optional ,
626+ } ) ;
458627 this . code . line ( `attr_reader :${ this . rubyName ( prop . name ) } ` ) ;
459628 }
460629 this . code . line ( '' ) ;
@@ -487,6 +656,10 @@ export class RubyGenerator extends Generator {
487656 } else {
488657 for ( const prop of resolvedAllProperties ) {
489658 const propRubyName = this . rubyName ( prop . name ) ;
659+ this . emitDocs ( prop , {
660+ propertyType : prop . type ,
661+ propertyOptional : prop . optional ,
662+ } ) ;
490663 this . code . open ( `def ${ propRubyName } ()` ) ;
491664 this . code . line ( `jsii_get_property("${ rubyDq ( prop . name ) } ")` ) ;
492665 this . code . close ( `end` ) ;
@@ -518,6 +691,11 @@ export class RubyGenerator extends Generator {
518691 return rubyParam ;
519692 } )
520693 . join ( ', ' ) ;
694+ this . emitDocs ( method , {
695+ params : method . parameters ,
696+ returns : method . spec ?. returns ,
697+ isMethod : true ,
698+ } ) ;
521699 this . code . open ( `def ${ this . rubyName ( method . name ) } (${ sigParams } )` ) ;
522700 for ( const p of method . parameters ) {
523701 const rubyParam = this . rubyName ( p . name ) ;
@@ -593,6 +771,7 @@ export class RubyGenerator extends Generator {
593771 ( i : any ) => `::${ this . rubyFullTypeName ( i ) } ` ,
594772 ) ;
595773
774+ this . emitDocs ( typeSpec ) ;
596775 this . code . open ( `class ${ prefix } ${ rubyName } < ${ baseClass } ` ) ;
597776
598777 for ( const mixin of interfaceMixins ) {
@@ -618,6 +797,7 @@ export class RubyGenerator extends Generator {
618797 } )
619798 . join ( ', ' ) ;
620799
800+ this . emitDocs ( initializer , { params : initializer . parameters } ) ;
621801 this . code . open ( `def initialize(${ initParams } )` ) ;
622802 for ( const p of initializer . parameters ) {
623803 const rubyParam = this . rubyName ( p . name ) ;
@@ -721,6 +901,11 @@ export class RubyGenerator extends Generator {
721901 } )
722902 . join ( ', ' ) ;
723903
904+ this . emitDocs ( method , {
905+ params : method . parameters ,
906+ returns : method . spec ?. returns ,
907+ isMethod : true ,
908+ } ) ;
724909 this . code . open ( `def self.${ this . rubyMethodName ( method ) } (${ sigParams } )` ) ;
725910 for ( const p of method . parameters ) {
726911 const rubyParam = this . rubyName ( p . name ) ;
@@ -745,6 +930,10 @@ export class RubyGenerator extends Generator {
745930 const rubyName = this . rubyPropertyName ( prop ) ;
746931
747932 if ( prop . static ) {
933+ this . emitDocs ( prop , {
934+ propertyType : prop . type ,
935+ propertyOptional : prop . optional ,
936+ } ) ;
748937 this . code . open ( `def self.${ rubyName } ()` ) ;
749938 this . code . line (
750939 `Jsii::Kernel.instance.get_static("${ rubyDq ( typeSpec . fqn ) } ", "${ rubyDq ( prop . name ) } ")` ,
@@ -765,6 +954,10 @@ export class RubyGenerator extends Generator {
765954 this . code . line ( '' ) ;
766955 }
767956 } else {
957+ this . emitDocs ( prop , {
958+ propertyType : prop . type ,
959+ propertyOptional : prop . optional ,
960+ } ) ;
768961 this . code . open ( `def ${ rubyName } ()` ) ;
769962 this . code . line ( `jsii_get_property("${ rubyDq ( prop . name ) } ")` ) ;
770963 this . code . close ( `end` ) ;
@@ -802,6 +995,11 @@ export class RubyGenerator extends Generator {
802995 } )
803996 . join ( ', ' ) ;
804997
998+ this . emitDocs ( method , {
999+ params : method . parameters ,
1000+ returns : method . spec ?. returns ,
1001+ isMethod : true ,
1002+ } ) ;
8051003 this . code . open ( `def ${ this . rubyMethodName ( method ) } (${ sigParams } )` ) ;
8061004 for ( const p of method . parameters ) {
8071005 const rubyParam = this . rubyName ( p . name ) ;
0 commit comments