diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index fcb93c45dc1dd..501d7a21a1624 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -48121,6 +48121,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkAliasSymbol(node);
if (node.kind === SyntaxKind.ImportSpecifier) {
checkModuleExportName(node.propertyName);
+
+ // Check for duplicate import names
+ const importSpecifier = node;
+ const namecheck = getTextOfIdentifierOrLiteral(importSpecifier.name);
+ const propertyNamecheck = importSpecifier.propertyName ? getTextOfIdentifierOrLiteral(importSpecifier.propertyName) : "";
+
+ if (namecheck === propertyNamecheck) {
+ errorOrSuggestion(/*isError*/ false, importSpecifier, Diagnostics.Redundant_named_import_0, namecheck);
+ }
+
if (
moduleExportNameIsDefault(node.propertyName || node.name) &&
getESModuleInterop(compilerOptions) &&
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 0d7e0bb39a8d4..1cf1d35502746 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -5337,6 +5337,11 @@
"category": "Error",
"code": 6189
},
+ "Redundant named import '{0}'.": {
+ "category": "Suggestion",
+ "code": 6190,
+ "reportsUnnecessary": true
+ },
"Whether to keep outdated console output in watch mode instead of clearing the screen.": {
"category": "Message",
"code": 6191
@@ -8267,6 +8272,14 @@
"category": "Message",
"code": 95197
},
+ "Simplify redundant import '{0}'": {
+ "category": "Message",
+ "code": 95198
+ },
+ "Simplify all redundant imports": {
+ "category": "Message",
+ "code": 95199
+ },
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
diff --git a/src/services/_namespaces/ts.codefix.ts b/src/services/_namespaces/ts.codefix.ts
index 3cf05630388de..2aecc1ece0566 100644
--- a/src/services/_namespaces/ts.codefix.ts
+++ b/src/services/_namespaces/ts.codefix.ts
@@ -74,3 +74,4 @@ export * from "../codefixes/splitTypeOnlyImport.js";
export * from "../codefixes/convertConstToLet.js";
export * from "../codefixes/fixExpectedComma.js";
export * from "../codefixes/fixAddVoidToPromise.js";
+export * from "../codefixes/removeUnnecessaryNamedImport.js";
diff --git a/src/services/codefixes/removeUnnecessaryNamedImport.ts b/src/services/codefixes/removeUnnecessaryNamedImport.ts
new file mode 100644
index 0000000000000..7c711b4927259
--- /dev/null
+++ b/src/services/codefixes/removeUnnecessaryNamedImport.ts
@@ -0,0 +1,53 @@
+import {
+ codeFixAll,
+ createCodeFixAction,
+ registerCodeFix,
+} from "../_namespaces/ts.codefix.js";
+import {
+ Diagnostics,
+ factory,
+ getTokenAtPosition,
+ isImportSpecifier,
+ SourceFile,
+ textChanges,
+ TextSpan,
+ tryCast,
+} from "../_namespaces/ts.js";
+
+const fixId = "removeUnnecessaryNamedImport";
+const errorCodes = [
+ Diagnostics.Redundant_named_import_0.code,
+];
+
+registerCodeFix({
+ errorCodes,
+ getCodeActions: function getCodeActionsToRemoveUnnecessaryNamedImport(context) {
+ const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span));
+ const token = getTokenAtPosition(context.sourceFile, context.span.start);
+ const importSpecifier = tryCast(token.parent, isImportSpecifier);
+
+ if (!importSpecifier) {
+ return;
+ }
+
+ if (changes.length > 0) {
+ return [createCodeFixAction(fixId, changes, [Diagnostics.Simplify_redundant_import_0, importSpecifier.name.text], fixId, Diagnostics.Simplify_all_redundant_imports)];
+ }
+ },
+ fixIds: [fixId],
+ getAllCodeActions: context => {
+ return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag));
+ },
+});
+
+function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) {
+ const token = getTokenAtPosition(sourceFile, span.start);
+ const importSpecifier = tryCast(token.parent, isImportSpecifier);
+ if (!importSpecifier) {
+ return;
+ }
+
+ if (importSpecifier.propertyName && importSpecifier.propertyName.text === importSpecifier.name.text) {
+ changeTracker.replaceNode(sourceFile, importSpecifier, factory.updateImportSpecifier(importSpecifier, importSpecifier.isTypeOnly, /*propertyName*/ undefined, importSpecifier.name));
+ }
+}
diff --git a/tests/cases/fourslash/codeFixRemoveUnnecessaryNamedImports.ts b/tests/cases/fourslash/codeFixRemoveUnnecessaryNamedImports.ts
new file mode 100644
index 0000000000000..a8f18c080da28
--- /dev/null
+++ b/tests/cases/fourslash/codeFixRemoveUnnecessaryNamedImports.ts
@@ -0,0 +1,31 @@
+///
+
+// @filename: /a.ts
+////export const foo = () => console.log('Hello world!');
+////export const bar = () => console.log('Hello world!');
+
+// @filename: /b.ts
+////import { foo as foo, bar as bar } from "./a";
+////foo();
+////bar();
+
+goTo.file("/b.ts");
+
+verify.codeFix({
+ description: 'Simplify redundant import \'foo\'',
+ index: 0,
+ newFileContent:
+`import { foo, bar as bar } from "./a";
+foo();
+bar();`
+});
+
+verify.codeFixAll({
+ fixAllDescription: ts.Diagnostics.Simplify_all_redundant_imports.message,
+ fixId: "removeUnnecessaryNamedImport",
+ newFileContent:
+`import { foo, bar } from "./a";
+foo();
+bar();`
+});
+
\ No newline at end of file
diff --git a/tests/cases/fourslash/redundantImportDiagnostic.ts b/tests/cases/fourslash/redundantImportDiagnostic.ts
new file mode 100644
index 0000000000000..993ed8d79f5e4
--- /dev/null
+++ b/tests/cases/fourslash/redundantImportDiagnostic.ts
@@ -0,0 +1,21 @@
+///
+
+// @filename: /a.ts
+//// export const bar = () => console.log('Hello world!');
+
+// @filename: /b.ts
+//// import { bar as bar } from "./a";
+//// bar();
+
+
+goTo.file("/b.ts");
+
+
+verify.getSuggestionDiagnostics([
+ {
+ message: "Redundant named import 'bar'.",
+ code: ts.Diagnostics.Redundant_named_import_0.code,
+ range: {pos: 9, end: 19, fileName:"/b.ts"},
+ reportsUnnecessary: true,
+ }
+]);
\ No newline at end of file