@@ -3,6 +3,24 @@ import { tsquery } from '@phenomnomnominal/tsquery';
33
44import { NgParselFieldDecorator } from '../model/decorator.model.js' ;
55
6+ function extractJSDocComment ( node : ts . Node | undefined ) : string | undefined {
7+ if ( ! node ) return undefined ;
8+
9+ const jsDocs = ( node as any ) . jsDoc as ts . JSDoc [ ] | undefined ;
10+ if ( ! jsDocs || jsDocs . length === 0 ) return undefined ;
11+
12+ const jsDoc = jsDocs [ 0 ] ;
13+ let result = ( jsDoc ?. comment as string ) ?? '' ;
14+
15+ if ( jsDoc ?. tags ) {
16+ for ( const tag of jsDoc . tags ) {
17+ result += `\n@${ tag . tagName . getText ( ) } ${ tag . comment ?? '' } ` ;
18+ }
19+ }
20+
21+ return result . trim ( ) ;
22+ }
23+
624export function parseInputsAndOutputs ( ast : ts . SourceFile ) : {
725 inputs : NgParselFieldDecorator [ ] ;
826 outputs : NgParselFieldDecorator [ ] ;
@@ -30,11 +48,14 @@ function parseDecoratedSetters(ast: ts.SourceFile): NgParselFieldDecorator[] {
3048 const name = ( decoratedSetters [ i ] as any ) ?. name ?. getText ( ) ;
3149 const type = ( decoratedSetters [ i ] as any ) ?. parameters [ 0 ] ?. type ?. getText ( ) ;
3250
51+ const jsDoc = extractJSDocComment ( decoratedSetters [ i ] ) ;
52+
3353 inputSetters . push ( {
3454 decorator : '@Input()' ,
3555 name,
3656 type,
3757 field : decoratedSetters [ i ] ?. getText ( ) || '' ,
58+ jsDoc,
3859 } ) ;
3960 }
4061
@@ -46,10 +67,10 @@ function parseDecoratedPropertyDeclarations(ast: ts.SourceFile): {
4667 outputs : NgParselFieldDecorator [ ] ;
4768} {
4869 /*
49- This is afaik the only way to get the Decorator name
50- - getDecorators() returns nothing
51- - canHaveDecorators() returns false
52- */
70+ This is afaik the only way to get the Decorator name
71+ - getDecorators() returns nothing
72+ - canHaveDecorators() returns false
73+ */
5374 const decoratorPropertyDecorator = [ ...tsquery ( ast , 'PropertyDeclaration:has(Decorator) > Decorator' ) ] ;
5475 const decoratorPropertyDeclaration = [ ...tsquery ( ast , 'PropertyDeclaration:has(Decorator)' ) ] ;
5576
@@ -65,12 +86,15 @@ function parseDecoratedPropertyDeclarations(ast: ts.SourceFile): {
6586 const initializer = ( decoratorPropertyDeclaration [ i ] as any ) ?. initializer ?. getText ( ) ;
6687 const field = `${ decorator } ${ name } ${ type ? ': ' + type : ' = ' + initializer } ` ;
6788
89+ const jsDoc = extractJSDocComment ( decoratorPropertyDeclaration [ i ] ! ) ;
90+
6891 const componentDecorator = {
6992 decorator : decorator as string ,
7093 name,
7194 type,
7295 initializer,
7396 field,
97+ jsDoc,
7498 } ;
7599
76100 if ( decorator ?. startsWith ( '@Inp' ) ) {
@@ -129,13 +153,16 @@ function parseSignalInputsAndModels(ast: ts.SourceFile): NgParselFieldDecorator[
129153 ) ,
130154 ] [ 0 ] ?. getText ( ) || 'inferred' ;
131155
156+ const jsDoc = extractJSDocComment ( input ) ;
157+
132158 if ( required ) {
133159 signalInputs . push ( {
134160 decorator,
135161 required,
136162 name : alias || name ,
137163 type,
138164 field,
165+ jsDoc,
139166 } ) ;
140167 } else {
141168 signalInputs . push ( {
@@ -145,6 +172,7 @@ function parseSignalInputsAndModels(ast: ts.SourceFile): NgParselFieldDecorator[
145172 name : alias || name ,
146173 type,
147174 field,
175+ jsDoc,
148176 } ) ;
149177 }
150178 } ) ;
@@ -153,6 +181,29 @@ function parseSignalInputsAndModels(ast: ts.SourceFile): NgParselFieldDecorator[
153181}
154182
155183function parseNewOutputAPI ( ast : ts . SourceFile ) : NgParselFieldDecorator [ ] {
184+ // First, try to find JSDoc comments directly in the source file
185+ const fullText = ast . getFullText ( ) ;
186+ const jsDocRegex = / \/ \* \* ( [ \s \S ] * ?) \* \/ / g;
187+ const jsDocMatches : { [ key : string ] : string } = { } ;
188+
189+ let match ;
190+ while ( ( match = jsDocRegex . exec ( fullText ) ) !== null ) {
191+ // Find the next non-whitespace character after the JSDoc comment
192+ let nextNonWhitespace = match . index + match [ 0 ] . length ;
193+ while ( nextNonWhitespace < fullText . length && / \s / . test ( fullText [ nextNonWhitespace ] ! ) ) {
194+ nextNonWhitespace ++ ;
195+ }
196+
197+ // Extract the field name if it's followed by an output declaration
198+ if ( nextNonWhitespace < fullText . length ) {
199+ const remainingText = fullText . substring ( nextNonWhitespace ) ;
200+ const outputMatch = / ( \w + ) \s * = \s * o u t p u t / . exec ( remainingText ) ;
201+ if ( outputMatch ) {
202+ jsDocMatches [ outputMatch [ 1 ] ! ] = match [ 0 ] ;
203+ }
204+ }
205+ }
206+
156207 const outputNodes = [ ...tsquery ( ast , 'PropertyDeclaration:has(CallExpression:has([name="output"]))' ) ] ;
157208 const outPutnodesFromObservable = [
158209 ...tsquery ( ast , 'PropertyDeclaration:has(CallExpression:has([name="outputFromObservable"]))' ) ,
@@ -167,18 +218,26 @@ function parseNewOutputAPI(ast: ts.SourceFile): NgParselFieldDecorator[] {
167218 const name = [ ...tsquery ( field , 'BinaryExpression > Identifier' ) ] [ 0 ] ?. getText ( ) || '' ;
168219 const type = [ ...tsquery ( field , 'CallExpression > *:last-child' ) ] [ 0 ] ?. getText ( ) || '' ;
169220
221+ // Try to get JSDoc from our map first, then fall back to extractJSDocComment
222+ let jsDoc = jsDocMatches [ name ] ;
223+ if ( ! jsDoc ) {
224+ jsDoc = extractJSDocComment ( node ) ;
225+ }
226+
170227 if ( isObservableOutput ) {
171228 outputs . push ( {
172229 decorator : 'outputFromObservable' ,
173230 name,
174231 field,
232+ jsDoc,
175233 } ) ;
176234 } else {
177235 outputs . push ( {
178236 decorator : 'output' ,
179237 name,
180238 type,
181239 field,
240+ jsDoc,
182241 } ) ;
183242 }
184243 } ) ;
0 commit comments