Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/rules/no-bare-urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,25 @@ export default /** @satisfies {NoBareUrlsRuleDefinition} */ ({
node: linkNode,
messageId: "bareUrl",
fix(fixer) {
return fixer.replaceText(linkNode, `<${text}>`);
let replacementText = `<${text}>`;
// GFM parses `www` autolinks with an `http://` URL.
if (url === `http://${text}`) {
const escapedLinkText = text.replace(
/[[\]\\*_~`]/gu,
"\\$&",
);
const escapedLinkDestination = url.replace(
/[\\()]/gu,
"\\$&",
);

replacementText = `[${escapedLinkText}](${escapedLinkDestination})`;
}

return fixer.replaceText(
linkNode,
replacementText,
);
},
});
}
Expand Down
104 changes: 104 additions & 0 deletions tests/rules/no-bare-urls.test.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GFM allows any character other than whitespace and < after the domain, and Example 626 treats the following entire URL as a valid autolink:

Playground

www.google.com/search?q=(business))+ok

The current autofix produces:

[www.google.com/search?q=(business))+ok](http://www.google.com/search?q=(business))+ok)

When parsed again, the actual link destination is truncated to:

http://www.google.com/search?q=(business)

This happens because CommonMark requires parentheses in a link destination without angle brackets to be escaped or balanced. Similarly, after autofixing www.example.com/a\*b, the backslash is interpreted as a backslash escape, changing the URL to /a*b. In other words, the autofix silently changes valid URLs to different destinations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another incorrect autofix behavior occurs when autofixing the following valid GFM input:

Playground

www.example.com/a]b

produces invalid link text:

[www.example.com/a]b](http://www.example.com/a]b)

I reproduced an issue where no-bare-urls throws Custom getRange() method must be implemented in the subclass during ESLint’s next autofix pass.

Additionally, after autofixing www.example.com/a*b*c and www.example.com/a~b~c, characters in the label are parsed as emphasis and strikethrough, respectively, changing the rendered output. The link text need to be escaped separately from the destination. CommonMark explicitly requires brackets within link text to be either backslash-escaped or part of a matched pair per the link text definition and Examples 528–529.

Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,110 @@ ruleTester.run("no-bare-urls", rule, {
" Code https://example.com/code?type=indent code",
],
invalid: [
{
code: "Visit www.example.com for details.",
output: "Visit [www.example.com](http://www.example.com) for details.",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 7,
endLine: 1,
endColumn: 22,
},
],
},
{
code: "www.google.com/search?q=(business))+ok",
output: "[www.google.com/search?q=(business))+ok](http://www.google.com/search?q=\\(business\\)\\)+ok)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 39,
},
],
},
{
code: "www.example.com/a\\*b",
output: "[www.example.com/a\\\\\\*b](http://www.example.com/a\\\\*b)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 21,
},
],
},
{
code: "www.example.com/a]b",
output: "[www.example.com/a\\]b](http://www.example.com/a]b)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 20,
},
],
},
{
code: "www.example.com/a[b",
output: "[www.example.com/a\\[b](http://www.example.com/a[b)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 20,
},
],
},
{
code: "www.example.com/a*b*c",
output: "[www.example.com/a\\*b\\*c](http://www.example.com/a*b*c)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 22,
},
],
},
{
code: "www.example.com/a~b~c",
output: "[www.example.com/a\\~b\\~c](http://www.example.com/a~b~c)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 22,
},
],
},
{
code: "www.example.com/a`b`c",
output: "[www.example.com/a\\`b\\`c](http://www.example.com/a`b`c)",
errors: [
{
messageId: "bareUrl",
line: 1,
column: 1,
endLine: 1,
endColumn: 22,
},
],
},
{
code: "https://www.example.com/",
output: "<https://www.example.com/>",
Expand Down