import * as detectNewline from "detect-newline" ; import { IImport , IParser , NamedMember } from "import-sort-parser" ; import { INamedMemberSorterFunction , ISorterFunction , IStyle , } from "import-sort-style" ; import StyleAPI from "./style/StyleAPI" ; export interface ISortResult { code : string ; changes : ICodeChange [ ] ; } export interface ICodeChange { start : number ; end : number ; code : string ; note ? : string ; } export default function importSort ( code , rawParser , rawStyle , file ? , options ? ) { let style ; const parser = typeof rawParser === "string" ? require ( rawParser ) : ( rawParser as IParser ) ; if ( typeof rawStyle === "string" ) { style = require ( rawStyle ) ; if ( ( style as any ) . default ) { style = ( style as any ) . default ; } } else { style = rawStyle as IStyle ; } return sortImports ( code , parser , style , file , options ) ; } export function sortImports ( code , parser , style , file ? , options ? ) { const items = addFallback ( style , file , options || { } ) ( StyleAPI ) ; const buckets = items . map ( ( ) => [ ] ) ; const imports = parser . parseImports ( code , { file , } ) ; if ( imports . length === 0 ) { return { code , changes : [ ] } ; } const eol = detectNewline . graceful ( code ) ; const changes = [ ] ; for ( const imported of imports ) { let sortedImport = imported ; const index = items . findIndex ( item => { sortedImport = sortNamedMembers ( imported , item . sortNamedMembers ) ; return ! ! item . match && item . match ( sortedImport ) ; } ) ; if ( index !== - 1 ) { buckets [ index ] . push ( sortedImport ) ; } } buckets . forEach ( ( bucket , index ) => { const { sort } = items [ index ] ; if ( ! sort ) { return ; } if ( ! Array . isArray ( sort ) ) { bucket . sort ( sort as ISorterFunction ) ; return ; } const sorters = sort as ISorterFunction [ ] ; if ( sorters . length === 0 ) { return ; } const multiSort = ( first , second ) : number => { let sorterIndex = 0 ; let comparison = 0 ; while ( comparison === 0 && sorters [ sorterIndex ] ) { comparison = sorters [ sorterIndex ] ( first , second ) ; sorterIndex += 1 ; } return comparison ; } ; bucket . sort ( multiSort ) ; } ) ; let importsCode = "" ; let separator = false ; buckets . forEach ( ( bucket , index ) => { if ( bucket . length > 0 && separator ) { importsCode += eol ; separator = false ; } bucket . forEach ( imported => { const importString = parser . formatImport ( code , imported , eol ) ; importsCode += importString + eol ; } ) ; if ( items [ index ] . separator && importsCode !== "" ) { separator = true ; } } ) ; let sortedCode = code ; imports . slice ( ) . reverse ( ) . forEach ( imported => { let importEnd = imported . end ; if ( sortedCode . charAt ( imported . end ) . match ( /\s/ ) ) { importEnd += 1 ; } changes . push ( { start : imported . start , end : importEnd , code : "" , note : "import-remove" , } ) ; sortedCode = sortedCode . slice ( 0 , imported . start ) + sortedCode . slice ( importEnd , code . length ) ; } ) ; const { start } = imports [ 0 ] ; let before = code . substring ( 0 , start ) ; let after = sortedCode . substring ( start , sortedCode . length ) ; const oldBeforeLength = before . length ; const oldAfterLength = after . length ; let beforeChange ; let afterChange ; before = before . replace ( /\s+$/ , match => { beforeChange = { start : start - match . length , end : start , code : eol + eol , note : "before-collapse" , } ; return eol + eol ; } ) ; after = after . replace ( /^\s+/ , match => { afterChange = { start , end : start + match . length , code : eol , note : "after-collapse" , } ; return eol ; } ) ; if ( before . match ( /^\s+$/ ) ) { beforeChange = { start : start - oldBeforeLength , end : start , code : "" , note : "before-trim" , } ; before = "" ; } if ( after . match ( /^\s+$/ ) ) { afterChange = { start , end : start + oldAfterLength , code : "" , note : "after-trim" , } ; after = "" ; } if ( afterChange ) { changes . push ( afterChange ) ; } if ( beforeChange ) { changes . push ( beforeChange ) ; } const change = { start : before . length , end : before . length , code : importsCode , note : "imports" , } ; changes . push ( change ) ; if ( code === before + importsCode + after ) { return { code , changes : [ ] } ; } return { code : before + importsCode + after , changes , } ; } function sortNamedMembers ( imported , rawSort ? ) { const sort = rawSort ; if ( ! sort ) { return imported ; } if ( ! Array . isArray ( sort ) ) { const singleSortedImport = { ... imported } ; singleSortedImport . namedMembers = [ ... imported . namedMembers ] . sort ( sort as INamedMemberSorterFunction ) ; return singleSortedImport ; } const sorters = sort as INamedMemberSorterFunction [ ] ; if ( sorters . length === 0 ) { return imported ; } const multiSort = ( first , second ) : number => { let sorterIndex = 0 ; let comparison = 0 ; while ( comparison === 0 && sorters [ sorterIndex ] ) { comparison = sorters [ sorterIndex ] ( first , second ) ; sorterIndex += 1 ; } return comparison ; } ; const sortedImport = { ... imported } ; sortedImport . namedMembers = [ ... imported . namedMembers ] . sort ( multiSort ) ; return sortedImport ; } export function applyChanges ( code , changes ) { let changedCode = code ; for ( const change of changes ) { changedCode = changedCode . slice ( 0 , change . start ) + change . code + changedCode . slice ( change . end , changedCode . length ) ; } return changedCode ; } function addFallback ( style , file ? , options ? ) { return styleApi => { const items = [ { separator : true } , { match : styleApi . always } ] ; return style ( styleApi , file , options ) . concat ( items ) ; } ; }