diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index b8c9a8e873..61f6d738e5 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -130,7 +130,7 @@ namespace M /*body */ createBlock(statements) ); - changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { suffix: newLineCharacter }); // replace statements with return statement const newStatement = createReturn( @@ -139,7 +139,7 @@ namespace M /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray )); - changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { suffix: newLineCharacter }); }); } { @@ -255,10 +255,10 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true, indentation: 8, delta: 0 }); + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); }); runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { @@ -287,13 +287,13 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); }); runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); @@ -312,13 +312,13 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); @@ -334,7 +334,7 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1")); @@ -352,16 +352,16 @@ namespace M { var a = 4; // comment 7 }`; runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { insertLeadingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter }); }); } { @@ -385,7 +385,7 @@ class A { } `; runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); const text2 = ` class A { @@ -395,7 +395,7 @@ class A { } `; runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); const text3 = ` class A { @@ -405,7 +405,7 @@ class A { } `; runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); } { @@ -498,7 +498,7 @@ function foo(a: number,b: string,c = true) { changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); }); } -{ + { const text = ` function foo( a: number, @@ -516,5 +516,139 @@ function foo( changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); }); } + { + const text = ` +const x = 1, y = 2;`; + runSingleFileTest("insertNodeInListAfter1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const /*x*/ x = 1, /*y*/ y = 2;`; + runSingleFileTest("insertNodeInListAfter3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const x = 1;`; + runSingleFileTest("insertNodeInListAfter5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const x = 1, + y = 2;`; + runSingleFileTest("insertNodeInListAfter6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const /*x*/ x = 1, + /*y*/ y = 2;`; + runSingleFileTest("insertNodeInListAfter8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +import { + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter16", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter17", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter18", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } }); } \ No newline at end of file diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 63fc846f8c..fc2be3cd0c 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -27,7 +27,7 @@ namespace ts.codefix { } } const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { suffix: context.newLineCharacter }); changeTracker.deleteNode(sourceFile, superCall); return [{ diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 27b655f19c..822c34842f 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -12,7 +12,7 @@ namespace ts.codefix { const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { suffix: context.newLineCharacter }); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 3e07efbc3d..743c13ccf3 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -130,7 +130,7 @@ namespace ts.codefix { // this is a module id -> module import declaration map const cachedImportDeclarations: (ImportDeclaration | ImportEqualsDeclaration)[][] = []; - let cachedNewImportInsertPosition: number; + let lastImportDeclaration: Node; const currentTokenMeaning = getMeaningFromLocation(token); if (context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) { @@ -138,14 +138,14 @@ namespace ts.codefix { return getCodeActionForImport(symbol, /*isDefault*/ false, /*isNamespaceImport*/ true); } - const allPotentialModules = checker.getAmbientModules(); + const candidateModules = checker.getAmbientModules(); for (const otherSourceFile of allSourceFiles) { if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) { - allPotentialModules.push(otherSourceFile.symbol); + candidateModules.push(otherSourceFile.symbol); } } - for (const moduleSymbol of allPotentialModules) { + for (const moduleSymbol of candidateModules) { context.cancellationToken.throwIfCancellationRequested(); // check the default export @@ -277,14 +277,12 @@ namespace ts.codefix { * If the existing import declaration already has a named import list, just * insert the identifier into that list. */ - const textChange = getTextChangeForImportClause(namedImportDeclaration.importClause); + const fileTextChanges = getTextChangeForImportClause(namedImportDeclaration.importClause); const moduleSpecifierWithoutQuotes = stripQuotes(namedImportDeclaration.moduleSpecifier.getText()); actions.push(createCodeAction( Diagnostics.Add_0_to_existing_import_declaration_from_1, [name, moduleSpecifierWithoutQuotes], - textChange.newText, - textChange.span, - sourceFile.fileName, + fileTextChanges, "InsertingIntoExistingImport", moduleSpecifierWithoutQuotes )); @@ -302,49 +300,31 @@ namespace ts.codefix { return declaration.moduleReference.getText(); } - function getTextChangeForImportClause(importClause: ImportClause): TextChange { - const newImportText = isDefault ? `default as ${name}` : name; + function getTextChangeForImportClause(importClause: ImportClause): FileTextChanges[] { + //const newImportText = isDefault ? `default as ${name}` : name; const importList = importClause.namedBindings; + const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)); // case 1: // original text: import default from "module" // change to: import default, { name } from "module" - if (!importList && importClause.name) { - const start = importClause.name.getEnd(); - return { - newText: `, { ${newImportText} }`, - span: { start, length: 0 } - }; - } - // case 2: // original text: import {} from "module" // change to: import { name } from "module" - if (importList.elements.length === 0) { - const start = importList.getStart(); - return { - newText: `{ ${newImportText} }`, - span: { start, length: importList.getEnd() - start } - }; + if (!importList || importList.elements.length === 0) { + const newImportClause = createImportClause(importClause.name, createNamedImports([newImportSpecifier])); + return createChangeTracker().replaceNode(sourceFile, importClause, newImportClause).getChanges(); } - // case 3: - // original text: import { foo, bar } from "module" - // change to: import { foo, bar, name } from "module" - const insertPoint = importList.elements[importList.elements.length - 1].getEnd(); /** * If the import list has one import per line, preserve that. Otherwise, insert on same line as last element * import { * foo * } from "./module"; */ - const startLine = getLineOfLocalPosition(sourceFile, importList.getStart()); - const endLine = getLineOfLocalPosition(sourceFile, importList.getEnd()); - const oneImportPerLine = endLine - startLine > importList.elements.length; - - return { - newText: `,${oneImportPerLine ? context.newLineCharacter : " "}${newImportText}`, - span: { start: insertPoint, length: 0 } - }; + return createChangeTracker().insertNodeInListAfter( + sourceFile, + importList.elements[importList.elements.length - 1], + newImportSpecifier).getChanges(); } function getCodeActionForNamespaceImport(declaration: ImportDeclaration | ImportEqualsDeclaration): ImportCodeAction { @@ -370,48 +350,47 @@ namespace ts.codefix { return createCodeAction( Diagnostics.Change_0_to_1, [name, `${namespacePrefix}.${name}`], - `${namespacePrefix}.`, - { start: token.getStart(), length: 0 }, - sourceFile.fileName, + createChangeTracker().replaceNode(sourceFile, token, createPropertyAccess(createIdentifier(namespacePrefix), name)).getChanges(), "CodeChange" ); } } function getCodeActionForNewImport(moduleSpecifier?: string): ImportCodeAction { - if (!cachedNewImportInsertPosition) { + if (!lastImportDeclaration) { // insert after any existing imports - let lastModuleSpecifierEnd = -1; - for (const moduleSpecifier of sourceFile.imports) { - const end = moduleSpecifier.getEnd(); - if (!lastModuleSpecifierEnd || end > lastModuleSpecifierEnd) { - lastModuleSpecifierEnd = end; + for (let i = sourceFile.statements.length - 1; i >= 0; i--) { + const statement = sourceFile.statements[i]; + if (statement.kind === SyntaxKind.ImportEqualsDeclaration || statement.kind === SyntaxKind.ImportDeclaration) { + lastImportDeclaration = statement; + break; } } - cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile.getLineEndOfPosition(lastModuleSpecifierEnd) : sourceFile.getStart(); } const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport()); - const importStatementText = isDefault - ? `import ${name} from "${moduleSpecifierWithoutQuotes}"` + const changeTracker = createChangeTracker(); + const importClause = isDefault + ? createImportClause(createIdentifier(name), /*namedBindings*/ undefined) : isNamespaceImport - ? `import * as ${name} from "${moduleSpecifierWithoutQuotes}"` - : `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`; + ? createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(name))) + : createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))])); + const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifierWithoutQuotes)); + if (!lastImportDeclaration) { + changeTracker.insertNodeAt(sourceFile, sourceFile.getStart(), importDecl, { suffix: `${context.newLineCharacter}${context.newLineCharacter}` }); + } + else { + changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: context.newLineCharacter }); + } // if this file doesn't have any import statements, insert an import statement and then insert a new line // between the only import statement and user code. Otherwise just insert the statement because chances // are there are already a new line seperating code and import statements. - const newText = cachedNewImportInsertPosition === sourceFile.getStart() - ? `${importStatementText};${context.newLineCharacter}${context.newLineCharacter}` - : `${context.newLineCharacter}${importStatementText};`; - return createCodeAction( Diagnostics.Import_0_from_1, [name, `"${moduleSpecifierWithoutQuotes}"`], - newText, - { start: cachedNewImportInsertPosition, length: 0 }, - sourceFile.fileName, + changeTracker.getChanges(), "NewImport", moduleSpecifierWithoutQuotes ); @@ -576,17 +555,19 @@ namespace ts.codefix { } + function createChangeTracker() { + return textChanges.ChangeTracker.fromCodeFixContext(context);; + } + function createCodeAction( description: DiagnosticMessage, diagnosticArgs: string[], - newText: string, - span: TextSpan, - fileName: string, + changes: FileTextChanges[], kind: ImportCodeActionKind, moduleSpecifier?: string): ImportCodeAction { return { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), - changes: [{ fileName, textChanges: [{ newText, span }] }], + changes, kind, moduleSpecifier }; diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index 1773427fce..f77f9723d0 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -132,7 +132,7 @@ namespace ts.codefix { else { const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, /*forDeleteOperation*/ true); + const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); return deleteRange({ pos: startPosition, end: namespaceImport.end }); } return deleteRange(namespaceImport); diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index 981ae09b2b..20d190de2f 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -276,8 +276,8 @@ namespace ts.formatting { function isOnToken(): boolean { Debug.assert(scanner !== undefined); - const current = (lastTokenInfo && lastTokenInfo.token.kind) || scanner.getToken(); - const startPos = (lastTokenInfo && lastTokenInfo.token.pos) || scanner.getStartPos(); + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 71536dc75f..847bebdfe0 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -28,6 +28,28 @@ namespace ts.textChanges { useNonAdjustedEndPosition?: boolean; } + export enum Position { + FullStart, + Start + } + + function skipWhitespacesAndLineBreaks(text: string, start: number) { + return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + } + + function hasCommentsBeforeLineBreak(text: string, start: number) { + let i = start; + while (i < text.length) { + const ch = text.charCodeAt(i); + if (isWhiteSpaceSingleLine(ch)) { + i++; + continue; + } + return ch === CharacterCodes.slash; + } + return false; + } + /** * Usually node.pos points to a position immediately after the previous token. * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: @@ -44,13 +66,13 @@ namespace ts.textChanges { export interface InsertNodeOptions { /** - * Set this value to true to make sure that node text of newly inserted node ends with new line + * Text to be inserted before the new node */ - insertTrailingNewLine?: boolean; + prefix?: string; /** - * Set this value to true to make sure that node text of newly inserted node starts with new line + * Text to be inserted after the new node */ - insertLeadingNewLine?: boolean; + suffix?: string; /** * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node */ @@ -66,12 +88,16 @@ namespace ts.textChanges { interface Change { readonly sourceFile: SourceFile; readonly range: TextRange; - readonly oldNode?: Node; + readonly useIndentationFromFile?: boolean; readonly node?: Node; readonly options?: ChangeNodeOptions; } - export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, forDeleteOperation: boolean) { + export function getSeparatorCharacter(separator: Token) { + return tokenToString(separator.kind); + } + + export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) { if (options.useNonAdjustedStartPosition) { return node.getFullStart(); } @@ -90,12 +116,12 @@ namespace ts.textChanges { // fullstart // when b is replaced - we usually want to keep the leading trvia // when b is deleted - we delete it - return forDeleteOperation ? fullStart : start; + return position === Position.Start ? start : fullStart; } // get start position of the line following the line that contains fullstart position let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + 1, sourceFile); // skip whitespaces/newlines - adjustedStartPosition = skipTrivia(sourceFile.text, adjustedStartPosition, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); } @@ -112,8 +138,19 @@ namespace ts.textChanges { : end; } - function isSeparator(node: Node, separator: Node): boolean { - return node.parent && (separator.kind === SyntaxKind.CommaToken || (separator.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + /** + * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element + */ + function isSeparator(node: Node, candidate: Node): candidate is Token { + return candidate && node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + } + + function spaces(count: number) { + let s = ""; + for (let i = 0; i < count; i++) { + s += " "; + } + return s; } export class ChangeTracker { @@ -132,7 +169,7 @@ namespace ts.textChanges { } public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, node, options, /*forDeleteOperation*/ true); + const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, node, options); this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); return this; @@ -144,7 +181,7 @@ namespace ts.textChanges { } public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ true); + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); return this; @@ -153,7 +190,7 @@ namespace ts.textChanges { public deleteNodeInList(sourceFile: SourceFile, node: Node) { const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile); if (!containingList) { - return; + return this; } const index = containingList.indexOf(node); if (index < 0) { @@ -167,10 +204,10 @@ namespace ts.textChanges { const nextToken = getTokenAtPosition(sourceFile, node.end); if (nextToken && isSeparator(node, nextToken)) { // find first non-whitespace position in the leading trivia of the node - const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); const nextElement = containingList[index + 1]; /// find first non-whitespace position in the leading trivia of the next node - const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); // shift next node so its first non-whitespace position will be moved to the first non-whitespace position of the deleted node this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } @@ -190,16 +227,16 @@ namespace ts.textChanges { } public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, /*forDeleteOperation*/ false); + const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start); const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options); - this.changes.push({ sourceFile, options, oldNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } }); return this; } public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ false); + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.changes.push({ sourceFile, options, oldNode: startNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } }); return this; } @@ -209,14 +246,149 @@ namespace ts.textChanges { } public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, before, options, /*forDeleteOperation*/ false); - this.changes.push({ sourceFile, options, oldNode: before, node: newNode, range: { pos: startPosition, end: startPosition } }); + const startPosition = getAdjustedStartPosition(sourceFile, before, options, Position.Start); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: startPosition } }); return this; } public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) { const endPosition = getAdjustedEndPosition(sourceFile, after, options); - this.changes.push({ sourceFile, options, oldNode: after, node: newNode, range: { pos: endPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: endPosition, end: endPosition } }); + return this; + } + + public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { + const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); + if (!containingList) { + return this; + } + const index = containingList.indexOf(after); + if (index < 0) { + return this; + } + const end = after.getEnd(); + if (index !== containingList.length - 1) { + // any element except the last one + // use next sibling as an anchor + const nextToken = getTokenAtPosition(sourceFile, after.end); + if (nextToken && isSeparator(after, nextToken)) { + // for list + // a, b, c + // create change for adding 'e' after 'a' as + // - find start of next element after a (it is b) + // - use this start as start and end position in final change + // - build text of change by formatting the text of node + separator + whitespace trivia of b + + // in multiline case it will work as + // a, + // b, + // c, + // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') + // a,* + //***insertedtext# + //###b, + // c, + // find line and character of the next element + const lineAndCharOfNextElement = getLineAndCharacterOfPosition(sourceFile, skipWhitespacesAndLineBreaks(sourceFile.text, containingList[index + 1].getFullStart())); + // find line and character of the token that precedes next element (usually it is separator) + const lineAndCharOfNextToken = getLineAndCharacterOfPosition(sourceFile, nextToken.end); + let prefix: string; + let startPos: number; + if (lineAndCharOfNextToken.line === lineAndCharOfNextElement.line) { + // next element is located on the same line with separator: + // a,$$$$b + // ^ ^ + // | |-next element + // |-separator + // where $$$ is some leading trivia + // for a newly inserted node we'll maintain the same relative position comparing to separator and replace leading trivia with spaces + // a, x,$$$$b + // ^ ^ ^ + // | | |-next element + // | |-new inserted node padded with spaces + // |-separator + startPos = nextToken.end; + prefix = spaces(lineAndCharOfNextElement.character - lineAndCharOfNextToken.character); + } + else { + // next element is located on different line that separator + // let insert position be the beginning of the line that contains next element + startPos = getStartPositionOfLine(lineAndCharOfNextElement.line, sourceFile); + } + + this.changes.push({ + sourceFile, + range: { pos: startPos, end: containingList[index + 1].getStart(sourceFile) }, + node: newNode, + useIndentationFromFile: true, + options: { + prefix, + // write separator and leading trivia of the next element as suffix + suffix: `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}` + } + }); + } + } + else { + const afterStart = after.getStart(sourceFile); + const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); + + let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken; + let multilineList = false; + + // insert element after the last element in the list that has more than one item + // pick the element preceding the after element to: + // - pick the separator + // - determine if list is a multiline + if (containingList.length === 1) { + // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise + // i.e. var x = 1 // this is x + // | new element will be inserted at this position + separator = SyntaxKind.CommaToken; + } + else { + // element has more than one element, pick separator from the list + const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); + separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; + // determine if list is multiline by checking lines of after element and element that precedes it. + const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); + multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; + } + if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { + // in this case we'll always treat containing list as multiline + multilineList = true; + } + if (multilineList) { + // insert separator immediately following the 'after' node to preserve comments in trailing trivia + this.changes.push({ + sourceFile, + range: { pos: end, end }, + node: createToken(separator), + options: {} + }); + // use the same indentation as 'after' item + const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.rulesProvider.getFormatOptions()); + // insert element before the line break on the line that contains 'after' element + let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); + if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { + insertPos-- + } + this.changes.push({ + sourceFile, + range: { pos: insertPos, end: insertPos }, + node: newNode, + options: { indentation, prefix: this.newLineCharacter } + }); + } + else { + this.changes.push({ + sourceFile, + range: { pos: end, end }, + node: newNode, + options: { prefix: `${tokenToString(separator)} ` } + }); + } + } return this; } @@ -267,13 +439,13 @@ namespace ts.textChanges { const formatOptions = this.rulesProvider.getFormatOptions(); const pos = change.range.pos; - const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; + const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; const initialIndentation = change.options.indentation !== undefined ? change.options.indentation - : change.oldNode - ? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || change.options.insertLeadingNewLine) + : change.useIndentationFromFile + ? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || (change.options.prefix == this.newLineCharacter)) : 0; const delta = change.options.delta !== undefined @@ -284,15 +456,9 @@ namespace ts.textChanges { let text = applyFormatting(nonFormattedText, sourceFile, initialIndentation, delta, this.rulesProvider); // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line - text = posStartsLine ? text : text.replace(/^\s+/, ""); - - if (options.insertLeadingNewLine) { - text = this.newLineCharacter + text; - } - if (options.insertTrailingNewLine) { - text = text + this.newLineCharacter; - } - return text; + // however keep indentation if it is was forced + text = posStartsLine || change.options.indentation !== undefined ? text : text.replace(/^\s+/, ""); + return (options.prefix || "") + text + (options.suffix || ""); } private static normalize(changes: Change[]) { @@ -377,9 +543,13 @@ namespace ts.textChanges { constructor(newLine: string) { this.writer = createTextWriter(newLine); this.onEmitNode = (hint, node, printCallback) => { - setPos(node, this.lastNonTriviaPosition); + if (node) { + setPos(node, this.lastNonTriviaPosition); + } printCallback(hint, node); - setEnd(node, this.lastNonTriviaPosition); + if (node) { + setEnd(node, this.lastNonTriviaPosition); + } }; this.onBeforeEmitNodeArray = nodes => { if (nodes) { diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter1.js b/tests/baselines/reference/textChanges/insertNodeInListAfter1.js new file mode 100644 index 0000000000..9b0ed7b819 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter1.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1, y = 2; +===MODIFIED=== + +const x = 1, z = 1, y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter10.js b/tests/baselines/reference/textChanges/insertNodeInListAfter10.js new file mode 100644 index 0000000000..300230bf51 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter10.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x +} from "bar" +===MODIFIED=== + +import { + x, b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter11.js b/tests/baselines/reference/textChanges/insertNodeInListAfter11.js new file mode 100644 index 0000000000..f0ba43a0f3 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter11.js @@ -0,0 +1,11 @@ +===ORIGINAL=== + +import { + x // this is x +} from "bar" +===MODIFIED=== + +import { + x, // this is x + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter12.js b/tests/baselines/reference/textChanges/insertNodeInListAfter12.js new file mode 100644 index 0000000000..79e7bb2c8b --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter12.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x +} from "bar" +===MODIFIED=== + +import { + x, a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter13.js b/tests/baselines/reference/textChanges/insertNodeInListAfter13.js new file mode 100644 index 0000000000..a7ac7179ab --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter13.js @@ -0,0 +1,11 @@ +===ORIGINAL=== + +import { + x // this is x +} from "bar" +===MODIFIED=== + +import { + x, // this is x + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter14.js b/tests/baselines/reference/textChanges/insertNodeInListAfter14.js new file mode 100644 index 0000000000..e44a21723b --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter14.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x +} from "bar" +===MODIFIED=== + +import { + x0, + x, + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter15.js b/tests/baselines/reference/textChanges/insertNodeInListAfter15.js new file mode 100644 index 0000000000..a80bdc2db5 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter15.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x // this is x +} from "bar" +===MODIFIED=== + +import { + x0, + x, // this is x + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter16.js b/tests/baselines/reference/textChanges/insertNodeInListAfter16.js new file mode 100644 index 0000000000..07f8db919f --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter16.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x +} from "bar" +===MODIFIED=== + +import { + x0, + x, + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter17.js b/tests/baselines/reference/textChanges/insertNodeInListAfter17.js new file mode 100644 index 0000000000..990139408e --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter17.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x // this is x +} from "bar" +===MODIFIED=== + +import { + x0, + x, // this is x + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter18.js b/tests/baselines/reference/textChanges/insertNodeInListAfter18.js new file mode 100644 index 0000000000..b25a3f4634 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter18.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x0, x +} from "bar" +===MODIFIED=== + +import { + x0, x, a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter2.js b/tests/baselines/reference/textChanges/insertNodeInListAfter2.js new file mode 100644 index 0000000000..32e8e63c0f --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter2.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1, y = 2; +===MODIFIED=== + +const x = 1, y = 2, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter3.js b/tests/baselines/reference/textChanges/insertNodeInListAfter3.js new file mode 100644 index 0000000000..70a0b0bddc --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter3.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const /*x*/ x = 1, /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, z = 1, /*y*/ y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter4.js b/tests/baselines/reference/textChanges/insertNodeInListAfter4.js new file mode 100644 index 0000000000..4a30acd707 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter4.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const /*x*/ x = 1, /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, /*y*/ y = 2, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter5.js b/tests/baselines/reference/textChanges/insertNodeInListAfter5.js new file mode 100644 index 0000000000..42d5fe2ca4 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter5.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1; +===MODIFIED=== + +const x = 1, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter6.js b/tests/baselines/reference/textChanges/insertNodeInListAfter6.js new file mode 100644 index 0000000000..06eae7372a --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter6.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const x = 1, + y = 2; +===MODIFIED=== + +const x = 1, + z = 1, + y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter7.js b/tests/baselines/reference/textChanges/insertNodeInListAfter7.js new file mode 100644 index 0000000000..bef0150368 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter7.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const x = 1, + y = 2; +===MODIFIED=== + +const x = 1, + y = 2, + z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter8.js b/tests/baselines/reference/textChanges/insertNodeInListAfter8.js new file mode 100644 index 0000000000..e3c44b1427 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter8.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const /*x*/ x = 1, + /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, + z = 1, + /*y*/ y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter9.js b/tests/baselines/reference/textChanges/insertNodeInListAfter9.js new file mode 100644 index 0000000000..510984b757 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter9.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const /*x*/ x = 1, + /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, + /*y*/ y = 2, + z = 1; \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport10.ts b/tests/cases/fourslash/importNameCodeFixExistingImport10.ts index 25246e7012..8a178e7273 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport10.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport10.ts @@ -16,6 +16,6 @@ verify.importFixAtPosition([ `{ v1, v2, -f1 + f1 }` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport11.ts b/tests/cases/fourslash/importNameCodeFixExistingImport11.ts index 304ffb896d..8822356c13 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport11.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport11.ts @@ -15,6 +15,7 @@ verify.importFixAtPosition([ `{ v1, v2, - v3, f1 + v3, + f1 }` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport9.ts b/tests/cases/fourslash/importNameCodeFixExistingImport9.ts index 05d1792745..51496abff1 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport9.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport9.ts @@ -11,7 +11,6 @@ verify.importFixAtPosition([ `{ - v1, -f1 + v1, f1 }` ]); \ No newline at end of file