Skip to content

Commit b31aa4e

Browse files
authored
Merge pull request #22087 from amcasey/AmbientModuleImports
Organize imports in ambient modules
2 parents 4d284d6 + 8ead7ab commit b31aa4e

File tree

5 files changed

+124
-31
lines changed

5 files changed

+124
-31
lines changed

src/compiler/utilities.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,8 @@ namespace ts {
425425
}
426426

427427
export function isAmbientModule(node: Node): boolean {
428-
return node && node.kind === SyntaxKind.ModuleDeclaration &&
429-
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
428+
return node && isModuleDeclaration(node) &&
429+
(node.name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(node));
430430
}
431431

432432
export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration {

src/harness/unittests/organizeImports.ts

+37
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,43 @@ F2();
325325
/*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K
326326
327327
F1();
328+
`,
329+
},
330+
libFile);
331+
332+
testOrganizeImports("AmbientModule",
333+
{
334+
path: "/test.ts",
335+
content: `
336+
declare module "mod" {
337+
import { F1 } from "lib";
338+
import * as NS from "lib";
339+
import { F2 } from "lib";
340+
341+
function F(f1: {} = F1, f2: {} = F2) {}
342+
}
343+
`,
344+
},
345+
libFile);
346+
347+
testOrganizeImports("TopLevelAndAmbientModule",
348+
{
349+
path: "/test.ts",
350+
content: `
351+
import D from "lib";
352+
353+
declare module "mod" {
354+
import { F1 } from "lib";
355+
import * as NS from "lib";
356+
import { F2 } from "lib";
357+
358+
function F(f1: {} = F1, f2: {} = F2) {}
359+
}
360+
361+
import E from "lib";
362+
import "lib";
363+
364+
D();
328365
`,
329366
},
330367
libFile);

src/services/organizeImports.ts

+38-29
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,55 @@ namespace ts.OrganizeImports {
1313
host: LanguageServiceHost,
1414
program: Program) {
1515

16-
// TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
16+
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
1717

1818
// All of the old ImportDeclarations in the file, in syntactic order.
19-
const oldImportDecls = sourceFile.statements.filter(isImportDeclaration);
19+
const topLevelImportDecls = sourceFile.statements.filter(isImportDeclaration);
20+
organizeImportsWorker(topLevelImportDecls);
2021

21-
if (oldImportDecls.length === 0) {
22-
return [];
22+
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
23+
const ambientModuleBody = getModuleBlock(ambientModule as ModuleDeclaration);
24+
const ambientModuleImportDecls = ambientModuleBody.statements.filter(isImportDeclaration);
25+
organizeImportsWorker(ambientModuleImportDecls);
2326
}
2427

25-
const oldImportGroups = group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier));
28+
return changeTracker.getChanges();
2629

27-
const sortedImportGroups = stableSort(oldImportGroups, (group1, group2) =>
28-
compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier));
30+
function organizeImportsWorker(oldImportDecls: ReadonlyArray<ImportDeclaration>) {
31+
if (length(oldImportDecls) === 0) {
32+
return;
33+
}
2934

30-
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
31-
getExternalModuleName(importGroup[0].moduleSpecifier)
32-
? coalesceImports(removeUnusedImports(importGroup, sourceFile, program))
33-
: importGroup);
35+
const oldImportGroups = group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier));
36+
const sortedImportGroups = stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier));
37+
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
38+
getExternalModuleName(importGroup[0].moduleSpecifier)
39+
? coalesceImports(removeUnusedImports(importGroup, sourceFile, program))
40+
: importGroup);
3441

35-
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
36-
37-
// Delete or replace the first import.
38-
if (newImportDecls.length === 0) {
39-
changeTracker.deleteNode(sourceFile, oldImportDecls[0]);
40-
}
41-
else {
42-
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
43-
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, {
44-
useNonAdjustedStartPosition: false,
45-
useNonAdjustedEndPosition: false,
46-
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
47-
});
48-
}
42+
// Delete or replace the first import.
43+
if (newImportDecls.length === 0) {
44+
changeTracker.deleteNode(sourceFile, oldImportDecls[0]);
45+
}
46+
else {
47+
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
48+
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, {
49+
useNonAdjustedStartPosition: false,
50+
useNonAdjustedEndPosition: false,
51+
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
52+
});
53+
}
4954

50-
// Delete any subsequent imports.
51-
for (let i = 1; i < oldImportDecls.length; i++) {
52-
changeTracker.deleteNode(sourceFile, oldImportDecls[i]);
55+
// Delete any subsequent imports.
56+
for (let i = 1; i < oldImportDecls.length; i++) {
57+
changeTracker.deleteNode(sourceFile, oldImportDecls[i]);
58+
}
5359
}
60+
}
5461

55-
return changeTracker.getChanges();
62+
function getModuleBlock(moduleDecl: ModuleDeclaration): ModuleBlock | undefined {
63+
const body = moduleDecl.body;
64+
return body && !isIdentifier(body) && (isModuleBlock(body) ? body : getModuleBlock(body));
5665
}
5766

5867
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>, sourceFile: SourceFile, program: Program) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// ==ORIGINAL==
2+
3+
declare module "mod" {
4+
import { F1 } from "lib";
5+
import * as NS from "lib";
6+
import { F2 } from "lib";
7+
8+
function F(f1: {} = F1, f2: {} = F2) {}
9+
}
10+
11+
// ==ORGANIZED==
12+
13+
declare module "mod" {
14+
import { F1, F2 } from "lib";
15+
16+
function F(f1: {} = F1, f2: {} = F2) {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// ==ORIGINAL==
2+
3+
import D from "lib";
4+
5+
declare module "mod" {
6+
import { F1 } from "lib";
7+
import * as NS from "lib";
8+
import { F2 } from "lib";
9+
10+
function F(f1: {} = F1, f2: {} = F2) {}
11+
}
12+
13+
import E from "lib";
14+
import "lib";
15+
16+
D();
17+
18+
// ==ORGANIZED==
19+
20+
import "lib";
21+
import D from "lib";
22+
23+
declare module "mod" {
24+
import { F1, F2 } from "lib";
25+
26+
function F(f1: {} = F1, f2: {} = F2) {}
27+
}
28+
29+
30+
D();

0 commit comments

Comments
 (0)