@@ -28,6 +28,7 @@ interface ReportValueImport {
28
28
typeSpecifiers : TSESTree . ImportClause [ ] ; // It has at least one element.
29
29
valueSpecifiers : TSESTree . ImportClause [ ] ;
30
30
unusedSpecifiers : TSESTree . ImportClause [ ] ;
31
+ inlineTypeSpecifiers : TSESTree . ImportSpecifier [ ] ;
31
32
}
32
33
33
34
function isImportToken (
@@ -106,7 +107,7 @@ export default util.createRule<Options, MessageIds>({
106
107
...( prefer === 'type-imports'
107
108
? {
108
109
// prefer type imports
109
- ImportDeclaration ( node : TSESTree . ImportDeclaration ) : void {
110
+ ImportDeclaration ( node ) : void {
110
111
const source = node . source . value ;
111
112
const sourceImports =
112
113
sourceImportsMap [ source ] ??
@@ -139,9 +140,18 @@ export default util.createRule<Options, MessageIds>({
139
140
}
140
141
141
142
const typeSpecifiers : TSESTree . ImportClause [ ] = [ ] ;
143
+ const inlineTypeSpecifiers : TSESTree . ImportSpecifier [ ] = [ ] ;
142
144
const valueSpecifiers : TSESTree . ImportClause [ ] = [ ] ;
143
145
const unusedSpecifiers : TSESTree . ImportClause [ ] = [ ] ;
144
146
for ( const specifier of node . specifiers ) {
147
+ if (
148
+ specifier . type === AST_NODE_TYPES . ImportSpecifier &&
149
+ specifier . importKind === 'type'
150
+ ) {
151
+ inlineTypeSpecifiers . push ( specifier ) ;
152
+ continue ;
153
+ }
154
+
145
155
const [ variable ] = context . getDeclaredVariables ( specifier ) ;
146
156
if ( variable . references . length === 0 ) {
147
157
unusedSpecifiers . push ( specifier ) ;
@@ -229,6 +239,7 @@ export default util.createRule<Options, MessageIds>({
229
239
typeSpecifiers,
230
240
valueSpecifiers,
231
241
unusedSpecifiers,
242
+ inlineTypeSpecifiers,
232
243
} ) ;
233
244
}
234
245
} ,
@@ -247,7 +258,11 @@ export default util.createRule<Options, MessageIds>({
247
258
node : report . node ,
248
259
messageId : 'typeOverValue' ,
249
260
* fix ( fixer ) {
250
- yield * fixToTypeImport ( fixer , report , sourceImports ) ;
261
+ yield * fixToTypeImportDeclaration (
262
+ fixer ,
263
+ report ,
264
+ sourceImports ,
265
+ ) ;
251
266
} ,
252
267
} ) ;
253
268
} else {
@@ -298,13 +313,17 @@ export default util.createRule<Options, MessageIds>({
298
313
...message ,
299
314
* fix ( fixer ) {
300
315
if ( isTypeImport ) {
301
- yield * fixToValueImportInDecoMeta (
316
+ yield * fixToValueImportDeclaration (
302
317
fixer ,
303
318
report ,
304
319
sourceImports ,
305
320
) ;
306
321
} else {
307
- yield * fixToTypeImport ( fixer , report , sourceImports ) ;
322
+ yield * fixToTypeImportDeclaration (
323
+ fixer ,
324
+ report ,
325
+ sourceImports ,
326
+ ) ;
308
327
}
309
328
} ,
310
329
} ) ;
@@ -322,7 +341,21 @@ export default util.createRule<Options, MessageIds>({
322
341
node,
323
342
messageId : 'valueOverType' ,
324
343
fix ( fixer ) {
325
- return fixToValueImport ( fixer , node ) ;
344
+ return fixRemoveTypeSpecifierFromImportDeclaration (
345
+ fixer ,
346
+ node ,
347
+ ) ;
348
+ } ,
349
+ } ) ;
350
+ } ,
351
+ 'ImportSpecifier[importKind = "type"]' (
352
+ node : TSESTree . ImportSpecifier ,
353
+ ) : void {
354
+ context . report ( {
355
+ node,
356
+ messageId : 'valueOverType' ,
357
+ fix ( fixer ) {
358
+ return fixRemoveTypeSpecifierFromImportSpecifier ( fixer , node ) ;
326
359
} ,
327
360
} ) ;
328
361
} ,
@@ -345,20 +378,19 @@ export default util.createRule<Options, MessageIds>({
345
378
namespaceSpecifier : TSESTree . ImportNamespaceSpecifier | null ;
346
379
namedSpecifiers : TSESTree . ImportSpecifier [ ] ;
347
380
} {
348
- const defaultSpecifier : TSESTree . ImportDefaultSpecifier | null =
381
+ const defaultSpecifier =
349
382
node . specifiers [ 0 ] . type === AST_NODE_TYPES . ImportDefaultSpecifier
350
383
? node . specifiers [ 0 ]
351
384
: null ;
352
- const namespaceSpecifier : TSESTree . ImportNamespaceSpecifier | null =
385
+ const namespaceSpecifier =
353
386
node . specifiers . find (
354
387
( specifier ) : specifier is TSESTree . ImportNamespaceSpecifier =>
355
388
specifier . type === AST_NODE_TYPES . ImportNamespaceSpecifier ,
356
389
) ?? null ;
357
- const namedSpecifiers : TSESTree . ImportSpecifier [ ] =
358
- node . specifiers . filter (
359
- ( specifier ) : specifier is TSESTree . ImportSpecifier =>
360
- specifier . type === AST_NODE_TYPES . ImportSpecifier ,
361
- ) ;
390
+ const namedSpecifiers = node . specifiers . filter (
391
+ ( specifier ) : specifier is TSESTree . ImportSpecifier =>
392
+ specifier . type === AST_NODE_TYPES . ImportSpecifier ,
393
+ ) ;
362
394
return {
363
395
defaultSpecifier,
364
396
namespaceSpecifier,
@@ -387,7 +419,6 @@ export default util.createRule<Options, MessageIds>({
387
419
const typeNamedSpecifiersTexts : string [ ] = [ ] ;
388
420
const removeTypeNamedSpecifiers : TSESLint . RuleFix [ ] = [ ] ;
389
421
if ( typeNamedSpecifiers . length === allNamedSpecifiers . length ) {
390
- // e.g.
391
422
// import Foo, {Type1, Type2} from 'foo'
392
423
// import DefType, {Type1, Type2} from 'foo'
393
424
const openingBraceToken = util . nullThrows (
@@ -496,7 +527,7 @@ export default util.createRule<Options, MessageIds>({
496
527
* import type { Already, Type1, Type2 } from 'foo'
497
528
* ^^^^^^^^^^^^^ insert
498
529
*/
499
- function insertToNamedImport (
530
+ function fixInsertNamedSpecifiersInNamedSpecifierList (
500
531
fixer : TSESLint . RuleFixer ,
501
532
target : TSESTree . ImportDeclaration ,
502
533
insertText : string ,
@@ -511,12 +542,12 @@ export default util.createRule<Options, MessageIds>({
511
542
) ;
512
543
const before = sourceCode . getTokenBefore ( closingBraceToken ) ! ;
513
544
if ( ! util . isCommaToken ( before ) && ! util . isOpeningBraceToken ( before ) ) {
514
- insertText = ',' + insertText ;
545
+ insertText = `, ${ insertText } ` ;
515
546
}
516
- return fixer . insertTextBefore ( closingBraceToken , insertText ) ;
547
+ return fixer . insertTextBefore ( closingBraceToken , ` ${ insertText } ` ) ;
517
548
}
518
549
519
- function * fixToTypeImport (
550
+ function * fixToTypeImportDeclaration (
520
551
fixer : TSESLint . RuleFixer ,
521
552
report : ReportValueImport ,
522
553
sourceImports : SourceImports ,
@@ -527,19 +558,17 @@ export default util.createRule<Options, MessageIds>({
527
558
classifySpecifier ( node ) ;
528
559
529
560
if ( namespaceSpecifier && ! defaultSpecifier ) {
530
- // e.g.
531
561
// import * as types from 'foo'
532
- yield * fixToTypeImportByInsertType ( fixer , node , false ) ;
562
+ yield * fixInsertTypeSpecifierForImportDeclaration ( fixer , node , false ) ;
533
563
return ;
534
564
} else if ( defaultSpecifier ) {
535
565
if (
536
566
report . typeSpecifiers . includes ( defaultSpecifier ) &&
537
567
namedSpecifiers . length === 0 &&
538
568
! namespaceSpecifier
539
569
) {
540
- // e.g.
541
570
// import Type from 'foo'
542
- yield * fixToTypeImportByInsertType ( fixer , node , true ) ;
571
+ yield * fixInsertTypeSpecifierForImportDeclaration ( fixer , node , true ) ;
543
572
return ;
544
573
}
545
574
} else {
@@ -549,9 +578,8 @@ export default util.createRule<Options, MessageIds>({
549
578
) &&
550
579
! namespaceSpecifier
551
580
) {
552
- // e.g.
553
581
// import {Type1, Type2} from 'foo'
554
- yield * fixToTypeImportByInsertType ( fixer , node , false ) ;
582
+ yield * fixInsertTypeSpecifierForImportDeclaration ( fixer , node , false ) ;
555
583
return ;
556
584
}
557
585
}
@@ -569,11 +597,12 @@ export default util.createRule<Options, MessageIds>({
569
597
const afterFixes : TSESLint . RuleFix [ ] = [ ] ;
570
598
if ( typeNamedSpecifiers . length ) {
571
599
if ( sourceImports . typeOnlyNamedImport ) {
572
- const insertTypeNamedSpecifiers = insertToNamedImport (
573
- fixer ,
574
- sourceImports . typeOnlyNamedImport ,
575
- fixesNamedSpecifiers . typeNamedSpecifiersText ,
576
- ) ;
600
+ const insertTypeNamedSpecifiers =
601
+ fixInsertNamedSpecifiersInNamedSpecifierList (
602
+ fixer ,
603
+ sourceImports . typeOnlyNamedImport ,
604
+ fixesNamedSpecifiers . typeNamedSpecifiersText ,
605
+ ) ;
577
606
if ( sourceImports . typeOnlyNamedImport . range [ 1 ] <= node . range [ 0 ] ) {
578
607
yield insertTypeNamedSpecifiers ;
579
608
} else {
@@ -594,7 +623,6 @@ export default util.createRule<Options, MessageIds>({
594
623
namespaceSpecifier &&
595
624
report . typeSpecifiers . includes ( namespaceSpecifier )
596
625
) {
597
- // e.g.
598
626
// import Foo, * as Type from 'foo'
599
627
// import DefType, * as Type from 'foo'
600
628
// import DefType, * as Type from 'foo'
@@ -665,7 +693,7 @@ export default util.createRule<Options, MessageIds>({
665
693
yield * afterFixes ;
666
694
}
667
695
668
- function * fixToTypeImportByInsertType (
696
+ function * fixInsertTypeSpecifierForImportDeclaration (
669
697
fixer : TSESLint . RuleFixer ,
670
698
node : TSESTree . ImportDeclaration ,
671
699
isDefaultImport : boolean ,
@@ -722,9 +750,19 @@ export default util.createRule<Options, MessageIds>({
722
750
}
723
751
}
724
752
}
753
+
754
+ // make sure we don't do anything like `import type {type T} from 'foo';`
755
+ for ( const specifier of node . specifiers ) {
756
+ if (
757
+ specifier . type === AST_NODE_TYPES . ImportSpecifier &&
758
+ specifier . importKind === 'type'
759
+ ) {
760
+ yield * fixRemoveTypeSpecifierFromImportSpecifier ( fixer , specifier ) ;
761
+ }
762
+ }
725
763
}
726
764
727
- function * fixToValueImportInDecoMeta (
765
+ function * fixToValueImportDeclaration (
728
766
fixer : TSESLint . RuleFixer ,
729
767
report : ReportValueImport ,
730
768
sourceImports : SourceImports ,
@@ -735,18 +773,16 @@ export default util.createRule<Options, MessageIds>({
735
773
classifySpecifier ( node ) ;
736
774
737
775
if ( namespaceSpecifier ) {
738
- // e.g.
739
776
// import type * as types from 'foo'
740
- yield * fixToValueImport ( fixer , node ) ;
777
+ yield * fixRemoveTypeSpecifierFromImportDeclaration ( fixer , node ) ;
741
778
return ;
742
779
} else if ( defaultSpecifier ) {
743
780
if (
744
781
report . valueSpecifiers . includes ( defaultSpecifier ) &&
745
782
namedSpecifiers . length === 0
746
783
) {
747
- // e.g.
748
784
// import type Type from 'foo'
749
- yield * fixToValueImport ( fixer , node ) ;
785
+ yield * fixRemoveTypeSpecifierFromImportDeclaration ( fixer , node ) ;
750
786
return ;
751
787
}
752
788
} else {
@@ -755,9 +791,8 @@ export default util.createRule<Options, MessageIds>({
755
791
report . valueSpecifiers . includes ( specifier ) ,
756
792
)
757
793
) {
758
- // e.g.
759
794
// import type {Type1, Type2} from 'foo'
760
- yield * fixToValueImport ( fixer , node ) ;
795
+ yield * fixRemoveTypeSpecifierFromImportDeclaration ( fixer , node ) ;
761
796
return ;
762
797
}
763
798
}
@@ -775,11 +810,12 @@ export default util.createRule<Options, MessageIds>({
775
810
const afterFixes : TSESLint . RuleFix [ ] = [ ] ;
776
811
if ( valueNamedSpecifiers . length ) {
777
812
if ( sourceImports . valueOnlyNamedImport ) {
778
- const insertTypeNamedSpecifiers = insertToNamedImport (
779
- fixer ,
780
- sourceImports . valueOnlyNamedImport ,
781
- fixesNamedSpecifiers . typeNamedSpecifiersText ,
782
- ) ;
813
+ const insertTypeNamedSpecifiers =
814
+ fixInsertNamedSpecifiersInNamedSpecifierList (
815
+ fixer ,
816
+ sourceImports . valueOnlyNamedImport ,
817
+ fixesNamedSpecifiers . typeNamedSpecifiersText ,
818
+ ) ;
783
819
if ( sourceImports . valueOnlyNamedImport . range [ 1 ] <= node . range [ 0 ] ) {
784
820
yield insertTypeNamedSpecifiers ;
785
821
} else {
@@ -800,7 +836,7 @@ export default util.createRule<Options, MessageIds>({
800
836
yield * afterFixes ;
801
837
}
802
838
803
- function * fixToValueImport (
839
+ function * fixRemoveTypeSpecifierFromImportDeclaration (
804
840
fixer : TSESLint . RuleFixer ,
805
841
node : TSESTree . ImportDeclaration ,
806
842
) : IterableIterator < TSESLint . RuleFix > {
@@ -824,5 +860,20 @@ export default util.createRule<Options, MessageIds>({
824
860
) ;
825
861
yield fixer . removeRange ( [ typeToken . range [ 0 ] , afterToken . range [ 0 ] ] ) ;
826
862
}
863
+
864
+ function * fixRemoveTypeSpecifierFromImportSpecifier (
865
+ fixer : TSESLint . RuleFixer ,
866
+ node : TSESTree . ImportSpecifier ,
867
+ ) : IterableIterator < TSESLint . RuleFix > {
868
+ const typeToken = util . nullThrows (
869
+ sourceCode . getFirstToken ( node , isTypeToken ) ,
870
+ util . NullThrowsReasons . MissingToken ( 'type' , node . type ) ,
871
+ ) ;
872
+ const afterToken = util . nullThrows (
873
+ sourceCode . getTokenAfter ( typeToken , { includeComments : true } ) ,
874
+ util . NullThrowsReasons . MissingToken ( 'any token' , node . type ) ,
875
+ ) ;
876
+ yield fixer . removeRange ( [ typeToken . range [ 0 ] , afterToken . range [ 0 ] ] ) ;
877
+ }
827
878
} ,
828
879
} ) ;
0 commit comments