diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts
index 62ba653961f..b5167124512 100644
--- a/src/services/refactors/extractSymbol.ts
+++ b/src/services/refactors/extractSymbol.ts
@@ -275,7 +275,7 @@ namespace ts.refactor.extractSymbol {
}
const cursorRequest = length === 0 && invoked;
- const startToken = getTokenAtPosition(sourceFile, span.start);
+ const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start);
const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span));
/* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for
refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 6b4330e3155..7516da5e8de 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -1141,6 +1141,20 @@ namespace ts {
}
}
+ /**
+ * Returns the first token where position is in [start, end),
+ * excluding `JsxText` tokens containing only whitespace.
+ */
+ export function findFirstNonJsxWhitespaceToken(sourceFile: SourceFile, position: number): Node | undefined {
+ let tokenAtPosition = getTokenAtPosition(sourceFile, position);
+ while (isWhiteSpaceOnlyJsxText(tokenAtPosition)) {
+ const nextToken = findNextToken(tokenAtPosition, tokenAtPosition.parent, sourceFile);
+ if (!nextToken) return;
+ tokenAtPosition = nextToken;
+ }
+ return tokenAtPosition;
+ }
+
/**
* The token on the left of the position is the token that strictly includes the position
* or sits to the left of the cursor if it is on a boundary. For example
diff --git a/tests/cases/fourslash/extract-method_jsxPreceedingWhitespace.ts b/tests/cases/fourslash/extract-method_jsxPreceedingWhitespace.ts
new file mode 100644
index 00000000000..388a6eea7ab
--- /dev/null
+++ b/tests/cases/fourslash/extract-method_jsxPreceedingWhitespace.ts
@@ -0,0 +1,36 @@
+///