From 220ff1abcee5fc3ab39a3fc0e96d7b34753fa6a6 Mon Sep 17 00:00:00 2001
From: Isabel Duan <isabelduan@microsoft.com>
Date: Wed, 19 Apr 2023 13:01:51 -0700
Subject: [PATCH 1/2] added `wordPattern` Regex to include more characters

---
 src/services/services.ts                      |  9 +++++-
 tests/cases/fourslash/linkedEditingJsxTag1.ts |  7 ++++
 .../cases/fourslash/linkedEditingJsxTag10.ts  |  2 ++
 .../cases/fourslash/linkedEditingJsxTag11.ts  | 32 +++++++++++++++++++
 tests/cases/fourslash/linkedEditingJsxTag2.ts |  2 ++
 tests/cases/fourslash/linkedEditingJsxTag4.ts |  2 ++
 tests/cases/fourslash/linkedEditingJsxTag5.ts |  4 +++
 tests/cases/fourslash/linkedEditingJsxTag6.ts |  4 +++
 tests/cases/fourslash/linkedEditingJsxTag7.ts |  5 ++-
 tests/cases/fourslash/linkedEditingJsxTag9.ts |  5 +++
 10 files changed, 70 insertions(+), 2 deletions(-)
 create mode 100644 tests/cases/fourslash/linkedEditingJsxTag11.ts

