@@ -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,126 @@ 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
+ ) ;
716
+
717
+ const isTypeOnlyImport = currentImport . node . importKind === 'type' ;
718
+ const isPreviousImportTypeOnlyImport = previousImport . node . importKind === 'type' ;
719
+
720
+ const isNormalImportNextToTypeOnlyImportAndRelevant = isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesAmongThemselves ;
721
+
722
+ const isTypeOnlyImportAndRelevant = isTypeOnlyImport && isSortingTypesAmongThemselves ;
723
+
724
+ // In the special case where newlinesBetweenTypeOnlyImports and
725
+ // consolidateIslands want the opposite thing, consolidateIslands wins
726
+ const newlinesBetweenTypeOnly = newlinesBetweenTypeOnlyImports === 'never'
727
+ && isConsolidatingSpaceBetweenImports
728
+ && isSortingTypesAmongThemselves
729
+ && ( isNormalImportNextToTypeOnlyImportAndRelevant
730
+ || previousImport . isMultiline
731
+ || currentImport . isMultiline )
732
+ ? 'always-and-inside-groups'
733
+ : newlinesBetweenTypeOnlyImports ;
734
+
735
+ const isNotIgnored = isTypeOnlyImportAndRelevant
736
+ && newlinesBetweenTypeOnly !== 'ignore'
737
+ || ! isTypeOnlyImportAndRelevant
738
+ && newlinesBetweenImports !== 'ignore' ;
739
+
740
+ if ( isNotIgnored ) {
741
+ const shouldAssertNewlineBetweenGroups = ( isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant )
742
+ && (
743
+ newlinesBetweenTypeOnly === 'always'
744
+ || newlinesBetweenTypeOnly === 'always-and-inside-groups'
745
+ )
746
+ || ! isTypeOnlyImportAndRelevant
747
+ && ! isNormalImportNextToTypeOnlyImportAndRelevant
748
+ && (
749
+ newlinesBetweenImports === 'always'
750
+ || newlinesBetweenImports === 'always-and-inside-groups'
751
+ ) ;
752
+
753
+ const shouldAssertNoNewlineWithinGroup = ( isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant )
754
+ && newlinesBetweenTypeOnly !== 'always-and-inside-groups'
755
+ || ! isTypeOnlyImportAndRelevant
756
+ && ! isNormalImportNextToTypeOnlyImportAndRelevant
757
+ && newlinesBetweenImports !== 'always-and-inside-groups' ;
758
+
759
+ const shouldAssertNoNewlineBetweenGroup = ! isSortingTypesAmongThemselves
760
+ || ! isNormalImportNextToTypeOnlyImportAndRelevant
761
+ || newlinesBetweenTypeOnly === 'never' ;
762
+
763
+ const isTheNewlineBetweenImportsInTheSameGroup = distinctGroup
764
+ && currentImport . rank === previousImport . rank
765
+ || ! distinctGroup
766
+ && ! isStartOfDistinctGroup ;
767
+
768
+ // Let's try to cut down on linting errors sent to the user
769
+ let alreadyReported = false ;
770
+
771
+ if ( shouldAssertNewlineBetweenGroups ) {
772
+ if ( currentImport . rank !== previousImport . rank && emptyLinesBetween === 0 ) {
773
+ if ( distinctGroup || ! distinctGroup && isStartOfDistinctGroup ) {
774
+ alreadyReported = true ;
775
+ context . report ( {
776
+ node : previousImport . node ,
777
+ message : 'There should be at least one empty line between import groups' ,
778
+ fix : fixNewLineAfterImport ( context , previousImport ) ,
779
+ } ) ;
780
+ }
781
+ } else if ( emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup ) {
782
+ if ( isTheNewlineBetweenImportsInTheSameGroup ) {
783
+ alreadyReported = true ;
784
+ context . report ( {
785
+ node : previousImport . node ,
786
+ message : 'There should be no empty line within import group' ,
787
+ fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
788
+ } ) ;
789
+ }
790
+ }
791
+ } else if ( emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup ) {
792
+ alreadyReported = true ;
793
+ context . report ( {
794
+ node : previousImport . node ,
795
+ message : 'There should be no empty line between import groups' ,
796
+ fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
797
+ } ) ;
798
+ }
683
799
684
- if ( newlinesBetweenImports === 'always'
685
- || newlinesBetweenImports === 'always-and-inside-groups' ) {
686
- if ( currentImport . rank !== previousImport . rank && emptyLinesBetween === 0 ) {
687
- if ( distinctGroup || ! distinctGroup && isStartOfDistinctGroup ) {
800
+ if ( ! alreadyReported && isConsolidatingSpaceBetweenImports ) {
801
+ if ( emptyLinesBetween === 0 && currentImport . isMultiline ) {
688
802
context . report ( {
689
803
node : previousImport . node ,
690
- message : 'There should be at least one empty line between import groups ' ,
804
+ message : 'There should be at least one empty line between this import and the multi-line import that follows it ' ,
691
805
fix : fixNewLineAfterImport ( context , previousImport ) ,
692
806
} ) ;
693
- }
694
- } else if ( emptyLinesBetween > 0
695
- && newlinesBetweenImports !== 'always-and-inside-groups' ) {
696
- if ( distinctGroup && currentImport . rank === previousImport . rank || ! distinctGroup && ! isStartOfDistinctGroup ) {
807
+ } else if ( emptyLinesBetween === 0 && previousImport . isMultiline ) {
808
+ context . report ( {
809
+ node : previousImport . node ,
810
+ message : 'There should be at least one empty line between this multi-line import and the import that follows it' ,
811
+ fix : fixNewLineAfterImport ( context , previousImport ) ,
812
+ } ) ;
813
+ } else if (
814
+ emptyLinesBetween > 0
815
+ && ! previousImport . isMultiline
816
+ && ! currentImport . isMultiline
817
+ && isTheNewlineBetweenImportsInTheSameGroup
818
+ ) {
697
819
context . report ( {
698
820
node : previousImport . node ,
699
- message : 'There should be no empty line within import group' ,
821
+ message :
822
+ 'There should be no empty lines between this single-line import and the single-line import that follows it' ,
700
823
fix : removeNewLineAfterImport ( context , currentImport , previousImport ) ,
701
824
} ) ;
702
825
}
703
826
}
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
827
}
711
828
712
829
previousImport = currentImport ;
@@ -781,6 +898,24 @@ module.exports = {
781
898
'never' ,
782
899
] ,
783
900
} ,
901
+ 'newlines-between-types' : {
902
+ enum : [
903
+ 'ignore' ,
904
+ 'always' ,
905
+ 'always-and-inside-groups' ,
906
+ 'never' ,
907
+ ] ,
908
+ } ,
909
+ consolidateIslands : {
910
+ enum : [
911
+ 'inside-groups' ,
912
+ 'never' ,
913
+ ] ,
914
+ } ,
915
+ sortTypesAmongThemselves : {
916
+ type : 'boolean' ,
917
+ default : false ,
918
+ } ,
784
919
named : {
785
920
default : false ,
786
921
oneOf : [ {
@@ -836,7 +971,10 @@ module.exports = {
836
971
create ( context ) {
837
972
const options = context . options [ 0 ] || { } ;
838
973
const newlinesBetweenImports = options [ 'newlines-between' ] || 'ignore' ;
974
+ const newlinesBetweenTypeOnlyImports = options [ 'newlines-between-types' ] || newlinesBetweenImports ;
839
975
const pathGroupsExcludedImportTypes = new Set ( options . pathGroupsExcludedImportTypes || [ 'builtin' , 'external' , 'object' ] ) ;
976
+ const sortTypesAmongThemselves = options . sortTypesAmongThemselves ;
977
+ const consolidateIslands = options . consolidateIslands || 'never' ;
840
978
841
979
const named = {
842
980
types : 'mixed' ,
@@ -879,6 +1017,9 @@ module.exports = {
879
1017
const importMap = new Map ( ) ;
880
1018
const exportMap = new Map ( ) ;
881
1019
1020
+ const isTypeGroupInGroups = ranks . omittedTypes . indexOf ( 'type' ) === - 1 ;
1021
+ const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves ;
1022
+
882
1023
function getBlockImports ( node ) {
883
1024
if ( ! importMap . has ( node ) ) {
884
1025
importMap . set ( node , [ ] ) ;
@@ -932,6 +1073,7 @@ module.exports = {
932
1073
ranks ,
933
1074
getBlockImports ( node . parent ) ,
934
1075
pathGroupsExcludedImportTypes ,
1076
+ isSortingTypesAmongThemselves ,
935
1077
) ;
936
1078
937
1079
if ( named . import ) {
@@ -983,6 +1125,7 @@ module.exports = {
983
1125
ranks ,
984
1126
getBlockImports ( node . parent ) ,
985
1127
pathGroupsExcludedImportTypes ,
1128
+ isSortingTypesAmongThemselves ,
986
1129
) ;
987
1130
} ,
988
1131
CallExpression ( node ) {
@@ -1005,6 +1148,7 @@ module.exports = {
1005
1148
ranks ,
1006
1149
getBlockImports ( block ) ,
1007
1150
pathGroupsExcludedImportTypes ,
1151
+ isSortingTypesAmongThemselves ,
1008
1152
) ;
1009
1153
} ,
1010
1154
...named . require && {
@@ -1092,8 +1236,18 @@ module.exports = {
1092
1236
} ,
1093
1237
'Program:exit' ( ) {
1094
1238
importMap . forEach ( ( imported ) => {
1095
- if ( newlinesBetweenImports !== 'ignore' ) {
1096
- makeNewlinesBetweenReport ( context , imported , newlinesBetweenImports , distinctGroup ) ;
1239
+ if ( newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore' ) {
1240
+ makeNewlinesBetweenReport (
1241
+ context ,
1242
+ imported ,
1243
+ newlinesBetweenImports ,
1244
+ newlinesBetweenTypeOnlyImports ,
1245
+ distinctGroup ,
1246
+ isSortingTypesAmongThemselves ,
1247
+ consolidateIslands === 'inside-groups'
1248
+ && ( newlinesBetweenImports === 'always-and-inside-groups'
1249
+ || newlinesBetweenTypeOnlyImports === 'always-and-inside-groups' ) ,
1250
+ ) ;
1097
1251
}
1098
1252
1099
1253
if ( alphabetize . order !== 'ignore' ) {
0 commit comments