ChangeTracker: Combine deleteDeclaration, deleteNode, and deleteNodeInList (#25276)

* ChangeTracker: Combine deleteDeclaration, deleteNode, and deleteNodeInList

* Change name

* Update API (#24966)
This commit is contained in:
Andy 2018-06-29 19:53:32 -07:00 committed by GitHub
parent 18e3f487a4
commit ded446573a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 127 additions and 156 deletions

View File

@ -13,7 +13,6 @@ namespace ts.codefix {
});
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void {
const deletedNodes: { node: Node, inList: boolean }[] = [];
const ctorSymbol = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position))!;
if (!ctorSymbol || !(ctorSymbol.flags & (SymbolFlags.Function | SymbolFlags.Variable))) {
@ -28,7 +27,7 @@ namespace ts.codefix {
switch (ctorDeclaration.kind) {
case SyntaxKind.FunctionDeclaration:
precedingNode = ctorDeclaration;
deleteNode(ctorDeclaration);
changes.delete(sourceFile, ctorDeclaration);
newClassDeclaration = createClassFromFunctionDeclaration(ctorDeclaration as FunctionDeclaration);
break;
@ -37,10 +36,10 @@ namespace ts.codefix {
newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration);
if ((<VariableDeclarationList>ctorDeclaration.parent).declarations.length === 1) {
copyComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
deleteNode(precedingNode);
changes.delete(sourceFile, precedingNode);
}
else {
deleteNode(ctorDeclaration, /*inList*/ true);
changes.delete(sourceFile, ctorDeclaration);
}
break;
}
@ -53,21 +52,6 @@ namespace ts.codefix {
// Because the preceding node could be touched, we need to insert nodes before delete nodes.
changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration);
for (const { node, inList } of deletedNodes) {
if (inList) {
changes.deleteNodeInList(sourceFile, node);
}
else {
changes.deleteNode(sourceFile, node);
}
}
function deleteNode(node: Node, inList = false) {
// If parent node has already been deleted, do nothing
if (!deletedNodes.some(n => isNodeDescendantOf(node, n.node))) {
deletedNodes.push({ node, inList });
}
}
function createClassElementsFromSymbol(symbol: Symbol) {
const memberElements: ClassElement[] = [];
@ -115,7 +99,7 @@ namespace ts.codefix {
// delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
const nodeToDelete = assignmentBinaryExpression.parent && assignmentBinaryExpression.parent.kind === SyntaxKind.ExpressionStatement
? assignmentBinaryExpression.parent : assignmentBinaryExpression;
deleteNode(nodeToDelete);
changes.delete(sourceFile, nodeToDelete);
if (!assignmentBinaryExpression.right) {
return createProperty([], modifiers, symbol.name, /*questionToken*/ undefined,

View File

@ -197,7 +197,7 @@ namespace ts.codefix {
if (isExportsOrModuleExportsOrAlias(sourceFile, left)) {
if (isExportsOrModuleExportsOrAlias(sourceFile, right)) {
// `const alias = module.exports;` or `module.exports = alias;` can be removed.
changes.deleteNode(sourceFile, assignment.parent);
changes.delete(sourceFile, assignment.parent);
}
else {
const replacement = isObjectLiteralExpression(right) ? tryChangeModuleExportsObject(right)
@ -297,7 +297,7 @@ namespace ts.codefix {
if (!right.name) changes.insertName(sourceFile, right, name);
const semi = findChildOfKind(parent, SyntaxKind.SemicolonToken, sourceFile);
if (semi) changes.deleteNode(sourceFile, semi, { useNonAdjustedEndPosition: true });
if (semi) changes.delete(sourceFile, semi);
}
else {
// `exports.f = function g() {}` -> `export const f = function g() {}` -- just replace `exports.` with `export const `

View File

@ -29,7 +29,7 @@ namespace ts.codefix {
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, constructor: ConstructorDeclaration, superCall: ExpressionStatement): void {
changes.insertNodeAtConstructorStart(sourceFile, constructor, superCall);
changes.deleteNode(sourceFile, superCall);
changes.delete(sourceFile, superCall);
}
function getNodes(sourceFile: SourceFile, pos: number): { readonly constructor: ConstructorDeclaration, readonly superCall: ExpressionStatement } | undefined {

View File

@ -32,14 +32,14 @@ namespace ts.codefix {
// falls through
case SyntaxKind.WhileStatement:
case SyntaxKind.ForStatement:
changes.deleteNode(sourceFile, container);
changes.delete(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);
changes.delete(sourceFile, statement);
}
}
}

View File

@ -22,7 +22,7 @@ namespace ts.codefix {
const importDecl = tryGetFullImport(token);
if (importDecl) {
const changes = textChanges.ChangeTracker.with(context, t => t.deleteNode(sourceFile, importDecl));
const changes = textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, importDecl));
return [createCodeFixAction(fixName, changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
}
const delDestructure = textChanges.ChangeTracker.with(context, t =>
@ -67,7 +67,7 @@ namespace ts.codefix {
case fixIdDelete: {
const importDecl = tryGetFullImport(token);
if (importDecl) {
changes.deleteDeclaration(sourceFile, importDecl);
changes.delete(sourceFile, importDecl);
}
else if (!tryDeleteFullDestructure(token, changes, sourceFile, checker, sourceFiles, /*isFixAll*/ true) &&
!tryDeleteFullVariableStatement(sourceFile, token, changes)) {
@ -94,7 +94,7 @@ namespace ts.codefix {
tryDeleteParameter(changes, sourceFile, decl, checker, sourceFiles, isFixAll);
}
else {
changes.deleteDeclaration(sourceFile, decl);
changes.delete(sourceFile, decl);
}
return true;
}
@ -102,7 +102,7 @@ namespace ts.codefix {
function tryDeleteFullVariableStatement(sourceFile: SourceFile, token: Node, changes: textChanges.ChangeTracker): boolean {
const declarationList = tryCast(token.parent, isVariableDeclarationList);
if (declarationList && declarationList.getChildren(sourceFile)[0] === token) {
changes.deleteDeclaration(sourceFile, declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList);
changes.delete(sourceFile, declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList);
return true;
}
return false;
@ -140,7 +140,7 @@ namespace ts.codefix {
FindAllReferences.Core.eachSymbolReferenceInFile(token, checker, sourceFile, (ref: Node) => {
if (ref.parent.kind === SyntaxKind.PropertyAccessExpression) ref = ref.parent;
if (ref.parent.kind === SyntaxKind.BinaryExpression && ref.parent.parent.kind === SyntaxKind.ExpressionStatement) {
changes.deleteDeclaration(sourceFile, ref.parent.parent);
changes.delete(sourceFile, ref.parent.parent);
}
});
}
@ -151,13 +151,13 @@ namespace ts.codefix {
tryDeleteParameter(changes, sourceFile, parent, checker, sourceFiles, isFixAll);
}
else {
changes.deleteDeclaration(sourceFile, isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent);
changes.delete(sourceFile, isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent);
}
}
function tryDeleteParameter(changes: textChanges.ChangeTracker, sourceFile: SourceFile, p: ParameterDeclaration, checker: TypeChecker, sourceFiles: ReadonlyArray<SourceFile>, isFixAll: boolean): void {
if (mayDeleteParameter(p, checker, isFixAll)) {
changes.deleteDeclaration(sourceFile, p);
changes.delete(sourceFile, p);
deleteUnusedArguments(changes, sourceFile, p, sourceFiles, checker);
}
}
@ -199,7 +199,7 @@ namespace ts.codefix {
FindAllReferences.Core.eachSignatureCall(deletedParameter.parent, sourceFiles, checker, call => {
const index = deletedParameter.parent.parameters.indexOf(deletedParameter);
if (call.arguments.length > index) { // Just in case the call didn't provide enough arguments.
changes.deleteDeclaration(sourceFile, call.arguments[index]);
changes.delete(sourceFile, call.arguments[index]);
}
});
}

View File

@ -63,10 +63,7 @@ namespace ts.OrganizeImports {
// Delete or replace the first import.
if (newImportDecls.length === 0) {
changeTracker.deleteNode(sourceFile, oldImportDecls[0], {
useNonAdjustedStartPosition: true, // Leave header comment in place
useNonAdjustedEndPosition: false,
});
changeTracker.delete(sourceFile, oldImportDecls[0]);
}
else {
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
@ -79,7 +76,7 @@ namespace ts.OrganizeImports {
// Delete any subsequent imports.
for (let i = 1; i < oldImportDecls.length; i++) {
changeTracker.deleteNode(sourceFile, oldImportDecls[i]);
changeTracker.delete(sourceFile, oldImportDecls[i]);
}
}
}

View File

@ -78,7 +78,7 @@ namespace ts.refactor {
function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: Info, changes: textChanges.ChangeTracker, checker: TypeChecker): void {
if (wasDefault) {
changes.deleteNode(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword)));
changes.delete(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword)));
}
else {
const exportKeyword = Debug.assertDefined(findModifier(exportNode, SyntaxKind.ExportKeyword));
@ -155,7 +155,7 @@ namespace ts.refactor {
}
else {
// `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";`
changes.deleteNode(importingSourceFile, ref);
changes.delete(importingSourceFile, ref);
changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec);
}
break;
@ -183,7 +183,7 @@ namespace ts.refactor {
changes.replaceNode(importingSourceFile, spec.parent, defaultImport);
}
else {
changes.deleteNodeInList(importingSourceFile, spec);
changes.delete(importingSourceFile, spec);
changes.insertNodeBefore(importingSourceFile, spec.parent, defaultImport);
}
}

View File

@ -1090,7 +1090,7 @@ namespace ts.refactor.extractSymbol {
// Consume
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
// If the parent is an expression statement, delete it.
changeTracker.deleteNode(context.file, node.parent, textChanges.useNonAdjustedPositions);
changeTracker.delete(context.file, node.parent);
}
else {
const localReference = createIdentifier(localNameText);

View File

@ -325,7 +325,7 @@ namespace ts.refactor {
break;
case SyntaxKind.ImportEqualsDeclaration:
if (isUnused(importDecl.name)) {
changes.deleteNode(sourceFile, importDecl);
changes.delete(sourceFile, importDecl);
}
break;
case SyntaxKind.VariableDeclaration:
@ -342,19 +342,19 @@ namespace ts.refactor {
const namedBindingsUnused = !namedBindings ||
(namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.every(e => isUnused(e.name)));
if (defaultUnused && namedBindingsUnused) {
changes.deleteNode(sourceFile, importDecl);
changes.delete(sourceFile, importDecl);
}
else {
if (name && defaultUnused) {
changes.deleteNode(sourceFile, name);
changes.delete(sourceFile, name);
}
if (namedBindings) {
if (namedBindingsUnused) {
changes.deleteNode(sourceFile, namedBindings);
changes.delete(sourceFile, namedBindings);
}
else if (namedBindings.kind === SyntaxKind.NamedImports) {
for (const element of namedBindings.elements) {
if (isUnused(element.name)) changes.deleteNodeInList(sourceFile, element);
if (isUnused(element.name)) changes.delete(sourceFile, element);
}
}
}
@ -365,20 +365,20 @@ namespace ts.refactor {
switch (name.kind) {
case SyntaxKind.Identifier:
if (isUnused(name)) {
changes.deleteNode(sourceFile, name);
changes.delete(sourceFile, name);
}
break;
case SyntaxKind.ArrayBindingPattern:
break;
case SyntaxKind.ObjectBindingPattern:
if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) {
changes.deleteNode(sourceFile,
changes.delete(sourceFile,
isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl);
}
else {
for (const element of name.elements) {
if (isIdentifier(element.name) && isUnused(element.name)) {
changes.deleteNode(sourceFile, element.name);
changes.delete(sourceFile, element.name);
}
}
}

View File

@ -212,9 +212,8 @@ namespace ts.textChanges {
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedDeclarations: { readonly sourceFile: SourceFile, readonly node: Node }[] = [];
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node }[] = [];
public static fromContext(context: TextChangesContext): ChangeTracker {
return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext);
@ -234,16 +233,8 @@ namespace ts.textChanges {
return this;
}
deleteDeclaration(sourceFile: SourceFile, node: Node): void {
this.deletedDeclarations.push({ sourceFile, node });
}
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
return this;
delete(sourceFile: SourceFile, node: Node): void {
this.deletedNodes.push({ sourceFile, node, });
}
public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void {
@ -263,32 +254,6 @@ namespace ts.textChanges {
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
public deleteNodeInList(sourceFile: SourceFile, node: Node) {
const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile);
if (!containingList) {
Debug.fail("node is not a list element");
return this;
}
const index = indexOfNode(containingList, node);
if (index < 0) {
return this;
}
if (containingList.length === 1) {
this.deleteNode(sourceFile, node);
return this;
}
// Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node.
// That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`.
Debug.assert(!this.deletedNodesInLists.has(node), "Deleting a node twice");
this.deletedNodesInLists.add(node);
this.deleteRange(sourceFile, {
pos: startPositionToDeleteNodeInList(sourceFile, node),
end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : startPositionToDeleteNodeInList(sourceFile, containingList[index + 1]),
});
return this;
}
public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode });
return this;
@ -538,7 +503,7 @@ namespace ts.textChanges {
if (lparen) {
// `() => {}` --> `function f() {}`
this.insertNodesAt(sourceFile, lparen.getStart(sourceFile), [createToken(SyntaxKind.FunctionKeyword), createIdentifier(name)], { joiner: " " });
this.deleteNode(sourceFile, arrow);
deleteNode(this, sourceFile, arrow);
}
else {
// `x => {}` -> `function f(x) {}`
@ -691,27 +656,26 @@ namespace ts.textChanges {
});
}
private finishTrailingCommaAfterDeletingNodesInList() {
this.deletedNodesInLists.forEach(node => {
private finishDeleteDeclarations(): void {
const deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
for (const { sourceFile, node } of this.deletedNodes) {
if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) {
deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node);
}
}
deletedNodesInLists.forEach(node => {
const sourceFile = node.getSourceFile();
const list = formatting.SmartIndenter.getContainingList(node, sourceFile)!;
if (node !== last(list)) return;
const lastNonDeletedIndex = findLastIndex(list, n => !this.deletedNodesInLists.has(n), list.length - 2);
const lastNonDeletedIndex = findLastIndex(list, n => !deletedNodesInLists.has(n), list.length - 2);
if (lastNonDeletedIndex !== -1) {
this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) });
}
});
}
private finishDeleteDeclarations(): void {
for (const { sourceFile, node } of this.deletedDeclarations) {
if (!this.deletedDeclarations.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) {
deleteDeclaration.deleteDeclaration(this, sourceFile, node);
}
}
}
/**
* Note: after calling this, the TextChanges object must be discarded!
* @param validate only for tests
@ -721,7 +685,6 @@ namespace ts.textChanges {
public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] {
this.finishDeleteDeclarations();
this.finishClassesWithNodesInsertedAtStart();
this.finishTrailingCommaAfterDeletingNodesInList();
const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate);
for (const { oldFile, fileName, statements } of this.newFiles) {
changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext));
@ -1037,7 +1000,7 @@ namespace ts.textChanges {
}
namespace deleteDeclaration {
export function deleteDeclaration(changes: ChangeTracker, sourceFile: SourceFile, node: Node): void {
export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
switch (node.kind) {
case SyntaxKind.Parameter: {
const oldFunction = node.parent;
@ -1062,28 +1025,30 @@ namespace ts.textChanges {
changes.replaceNode(sourceFile, oldFunction, newFunction);
}
else {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
break;
}
case SyntaxKind.ImportDeclaration:
changes.deleteNode(sourceFile, node);
deleteNode(changes, sourceFile, node,
// For first import, leave header comment in place
node === sourceFile.imports[0].parent ? { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: false } : undefined);
break;
case SyntaxKind.BindingElement:
const pattern = (node as BindingElement).parent;
const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && node !== last(pattern.elements);
if (preserveComma) {
changes.deleteNode(sourceFile, node);
deleteNode(changes, sourceFile, node);
}
else {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
break;
case SyntaxKind.VariableDeclaration:
deleteVariableDeclaration(changes, sourceFile, node as VariableDeclaration);
deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration);
break;
case SyntaxKind.TypeParameter: {
@ -1098,7 +1063,7 @@ namespace ts.textChanges {
changes.deleteNodeRange(sourceFile, previousToken, nextToken);
}
else {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
break;
}
@ -1109,7 +1074,7 @@ namespace ts.textChanges {
deleteImportBinding(changes, sourceFile, namedImports);
}
else {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
break;
@ -1122,10 +1087,10 @@ namespace ts.textChanges {
deleteDefaultImport(changes, sourceFile, node.parent);
}
else if (isCallLikeExpression(node.parent)) {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
else {
changes.deleteNode(sourceFile, node);
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { useNonAdjustedEndPosition: true } : undefined);
}
}
}
@ -1133,7 +1098,7 @@ namespace ts.textChanges {
function deleteDefaultImport(changes: ChangeTracker, sourceFile: SourceFile, importClause: ImportClause): void {
if (!importClause.namedBindings) {
// Delete the whole import
changes.deleteNode(sourceFile, importClause.parent);
deleteNode(changes, sourceFile, importClause.parent);
}
else {
// import |d,| * as ns from './file'
@ -1145,7 +1110,7 @@ namespace ts.textChanges {
changes.deleteRange(sourceFile, { pos: start, end });
}
else {
changes.deleteNode(sourceFile, importClause.name!);
deleteNode(changes, sourceFile, importClause.name!);
}
}
}
@ -1163,11 +1128,11 @@ namespace ts.textChanges {
// |import * as ns from './file'|
// |import { a } from './file'|
const importDecl = getAncestor(node, SyntaxKind.ImportDeclaration)!;
changes.deleteNode(sourceFile, importDecl);
deleteNode(changes, sourceFile, importDecl);
}
}
function deleteVariableDeclaration(changes: ChangeTracker, sourceFile: SourceFile, node: VariableDeclaration): void {
function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: VariableDeclaration): void {
const { parent } = node;
if (parent.kind === SyntaxKind.CatchClause) {
@ -1177,7 +1142,7 @@ namespace ts.textChanges {
}
if (parent.declarations.length !== 1) {
changes.deleteNodeInList(sourceFile, node);
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
return;
}
@ -1189,11 +1154,11 @@ namespace ts.textChanges {
break;
case SyntaxKind.ForStatement:
changes.deleteNode(sourceFile, parent);
deleteNode(changes, sourceFile, parent);
break;
case SyntaxKind.VariableStatement:
changes.deleteNode(sourceFile, gp);
deleteNode(changes, sourceFile, gp);
break;
default:
@ -1201,4 +1166,31 @@ namespace ts.textChanges {
}
}
}
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
// Exported for tests only! (TODO: improve tests to not need this)
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}): void {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void {
const containingList = Debug.assertDefined(formatting.SmartIndenter.getContainingList(node, sourceFile));
const index = indexOfNode(containingList, node);
Debug.assert(index !== -1);
if (containingList.length === 1) {
deleteNode(changes, sourceFile, node);
return;
}
// Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node.
// That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`.
Debug.assert(!deletedNodesInLists.has(node), "Deleting a node twice");
deletedNodesInLists.add(node);
changes.deleteRange(sourceFile, {
pos: startPositionToDeleteNodeInList(sourceFile, node),
end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : startPositionToDeleteNodeInList(sourceFile, containingList[index + 1]),
});
}
}

View File

@ -128,6 +128,7 @@ function bar() {
function findVariableDeclarationContaining(name: string, sourceFile: SourceFile): VariableDeclaration {
return cast(findChild(name, sourceFile), isVariableDeclaration);
}
const { deleteNode } = textChanges;
{
const text = `
var x = 1; // some comment - 1
@ -138,19 +139,19 @@ var y = 2; // comment 3
var z = 3; // comment 4
`;
runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile));
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile));
});
runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("x", sourceFile));
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile));
});
}
{
@ -377,25 +378,25 @@ class A {
{
const text = `var a = 1, b = 2, c = 3;`;
runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `var a = 1,b = 2,c = 3;`;
runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -406,13 +407,13 @@ namespace M {
c = 3;
}`;
runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -425,13 +426,13 @@ namespace M {
c = 3; // comment 5
}`;
runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -440,13 +441,13 @@ function foo(a: number, b: string, c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -455,13 +456,13 @@ function foo(a: number,b: string,c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -473,13 +474,13 @@ function foo(
return 1;
}`;
runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
changeTracker.delete(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
changeTracker.delete(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
changeTracker.delete(sourceFile, findChild("c", sourceFile));
});
}
{
@ -661,7 +662,7 @@ class A {
}
`;
runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
deleteNode(changeTracker, sourceFile, findChild("x", sourceFile));
});
}
{
@ -672,7 +673,7 @@ class A {
}
`;
runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
deleteNode(changeTracker, sourceFile, findChild("x", sourceFile));
});
}
{

View File

@ -11494,21 +11494,17 @@ declare namespace ts.textChanges {
private readonly formatContext;
private readonly changes;
private readonly newFiles;
private readonly deletedNodesInLists;
private readonly classesWithNodesInsertedAtStart;
private readonly deletedDeclarations;
private readonly deletedNodes;
static fromContext(context: TextChangesContext): ChangeTracker;
static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[];
/** Public for tests only. Other callers should use `ChangeTracker.with`. */
constructor(newLineCharacter: string, formatContext: formatting.FormatContext);
deleteRange(sourceFile: SourceFile, range: TextRange): this;
deleteDeclaration(sourceFile: SourceFile, node: Node): void;
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
deleteNode(sourceFile: SourceFile, node: Node, options?: ConfigurableStartEnd): this;
delete(sourceFile: SourceFile, node: Node): void;
deleteModifier(sourceFile: SourceFile, modifier: Modifier): void;
deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options?: ConfigurableStartEnd): this;
deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options?: ConfigurableStartEnd): void;
deleteNodeInList(sourceFile: SourceFile, node: Node): this;
replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options?: InsertNodeOptions): this;
replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options?: ChangeNodeOptions): this;
replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options?: ChangeNodeOptions): void;
@ -11551,7 +11547,6 @@ declare namespace ts.textChanges {
*/
insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node, containingList?: NodeArray<Node> | undefined): this;
private finishClassesWithNodesInsertedAtStart;
private finishTrailingCommaAfterDeletingNodesInList;
private finishDeleteDeclarations;
/**
* Note: after calling this, the TextChanges object must be discarded!
@ -11565,6 +11560,8 @@ declare namespace ts.textChanges {
type ValidateNonFormattedText = (node: Node, text: string) => void;
function applyChanges(text: string, changes: ReadonlyArray<TextChange>): string;
function isValidLocationToAddComment(sourceFile: SourceFile, position: number): boolean;
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options?: ConfigurableStartEnd): void;
}
declare namespace ts {
interface CodeFixRegistration {

View File

@ -17,6 +17,5 @@ function F() {
let i = 0;
const /*RENAME*/newLocal = i++;
function F() {
}

View File

@ -17,6 +17,5 @@ function F() {
let i = 0;
const /*RENAME*/newLocal = i++;
function F() {
}

View File

@ -7,5 +7,7 @@
verify.codeFix({
description: "Remove import from 'mod'",
newFileContent: " // trailing trivia",
newFileContent:
`// leading trivia
// trailing trivia`,
});