diff --git a/src/services/services.ts b/src/services/services.ts
index acd845c7979c6..cb2c73939bdd7 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -2492,6 +2492,9 @@ export function createLanguageService(
         const token = findPrecedingToken(position, sourceFile);
         if (!token || token.parent.kind === SyntaxKind.SourceFile) return undefined;
 
+        // matches more than valid tag names to allow linked editing when typing is in progress or tag name is incomplete
+        const jsxTagWordPattern = "[a-zA-Z0-9:\\-\\._$]*";
+
         if (isJsxFragment(token.parent.parent)) {
             const openFragment = token.parent.parent.openingFragment;
             const closeFragment = token.parent.parent.closingFragment;
@@ -2503,7 +2506,10 @@ export function createLanguageService(
             // only allows linked editing right after opening bracket: <| ></| >
             if ((position !== openPos) && (position !== closePos)) return undefined;
 
-            return { ranges: [{ start: openPos, length: 0 }, { start: closePos, length: 0 }] };
+            return {
+                ranges: [{ start: openPos, length: 0 }, { start: closePos, length: 0 }],
+                wordPattern: jsxTagWordPattern,
+            };
         }
         else {
             // determines if the cursor is in an element tag
@@ -2534,6 +2540,7 @@ export function createLanguageService(
 
             return {
                 ranges: [{ start: openTagStart, length: openTagEnd - openTagStart }, { start: closeTagStart, length: closeTagEnd - closeTagStart }],
+                wordPattern: jsxTagWordPattern,
             };
         }
     }
diff --git a/tests/cases/fourslash/linkedEditingJsxTag1.ts b/tests/cases/fourslash/linkedEditingJsxTag1.ts
index ab0183c9c1e15..2fe2624231d63 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag1.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag1.ts
@@ -20,17 +20,24 @@
 ////    </d/*14*/iv>
 ////);/*d*/
 
+// goTo.marker("a");
+
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
 const linkedCursors1 = {
     ranges: [{ start: test.markerByName("0").position, length: 3 }, { start: test.markerByName("5").position, length: 3 }],
+    wordPattern, 
 };
 const linkedCursors2 = {
     ranges: [{ start: test.markerByName("9").position - 1, length: 3 }, { start: test.markerByName("14").position - 1, length: 3 }],
+    wordPattern,
 };
 const linkedCursors3 = {
     ranges: [{ start: test.markerByName("10").position - 1, length: 3 }, { start: test.markerByName("13").position - 1, length: 3 }],
+    wordPattern,
 };
 const linkedCursors4 = {
     ranges: [{ start: test.markerByName("11").position - 1, length: 1 }, { start: test.markerByName("12").position, length: 1 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag10.ts b/tests/cases/fourslash/linkedEditingJsxTag10.ts
index 0cc40d35da978..509f858010e7e 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag10.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag10.ts
@@ -48,8 +48,10 @@
 // @Filename: /jsx15.tsx
 ////const jsx = </*15*/div> </*15a*/> <//*15b*/div> <//*15c*/>;
 
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
 const linkedCursors9 = {
     ranges: [{ start: test.markerByName("9").position, length: 3 }, { start:  test.markerByName("9a").position,  length: 3 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag11.ts b/tests/cases/fourslash/linkedEditingJsxTag11.ts
new file mode 100644
index 0000000000000..668c1bb2692b8
--- /dev/null
+++ b/tests/cases/fourslash/linkedEditingJsxTag11.ts
@@ -0,0 +1,32 @@
+/// <reference path='fourslash.ts' />
+
+// for readability 
+////const jsx = (
+////   <div style={{ color: 'red' }}>
+////      <p>
+////         <img />
+////      </p>
+////   </div>
+////);
+
+// @Filename: /attrs.tsx
+//// </*1*/fbt/*2*/:en/*3*/um knownProp="accepted"
+////     unknownProp="rejected">
+//// </f/*4*/bt:e/*5*/num>;
+
+
+const startPos = test.markerByName("1").position;
+const endPos =  test.markerByName("4").position - 1;
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
+const linkedCursors = {
+    ranges: [{ start: startPos, length: 8 }, { start: endPos, length: 8 }],
+    wordPattern,
+};
+
+verify.linkedEditing( {
+    "1": linkedCursors,
+    "2": linkedCursors,
+    "3": linkedCursors,
+    "4": linkedCursors,
+    "5": linkedCursors,
+});
\ No newline at end of file
diff --git a/tests/cases/fourslash/linkedEditingJsxTag2.ts b/tests/cases/fourslash/linkedEditingJsxTag2.ts
index 35c5a119653c9..d16821137bc96 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag2.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag2.ts
@@ -28,8 +28,10 @@
 
 const startPos = test.markerByName("0").position;
 const endPos =  test.markerByName("6").position - 2;
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
 const linkedCursors = {
     ranges: [{ start: startPos, length: 3 }, { start: endPos, length: 3 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag4.ts b/tests/cases/fourslash/linkedEditingJsxTag4.ts
index b130c55e024f9..bd4e9f7179119 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag4.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag4.ts
@@ -27,8 +27,10 @@
 
 const startPos = test.markerByName("0").position;
 const endPos =  test.markerByName("6").position;
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
 const linkedCursors = {
     ranges: [{ start: startPos, length: 3 }, { start: endPos, length: 3 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag5.ts b/tests/cases/fourslash/linkedEditingJsxTag5.ts
index 2e7dbe56acea2..87379e19a3e4b 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag5.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag5.ts
@@ -20,16 +20,20 @@
 ////   </*12*/ //*13*/div>
 ////);
 
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
+
 const startPos1 = test.markerByName("1").position - 3;
 const endPos1 =  test.markerByName("2").position - 3;
 const linkedCursors1 = {
     ranges: [{ start: startPos1, length: 3 }, { start: endPos1, length: 3 }],
+    wordPattern,
 };
 
 const startPos2 = test.markerByName("6").position - 3;
 const endPos2 =  test.markerByName("7").position - 3;
 const linkedCursors2 = {
     ranges: [{ start: startPos2, length: 3 }, { start: endPos2, length: 3 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag6.ts b/tests/cases/fourslash/linkedEditingJsxTag6.ts
index 8cac987be18cb..482fec57bb2a7 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag6.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag6.ts
@@ -16,16 +16,20 @@
 ////     <//*30*/foo  /*31*/ .// hi/*32*/
 ////     /*33*/bar>
 
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
+
 const startPos1 = test.markerByName("start").position;
 const endPos1 =  test.markerByName("end").position;
 const linkedCursors1 = {
     ranges: [{ start: startPos1, length: 19 }, { start: endPos1,  length: 19 }],
+    wordPattern,
 };
 
 const startPos2 = test.markerByName("26").position;
 const endPos2 =  test.markerByName("30").position;
 const linkedCursors2 = {
     ranges: [{ start: startPos2, length: 21 }, { start: endPos2,  length: 21 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {
diff --git a/tests/cases/fourslash/linkedEditingJsxTag7.ts b/tests/cases/fourslash/linkedEditingJsxTag7.ts
index c401de04cb5d0..9a746c04f0fbd 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag7.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag7.ts
@@ -30,17 +30,20 @@
 ////    </>
 ////);/*e*/
 
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
+
 const startPos1 = test.markerByName("0").position;
 const endPos1 =  test.markerByName("3").position;
 const linkedCursors1 = {
     ranges: [{ start: startPos1, length: 0 }, { start: endPos1, length: 0 }],
-    // wordPattern : undefined
+    wordPattern,
 };
 
 const startPos2 = test.markerByName("10").position;
 const endPos2 =  test.markerByName("14").position;
 const linkedCursors2 = {
     ranges: [{ start: startPos2, length: 0 }, { start: endPos2, length: 0 }],
+    wordPattern,
 };
 
 verify.linkedEditing({
diff --git a/tests/cases/fourslash/linkedEditingJsxTag9.ts b/tests/cases/fourslash/linkedEditingJsxTag9.ts
index 67bd28d1fc5e7..7f363a8ed619f 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag9.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag9.ts
@@ -28,15 +28,20 @@
 ////    <//*23*/ /*24*///*25*/* even/*26*/ more comment *//*27*/ d/*28*/iv /* b/*29*/ye */>
 ////);
 
+const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
+
 const markers = test.markers();
 const linkedCursors1 = {
     ranges: [{ start: markers[1].position, length: 3 }, { start: markers[5].position, length: 3 }],
+    wordPattern,
 };
 const linkedCursors2 = {
     ranges: [{ start: markers[7].position, length: 3 }, { start: markers[10].position, length: 3 }],
+    wordPattern,
 };
 const linkedCursors3 = {
     ranges: [{ start: markers[20].position - 2, length: 3 }, { start: markers[28].position - 1, length: 3 }],
+    wordPattern,
 };
 
 verify.linkedEditing( {

From 3d460f66afdd768a111fd8f990222385566fe944 Mon Sep 17 00:00:00 2001
From: Isabel Duan <isabelduan@microsoft.com>
Date: Thu, 20 Apr 2023 13:28:43 -0700
Subject: [PATCH 2/2] add tests

---
 .../cases/fourslash/linkedEditingJsxTag11.ts  | 53 ++++++++++++++-----
 1 file changed, 41 insertions(+), 12 deletions(-)

diff --git a/tests/cases/fourslash/linkedEditingJsxTag11.ts b/tests/cases/fourslash/linkedEditingJsxTag11.ts
index 668c1bb2692b8..3755f78a8cb0b 100644
--- a/tests/cases/fourslash/linkedEditingJsxTag11.ts
+++ b/tests/cases/fourslash/linkedEditingJsxTag11.ts
@@ -9,24 +9,53 @@
 ////   </div>
 ////);
 
-// @Filename: /attrs.tsx
-//// </*1*/fbt/*2*/:en/*3*/um knownProp="accepted"
+// @Filename: /customElements.tsx
+//// const jsx = </*1*/fbt/*2*/:en/*3*/um knownProp="accepted"
 ////     unknownProp="rejected">
 //// </f/*4*/bt:e/*5*/num>;
+////
+//// const customElement = </*6*/custom/*7*/-element/*8*/></custom/*9*/-element>;
+////
+//// const standardElement = 
+////    </*10*/Link/*11*/ href="/hello" passHref>
+////        </*12*/But/*13*/ton component="a">
+////            Next
+////        </But/*14*/ton>
+////    </Li/*15*/nk>;
 
-
-const startPos = test.markerByName("1").position;
-const endPos =  test.markerByName("4").position - 1;
 const wordPattern =  "[a-zA-Z0-9:\\-\\._$]*";
-const linkedCursors = {
-    ranges: [{ start: startPos, length: 8 }, { start: endPos, length: 8 }],
+
+const linkedCursors1 = {
+    ranges: [{ start: test.markerByName("1").position, length: 8 }, { start: test.markerByName("4").position - 1, length: 8 }],
+    wordPattern,
+};
+const linkedCursors2 = {
+    ranges: [{ start: test.markerByName("6").position, length: 14 }, { start: test.markerByName("9").position - 6, length: 14 }],
+    wordPattern,
+};
+const linkedCursors3 = {
+    ranges: [{ start: test.markerByName("10").position, length: 4 }, { start: test.markerByName("15").position - 2, length: 4 }],
+    wordPattern,
+};
+const linkedCursors4 = {
+    ranges: [{ start: test.markerByName("12").position, length: 6 }, { start: test.markerByName("14").position - 3, length: 6 }],
     wordPattern,
 };
 
 verify.linkedEditing( {
-    "1": linkedCursors,
-    "2": linkedCursors,
-    "3": linkedCursors,
-    "4": linkedCursors,
-    "5": linkedCursors,
+    "1": linkedCursors1,
+    "2": linkedCursors1,
+    "3": linkedCursors1,
+    "4": linkedCursors1,
+    "5": linkedCursors1,
+    "6": linkedCursors2,
+    "7": linkedCursors2,
+    "8": linkedCursors2,
+    "9": linkedCursors2,
+    "10": linkedCursors3,
+    "11": linkedCursors3,
+    "12": linkedCursors4,
+    "13": linkedCursors4,
+    "14": linkedCursors4,
+    "15": linkedCursors3,
 });
\ No newline at end of file