1
1
import {
2
2
Diagnostics ,
3
3
factory ,
4
- forEach ,
4
+ flatMap ,
5
+ getNewLineOrDefaultFromHost ,
5
6
getSynthesizedDeepClone ,
6
7
getTokenAtPosition ,
7
8
hasJSDocNodes ,
8
9
InterfaceDeclaration ,
9
10
isJSDocTypedefTag ,
10
11
isJSDocTypeLiteral ,
12
+ JSDoc ,
11
13
JSDocPropertyLikeTag ,
14
+ JSDocTag ,
12
15
JSDocTypedefTag ,
13
16
JSDocTypeExpression ,
14
17
JSDocTypeLiteral ,
@@ -29,12 +32,14 @@ registerCodeFix({
29
32
fixIds : [ fixId ] ,
30
33
errorCodes,
31
34
getCodeActions ( context ) {
35
+ const newLineCharacter = getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) ;
32
36
const node = getTokenAtPosition (
33
37
context . sourceFile ,
34
38
context . span . start
35
39
) ;
36
40
if ( ! node ) return ;
37
- const changes = textChanges . ChangeTracker . with ( context , t => doChange ( t , node , context . sourceFile ) ) ;
41
+
42
+ const changes = textChanges . ChangeTracker . with ( context , t => doChange ( t , node , context . sourceFile , newLineCharacter ) ) ;
38
43
39
44
if ( changes . length > 0 ) {
40
45
return [
@@ -48,35 +53,102 @@ registerCodeFix({
48
53
] ;
49
54
}
50
55
} ,
51
- getAllCodeActions : context => codeFixAll ( context , errorCodes , ( changes , diag ) => {
52
- const node = getTokenAtPosition ( diag . file , diag . start ) ;
53
- if ( node ) doChange ( changes , node , diag . file ) ;
54
- } )
56
+ getAllCodeActions : context => codeFixAll (
57
+ context ,
58
+ errorCodes ,
59
+ ( changes , diag ) => {
60
+ const newLineCharacter = getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) ;
61
+ const node = getTokenAtPosition ( diag . file , diag . start ) ;
62
+ const fixAll = true ;
63
+ if ( node ) doChange ( changes , node , diag . file , newLineCharacter , fixAll ) ;
64
+ }
65
+ )
55
66
} ) ;
56
67
57
- function doChange ( changes : textChanges . ChangeTracker , node : Node , sourceFile : SourceFile ) {
58
- if ( isJSDocTypedefTag ( node ) ) {
59
- fixSingleTypeDef ( changes , node , sourceFile ) ;
60
- }
61
- }
62
-
63
- function fixSingleTypeDef (
68
+ function doChange (
64
69
changes : textChanges . ChangeTracker ,
65
- typeDefNode : JSDocTypedefTag | undefined ,
70
+ node : Node ,
66
71
sourceFile : SourceFile ,
72
+ newLine : string ,
73
+ fixAll = false
67
74
) {
68
- if ( ! typeDefNode ) return ;
75
+ if ( ! isJSDocTypedefTag ( node ) ) return ;
69
76
70
- const declaration = createDeclaration ( typeDefNode ) ;
77
+ const declaration = createDeclaration ( node ) ;
71
78
if ( ! declaration ) return ;
72
79
73
- const comment = typeDefNode . parent ;
80
+ const commentNode = node . parent ;
81
+
82
+ const { leftSibling, rightSibling } = getLeftAndRightSiblings ( node ) ;
83
+
84
+ let pos = commentNode . getStart ( ) ;
85
+ let prefix = "" ;
86
+
87
+ // the first @typedef is the comment block with a text comment above
88
+ if ( ! leftSibling && commentNode . comment ) {
89
+ pos = findEndOfTextBetween ( commentNode , commentNode . getStart ( ) , node . getStart ( ) ) ;
90
+ prefix = `${ newLine } */${ newLine } ` ;
91
+ }
92
+
93
+ if ( leftSibling ) {
94
+ if ( fixAll && isJSDocTypedefTag ( leftSibling ) ) {
95
+ // Don't need to keep empty comment clock between created interfaces
96
+ pos = node . getStart ( ) ;
97
+ prefix = "" ;
98
+ }
99
+ else {
100
+ pos = findEndOfTextBetween ( commentNode , leftSibling . getStart ( ) , node . getStart ( ) ) ;
101
+ prefix = `${ newLine } */${ newLine } ` ;
102
+ }
103
+ }
104
+
105
+ let end = commentNode . getEnd ( ) ;
106
+ let suffix = "" ;
107
+
108
+ if ( rightSibling ) {
109
+ if ( fixAll && isJSDocTypedefTag ( rightSibling ) ) {
110
+ // Don't need to keep empty comment clock between created interfaces
111
+ end = rightSibling . getStart ( ) ;
112
+ suffix = `${ newLine } ${ newLine } ` ;
113
+ }
114
+ else {
115
+ end = rightSibling . getStart ( ) ;
116
+ suffix = `${ newLine } /**${ newLine } * ` ;
117
+ }
118
+ }
74
119
75
- changes . replaceNode (
76
- sourceFile ,
77
- comment ,
78
- declaration
120
+ changes . replaceRange ( sourceFile , { pos, end } , declaration , { prefix, suffix } ) ;
121
+ }
122
+
123
+ function getLeftAndRightSiblings ( typedefNode : JSDocTypedefTag ) : { leftSibling ?: Node , rightSibling ?: Node } {
124
+
125
+ const commentNode = typedefNode . parent ;
126
+ const maxChildIndex = commentNode . getChildCount ( ) - 1 ;
127
+
128
+ const currentNodeIndex = commentNode . getChildren ( ) . findIndex (
129
+ ( n ) => n . getStart ( ) === typedefNode . getStart ( ) && n . getEnd ( ) === typedefNode . getEnd ( )
79
130
) ;
131
+
132
+ const leftSibling = currentNodeIndex > 0 ? commentNode . getChildAt ( currentNodeIndex - 1 ) : undefined ;
133
+ const rightSibling = currentNodeIndex < maxChildIndex ? commentNode . getChildAt ( currentNodeIndex + 1 ) : undefined ;
134
+
135
+ return { leftSibling, rightSibling } ;
136
+ }
137
+
138
+ /**
139
+ * Finds the index of the last meaningful symbol (except empty spaces, * and /) in the comment
140
+ * between start and end positions
141
+ */
142
+ function findEndOfTextBetween ( jsDocComment : JSDoc , from : number , to : number ) : number {
143
+ const comment = jsDocComment . getText ( ) . substring ( from - jsDocComment . getStart ( ) , to - jsDocComment . getStart ( ) ) ;
144
+
145
+ for ( let i = comment . length ; i > 0 ; i -- ) {
146
+ if ( ! / [ * \/ \s ] / g. test ( comment . substring ( i - 1 , i ) ) ) {
147
+ return from + i ;
148
+ }
149
+ }
150
+
151
+ return to ;
80
152
}
81
153
82
154
function createDeclaration ( tag : JSDocTypedefTag ) : InterfaceDeclaration | TypeAliasDeclaration | undefined {
@@ -86,7 +158,7 @@ function createDeclaration(tag: JSDocTypedefTag): InterfaceDeclaration | TypeAli
86
158
if ( ! typeName ) return ;
87
159
88
160
// For use case @typedef {object }Foo @property {bar }number
89
- // But object type can be nested, meaning the value in the k/v pair can be object itself
161
+ // But object type can be nested, meaning the value in the k/v pair can be the object itself
90
162
if ( typeExpression . kind === SyntaxKind . JSDocTypeLiteral ) {
91
163
return createInterfaceForTypeLiteral ( typeName , typeExpression ) ;
92
164
}
@@ -103,14 +175,14 @@ function createInterfaceForTypeLiteral(
103
175
) : InterfaceDeclaration | undefined {
104
176
const propertySignatures = createSignatureFromTypeLiteral ( typeLiteral ) ;
105
177
if ( ! some ( propertySignatures ) ) return ;
106
- const interfaceDeclaration = factory . createInterfaceDeclaration (
178
+
179
+ return factory . createInterfaceDeclaration (
107
180
/*modifiers*/ undefined ,
108
181
typeName ,
109
182
/*typeParameters*/ undefined ,
110
183
/*heritageClauses*/ undefined ,
111
184
propertySignatures ,
112
185
) ;
113
- return interfaceDeclaration ;
114
186
}
115
187
116
188
function createTypeAliasForTypeExpression (
@@ -119,13 +191,13 @@ function createTypeAliasForTypeExpression(
119
191
) : TypeAliasDeclaration | undefined {
120
192
const typeReference = getSynthesizedDeepClone ( typeExpression . type ) ;
121
193
if ( ! typeReference ) return ;
122
- const declaration = factory . createTypeAliasDeclaration (
194
+
195
+ return factory . createTypeAliasDeclaration (
123
196
/*modifiers*/ undefined ,
124
197
factory . createIdentifier ( typeName ) ,
125
198
/*typeParameters*/ undefined ,
126
199
typeReference
127
200
) ;
128
- return declaration ;
129
201
}
130
202
131
203
function createSignatureFromTypeLiteral ( typeLiteral : JSDocTypeLiteral ) : PropertySignature [ ] | undefined {
@@ -150,29 +222,28 @@ function createSignatureFromTypeLiteral(typeLiteral: JSDocTypeLiteral): Property
150
222
151
223
if ( typeReference && name ) {
152
224
const questionToken = isOptional ? factory . createToken ( SyntaxKind . QuestionToken ) : undefined ;
153
- const prop = factory . createPropertySignature (
225
+
226
+ return factory . createPropertySignature (
154
227
/*modifiers*/ undefined ,
155
228
name ,
156
229
questionToken ,
157
230
typeReference
158
231
) ;
159
-
160
- return prop ;
161
232
}
162
233
} ;
163
234
164
- const props = mapDefined ( propertyTags , getSignature ) ;
165
- return props ;
235
+ return mapDefined ( propertyTags , getSignature ) ;
166
236
}
167
237
168
238
function getPropertyName ( tag : JSDocPropertyLikeTag ) : string | undefined {
169
239
return tag . name . kind === SyntaxKind . Identifier ? tag . name . text : tag . name . right . text ;
170
240
}
171
241
172
242
/** @internal */
173
- export function getJSDocTypedefNode ( node : Node ) : JSDocTypedefTag | undefined {
243
+ export function getJSDocTypedefNodes ( node : Node ) : readonly JSDocTag [ ] {
174
244
if ( hasJSDocNodes ( node ) ) {
175
- return forEach ( node . jsDoc , ( node ) => node . tags ?. find ( isJSDocTypedefTag ) ) ;
245
+ return flatMap ( node . jsDoc , ( doc ) => doc . tags ?. filter ( ( tag ) => isJSDocTypedefTag ( tag ) ) ) ;
176
246
}
177
- return undefined ;
247
+
248
+ return [ ] ;
178
249
}
0 commit comments