@@ -16,75 +16,105 @@ module.exports = {
16
16
} ,
17
17
create : function ( context ) {
18
18
const sourceCode = context . getSourceCode ( ) ;
19
- let previousDeclaration = null ;
20
19
21
20
/**
22
21
* Gets the local name of the first imported module.
23
22
* @param {ASTNode } node - the ImportDeclaration node.
24
23
* @returns {?string } the local name of the first imported module.
25
24
*/
26
25
function getFirstLocalMemberName ( node ) {
27
- if ( node . specifiers [ 0 ] ) {
26
+ if ( node . type === 'ImportDeclaration' && node . specifiers [ 0 ] ) {
28
27
return node . specifiers [ 0 ] . local . name . toLowerCase ( ) ;
29
28
}
30
29
return null ;
31
-
32
30
}
33
31
34
32
return {
35
- ImportDeclaration ( node ) {
36
- if ( previousDeclaration ) {
37
- let currentLocalMemberName = getFirstLocalMemberName ( node ) ,
38
- previousLocalMemberName = getFirstLocalMemberName ( previousDeclaration ) ;
33
+ Program ( node ) {
34
+ let lastImportDeclaration = null ;
35
+ node . body . forEach ( ( statement , i ) => {
36
+ if ( statement . type === 'ImportDeclaration' ) {
37
+ const importSpecifiers = statement . specifiers . filter ( specifier => specifier . type === 'ImportSpecifier' ) ;
38
+ const getSortableName = specifier => specifier . local . name . toLowerCase ( ) ;
39
+ const firstUnsortedIndex = importSpecifiers . map ( getSortableName ) . findIndex ( ( name , index , array ) => array [ index - 1 ] > name ) ;
39
40
40
- if ( previousLocalMemberName &&
41
- currentLocalMemberName &&
42
- currentLocalMemberName < previousLocalMemberName
43
- ) {
44
- context . report ( {
45
- node,
46
- message : 'Imports should be sorted alphabetically.'
47
- } ) ;
48
- }
49
- }
41
+ if ( firstUnsortedIndex !== - 1 ) {
42
+ context . report ( {
43
+ node : importSpecifiers [ firstUnsortedIndex ] ,
44
+ message : "Member '{{memberName}}' of the import declaration should be sorted alphabetically." ,
45
+ data : { memberName : importSpecifiers [ firstUnsortedIndex ] . local . name } ,
46
+ fix ( fixer ) {
47
+ return fixer . replaceTextRange (
48
+ [ importSpecifiers [ 0 ] . range [ 0 ] , importSpecifiers [ importSpecifiers . length - 1 ] . range [ 1 ] ] ,
49
+ importSpecifiers
50
+ // Clone the importSpecifiers array to avoid mutating it
51
+ . slice ( )
50
52
51
- const importSpecifiers = node . specifiers . filter ( specifier => specifier . type === 'ImportSpecifier' ) ;
52
- const getSortableName = specifier => specifier . local . name . toLowerCase ( ) ;
53
- const firstUnsortedIndex = importSpecifiers . map ( getSortableName ) . findIndex ( ( name , index , array ) => array [ index - 1 ] > name ) ;
53
+ // Sort the array into the desired order
54
+ . sort ( ( specifierA , specifierB ) => {
55
+ const aName = getSortableName ( specifierA ) ;
56
+ const bName = getSortableName ( specifierB ) ;
57
+ return aName > bName ? 1 : - 1 ;
58
+ } )
54
59
55
- if ( firstUnsortedIndex !== - 1 ) {
56
- context . report ( {
57
- node : importSpecifiers [ firstUnsortedIndex ] ,
58
- message : "Member '{{memberName}}' of the import declaration should be sorted alphabetically." ,
59
- data : { memberName : importSpecifiers [ firstUnsortedIndex ] . local . name } ,
60
- fix ( fixer ) {
61
- return fixer . replaceTextRange (
62
- [ importSpecifiers [ 0 ] . range [ 0 ] , importSpecifiers [ importSpecifiers . length - 1 ] . range [ 1 ] ] ,
63
- importSpecifiers
64
- // Clone the importSpecifiers array to avoid mutating it
65
- . slice ( )
60
+ // Build a string out of the sorted list of import specifiers and the text between the originals
61
+ . reduce ( ( sourceText , specifier , index ) => {
62
+ const textAfterSpecifier = index === importSpecifiers . length - 1
63
+ ? ''
64
+ : sourceCode . getText ( ) . slice ( importSpecifiers [ index ] . range [ 1 ] , importSpecifiers [ index + 1 ] . range [ 0 ] ) ;
66
65
67
- // Sort the array into the desired order
68
- . sort ( ( specifierA , specifierB ) => {
69
- const aName = getSortableName ( specifierA ) ;
70
- const bName = getSortableName ( specifierB ) ;
71
- return aName > bName ? 1 : - 1 ;
72
- } )
66
+ return sourceText + sourceCode . getText ( specifier ) + textAfterSpecifier ;
67
+ } , '' )
68
+ ) ;
69
+ }
70
+ } ) ;
71
+ } else if ( lastImportDeclaration ) {
72
+ let currentLocalMemberName = getFirstLocalMemberName ( statement ) ;
73
+ let previousLocalMemberName = getFirstLocalMemberName ( lastImportDeclaration ) ;
74
+ if ( previousLocalMemberName &&
75
+ currentLocalMemberName &&
76
+ currentLocalMemberName < previousLocalMemberName
77
+ ) {
78
+ context . report ( {
79
+ node,
80
+ message : 'Imports should be sorted alphabetically.' ,
81
+ fix ( fixer ) {
82
+ let allImports = [ ] ;
83
+ for ( let statement of node . body ) {
84
+ if ( statement . type === 'ImportDeclaration' ) {
85
+ allImports . push ( statement ) ;
86
+ } else {
87
+ // Do not replace if there are other statements between imports.
88
+ break ;
89
+ }
90
+ }
73
91
74
- // Build a string out of the sorted list of import specifiers and the text between the originals
75
- . reduce ( ( sourceText , specifier , index ) => {
76
- const textAfterSpecifier = index === importSpecifiers . length - 1
77
- ? ''
78
- : sourceCode . getText ( ) . slice ( importSpecifiers [ index ] . range [ 1 ] , importSpecifiers [ index + 1 ] . range [ 0 ] ) ;
92
+ let sortedImports = allImports . slice ( ) . sort ( ( a , b ) => {
93
+ let aName = getFirstLocalMemberName ( a ) ;
94
+ let bName = getFirstLocalMemberName ( b ) ;
95
+ if ( aName === bName ) {
96
+ return 0 ;
97
+ }
98
+ return aName < bName ? - 1 : 1 ;
99
+ } ) ;
79
100
80
- return sourceText + sourceCode . getText ( specifier ) + textAfterSpecifier ;
81
- } , '' )
82
- ) ;
101
+ return fixer . replaceTextRange (
102
+ [ allImports [ 0 ] . range [ 0 ] , allImports [ allImports . length - 1 ] . range [ 1 ] ] ,
103
+ sortedImports . reduce ( ( sourceText , statement , index ) => {
104
+ const textAfterStatement = index === allImports . length - 1
105
+ ? ''
106
+ : sourceCode . getText ( ) . slice ( allImports [ index ] . range [ 1 ] , allImports [ index + 1 ] . range [ 0 ] ) ;
107
+ return sourceText + sourceCode . getText ( statement ) + textAfterStatement ;
108
+ } , '' )
109
+ ) ;
110
+ }
111
+ } ) ;
112
+ }
83
113
}
84
- } ) ;
85
- }
86
114
87
- previousDeclaration = node ;
115
+ lastImportDeclaration = statement ;
116
+ }
117
+ } ) ;
88
118
}
89
119
} ;
90
120
}
0 commit comments