@@ -513,33 +513,59 @@ function computePathRank(ranks, pathGroups, path, maxPosition) {
513
513
}
514
514
}
515
515
516
- function computeRank ( context , ranks , importEntry , excludedImportTypes ) {
516
+ function computeRank ( context , ranks , importEntry , excludedImportTypes , isSortingTypesAmongThemselves ) {
517
517
let impType ;
518
518
let rank ;
519
+
520
+ const isTypeGroupInGroups = ranks . omittedTypes . indexOf ( 'type' ) === - 1 ;
521
+ const isTypeOnlyImport = importEntry . node . importKind === 'type' ;
522
+ const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes . has ( 'type' )
523
+
519
524
if ( importEntry . type === 'import:object' ) {
520
525
impType = 'object' ;
521
- } else if ( importEntry . node . importKind === 'type' && ranks . omittedTypes . indexOf ( 'type' ) === - 1 ) {
526
+ } else if ( isTypeOnlyImport && isTypeGroupInGroups && ! isSortingTypesAmongThemselves ) {
522
527
impType = 'type' ;
523
528
} else {
524
529
impType = importType ( importEntry . value , context ) ;
525
530
}
526
- if ( ! excludedImportTypes . has ( impType ) ) {
531
+
532
+ if ( ! excludedImportTypes . has ( impType ) && ! isExcludedFromPathRank ) {
527
533
rank = computePathRank ( ranks . groups , ranks . pathGroups , importEntry . value , ranks . maxPosition ) ;
528
534
}
529
- if ( typeof rank === 'undefined' ) {
535
+
536
+ if ( rank === undefined ) {
530
537
rank = ranks . groups [ impType ] ;
538
+
539
+ if ( rank === undefined ) {
540
+ return - 1 ;
541
+ }
531
542
}
543
+
544
+ if ( isTypeOnlyImport && isSortingTypesAmongThemselves ) {
545
+ rank = ranks . groups [ 'type' ] + rank / 10 ;
546
+ }
547
+
532
548
if ( importEntry . type !== 'import' && ! importEntry . type . startsWith ( 'import:' ) ) {
533
549
rank += 100 ;
534
550
}
535
551
536
552
return rank ;
537
553
}
538
554
539
- function registerNode ( context , importEntry , ranks , imported , excludedImportTypes ) {
540
- const rank = computeRank ( context , ranks , importEntry , excludedImportTypes ) ;
555
+ function registerNode ( context , importEntry , ranks , imported , excludedImportTypes , isSortingTypesAmongThemselves ) {
556
+ const rank = computeRank ( context , ranks , importEntry , excludedImportTypes , isSortingTypesAmongThemselves ) ;
541
557
if ( rank !== - 1 ) {
542
- imported . push ( { ...importEntry , rank } ) ;
558
+ let importNode = importEntry . node ;
559
+
560
+ if ( importEntry . type === 'require' && importNode . parent . parent . type === 'VariableDeclaration' ) {
561
+ importNode = importNode . parent . parent ;
562
+ }
563
+
564
+ imported . push ( {
565
+ ...importEntry ,
566
+ rank,
567
+ isMultiline : importNode . loc . end . line !== importNode . loc . start . line
568
+ } ) ;
543
569
}
544
570
}
545
571
@@ -665,7 +691,7 @@ function removeNewLineAfterImport(context, currentImport, previousImport) {
665
691
return undefined ;
666
692
}
667
693
668
- function makeNewlinesBetweenReport ( context , imported , newlinesBetweenImports , distinctGroup ) {
694
+ function makeNewlinesBetweenReport ( context , imported , newlinesBetweenImports , newlinesBetweenTypeOnlyImports_ , distinctGroup , isSortingTypesAmongThemselves , isConsolidatingSpaceBetweenImports ) {
669
695
const getNumberOfEmptyLinesBetween = ( currentImport , previousImport ) => {
670
696
const linesBetweenImports = getSourceCode ( context ) . lines . slice (
671
697
previousImport . node . loc . end . line ,
@@ -678,35 +704,124 @@ function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, di
678
704
let previousImport = imported [ 0 ] ;
679
705
680
706
imported . slice ( 1 ) . forEach ( function ( currentImport ) {
681
- const emptyLinesBetween = getNumberOfEmptyLinesBetween ( currentImport , previousImport ) ;
682
- const isStartOfDistinctGroup = getIsStartOfDistinctGroup ( currentImport , previousImport ) ;
707
+ const emptyLinesBetween = getNumberOfEmptyLinesBetween (
708
+ currentImport ,
709
+ previousImport
710
+ ) ;
711
+
712
+ const isStartOfDistinctGroup = getIsStartOfDistinctGroup (
713
+ currentImport ,
714
+ previousImport
715
+ ) ;
683
716
684
- if ( newlinesBetweenImports === 'always'
685
- || newlinesBetweenImports === 'always-and-inside-groups' ) {
686
- if ( currentImport . rank !== previousImport . rank && emptyLinesBetween === 0 ) {
687
- if ( distinctGroup || ! distinctGroup && isStartOfDistinctGroup ) {
717
+ const isTypeOnlyImport = currentImport . node . importKind === 'type' ;
718
+ const isPreviousImportTypeOnlyImport = previousImport . node . importKind === 'type' ;
719
+
720
+ const isNormalImportNextToTypeOnlyImportAndRelevant =
721
+ isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesAmongThemselves ;
722
+
723
+ const isTypeOnlyImportAndRelevant =
724
+ isTypeOnlyImport && isSortingTypesAmongThemselves ;
725
+
726
+ // In the special case where newlinesBetweenTypeOnlyImports and
727
+ // consolidateIslands want the opposite thing, consolidateIslands wins
728
+ const newlinesBetweenTypeOnlyImports =
729
+ newlinesBetweenTypeOnlyImports_ === 'never' &&
730
+ isConsolidatingSpaceBetweenImports &&
731
+ isSortingTypesAmongThemselves &&
732
+ ( isNormalImportNextToTypeOnlyImportAndRelevant ||
733
+ previousImport . isMultiline ||
734
+ currentImport . isMultiline )
735
+ ? 'always-and-inside-groups'
736
+ : newlinesBetweenTypeOnlyImports_ ;
737
+
738
+ const isNotIgnored =
739
+ ( isTypeOnlyImportAndRelevant &&
740
+ newlinesBetweenTypeOnlyImports !== 'ignore' ) ||
741
+ ( ! isTypeOnlyImportAndRelevant && newlinesBetweenImports !== 'ignore' ) ;
742
+
743
+ if ( isNotIgnored ) {
744
+ const shouldAssertNewlineBetweenGroups =
745
+ ( ( isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant ) &&
746
+ ( newlinesBetweenTypeOnlyImports === 'always' ||
747
+ newlinesBetweenTypeOnlyImports === 'always-and-inside-groups' ) ) ||
748
+ ( ( ! isTypeOnlyImportAndRelevant && ! isNormalImportNextToTypeOnlyImportAndRelevant ) &&
749
+ ( newlinesBetweenImports === 'always' ||
750
+ newlinesBetweenImports === 'always-and-inside-groups' ) ) ;
751
+
752
+ const shouldAssertNoNewlineWithinGroup =
753
+ ( ( isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant ) &&
754
+ ( newlinesBetweenTypeOnlyImports !== 'always-and-inside-groups' ) ) ||
755
+ ( ( ! isTypeOnlyImportAndRelevant && ! isNormalImportNextToTypeOnlyImportAndRelevant ) &&
756
+ ( newlinesBetweenImports !== 'always-and-inside-groups' ) ) ;
757
+
758
+ const shouldAssertNoNewlineBetweenGroup =
759
+ ! isSortingTypesAmongThemselves ||
760
+ ! isNormalImportNextToTypeOnlyImportAndRelevant ||
761
+ newlinesBetweenTypeOnlyImports === 'never' ;
762
+
763
+ const isTheNewlineBetweenImportsInTheSameGroup = ( distinctGroup && currentImport . rank === previousImport . rank ) ||
764
+ ( ! distinctGroup && ! isStartOfDistinctGroup ) ;
765
+
766
+ // Let's try to cut down on linting errors sent to the user
767
+ let alreadyReported = false ;
768
+
769
+ if ( shouldAssertNewlineBetweenGroups ) {
770
+ if ( currentImport . rank !== previousImport . rank && emptyLinesBetween === 0 ) {
771
+ if ( distinctGroup || ! distinctGroup && isStartOfDistinctGroup ) {
772
+ alreadyReported = true ;
773
+ context . report ( {
774
+ node : previousImport . node ,
775
+ message : 'There should be at least one empty line between import groups' ,
776
+ fix : fixNewLineAfterImport ( context , previousImport ) ,
777
+ } ) ;
778
+ }
779
+ } else if ( emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup ) {
780
+ if ( isTheNewlineBetweenImportsInTheSameGroup ) {
781
+ alreadyReported = true ;
782
+ context . report ( {
783
+ node : previousImport . node ,
784
+ message : 'There should be no empty line within import group' ,
785
+ fix : removeNewLineAfterImport ( context , currentImport , previousImport )
786
+ } ) ;
787
+ }
788
+ }
789
+ } else if ( emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup ) {
790
+ alreadyReported = true ;
791
+ context . report ( {
792
+ node : previousImport . node ,
793
+ message : 'There should be no empty line between import groups' ,
794
+ fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
795
+ } ) ;
796
+ }
797
+
798
+ if ( ! alreadyReported && isConsolidatingSpaceBetweenImports ) {
799
+ if ( emptyLinesBetween === 0 && currentImport . isMultiline ) {
688
800
context . report ( {
689
801
node : previousImport . node ,
690
- message : 'There should be at least one empty line between import groups ' ,
802
+ message : 'There should be at least one empty line between this import and the multi-line import that follows it ' ,
691
803
fix : fixNewLineAfterImport ( context , previousImport ) ,
692
804
} ) ;
693
- }
694
- } else if ( emptyLinesBetween > 0
695
- && newlinesBetweenImports !== 'always-and-inside-groups' ) {
696
- if ( distinctGroup && currentImport . rank === previousImport . rank || ! distinctGroup && ! isStartOfDistinctGroup ) {
805
+ } else if ( emptyLinesBetween === 0 && previousImport . isMultiline ) {
697
806
context . report ( {
698
807
node : previousImport . node ,
699
- message : 'There should be no empty line within import group' ,
700
- fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
808
+ message : 'There should be at least one empty line between this multi-line import and the import that follows it' ,
809
+ fix : fixNewLineAfterImport ( context , previousImport ) ,
810
+ } ) ;
811
+ } else if (
812
+ emptyLinesBetween > 0 &&
813
+ ! previousImport . isMultiline &&
814
+ ! currentImport . isMultiline &&
815
+ isTheNewlineBetweenImportsInTheSameGroup
816
+ ) {
817
+ context . report ( {
818
+ node : previousImport . node ,
819
+ message :
820
+ 'There should be no empty lines between this single-line import and the single-line import that follows it' ,
821
+ fix : removeNewLineAfterImport ( context , currentImport , previousImport )
701
822
} ) ;
702
823
}
703
824
}
704
- } else if ( emptyLinesBetween > 0 ) {
705
- context . report ( {
706
- node : previousImport . node ,
707
- message : 'There should be no empty line between import groups' ,
708
- fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
709
- } ) ;
710
825
}
711
826
712
827
previousImport = currentImport ;
@@ -781,6 +896,24 @@ module.exports = {
781
896
'never' ,
782
897
] ,
783
898
} ,
899
+ 'newlines-between-types' : {
900
+ enum : [
901
+ 'ignore' ,
902
+ 'always' ,
903
+ 'always-and-inside-groups' ,
904
+ 'never' ,
905
+ ] ,
906
+ } ,
907
+ consolidateIslands : {
908
+ enum : [
909
+ 'inside-groups' ,
910
+ 'never' ,
911
+ ] ,
912
+ } ,
913
+ sortTypesAmongThemselves : {
914
+ type : 'boolean' ,
915
+ default : false ,
916
+ } ,
784
917
named : {
785
918
default : false ,
786
919
oneOf : [ {
@@ -836,7 +969,10 @@ module.exports = {
836
969
create ( context ) {
837
970
const options = context . options [ 0 ] || { } ;
838
971
const newlinesBetweenImports = options [ 'newlines-between' ] || 'ignore' ;
972
+ const newlinesBetweenTypeOnlyImports = options [ 'newlines-between-types' ] || newlinesBetweenImports ;
839
973
const pathGroupsExcludedImportTypes = new Set ( options . pathGroupsExcludedImportTypes || [ 'builtin' , 'external' , 'object' ] ) ;
974
+ const sortTypesAmongThemselves = options . sortTypesAmongThemselves ;
975
+ const consolidateIslands = options . consolidateIslands || 'never' ;
840
976
841
977
const named = {
842
978
types : 'mixed' ,
@@ -879,6 +1015,9 @@ module.exports = {
879
1015
const importMap = new Map ( ) ;
880
1016
const exportMap = new Map ( ) ;
881
1017
1018
+ const isTypeGroupInGroups = ranks . omittedTypes . indexOf ( 'type' ) === - 1 ;
1019
+ const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves ;
1020
+
882
1021
function getBlockImports ( node ) {
883
1022
if ( ! importMap . has ( node ) ) {
884
1023
importMap . set ( node , [ ] ) ;
@@ -932,6 +1071,7 @@ module.exports = {
932
1071
ranks ,
933
1072
getBlockImports ( node . parent ) ,
934
1073
pathGroupsExcludedImportTypes ,
1074
+ isSortingTypesAmongThemselves
935
1075
) ;
936
1076
937
1077
if ( named . import ) {
@@ -983,6 +1123,7 @@ module.exports = {
983
1123
ranks ,
984
1124
getBlockImports ( node . parent ) ,
985
1125
pathGroupsExcludedImportTypes ,
1126
+ isSortingTypesAmongThemselves
986
1127
) ;
987
1128
} ,
988
1129
CallExpression ( node ) {
@@ -1005,6 +1146,7 @@ module.exports = {
1005
1146
ranks ,
1006
1147
getBlockImports ( block ) ,
1007
1148
pathGroupsExcludedImportTypes ,
1149
+ isSortingTypesAmongThemselves
1008
1150
) ;
1009
1151
} ,
1010
1152
...named . require && {
@@ -1092,8 +1234,18 @@ module.exports = {
1092
1234
} ,
1093
1235
'Program:exit' ( ) {
1094
1236
importMap . forEach ( ( imported ) => {
1095
- if ( newlinesBetweenImports !== 'ignore' ) {
1096
- makeNewlinesBetweenReport ( context , imported , newlinesBetweenImports , distinctGroup ) ;
1237
+ if ( newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore' ) {
1238
+ makeNewlinesBetweenReport (
1239
+ context ,
1240
+ imported ,
1241
+ newlinesBetweenImports ,
1242
+ newlinesBetweenTypeOnlyImports ,
1243
+ distinctGroup ,
1244
+ isSortingTypesAmongThemselves ,
1245
+ consolidateIslands === 'inside-groups' &&
1246
+ ( newlinesBetweenImports === 'always-and-inside-groups' ||
1247
+ newlinesBetweenTypeOnlyImports === 'always-and-inside-groups' )
1248
+ ) ;
1097
1249
}
1098
1250
1099
1251
if ( alphabetize . order !== 'ignore' ) {
0 commit comments