1- /* eslint-disable n/prefer-global/process, unicorn/no-process-exit */
2- import { readFileSync , writeFileSync } from 'node:fs' ;
1+ /* eslint-disable n/prefer-global/process, unicorn/no-process-exit, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument */
2+ import { readFileSync } from 'node:fs' ;
33import { execSync } from 'node:child_process' ;
4+ import { Project } from 'ts-morph' ;
45// Import index.ts to populate the test data via side effect
56// eslint-disable-next-line import-x/no-unassigned-import
67import './index.ts' ;
@@ -17,16 +18,21 @@ if (dtsContent.includes(marker)) {
1718 process . exit ( 1 ) ;
1819}
1920
20- // Process each exported function
21- const lines = dtsContent . split ( '\n' ) ;
22- const outputLines : string [ ] = [ ] ;
21+ // Create a ts-morph project and load the file
22+ const project = new Project ( ) ;
23+ const sourceFile = project . createSourceFile ( 'temp.d.ts' , dtsContent , { overwrite : true } ) ;
24+
2325let examplesAdded = 0 ;
2426
25- for ( const line of lines ) {
26- // Check if this is a function declaration
27- const match = / ^ e x p o r t d e c l a r e c o n s t ( \w + ) : / . exec ( line ) ;
28- if ( match ) {
29- const functionName = match [ 1 ] ;
27+ // Process each exported variable declaration (these are the function declarations)
28+ for ( const statement of sourceFile . getVariableStatements ( ) ) {
29+ // Only process exported statements
30+ if ( ! statement . isExported ( ) ) {
31+ continue ;
32+ }
33+
34+ for ( const declaration of statement . getDeclarations ( ) ) {
35+ const functionName = declaration . getName ( ) ;
3036
3137 // Get the tests/examples for this function
3238 const examples = getTests ( functionName ) ;
@@ -37,104 +43,115 @@ for (const line of lines) {
3743 const urlExamples = examples . filter ( ( url : string ) => url . startsWith ( 'http' ) ) ;
3844
3945 if ( urlExamples . length > 0 ) {
40- // Check if there's an existing JSDoc block immediately before this line
41- let jsDocumentEndIndex = - 1 ;
42- let jsDocumentStartIndex = - 1 ;
43- let isSingleLineJsDocument = false ;
44-
45- // Look backwards from outputLines to find JSDoc
46- for ( let index = outputLines . length - 1 ; index >= 0 ; index -- ) {
47- const previousLine = outputLines [ index ] ;
48- const trimmed = previousLine . trim ( ) ;
49-
50- if ( trimmed === '' ) {
51- continue ; // Skip empty lines
46+ // Get or create JSDoc for this statement (not the declaration)
47+ const jsDoc = statement . getJsDocs ( ) [ 0 ] ;
48+
49+ if ( jsDoc ) {
50+ // Add @example tags to existing JSDoc
51+ const existingTags = jsDoc . getTags ( ) ;
52+ const description = jsDoc . getDescription ( ) . trim ( ) ;
53+
54+ // Build new JSDoc content
55+ const newJsDocLines : string [ ] = [ ] ;
56+ if ( description ) {
57+ newJsDocLines . push ( description ) ;
5258 }
5359
54- // Check for single-line JSDoc: /** ... */
55- if ( trimmed . startsWith ( '/**' ) && trimmed . endsWith ( '*/' ) && trimmed . length > 5 ) {
56- jsDocumentStartIndex = index ;
57- jsDocumentEndIndex = index ;
58- isSingleLineJsDocument = true ;
59- break ;
60+ // Add existing tags (that aren't @example tags)
61+ for ( const tag of existingTags ) {
62+ if ( tag . getTagName ( ) !== 'example' ) {
63+ newJsDocLines . push ( tag . getText ( ) ) ;
64+ }
6065 }
6166
62- // Check for multi-line JSDoc ending
63- if ( trimmed === '*/' ) {
64- jsDocumentEndIndex = index ;
65- // Now find the start of this JSDoc
66- for ( let k = index - 1 ; k >= 0 ; k -- ) {
67- if ( outputLines [ k ] . trim ( ) . startsWith ( '/**' ) ) {
68- jsDocumentStartIndex = k ;
69- break ;
70- }
71- }
67+ // Add new @example tags
68+ for ( const url of urlExamples ) {
69+ newJsDocLines . push ( `@example ${ url } ` ) ;
70+ }
7271
73- break ;
72+ // Replace the JSDoc
73+ jsDoc . remove ( ) ;
74+ statement . addJsDoc ( newJsDocLines . join ( '\n' ) ) ;
75+ } else {
76+ // Create new JSDoc with examples
77+ const jsDocLines : string [ ] = [ ] ;
78+ for ( const url of urlExamples ) {
79+ jsDocLines . push ( `@example ${ url } ` ) ;
7480 }
7581
76- // If we hit a non-JSDoc line, there's no JSDoc block
77- break ;
82+ statement . addJsDoc ( jsDocLines . join ( '\n' ) ) ;
7883 }
7984
80- if ( jsDocumentStartIndex >= 0 && jsDocumentEndIndex >= 0 ) {
81- // Extend existing JSDoc block
82- if ( isSingleLineJsDocument ) {
83- // Convert single-line to multi-line and add examples
84- const singleLineContent = outputLines [ jsDocumentStartIndex ] ;
85- // Extract the comment text without /** and */
86- const commentText = singleLineContent . trim ( ) . slice ( 3 , - 2 ) . trim ( ) ;
87-
88- // Replace the single line with multi-line format
89- outputLines [ jsDocumentStartIndex ] = '/**' ;
90- if ( commentText ) {
91- outputLines . splice ( jsDocumentStartIndex + 1 , 0 , ` * ${ commentText } ` ) ;
92- }
85+ examplesAdded += urlExamples . length ;
86+ }
87+ }
88+ }
89+ }
9390
94- // Add examples after the existing content
95- const insertIndex = jsDocumentStartIndex + ( commentText ? 2 : 1 ) ;
96- for ( const url of urlExamples ) {
97- outputLines . splice ( insertIndex + urlExamples . indexOf ( url ) , 0 , ` * @example ${ url } ` ) ;
98- }
91+ // Also process exported type aliases (like RepoExplorerInfo)
92+ for ( const typeAlias of sourceFile . getTypeAliases ( ) ) {
93+ if ( ! typeAlias . isExported ( ) ) {
94+ continue ;
95+ }
9996
100- outputLines . splice ( insertIndex + urlExamples . length , 0 , ' */' ) ;
101- examplesAdded += urlExamples . length ;
102- } else {
103- // Insert @example lines before the closing */
104- for ( const url of urlExamples ) {
105- outputLines . splice ( jsDocumentEndIndex , 0 , ` * @example ${ url } ` ) ;
106- }
97+ const typeName = typeAlias . getName ( ) ;
10798
108- examplesAdded += urlExamples . length ;
109- }
110- } else {
111- // Add new JSDoc comment with examples before the declaration
112- outputLines . push ( '/**' ) ;
113- for ( const url of urlExamples ) {
114- outputLines . push ( ` * @example ${ url } ` ) ;
99+ // Get the tests/examples for this type (unlikely but keeping consistency)
100+ const examples = getTests ( typeName ) ;
101+
102+ if ( examples && examples . length > 0 && examples [ 0 ] !== 'combinedTestOnly' ) {
103+ const urlExamples = examples . filter ( ( url : string ) => url . startsWith ( 'http' ) ) ;
104+
105+ if ( urlExamples . length > 0 ) {
106+ const jsDoc = typeAlias . getJsDocs ( ) [ 0 ] ;
107+
108+ if ( jsDoc ) {
109+ const existingTags = jsDoc . getTags ( ) ;
110+ const description = jsDoc . getDescription ( ) . trim ( ) ;
111+
112+ const newJsDocLines : string [ ] = [ ] ;
113+ if ( description ) {
114+ newJsDocLines . push ( description ) ;
115+ }
116+
117+ for ( const tag of existingTags ) {
118+ if ( tag . getTagName ( ) !== 'example' ) {
119+ newJsDocLines . push ( tag . getText ( ) ) ;
115120 }
121+ }
122+
123+ for ( const url of urlExamples ) {
124+ newJsDocLines . push ( `@example ${ url } ` ) ;
125+ }
116126
117- outputLines . push ( ' */' ) ;
118- examplesAdded += urlExamples . length ;
127+ jsDoc . remove ( ) ;
128+ typeAlias . addJsDoc ( newJsDocLines . join ( '\n' ) ) ;
129+ } else {
130+ const jsDocLines : string [ ] = [ ] ;
131+ for ( const url of urlExamples ) {
132+ jsDocLines . push ( `@example ${ url } ` ) ;
119133 }
134+
135+ typeAlias . addJsDoc ( jsDocLines . join ( '\n' ) ) ;
120136 }
137+
138+ examplesAdded += urlExamples . length ;
121139 }
122140 }
123-
124- outputLines . push ( line ) ;
125141}
126142
127- // Add marker at the beginning
128- const finalContent = `${ marker } \n${ outputLines . join ( '\n' ) } ` ;
129-
130143// Validate that we added some examples
131144if ( examplesAdded === 0 ) {
132145 console . error ( '❌ Error: No examples were added. This likely indicates a problem with the script.' ) ;
133146 process . exit ( 1 ) ;
134147}
135148
149+ // Get the modified content and add marker
150+ const modifiedContent = sourceFile . getFullText ( ) ;
151+ const finalContent = `${ marker } \n${ modifiedContent } ` ;
152+
136153// Write the modified content back
137- writeFileSync ( dtsPath , finalContent , 'utf8' ) ;
154+ sourceFile . getProject ( ) . createSourceFile ( dtsPath , finalContent , { overwrite : true } ) . saveSync ( ) ;
138155
139156console . log ( `✓ Added ${ examplesAdded } example URLs to index.d.ts` ) ;
140157
0 commit comments