@@ -15,16 +15,15 @@ namespace ts.OrganizeImports {
1515 preferences : UserPreferences ,
1616 skipDestructiveCodeActions ?: boolean
1717 ) {
18-
1918 const changeTracker = textChanges . ChangeTracker . fromContext ( { host, formatContext, preferences } ) ;
2019
2120 const coalesceAndOrganizeImports = ( importGroup : readonly ImportDeclaration [ ] ) => stableSort (
2221 coalesceImports ( removeUnusedImports ( importGroup , sourceFile , program , skipDestructiveCodeActions ) ) ,
2322 ( s1 , s2 ) => compareImportsOrRequireStatements ( s1 , s2 ) ) ;
2423
2524 // All of the old ImportDeclarations in the file, in syntactic order.
26- const topLevelImportDecls = sourceFile . statements . filter ( isImportDeclaration ) ;
27- organizeImportsWorker ( topLevelImportDecls , coalesceAndOrganizeImports ) ;
25+ const topLevelImportGroupDecls = groupImportsByNewlineContiguous ( sourceFile , sourceFile . statements . filter ( isImportDeclaration ) ) ;
26+ topLevelImportGroupDecls . forEach ( importGroupDecl => organizeImportsWorker ( importGroupDecl , coalesceAndOrganizeImports ) ) ;
2827
2928 // All of the old ExportDeclarations in the file, in syntactic order.
3029 const topLevelExportDecls = sourceFile . statements . filter ( isExportDeclaration ) ;
@@ -33,8 +32,8 @@ namespace ts.OrganizeImports {
3332 for ( const ambientModule of sourceFile . statements . filter ( isAmbientModule ) ) {
3433 if ( ! ambientModule . body ) continue ;
3534
36- const ambientModuleImportDecls = ambientModule . body . statements . filter ( isImportDeclaration ) ;
37- organizeImportsWorker ( ambientModuleImportDecls , coalesceAndOrganizeImports ) ;
35+ const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous ( sourceFile , ambientModule . body . statements . filter ( isImportDeclaration ) ) ;
36+ ambientModuleImportGroupDecls . forEach ( importGroupDecl => organizeImportsWorker ( importGroupDecl , coalesceAndOrganizeImports ) ) ;
3837
3938 const ambientModuleExportDecls = ambientModule . body . statements . filter ( isExportDeclaration ) ;
4039 organizeImportsWorker ( ambientModuleExportDecls , coalesceExports ) ;
@@ -88,6 +87,48 @@ namespace ts.OrganizeImports {
8887 }
8988 }
9089
90+ function groupImportsByNewlineContiguous ( sourceFile : SourceFile , importDecls : ImportDeclaration [ ] ) : ImportDeclaration [ ] [ ] {
91+ const scanner = createScanner ( sourceFile . languageVersion , /*skipTrivia*/ false , sourceFile . languageVariant ) ;
92+ const groupImports : ImportDeclaration [ ] [ ] = [ ] ;
93+ let groupIndex = 0 ;
94+ for ( const topLevelImportDecl of importDecls ) {
95+ if ( isNewGroup ( sourceFile , topLevelImportDecl , scanner ) ) {
96+ groupIndex ++ ;
97+ }
98+
99+ if ( ! groupImports [ groupIndex ] ) {
100+ groupImports [ groupIndex ] = [ ] ;
101+ }
102+
103+ groupImports [ groupIndex ] . push ( topLevelImportDecl ) ;
104+ }
105+
106+ return groupImports ;
107+ }
108+
109+ // a new group is created if an import includes at least two new line
110+ // new line from multi-line comment doesn't count
111+ function isNewGroup ( sourceFile : SourceFile , topLevelImportDecl : ImportDeclaration , scanner : Scanner ) {
112+ const startPos = topLevelImportDecl . getFullStart ( ) ;
113+ const endPos = topLevelImportDecl . getStart ( ) ;
114+ scanner . setText ( sourceFile . text , startPos , endPos - startPos ) ;
115+
116+ let numberOfNewLines = 0 ;
117+ while ( scanner . getTokenPos ( ) < endPos ) {
118+ const tokenKind = scanner . scan ( ) ;
119+
120+ if ( tokenKind === SyntaxKind . NewLineTrivia ) {
121+ numberOfNewLines ++ ;
122+
123+ if ( numberOfNewLines >= 2 ) {
124+ return true ;
125+ }
126+ }
127+ }
128+
129+ return false ;
130+ }
131+
91132 function removeUnusedImports ( oldImports : readonly ImportDeclaration [ ] , sourceFile : SourceFile , program : Program , skipDestructiveCodeActions : boolean | undefined ) {
92133 // As a precaution, consider unused import detection to be destructive (GH #43051)
93134 if ( skipDestructiveCodeActions ) {
0 commit comments