Repository: prettier/prettier (JavaScript)

Issue:
When prettier inserts <> characters in markdown links, it should encode existing <> characters too.
**Prettier 3.0.3**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBtANgSygawLoAUAFjDAA4DOSA9NQO4MB0A5hBM+nIwGYBO1BAJTc2AWgB8AIwCGvQYwA6UEABoQEMjEzQKyULN4Q6ABVkJdKaejrSAnrrWTe0sDjgwAytIC2cADLYcMjcVhRwjs6u7h5kLtjMyDC8AK7hIGHemIkpaRTxnACKyRDwwaFpAFYUAB4e+XBFJUFIIehhagCOxfDGhpTIINIUolBwcAAmE6ogSdKYWFDMAMIQ3t7SA1bo03mLnACCpLyYksk9cLwBo2VtaSTe6ADqRJjwFLFgcB7mr5gAbq9bAMwBQHCA-qkAJJQSawDxgY6afYwjwwWycG7tEBkQxhR7OMgDHFwMK8P5BNTYUkwXrSZjrTFpWK8UkDda8HDjIzKNQ47AwR6YcYwIjIAAcAAY1Lw4F1MDLafSNi1ymoYNJJILhaKkAAmNTJMIAFQ1FlaWLg3kkE0m4z80kWyTpcAAYhBeOtSPFNmcICAAL7+oA)
<!-- prettier-ignore -->
```sh
--parser markdown
```

**Input:**
<!-- prettier-ignore -->
```markdown
[link](https://www.google.fr/()foo->bar).
```
Note that the link contains a `>` character.

**Output:**
<!-- prettier-ignore -->
```markdown
[link](<https://www.google.fr/()foo->bar>).
```
Now it's broken because of the `>` character inside the link matching the first `<`.


**Expected behavior:**
Either:
<!-- prettier-ignore -->
```markdown
[link](https://www.google.fr/()foo->bar).
```
(no change -- this is valid markdown)

or:
<!-- prettier-ignore -->
```markdown
[link](<https://www.google.fr/()foo%3Ebar>).
```
The latter is probably easier to implement.

See the spec in https://spec.commonmark.org/0.30/#link-destination.

Thanks!



Hints:

Patch:
diff --git a/src/language-markdown/clean.js b/src/language-markdown/clean.js
--- a/src/language-markdown/clean.js
+++ b/src/language-markdown/clean.js
@@ -48,6 +48,16 @@ function clean(ast, newObj, parent) {
     newObj.label = collapseWhiteSpace(ast.label);
   }
 
