Skip to content

Commit b8dcf27

Browse files
authored
Merge pull request microsoft#31482 from andrewbranch/bug/31338
Organize imports: don’t delete import declarations used for module augmentation
2 parents 953153e + 60d0bb9 commit b8dcf27

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

src/services/organizeImports.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ namespace ts.OrganizeImports {
8989
const usedImports: ImportDeclaration[] = [];
9090

9191
for (const importDecl of oldImports) {
92-
const {importClause} = importDecl;
92+
const { importClause, moduleSpecifier } = importDecl;
9393

9494
if (!importClause) {
9595
// Imports without import clauses are assumed to be included for their side effects and are not removed.
@@ -125,6 +125,23 @@ namespace ts.OrganizeImports {
125125
if (name || namedBindings) {
126126
usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings));
127127
}
128+
// If a module is imported to be augmented, it’s used
129+
else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) {
130+
// If we’re in a declaration file, it’s safe to remove the import clause from it
131+
if (sourceFile.isDeclarationFile) {
132+
usedImports.push(createImportDeclaration(
133+
importDecl.decorators,
134+
importDecl.modifiers,
135+
/*importClause*/ undefined,
136+
moduleSpecifier));
137+
}
138+
// If we’re not in a declaration file, we can’t remove the import clause even though
139+
// the imported symbols are unused, because removing them makes it look like the import
140+
// declaration has side effects, which will cause it to be preserved in the JS emit.
141+
else {
142+
usedImports.push(importDecl);
143+
}
144+
}
128145
}
129146

130147
return usedImports;
@@ -135,6 +152,13 @@ namespace ts.OrganizeImports {
135152
}
136153
}
137154

155+
function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) {
156+
const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text;
157+
return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName =>
158+
isStringLiteral(moduleName)
159+
&& moduleName.text === moduleSpecifierText);
160+
}
161+
138162
function getExternalModuleName(specifier: Expression) {
139163
return specifier !== undefined && isStringLiteralLike(specifier)
140164
? specifier.text

src/testRunner/unittests/services/organizeImports.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
namespace ts {
2-
describe("unittests:: services:: Organize imports", () => {
2+
describe("unittests:: services:: organizeImports", () => {
33
describe("Sort imports", () => {
44
it("Sort - non-relative vs non-relative", () => {
55
assertSortsBefore(
@@ -343,6 +343,36 @@ import { } from "lib";
343343
},
344344
libFile);
345345

346+
testOrganizeImports("Unused_false_positive_module_augmentation",
347+
{
348+
path: "/test.d.ts",
349+
content: `
350+
import foo from 'foo';
351+
import { Caseless } from 'caseless';
352+
353+
declare module 'foo' {}
354+
declare module 'caseless' {
355+
interface Caseless {
356+
test(name: KeyType): boolean;
357+
}
358+
}`
359+
});
360+
361+
testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file",
362+
{
363+
path: "/test.ts",
364+
content: `
365+
import foo from 'foo';
366+
import { Caseless } from 'caseless';
367+
368+
declare module 'foo' {}
369+
declare module 'caseless' {
370+
interface Caseless {
371+
test(name: KeyType): boolean;
372+
}
373+
}`
374+
});
375+
346376
testOrganizeImports("Unused_false_positive_shorthand_assignment",
347377
{
348378
path: "/test.ts",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ==ORIGINAL==
2+
3+
import foo from 'foo';
4+
import { Caseless } from 'caseless';
5+
6+
declare module 'foo' {}
7+
declare module 'caseless' {
8+
interface Caseless {
9+
test(name: KeyType): boolean;
10+
}
11+
}
12+
// ==ORGANIZED==
13+
14+
import 'caseless';
15+
import 'foo';
16+
17+
declare module 'foo' {}
18+
declare module 'caseless' {
19+
interface Caseless {
20+
test(name: KeyType): boolean;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ==ORIGINAL==
2+
3+
import foo from 'foo';
4+
import { Caseless } from 'caseless';
5+
6+
declare module 'foo' {}
7+
declare module 'caseless' {
8+
interface Caseless {
9+
test(name: KeyType): boolean;
10+
}
11+
}
12+
// ==ORGANIZED==
13+
14+
import { Caseless } from 'caseless';
15+
import foo from 'foo';
16+
17+
declare module 'foo' {}
18+
declare module 'caseless' {
19+
interface Caseless {
20+
test(name: KeyType): boolean;
21+
}
22+
}

0 commit comments

Comments
 (0)