mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Add code fix to remove unreachable code (#24028)
* Add code fix to remove unreachable code * Code review * Preserve more kinds of statements
This commit is contained in:
@@ -4249,5 +4249,13 @@
|
||||
"Move to a new file": {
|
||||
"category": "Message",
|
||||
"code": 95049
|
||||
},
|
||||
"Remove unreachable code": {
|
||||
"category": "Message",
|
||||
"code": 95050
|
||||
},
|
||||
"Remove all unreachable code": {
|
||||
"category": "Message",
|
||||
"code": 95051
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"../services/codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"../services/codefixes/fixForgottenThisPropertyAccess.ts",
|
||||
"../services/codefixes/fixUnusedIdentifier.ts",
|
||||
"../services/codefixes/fixUnreachableCode.ts",
|
||||
"../services/codefixes/fixJSDocTypes.ts",
|
||||
"../services/codefixes/fixAwaitInSyncFunction.ts",
|
||||
"../services/codefixes/disableJsDiagnostics.ts",
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"../services/codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"../services/codefixes/fixForgottenThisPropertyAccess.ts",
|
||||
"../services/codefixes/fixUnusedIdentifier.ts",
|
||||
"../services/codefixes/fixUnreachableCode.ts",
|
||||
"../services/codefixes/fixJSDocTypes.ts",
|
||||
"../services/codefixes/fixAwaitInSyncFunction.ts",
|
||||
"../services/codefixes/disableJsDiagnostics.ts",
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
"../services/codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"../services/codefixes/fixForgottenThisPropertyAccess.ts",
|
||||
"../services/codefixes/fixUnusedIdentifier.ts",
|
||||
"../services/codefixes/fixUnreachableCode.ts",
|
||||
"../services/codefixes/fixJSDocTypes.ts",
|
||||
"../services/codefixes/fixAwaitInSyncFunction.ts",
|
||||
"../services/codefixes/disableJsDiagnostics.ts",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "fixUnreachableCode";
|
||||
const errorCodes = [Diagnostics.Unreachable_code_detected.code];
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start));
|
||||
return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unreachable_code, fixId, Diagnostics.Remove_all_unreachable_code)];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start)),
|
||||
});
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number): void {
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const statement = findAncestor(token, isStatement);
|
||||
Debug.assert(statement.getStart(sourceFile) === token.getStart(sourceFile));
|
||||
|
||||
const container = (isBlock(statement.parent) ? statement.parent : statement).parent;
|
||||
switch (container.kind) {
|
||||
case SyntaxKind.IfStatement:
|
||||
if ((container as IfStatement).elseStatement) {
|
||||
if (isBlock(statement.parent)) {
|
||||
changes.deleteNodeRange(sourceFile, first(statement.parent.statements), last(statement.parent.statements));
|
||||
}
|
||||
else {
|
||||
changes.replaceNode(sourceFile, statement, createBlock(emptyArray));
|
||||
}
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
changes.deleteNode(sourceFile, container);
|
||||
break;
|
||||
default:
|
||||
if (isBlock(statement.parent)) {
|
||||
split(sliceAfter(statement.parent.statements, statement), shouldRemove, (start, end) => changes.deleteNodeRange(sourceFile, start, end));
|
||||
}
|
||||
else {
|
||||
changes.deleteNode(sourceFile, statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shouldRemove(s: Statement): boolean {
|
||||
// Don't remove statements that can validly be used before they appear.
|
||||
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) &&
|
||||
// `var x;` may declare a variable used above
|
||||
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer));
|
||||
}
|
||||
|
||||
function isPurelyTypeDeclaration(s: Statement): boolean {
|
||||
switch (s.kind) {
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
return true;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated;
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return hasModifier(s, ModifierFlags.Const);
|
||||
}
|
||||
}
|
||||
|
||||
function sliceAfter<T>(arr: ReadonlyArray<T>, value: T): ReadonlyArray<T> {
|
||||
const index = arr.indexOf(value);
|
||||
Debug.assert(index !== -1);
|
||||
return arr.slice(index);
|
||||
}
|
||||
|
||||
// Calls 'cb' with the start and end of each range where 'pred' is true.
|
||||
function split<T>(arr: ReadonlyArray<T>, pred: (t: T) => boolean, cb: (start: T, end: T) => void): void {
|
||||
let start: T | undefined;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const value = arr[i];
|
||||
if (pred(value)) {
|
||||
start = start || value;
|
||||
}
|
||||
else {
|
||||
if (start) {
|
||||
cb(start, arr[i - 1]);
|
||||
start = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start) cb(start, arr[arr.length - 1]);
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,7 @@
|
||||
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"codefixes/fixForgottenThisPropertyAccess.ts",
|
||||
"codefixes/fixUnusedIdentifier.ts",
|
||||
"codefixes/fixUnreachableCode.ts",
|
||||
"codefixes/fixJSDocTypes.ts",
|
||||
"codefixes/fixAwaitInSyncFunction.ts",
|
||||
"codefixes/disableJsDiagnostics.ts",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////function f() {
|
||||
//// return f();
|
||||
//// return 1;
|
||||
//// function f() {}
|
||||
//// return 2;
|
||||
//// type T = number;
|
||||
//// interface I {}
|
||||
//// const enum E {}
|
||||
//// enum E {}
|
||||
//// namespace N { export type T = number; }
|
||||
//// namespace N { export const x = 0; }
|
||||
//// var x;
|
||||
//// var y = 0;
|
||||
////}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Remove unreachable code",
|
||||
index: 0,
|
||||
newFileContent:
|
||||
`function f() {
|
||||
return f();
|
||||
function f() {}
|
||||
type T = number;
|
||||
interface I {}
|
||||
const enum E {}
|
||||
namespace N { export type T = number; }
|
||||
var x;
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////if (false) a;
|
||||
////if (false) {
|
||||
//// a;
|
||||
////}
|
||||
////
|
||||
////// No good way to delete just the 'if' part
|
||||
////if (false) a; else b;
|
||||
////if (false) {
|
||||
//// a;
|
||||
////} else {
|
||||
//// b;
|
||||
////}
|
||||
////
|
||||
////while (false) a;
|
||||
////while (false) {
|
||||
//// a;
|
||||
////}
|
||||
////
|
||||
////for (let x = 0; false; ++x) a;
|
||||
////for (let x = 0; false; ++x) {
|
||||
//// a;
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixUnreachableCode",
|
||||
fixAllDescription: "Remove all unreachable code",
|
||||
newFileContent:
|
||||
`
|
||||
// No good way to delete just the 'if' part
|
||||
if (false) { } else b;
|
||||
if (false) {
|
||||
} else {
|
||||
b;
|
||||
}
|
||||
|
||||
|
||||
`,
|
||||
});
|
||||
Reference in New Issue
Block a user