1
- import { docsUrl } from '../docs-url'
1
+ import { TSESLint , TSESTree } from '@typescript-eslint/utils'
2
+ import { createRule } from '../utils'
2
3
3
- function isComma ( token ) {
4
+ function isComma ( token : TSESTree . Token ) : token is TSESTree . PunctuatorToken {
4
5
return token . type === 'Punctuator' && token . value === ','
5
6
}
6
7
7
- function removeSpecifiers ( fixes , fixer , sourceCode , specifiers ) {
8
+ function removeSpecifiers (
9
+ fixes : TSESLint . RuleFix [ ] ,
10
+ fixer : TSESLint . RuleFixer ,
11
+ sourceCode : Readonly < TSESLint . SourceCode > ,
12
+ specifiers : TSESTree . ImportSpecifier [ ] ,
13
+ ) {
8
14
for ( const specifier of specifiers ) {
9
15
// remove the trailing comma
10
16
const token = sourceCode . getTokenAfter ( specifier )
@@ -15,7 +21,12 @@ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
15
21
}
16
22
}
17
23
18
- function getImportText ( node , sourceCode , specifiers , kind ) {
24
+ function getImportText (
25
+ node : TSESTree . ImportDeclaration ,
26
+ sourceCode : Readonly < TSESLint . SourceCode > ,
27
+ specifiers : TSESTree . ImportSpecifier [ ] ,
28
+ kind : 'type' | 'typeof' ,
29
+ ) {
19
30
const sourceString = sourceCode . getText ( node . source )
20
31
if ( specifiers . length === 0 ) {
21
32
return ''
@@ -31,14 +42,18 @@ function getImportText(node, sourceCode, specifiers, kind) {
31
42
return `import ${ kind } {${ names . join ( ', ' ) } } from ${ sourceString } ;`
32
43
}
33
44
34
- module . exports = {
45
+ type Options = 'prefer-inline' | 'prefer-top-level'
46
+
47
+ type MessageId = 'inline' | 'topLevel'
48
+
49
+ export = createRule < [ Options ?] , MessageId > ( {
50
+ name : 'consistent-type-specifier-style' ,
35
51
meta : {
36
52
type : 'suggestion' ,
37
53
docs : {
38
54
category : 'Style guide' ,
39
55
description :
40
56
'Enforce or ban the use of inline type-only markers for named imports.' ,
41
- url : docsUrl ( 'consistent-type-specifier-style' ) ,
42
57
} ,
43
58
fixable : 'code' ,
44
59
schema : [
@@ -48,8 +63,14 @@ module.exports = {
48
63
default : 'prefer-inline' ,
49
64
} ,
50
65
] ,
66
+ messages : {
67
+ inline :
68
+ 'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.' ,
69
+ topLevel :
70
+ 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.' ,
71
+ } ,
51
72
} ,
52
-
73
+ defaultOptions : [ ] ,
53
74
create ( context ) {
54
75
const sourceCode = context . getSourceCode ( )
55
76
@@ -75,20 +96,19 @@ module.exports = {
75
96
76
97
context . report ( {
77
98
node,
78
- message :
79
- 'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.' ,
99
+ messageId : 'inline' ,
80
100
data : {
81
101
kind : node . importKind ,
82
102
} ,
83
103
fix ( fixer ) {
84
104
const kindToken = sourceCode . getFirstToken ( node , { skip : 1 } )
85
105
86
- return [ ] . concat (
106
+ return [
87
107
kindToken ? fixer . remove ( kindToken ) : [ ] ,
88
108
node . specifiers . map ( specifier =>
89
109
fixer . insertTextBefore ( specifier , `${ node . importKind } ` ) ,
90
110
) ,
91
- )
111
+ ] . flat ( )
92
112
} ,
93
113
} )
94
114
} ,
@@ -101,6 +121,7 @@ module.exports = {
101
121
if (
102
122
// already top-level is valid
103
123
node . importKind === 'type' ||
124
+ // @ts -expect-error - flow type
104
125
node . importKind === 'typeof' ||
105
126
// no specifiers (import {} from '') cannot have inline - so is valid
106
127
node . specifiers . length === 0 ||
@@ -113,19 +134,28 @@ module.exports = {
113
134
return
114
135
}
115
136
116
- const typeSpecifiers = [ ]
117
- const typeofSpecifiers = [ ]
118
- const valueSpecifiers = [ ]
119
- let defaultSpecifier = null
137
+ const typeSpecifiers : TSESTree . ImportSpecifier [ ] = [ ]
138
+ const typeofSpecifiers : TSESTree . ImportSpecifier [ ] = [ ]
139
+ const valueSpecifiers : TSESTree . ImportSpecifier [ ] = [ ]
140
+
141
+ let defaultSpecifier : TSESTree . ImportDefaultSpecifier | null = null
142
+
120
143
for ( const specifier of node . specifiers ) {
121
144
if ( specifier . type === 'ImportDefaultSpecifier' ) {
122
145
defaultSpecifier = specifier
123
146
continue
124
147
}
125
148
149
+ if ( ! ( 'importKind' in specifier ) ) {
150
+ continue
151
+ }
152
+
126
153
if ( specifier . importKind === 'type' ) {
127
154
typeSpecifiers . push ( specifier )
128
- } else if ( specifier . importKind === 'typeof' ) {
155
+ } else if (
156
+ // @ts -expect-error - flow type
157
+ specifier . importKind === 'typeof'
158
+ ) {
129
159
typeofSpecifiers . push ( specifier )
130
160
} else if (
131
161
specifier . importKind === 'value' ||
@@ -154,15 +184,14 @@ module.exports = {
154
184
node . specifiers . length
155
185
) {
156
186
// all specifiers have inline specifiers - so we replace the entire import
157
- const kind = [ ] . concat (
158
- typeSpecifiers . length > 0 ? 'type' : [ ] ,
159
- typeofSpecifiers . length > 0 ? 'typeof' : [ ] ,
160
- )
187
+ const kind = [
188
+ typeSpecifiers . length > 0 ? ( 'type' as const ) : [ ] ,
189
+ typeofSpecifiers . length > 0 ? ( 'typeof' as const ) : [ ] ,
190
+ ] . flat ( )
161
191
162
192
context . report ( {
163
193
node,
164
- message :
165
- 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.' ,
194
+ messageId : 'topLevel' ,
166
195
data : {
167
196
kind : kind . join ( '/' ) ,
168
197
} ,
@@ -175,13 +204,12 @@ module.exports = {
175
204
for ( const specifier of typeSpecifiers . concat ( typeofSpecifiers ) ) {
176
205
context . report ( {
177
206
node : specifier ,
178
- message :
179
- 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.' ,
207
+ messageId : 'topLevel' ,
180
208
data : {
181
209
kind : specifier . importKind ,
182
210
} ,
183
211
fix ( fixer ) {
184
- const fixes = [ ]
212
+ const fixes : TSESLint . RuleFix [ ] = [ ]
185
213
186
214
// if there are no value specifiers, then the other report fixer will be called, not this one
187
215
@@ -201,7 +229,7 @@ module.exports = {
201
229
// import { Value, } from 'mod';
202
230
const maybeComma = sourceCode . getTokenAfter (
203
231
valueSpecifiers [ valueSpecifiers . length - 1 ] ,
204
- )
232
+ ) !
205
233
if ( isComma ( maybeComma ) ) {
206
234
fixes . push ( fixer . remove ( maybeComma ) )
207
235
}
@@ -220,7 +248,10 @@ module.exports = {
220
248
token => token . type === 'Punctuator' && token . value === '}' ,
221
249
)
222
250
fixes . push (
223
- fixer . removeRange ( [ comma . range [ 0 ] , closingBrace . range [ 1 ] ] ) ,
251
+ fixer . removeRange ( [
252
+ comma ! . range [ 0 ] ,
253
+ closingBrace ! . range [ 1 ] ,
254
+ ] ) ,
224
255
)
225
256
}
226
257
@@ -235,4 +266,4 @@ module.exports = {
235
266
} ,
236
267
}
237
268
} ,
238
- }
269
+ } )
0 commit comments