@@ -16,75 +16,105 @@ module.exports = {
1616 } ,
1717 create : function ( context ) {
1818 const sourceCode = context . getSourceCode ( ) ;
19- let previousDeclaration = null ;
2019
2120 /**
2221 * Gets the local name of the first imported module.
2322 * @param {ASTNode } node - the ImportDeclaration node.
2423 * @returns {?string } the local name of the first imported module.
2524 */
2625 function getFirstLocalMemberName ( node ) {
27- if ( node . specifiers [ 0 ] ) {
26+ if ( node . type === 'ImportDeclaration' && node . specifiers [ 0 ] ) {
2827 return node . specifiers [ 0 ] . local . name . toLowerCase ( ) ;
2928 }
3029 return null ;
31-
3230 }
3331
3432 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 ) ;
3940
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 ( )
5052
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+ } )
5459
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 ] ) ;
6665
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+ }
7391
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+ } ) ;
79100
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+ }
83113 }
84- } ) ;
85- }
86114
87- previousDeclaration = node ;
115+ lastImportDeclaration = statement ;
116+ }
117+ } ) ;
88118 }
89119 } ;
90120 }
0 commit comments