This commit is contained in:
fisker Cheung authored and GitHub committed 2026-06-26 20:30:13 +08:00
1 parent af720efb42
commit ca09efd42c
6 files changed
+46 -151

No files matched your search

-15
View File
@@ -1,15 +0,0 @@
#### Fix URL formatting edge cases (#19150 by @andersk)
Avoid corrupting a link, image, or link reference definition with an empty URL and a title, or with certain characters like `<`, `(`, `\`, by using `<...>` wrapping and `\` escaping as appropriate.
<!-- prettier-ignore -->
```md
<!-- Input -->
[link](<> "title"), ![image](<\<(\\>)
<!-- Prettier stable -->
[link]( "title"), ![image](<(\)
<!-- Prettier main -->
[link](<> "title"), ![image](<\<(\\>)
```
+24 -22
View File
@@ -1,4 +1,5 @@
import collapseWhiteSpace from "collapse-white-space";
import escapeStringRegexp from "escape-string-regexp";
import {
align,
DOC_TYPE_STRING,
@@ -223,7 +224,7 @@ function printMdast(path, options, print) {
"[",
printChildren(path, options, print),
"](",
node.url === "" && node.title !== null ? "<>" : printUrl(node.url),
printUrl(node.url, ")"),
printTitle(node.title, options),
")",
];
@@ -238,7 +239,7 @@ function printMdast(path, options, print) {
"![",
printImageAlt(node, options),
"](",
node.url === "" && node.title !== null ? "<>" : printUrl(node.url),
printUrl(node.url, ")"),
printTitle(node.title, options),
")",
];
@@ -506,23 +507,30 @@ 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]
* @returns {string}
*/
function printUrl(url) {
// Backslash followed by ASCII punctuation would be misinterpreted as an
// escape sequence, so must itself be escaped.
url = url.replaceAll(/\\(?![^!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g, "\\\\");
// CommonMark forbids ASCII controls, space, unbalanced parentheses, and
// initial <, unless wrapped in <> with any inner < or > escaped. CommonMark
// only suggests implementations "should" support at least three levels of
// parenthesis nesting, so it's unclear whether we can safely rely on three
// levels as we do here, but we certainly can't expect more.
// eslint-disable-next-line no-control-regex
return /[\x00-\x1f\x7f ]|^</.test(url) ||
!/^(?:[^()]|\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))*$/.test(url)
? `<${url.replaceAll(/([<>])/g, String.raw`\$1`)}>`
function printUrl(url, dangerousCharOrChars = []) {
const dangerousChars = [
" ",
...(Array.isArray(dangerousCharOrChars)
? dangerousCharOrChars
: [dangerousCharOrChars]),
];
return new RegExp(
dangerousChars.map((x) => escapeStringRegexp(x)).join("|"),
).test(url)
? `<${encodeUrl(url, "<>")}>`
: url;
}
@@ -539,13 +547,7 @@ function printTitle(title, options, printSpace = true) {
title = title.replaceAll(/\\(?=["')])/g, "");
}
if (
title.includes('"') &&
title.includes("'") &&
!title.includes("(") &&
!title.includes(")")
) {
title = title.replaceAll("\\", "\\\\");
if (title.includes('"') && title.includes("'") && !title.includes(")")) {
return `(${title})`; // avoid escaped quotes
}
const quote = getPreferredQuote(title, options.singleQuote);
@@ -982,7 +982,7 @@ proseWrap: "always"
[link]((foo)and(bar))
=====================================output=====================================
[link]((foo)and(bar))
[link](<(foo)and(bar)>)
================================================================================
`;
@@ -996,7 +996,7 @@ proseWrap: "always"
[link](foo(and\\(bar\\)))
=====================================output=====================================
[link](foo(and(bar)))
[link](<foo(and(bar))>)
================================================================================
`;
@@ -1010,7 +1010,7 @@ proseWrap: "always"
[link](<foo(and(bar))>)
=====================================output=====================================
[link](foo(and(bar)))
[link](<foo(and(bar))>)
================================================================================
`;
@@ -8340,7 +8340,7 @@ proseWrap: "always"
[link](\\(foo\\))
=====================================output=====================================
[link]((foo))
[link](<(foo)>)
================================================================================
`;
@@ -8354,7 +8354,7 @@ proseWrap: "always"
[link](foo(and(bar)))
=====================================output=====================================
[link](foo(and(bar)))
[link](<foo(and(bar))>)
================================================================================
`;
@@ -57,63 +57,28 @@ singleQuote: true
[link](https://www.google.fr/foo-%3Ebar)
[link](https://www.google.fr/foo-<bar)
[link](https://www.google.fr/foo-%3Cbar)
[link](\\<)
[link](\\()
[link](\\\\)
[link](< >)
[link](<> "title")
[link](<>)
[link]()
![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](\\<)
![link](\\()
![link](\\\\)
![link](< >)
![link](<> "title")
![link](<>)
![link]()
[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]: \\<
[link]: \\(
[link]: \\\\
[link]: < >
[link]: <> "title"
[link]: <>
=====================================output=====================================
[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](<\\<>) [link](<(>) [link](\\\\)
[link](< >) [link](<> 'title') [link]() [link]()
![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-%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](<\\<>) ![link](<(>) ![link](\\\\)
![link](< >) ![link](<> 'title') ![link]() ![link]()
[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]: <\\<>
[link]: <(>
[link]: \\\\
[link]: < >
[link]: <> 'title'
[link]: <>
![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
================================================================================
`;
@@ -129,63 +94,28 @@ proseWrap: "always"
[link](https://www.google.fr/foo-%3Ebar)
[link](https://www.google.fr/foo-<bar)
[link](https://www.google.fr/foo-%3Cbar)
[link](\\<)
[link](\\()
[link](\\\\)
[link](< >)
[link](<> "title")
[link](<>)
[link]()
![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](\\<)
![link](\\()
![link](\\\\)
![link](< >)
![link](<> "title")
![link](<>)
![link]()
[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]: \\<
[link]: \\(
[link]: \\\\
[link]: < >
[link]: <> "title"
[link]: <>
=====================================output=====================================
[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](<\\<>) [link](<(>) [link](\\\\)
[link](< >) [link](<> "title") [link]() [link]()
![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-%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](<\\<>) ![link](<(>) ![link](\\\\)
![link](< >) ![link](<> "title") ![link]() ![link]()
[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]: <\\<>
[link]: <(>
[link]: \\\\
[link]: < >
[link]: <> "title"
[link]: <>
![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
================================================================================
`;
+1 -23
View File
@@ -3,35 +3,13 @@
[link](https://www.google.fr/foo-%3Ebar)
[link](https://www.google.fr/foo-<bar)
[link](https://www.google.fr/foo-%3Cbar)
[link](\<)
[link](\()
[link](\\)
[link](< >)
[link](<> "title")
[link](<>)
[link]()
![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](\<)
![link](\()
![link](\\)
![link](< >)
![link](<> "title")
![link](<>)
![link]()
[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]: \<
[link]: \(
[link]: \\
[link]: < >
[link]: <> "title"
[link]: <>
[link]: https://www.google.fr/foo-%3Cbar