1
1
import {
2
- Node , Project , ScriptTarget , SyntaxKind , TypeFormatFlags ,
2
+ Node , Project , ScriptTarget , SyntaxKind , TypeFormatFlags
3
3
} from "ts-morph" ;
4
4
5
+ import { versionMajorMinor as tsVersionMajorMinor } from "typescript" ;
6
+
5
7
import type {
6
8
CompilerOptions ,
7
9
ClassDeclaration ,
@@ -46,6 +48,20 @@ type ObjectProperty = JSDocableNode & TypedNode & (
46
48
) ;
47
49
type ClassMemberNode = JSDocableNode & ModifierableNode & ObjectProperty & MethodDeclaration ;
48
50
51
+ interface MajorMinorVersion {
52
+ major : number ;
53
+ minor : number ;
54
+ }
55
+
56
+ function parseTsVersion ( majorMinor : string ) : MajorMinorVersion {
57
+ const [ major , minor ] = majorMinor . split ( "." ) . map ( v => parseInt ( v ) ) ;
58
+ return { major, minor } ;
59
+ }
60
+
61
+ function isTsVersionAtLeast ( tsVersion : MajorMinorVersion , major : number , minor : number ) : boolean {
62
+ return tsVersion . major > major || ( tsVersion . major === major && tsVersion . minor >= minor ) ;
63
+ }
64
+
49
65
/** Get children for object node */
50
66
function getChildProperties ( node : Node ) : ObjectProperty [ ] {
51
67
const properties = node ?. getType ( ) ?. getProperties ( ) ;
@@ -102,7 +118,7 @@ function nodeIsOnlyUsedInTypePosition(node: Node & ReferenceFindableNode): boole
102
118
}
103
119
104
120
/** Generate `@typedef` declarations for type imports */
105
- function generateImportDeclarationDocumentation (
121
+ function generateImportDeclarationDocumentationViaTypedef (
106
122
importDeclaration : ImportDeclaration ,
107
123
) : string {
108
124
let typedefs = "" ;
@@ -131,6 +147,49 @@ function generateImportDeclarationDocumentation(
131
147
return typedefs ;
132
148
}
133
149
150
+ /** Generate `@import` JSDoc declarations for type imports */
151
+ function generateImportDeclarationDocumentationViaImportTag (
152
+ importDeclaration : ImportDeclaration ,
153
+ ) : string {
154
+ const moduleSpecifier = importDeclaration . getModuleSpecifierValue ( ) ;
155
+ const declarationIsTypeOnly = importDeclaration . isTypeOnly ( ) ;
156
+
157
+ const imports : { default : string | undefined , named : string [ ] } = {
158
+ default : undefined ,
159
+ named : [ ] ,
160
+ } ;
161
+
162
+ const defaultImport = importDeclaration . getDefaultImport ( ) ;
163
+ const defaultImportName = defaultImport ?. getText ( ) ;
164
+ if ( defaultImport ) {
165
+ if ( declarationIsTypeOnly || nodeIsOnlyUsedInTypePosition ( defaultImport ) ) {
166
+ imports . default = defaultImportName ;
167
+ }
168
+ }
169
+
170
+ for ( const namedImport of importDeclaration . getNamedImports ( ) ?? [ ] ) {
171
+ const name = namedImport . getName ( ) ;
172
+ const aliasNode = namedImport . getAliasNode ( ) ;
173
+ const alias = aliasNode ?. getText ( ) ;
174
+ if ( declarationIsTypeOnly || namedImport . isTypeOnly ( ) || nodeIsOnlyUsedInTypePosition ( aliasNode || namedImport . getNameNode ( ) ) ) {
175
+ if ( alias !== undefined ) {
176
+ imports . named . push ( `${ name } as ${ alias } ` ) ;
177
+ } else {
178
+ imports . named . push ( name ) ;
179
+ }
180
+ }
181
+ }
182
+
183
+ const importParts : string [ ] = [ ] ;
184
+ if ( imports . default !== undefined ) {
185
+ importParts . push ( imports . default ) ;
186
+ }
187
+ if ( imports . named . length > 0 ) {
188
+ importParts . push ( `{ ${ imports . named . join ( ", " ) } }` ) ;
189
+ }
190
+ return importParts . length > 0 ? `/** @import ${ importParts . join ( ", " ) } from '${ moduleSpecifier } ' */` : "" ;
191
+ }
192
+
134
193
/**
135
194
* Generate `@param` documentation from function parameters for functionNode, storing it in docNode
136
195
*/
@@ -564,13 +623,17 @@ function generateNamespaceDocumentation(namespace: ModuleDeclaration, prefix = "
564
623
* Generate documentation for a source file
565
624
* @param sourceFile The source file to generate documentation for
566
625
*/
567
- function generateDocumentationForSourceFile ( sourceFile : SourceFile ) : void {
626
+ function generateDocumentationForSourceFile ( sourceFile : SourceFile , tsVersion : MajorMinorVersion ) : void {
568
627
sourceFile . getClasses ( ) . forEach ( generateClassDocumentation ) ;
569
628
570
629
const namespaceAdditions = sourceFile . getModules ( )
571
630
. map ( ( namespace ) => generateNamespaceDocumentation ( namespace ) )
572
631
. flat ( 2 ) ;
573
632
633
+ const generateImportDeclarationDocumentation = isTsVersionAtLeast ( tsVersion , 5 , 5 )
634
+ ? generateImportDeclarationDocumentationViaImportTag
635
+ : generateImportDeclarationDocumentationViaTypedef ;
636
+
574
637
const importDeclarations = sourceFile . getImportDeclarations ( )
575
638
. map ( ( declaration ) => generateImportDeclarationDocumentation ( declaration ) . trim ( ) )
576
639
. join ( "\n" )
@@ -641,8 +704,9 @@ export function transpileProject(tsconfig: string, debug = false): void {
641
704
tsConfigFilePath : tsconfig ,
642
705
} ) ;
643
706
707
+ const tsVersion = parseTsVersion ( tsVersionMajorMinor ) ;
644
708
const sourceFiles = project . getSourceFiles ( ) ;
645
- sourceFiles . forEach ( ( sourceFile ) => generateDocumentationForSourceFile ( sourceFile ) ) ;
709
+ sourceFiles . forEach ( ( sourceFile ) => generateDocumentationForSourceFile ( sourceFile , tsVersion ) ) ;
646
710
647
711
const preEmitDiagnostics = project . getPreEmitDiagnostics ( ) ;
648
712
if ( preEmitDiagnostics . length && project . getCompilerOptions ( ) . noEmitOnError ) {
@@ -674,6 +738,9 @@ export function transpileProject(tsconfig: string, debug = false): void {
674
738
* See https://www.typescriptlang.org/tsconfig#compilerOptions
675
739
* @param [inMemory=false] Whether to store the file in memory while transpiling
676
740
* @param [debug=false] Whether to log errors
741
+ * @param [tsVersion=<current>] Major and minor version of TypeScript, used to check for
742
+ * certain features such as whether to `@import` or `@typedef` JSDoc tags for imports.
743
+ * Defaults to the current TypeScript version.
677
744
* @returns Transpiled code (or the original source code if something went wrong)
678
745
*/
679
746
export function transpileFile (
@@ -683,15 +750,19 @@ export function transpileFile(
683
750
compilerOptions = { } ,
684
751
inMemory = false ,
685
752
debug = false ,
753
+ tsVersion = tsVersionMajorMinor ,
686
754
} : {
687
755
code : string ;
688
756
filename ?: string ;
689
757
compilerOptions ?: CompilerOptions ;
690
758
inMemory ?: boolean ;
691
759
debug ?: boolean ;
760
+ tsVersion ?: string ;
692
761
} ,
693
762
) : string {
694
763
try {
764
+ const parsedTsVersion = parseTsVersion ( tsVersion ) ;
765
+
695
766
const project = new Project ( {
696
767
defaultCompilerOptions : {
697
768
target : ScriptTarget . ESNext ,
@@ -714,7 +785,7 @@ export function transpileFile(
714
785
sourceFile = project . createSourceFile ( sourceFilename , code ) ;
715
786
}
716
787
717
- generateDocumentationForSourceFile ( sourceFile ) ;
788
+ generateDocumentationForSourceFile ( sourceFile , parsedTsVersion ) ;
718
789
719
790
const preEmitDiagnostics = project . getPreEmitDiagnostics ( ) ;
720
791
if ( preEmitDiagnostics . length && project . getCompilerOptions ( ) . noEmitOnError ) {
0 commit comments