Skip to content

Commit 6e0447f

Browse files
Minh Quyjakebailey
Minh Quy
andauthored
[Feature] - Automatically create sort groups based on newlines (#48330)
Co-authored-by: Jake Bailey <[email protected]>
1 parent f654f18 commit 6e0447f

9 files changed

+141
-42
lines changed

src/services/organizeImports.ts

+46-5
Original file line numberDiff line numberDiff line change
@@ -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) {

src/testRunner/unittests/services/organizeImports.ts

-17
Original file line numberDiff line numberDiff line change
@@ -679,23 +679,6 @@ import "lib1";
679679
{ path: "/lib1.ts", content: "" },
680680
{ path: "/lib2.ts", content: "" });
681681

682-
testOrganizeImports("SortComments",
683-
/*skipDestructiveCodeActions*/ false,
684-
{
685-
path: "/test.ts",
686-
content: `
687-
// Header
688-
import "lib3";
689-
// Comment2
690-
import "lib2";
691-
// Comment1
692-
import "lib1";
693-
`,
694-
},
695-
{ path: "/lib1.ts", content: "" },
696-
{ path: "/lib2.ts", content: "" },
697-
{ path: "/lib3.ts", content: "" });
698-
699682
testOrganizeImports("AmbientModule",
700683
/*skipDestructiveCodeActions*/ false,
701684
{

tests/baselines/reference/organizeImports/SortComments.ts

-17
This file was deleted.

tests/baselines/reference/organizeImports/TopLevelAndAmbientModule.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ D();
1717

1818
// ==ORGANIZED==
1919

20-
import "lib";
2120
import D from "lib";
2221

2322
declare module "mod" {
@@ -26,5 +25,6 @@ declare module "mod" {
2625
function F(f1: {} = F1, f2: {} = F2) {}
2726
}
2827

28+
import "lib";
2929

3030
D();

tests/cases/fourslash/organizeImports6.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
//// anotherThing;
1717

1818
verify.organizeImports(
19-
`import * as anotherThing from "someopath"; /* small comment */ // single line one.
19+
`/* some comment here
20+
* and there
21+
*/
22+
import * as anotherThing from "someopath"; /* small comment */ // single line one.
23+
/* some comment here
24+
* and there
25+
*/
2026
21-
anotherThing;`);
27+
anotherThing;`);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////// polyfill
4+
////import c from "C";
5+
////// not polyfill
6+
////import d from "D";
7+
////import a from "A";
8+
////import b from "B";
9+
////
10+
////console.log(a, b, c, d)
11+
12+
verify.organizeImports(
13+
`// polyfill
14+
import c from "C";
15+
// not polyfill
16+
import a from "A";
17+
import b from "B";
18+
import d from "D";
19+
20+
console.log(a, b, c, d)`
21+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////import c from "C";
4+
////
5+
////
6+
////import d from "D";
7+
////import a from "A";
8+
////import b from "B";
9+
////
10+
////console.log(a, b, c, d)
11+
12+
verify.organizeImports(
13+
`import c from "C";
14+
15+
16+
import a from "A";
17+
import b from "B";
18+
import d from "D";
19+
20+
console.log(a, b, c, d)`
21+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////// polyfill
4+
////import c from "C";
5+
/////*
6+
////* demo
7+
////*/
8+
////import d from "D";
9+
////import a from "A";
10+
////import b from "B";
11+
////
12+
////console.log(a, b, c, d)
13+
14+
verify.organizeImports(
15+
`// polyfill
16+
import c from "C";
17+
/*
18+
* demo
19+
*/
20+
import a from "A";
21+
import b from "B";
22+
import d from "D";
23+
24+
console.log(a, b, c, d)`
25+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////import c from "C";
4+
////
5+
////import d from "D";
6+
////import a from "A"; // not count
7+
////import b from "B";
8+
////
9+
////console.log(a, b, c, d)
10+
11+
verify.organizeImports(
12+
`import c from "C";
13+
14+
import a from "A"; // not count
15+
import b from "B";
16+
import d from "D";
17+
18+
console.log(a, b, c, d)`
19+
);

0 commit comments

Comments
 (0)