+  if (
+    (ast.type === "link" || ast.type === "image") &&
+    ast.url &&
+    ast.url.includes("(")
+  ) {
+    for (const character of "<>") {
+      newObj.url = ast.url.replaceAll(character, encodeURIComponent(character));
+    }
+  }
+
   if (
     (ast.type === "definition" ||
       ast.type === "link" ||
diff --git a/src/language-markdown/printer-markdown.js b/src/language-markdown/printer-markdown.js
--- a/src/language-markdown/printer-markdown.js
+++ b/src/language-markdown/printer-markdown.js
@@ -778,6 +778,13 @@ function shouldRemainTheSameContent(path) {
   );
 }
 
+const encodeUrl = (url, characters) => {
+  for (const character of characters) {
+    url = url.replaceAll(character, encodeURIComponent(character));
+  }
+  return url;
+};
+
 /**
  * @param {string} url
  * @param {string[] | string} [dangerousCharOrChars]
@@ -790,8 +797,9 @@ function printUrl(url, dangerousCharOrChars = []) {
       ? dangerousCharOrChars
       : [dangerousCharOrChars]),
   ];
+
   return new RegExp(dangerousChars.map((x) => `\\${x}`).join("|")).test(url)
-    ? `<${url}>`
+    ? `<${encodeUrl(url, "<>")}>`
     : url;
 }
 


Test patch:
diff --git a/tests/format/markdown/link/__snapshots__/jsfmt.spec.js.snap b/tests/format/markdown/link/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/markdown/link/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/markdown/link/__snapshots__/jsfmt.spec.js.snap
@@ -47,6 +47,83 @@ proseWrap: "always"
 ================================================================================
 `;
 
+exports[`encodedLink.md - {"proseWrap":"always","singleQuote":true} format 1`] = `
+====================================options=====================================
+parsers: ["markdown"]
+printWidth: 80
+proseWrap: "always"
+singleQuote: true
+                                                                                | printWidth
+=====================================input======================================
+[link](https://www.google.fr/()foo->bar)
+[link](https://www.google.fr/foo->bar)
+[link](https://www.google.fr/foo-%3Ebar)
+[link](https://www.google.fr/foo-<bar)
+[link](https://www.google.fr/foo-%3Cbar)
+![link](https://www.google.fr/()foo->bar)
+![link](https://www.google.fr/foo->bar)
+![link](https://www.google.fr/foo-%3Ebar)
+![link](https://www.google.fr/foo-<bar)
+![link](https://www.google.fr/foo-%3Cbar)
+[link]: https://www.google.fr/()foo->bar
+[link]: https://www.google.fr/foo->bar
+[link]: https://www.google.fr/foo-%3Ebar
+[link]: https://www.google.fr/foo-<bar
+[link]: https://www.google.fr/foo-%3Cbar
+=====================================output=====================================
+[link](<https://www.google.fr/()foo-%3Ebar>)
+[link](https://www.google.fr/foo->bar) [link](https://www.google.fr/foo-%3Ebar)
+[link](https://www.google.fr/foo-<bar) [link](https://www.google.fr/foo-%3Cbar)
+![link](<https://www.google.fr/()foo-%3Ebar>)
+![link](https://www.google.fr/foo->bar)
+![link](https://www.google.fr/foo-%3Ebar)
+![link](https://www.google.fr/foo-<bar)
+![link](https://www.google.fr/foo-%3Cbar) [link]:
+https://www.google.fr/()foo->bar [link]: https://www.google.fr/foo->bar [link]:
+https://www.google.fr/foo-%3Ebar [link]: https://www.google.fr/foo-<bar [link]:
+https://www.google.fr/foo-%3Cbar
+
+================================================================================
+`;
+
+exports[`encodedLink.md - {"proseWrap":"always"} format 1`] = `
+====================================options=====================================
+parsers: ["markdown"]
+printWidth: 80
+proseWrap: "always"
+                                                                                | printWidth
+=====================================input======================================
+[link](https://www.google.fr/()foo->bar)
+[link](https://www.google.fr/foo->bar)
+[link](https://www.google.fr/foo-%3Ebar)
+[link](https://www.google.fr/foo-<bar)
+[link](https://www.google.fr/foo-%3Cbar)
+![link](https://www.google.fr/()foo->bar)
+![link](https://www.google.fr/foo->bar)
+![link](https://www.google.fr/foo-%3Ebar)
+![link](https://www.google.fr/foo-<bar)
+![link](https://www.google.fr/foo-%3Cbar)
+[link]: https://www.google.fr/()foo->bar
+[link]: https://www.google.fr/foo->bar
+[link]: https://www.google.fr/foo-%3Ebar
+[link]: https://www.google.fr/foo-<bar
+[link]: https://www.google.fr/foo-%3Cbar
+=====================================output=====================================
+[link](<https://www.google.fr/()foo-%3Ebar>)
+[link](https://www.google.fr/foo->bar) [link](https://www.google.fr/foo-%3Ebar)
+[link](https://www.google.fr/foo-<bar) [link](https://www.google.fr/foo-%3Cbar)
+![link](<https://www.google.fr/()foo-%3Ebar>)
+![link](https://www.google.fr/foo->bar)
+![link](https://www.google.fr/foo-%3Ebar)
+![link](https://www.google.fr/foo-<bar)
+![link](https://www.google.fr/foo-%3Cbar) [link]:
+https://www.google.fr/()foo->bar [link]: https://www.google.fr/foo->bar [link]:
+https://www.google.fr/foo-%3Ebar [link]: https://www.google.fr/foo-<bar [link]:
+https://www.google.fr/foo-%3Cbar
+
+================================================================================
+`;
+
 exports[`entity.md - {"proseWrap":"always","singleQuote":true} format 1`] = `
 ====================================options=====================================
 parsers: ["markdown"]
diff --git a/tests/format/markdown/link/encodedLink.md b/tests/format/markdown/link/encodedLink.md
new file mode 100644
--- /dev/null
+++ b/tests/format/markdown/link/encodedLink.md
@@ -0,0 +1,15 @@
+[link](https://www.google.fr/()foo->bar)
+[link](https://www.google.fr/foo->bar)
+[link](https://www.google.fr/foo-%3Ebar)
+[link](https://www.google.fr/foo-<bar)
+[link](https://www.google.fr/foo-%3Cbar)
+![link](https://www.google.fr/()foo->bar)
+![link](https://www.google.fr/foo->bar)
+![link](https://www.google.fr/foo-%3Ebar)
+![link](https://www.google.fr/foo-<bar)
+![link](https://www.google.fr/foo-%3Cbar)
+[link]: https://www.google.fr/()foo->bar
+[link]: https://www.google.fr/foo->bar
+[link]: https://www.google.fr/foo-%3Ebar
+[link]: https://www.google.fr/foo-<bar
+[link]: https://www.google.fr/foo-%3Cbar
\ No newline at end of file