Skip to content

Commit fd2dd2e

Browse files
authored
Merge pull request #17078 from minestarks/removeimportfix
Code fix to remove unused named import should preserve default import
2 parents 7b5e1e9 + abb229e commit fd2dd2e

File tree

5 files changed

+69
-27
lines changed

5 files changed

+69
-27
lines changed

src/harness/fourslash.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,14 @@ namespace FourSlash {
18061806
this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText);
18071807
const editDelta = edit.newText.length - edit.span.length;
18081808
if (offsetStart <= this.currentCaretPosition) {
1809-
this.currentCaretPosition += editDelta;
1809+
if (offsetEnd <= this.currentCaretPosition) {
1810+
// The entirety of the edit span falls before the caret position, shift the caret accordingly
1811+
this.currentCaretPosition += editDelta;
1812+
}
1813+
else {
1814+
// The span being replaced includes the caret position, place the caret at the beginning of the span
1815+
this.currentCaretPosition = offsetStart;
1816+
}
18101817
}
18111818
runningOffset += editDelta;
18121819
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
@@ -2354,7 +2361,7 @@ namespace FourSlash {
23542361
private applyCodeActions(actions: ts.CodeAction[], index?: number): void {
23552362
if (index === undefined) {
23562363
if (!(actions && actions.length === 1)) {
2357-
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
2364+
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : "" }`);
23582365
}
23592366
index = 0;
23602367
}

src/services/codefixes/fixUnusedIdentifier.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ namespace ts.codefix {
2525
return [deleteNode(token.parent)];
2626

2727
default:
28-
return [deleteDefault()];
28+
return deleteDefault();
2929
}
3030

31-
function deleteDefault() {
31+
function deleteDefault(): CodeAction[] | undefined {
3232
if (isDeclarationName(token)) {
33-
return deleteNode(token.parent);
33+
return [deleteNode(token.parent)];
3434
}
3535
else if (isLiteralComputedPropertyDeclarationName(token)) {
36-
return deleteNode(token.parent.parent);
36+
return [deleteNode(token.parent.parent)];
3737
}
3838
else {
3939
return undefined;
@@ -87,20 +87,16 @@ namespace ts.codefix {
8787
case SyntaxKind.ImportSpecifier:
8888
const namedImports = <NamedImports>parent.parent;
8989
if (namedImports.elements.length === 1) {
90-
// Only 1 import and it is unused. So the entire declaration should be removed.
91-
const importSpec = getAncestor(identifier, SyntaxKind.ImportDeclaration);
92-
return [deleteNode(importSpec)];
90+
return deleteNamedImportBinding(namedImports);
9391
}
9492
else {
9593
// delete import specifier
9694
return [deleteNodeInList(parent)];
9795
}
9896

99-
// handle case where "import d, * as ns from './file'"
100-
// or "'import {a, b as ns} from './file'"
10197
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
10298
const importClause = <ImportClause>parent;
103-
if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'|
99+
if (!importClause.namedBindings) { // |import d from './file'|
104100
const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration);
105101
return [deleteNode(importDecl)];
106102
}
@@ -118,22 +114,30 @@ namespace ts.codefix {
118114
}
119115

120116
case SyntaxKind.NamespaceImport:
121-
const namespaceImport = <NamespaceImport>parent;
122-
if (namespaceImport.name === identifier && !(<ImportClause>namespaceImport.parent).name) {
123-
const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration);
124-
return [deleteNode(importDecl)];
125-
}
126-
else {
127-
const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1, /*includeJsDocComment*/ false);
128-
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
129-
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart);
130-
return [deleteRange({ pos: startPosition, end: namespaceImport.end })];
131-
}
132-
return [deleteRange(namespaceImport)];
133-
}
117+
return deleteNamedImportBinding(<NamespaceImport>parent);
134118

135119
default:
136-
return [deleteDefault()];
120+
return deleteDefault();
121+
}
122+
}
123+
124+
function deleteNamedImportBinding(namedBindings: NamedImportBindings): CodeAction[] | undefined {
125+
if ((<ImportClause>namedBindings.parent).name) {
126+
// Delete named imports while preserving the default import
127+
// import d|, * as ns| from './file'
128+
// import d|, { a }| from './file'
129+
const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false);
130+
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
131+
return [deleteRange({ pos: previousToken.getStart(), end: namedBindings.end })];
132+
}
133+
return undefined;
134+
}
135+
else {
136+
// Delete the entire import declaration
137+
// |import * as ns from './file'|
138+
// |import { a } from './file'|
139+
const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration);
140+
return [deleteNode(importDecl)];
137141
}
138142
}
139143

tests/cases/fourslash/formattingOnEnter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@
66

77
goTo.marker();
88
edit.insertLine("");
9-
verify.currentLineContentIs('class bar {');
9+
verify.currentFileContentIs(
10+
`class foo { }
11+
class bar {
12+
}
13+
// new line here`);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @Filename: file2.ts
5+
//// [| import A, { x } from './a'; |]
6+
//// console.log(A);
7+
8+
// @Filename: file1.ts
9+
//// export default 10;
10+
//// export var x = 10;
11+
12+
verify.rangeAfterCodeFix("import A from './a';");
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @Filename: file2.ts
5+
//// [| import /* 1 */ A /* 2 */, /* 3 */ { /* 4 */ x /* 5 */ } /* 6 */ from './a'; |]
6+
//// console.log(A);
7+
8+
// @Filename: file1.ts
9+
//// export default 10;
10+
//// export var x = 10;
11+
12+
13+
// It's ambiguous which token comment /* 6 */ applies to or whether it should be removed.
14+
// In the current implementation the comment is left behind, but this behavior isn't a requirement.
15+
verify.rangeAfterCodeFix("import /* 1 */ A /* 2 */ /* 6 */ from './a';");

0 commit comments

Comments
 (0)