diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index 5fb7e97863e6a..96ecd4991526a 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -919,7 +919,7 @@ namespace ts {
let containerEnd = -1;
let declarationListContainerEnd = -1;
let currentLineMap: readonly number[] | undefined;
- let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined;
+ let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number }[] | undefined;
let hasWrittenComment = false;
let commentsDisabled = !!printerOptions.removeComments;
let lastNode: Node | undefined;
@@ -4488,7 +4488,7 @@ namespace ts {
// JsxText will be written with its leading whitespace, so don't add more manually.
return 0;
}
- else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode) && previousNode.parent === nextNode.parent) {
+ else if (siblingNodePositionsAreComparable(previousNode, nextNode)) {
if (preserveSourceNewlines) {
return getEffectiveLines(
includeComments => getLinesBetweenRangeEndAndRangeStart(
@@ -4509,6 +4509,33 @@ namespace ts {
return format & ListFormat.MultiLine ? 1 : 0;
}
+ function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) {
+ if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode) || previousNode.parent !== nextNode.parent) {
+ return false;
+ }
+
+ if (isImportSpecifier(previousNode)) {
+ const getNextSpecifier = (node: ImportSpecifier): ImportSpecifier | undefined => {
+ if (!node.parent) {
+ return;
+ }
+ for (const sibling of node.parent.elements) {
+ if (node.pos < sibling.pos) {
+ return sibling;
+ }
+ }
+ };
+
+ // Get the next specifier and compare against nextNode. If they are not equal, nodes have been rearranged and positions cannot be compared.
+ const nextSpecifier = getNextSpecifier(previousNode);
+ if (nextSpecifier && nextSpecifier !== nextNode) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
function getClosingLineTerminatorCount(parentNode: TextRange, children: readonly Node[], format: ListFormat): number {
if (format & ListFormat.PreserveLines || preserveSourceNewlines) {
if (format & ListFormat.PreferNewLine) {
@@ -4659,7 +4686,7 @@ namespace ts {
const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode);
return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` :
neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` :
- `"${escapeNonAsciiString(text)}"`;
+ `"${escapeNonAsciiString(text)}"`;
}
else {
return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape);
diff --git a/src/compiler/program.ts b/src/compiler/program.ts
index 7b71f011700c1..6401f4ed1514b 100644
--- a/src/compiler/program.ts
+++ b/src/compiler/program.ts
@@ -2354,22 +2354,6 @@ namespace ts {
}
}
- /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */
- function getNodeAtPosition(sourceFile: SourceFile, position: number): Node {
- let current: Node = sourceFile;
- const getContainingChild = (child: Node) => {
- if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) {
- return child;
- }
- };
- while (true) {
- const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild);
- if (!child) {
- return current;
- }
- current = child;
- }
- }
}
function getLibFileFromReference(ref: FileReference) {
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index 0e0c589e8a9fa..f00db59682bdc 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -7081,4 +7081,21 @@ namespace ts {
return false;
}
}
+
+ /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */
+ export function getNodeAtPosition(sourceFile: SourceFile, position: number): Node {
+ let current: Node = sourceFile;
+ const getContainingChild = (child: Node) => {
+ if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) {
+ return child;
+ }
+ };
+ while (true) {
+ const child = isSourceFileJS(sourceFile) && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild);
+ if (!child) {
+ return current;
+ }
+ current = child;
+ }
+ }
}
diff --git a/tests/cases/fourslash/organizeImports1.ts b/tests/cases/fourslash/organizeImports1.ts
new file mode 100644
index 0000000000000..7e25a4afde7c4
--- /dev/null
+++ b/tests/cases/fourslash/organizeImports1.ts
@@ -0,0 +1,32 @@
+///
+
+// Regression test for bug #41417
+
+//// import {
+//// d, d as D,
+//// c,
+//// c as C, b,
+//// b as B, a
+//// } from './foo';
+//// import {
+//// h, h as H,
+//// g,
+//// g as G, f,
+//// f as F, e
+//// } from './foo';
+////
+//// console.log(a, B, b, c, C, d, D);
+//// console.log(e, f, F, g, G, H, h);
+
+verify.organizeImports(
+`import {
+ a, b,
+ b as B, c,
+ c as C, d, d as D, e, f,
+ f as F, g,
+ g as G, h, h as H
+} from './foo';
+
+console.log(a, B, b, c, C, d, D);
+console.log(e, f, F, g, G, H, h);`
+);
\ No newline at end of file
diff --git a/tests/cases/fourslash/organizeImports2.ts b/tests/cases/fourslash/organizeImports2.ts
new file mode 100644
index 0000000000000..2e26e630bc39a
--- /dev/null
+++ b/tests/cases/fourslash/organizeImports2.ts
@@ -0,0 +1,16 @@
+///
+
+// Regression test for bug #41417
+
+//// import {
+//// Foo
+//// , Bar
+//// } from "foo"
+////
+//// console.log(Foo, Bar);
+
+verify.organizeImports(
+`import { Bar, Foo } from "foo";
+
+console.log(Foo, Bar);`
+);
\ No newline at end of file
diff --git a/tests/cases/fourslash/organizeImports3.ts b/tests/cases/fourslash/organizeImports3.ts
new file mode 100644
index 0000000000000..afba640a898dd
--- /dev/null
+++ b/tests/cases/fourslash/organizeImports3.ts
@@ -0,0 +1,18 @@
+///
+
+// Regression test for bug #41417
+
+//// import {
+//// Bar
+//// , Foo
+//// } from "foo"
+////
+//// console.log(Foo, Bar);
+
+verify.organizeImports(
+`import {
+ Bar,
+ Foo
+} from "foo";
+
+console.log(Foo, Bar);`);
\ No newline at end of file