Skip to content

Commit 3a792f8

Browse files
Create Linter Rule to Remove Quotes Around Internal Links (#53510)
Co-authored-by: Rachael Sewell <[email protected]>
1 parent ecc1417 commit 3a792f8

File tree

12 files changed

+133
-10
lines changed

12 files changed

+133
-10
lines changed

content/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/adding-an-email-address-to-your-github-account.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Adding an email address to your GitHub account
3-
intro: '{% data variables.product.product_name %} allows you to add as many email addresses to your account as you like. If you set an email address in your local Git configuration, you will need to add it to your account settings in order to connect your commits to your account. For more information about your email address and commits, see "[Setting your commit email address](/articles/setting-your-commit-email-address/)."'
3+
intro: '{% data variables.product.product_name %} allows you to add as many email addresses to your account as you like. If you set an email address in your local Git configuration, you will need to add it to your account settings in order to connect your commits to your account. For more information about your email address and commits, see [Setting your commit email address](/articles/setting-your-commit-email-address/).'
44
redirect_from:
55
- /articles/adding-an-email-address-to-your-github-account
66
- /github/setting-up-and-managing-your-github-user-account/adding-an-email-address-to-your-github-account

content/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/viewing-peoples-roles-in-an-organization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Viewing people's roles in an organization
3-
intro: 'You can view a list of the people in your organization and filter by their role. For more information on organization roles, see "[Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization)."'
3+
intro: 'You can view a list of the people in your organization and filter by their role. For more information on organization roles, see [Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization).'
44
permissions: Organization members can see people's roles in the organization.
55
redirect_from:
66
- /articles/viewing-people-s-roles-in-an-organization

content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-governance.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ To learn more, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-
3030
To import a prebuilt ruleset created by {% data variables.product.company_short %}, see [`github/ruleset-recipes`](https://github.com/github/ruleset-recipes).
3131

3232
{% ifversion repo-rules-management %}
33-
{% data reusables.repositories.import-a-ruleset-conceptual %} For more information, see "[AUTOTITLE](/organizations/managing-organization-settings/managing-rulesets-for-repositories-in-your-organization#using-ruleset-history)."
33+
{% data reusables.repositories.import-a-ruleset-conceptual %} For more information, see [AUTOTITLE](/organizations/managing-organization-settings/managing-rulesets-for-repositories-in-your-organization#using-ruleset-history).
3434
{% endif %}
3535

3636
## How will I define where my ruleset applies?
@@ -79,7 +79,7 @@ The following are eligible for bypass access:
7979

8080
Select all organizations, choose a selection of existing organizations, or set a dynamic list by name. If you use {% data variables.product.prodname_emus %}, you can also choose to target all repositories owned by users in your enterprise.
8181

82-
If you set a dynamic list, you'll add one or more naming patterns using `fnmatch` syntax. For example, the string `*open-source` would match any organization with a name that ends with `open-source`. For syntax details, see "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#using-fnmatch-syntax)."
82+
If you set a dynamic list, you'll add one or more naming patterns using `fnmatch` syntax. For example, the string `*open-source` would match any organization with a name that ends with `open-source`. For syntax details, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#using-fnmatch-syntax).
8383

8484
### Choosing which repositories to target in your enterprise
8585

@@ -91,7 +91,7 @@ Within the selected organizations, you can target all repositories or target a d
9191

9292
### Selecting branch or tag protections
9393

94-
In the "Branch protections" or "Tag protections" section, select the rules you want to include in the ruleset. When you select a rule, you may be able to enter additional settings for the rule. For more information on the rules, see "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)"
94+
In the "Branch protections" or "Tag protections" section, select the rules you want to include in the ruleset. When you select a rule, you may be able to enter additional settings for the rule. For more information on the rules, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)
9595

9696
### Adding metadata restrictions
9797

@@ -132,7 +132,7 @@ You can grant certain roles, teams, or apps bypass permissions as well as the ab
132132

133133
Select all organizations, choose a selection of existing organizations, or set a dynamic list by name. If you use {% data variables.product.prodname_emus %}, you can also choose to target all repositories owned by users in your enterprise.
134134

135-
If you set a dynamic list, you'll add one or more naming patterns using `fnmatch` syntax. For example, the string `*open-source` would match any organization with a name that ends with `open-source`. For syntax details, see "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#using-fnmatch-syntax)."
135+
If you set a dynamic list, you'll add one or more naming patterns using `fnmatch` syntax. For example, the string `*open-source` would match any organization with a name that ends with `open-source`. For syntax details, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#using-fnmatch-syntax).
136136

137137
### Choosing which repositories to target in your enterprise
138138

content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/managing-policies-for-code-governance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ You can edit a ruleset to change parts of the ruleset, such as the name, bypass
3232
1. On the "Rulesets" page, click the name of the ruleset you want to edit.
3333
1. Change the ruleset as required.
3434

35-
For information on the available rules, see "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)"
35+
For information on the available rules, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)
3636

3737
1. At the bottom of the page, click **Save changes**.
3838

content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ For details of how to change the model for {% data variables.product.prodname_co
5252

5353
## Leaving feedback
5454

55-
To leave feedback about Claude 3.5 Sonnet in {% data variables.product.prodname_copilot %}, or to ask a question, see the {% data variables.product.prodname_github_community %} discussion "[Claude 3.5 Sonnet is now available to all {% data variables.product.prodname_copilot_short %} users in Public Preview](https://github.com/orgs/community/discussions/143337)."
55+
To leave feedback about Claude 3.5 Sonnet in {% data variables.product.prodname_copilot %}, or to ask a question, see the {% data variables.product.prodname_github_community %} discussion [Claude 3.5 Sonnet is now available to all {% data variables.product.prodname_copilot_short %} users in Public Preview](https://github.com/orgs/community/discussions/143337).

content/repositories/viewing-activity-and-data-for-your-repository/viewing-traffic-to-a-repository.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Viewing traffic to a repository
33
intro: 'Anyone with push access to a repository can view its traffic, including full clones (not fetches), visitors from the past 14 days, referring sites, and popular content in the traffic graph.'
4-
product: 'This repository insights graph is available in public repositories with {% data variables.product.prodname_free_user %} and {% data variables.product.prodname_free_team %} for organizations, and in public and private repositories with {% data variables.product.prodname_pro %}, {% data variables.product.prodname_team %}, and {% data variables.product.prodname_ghe_cloud %}.{% ifversion fpt %} For more information, see "[About repository graphs](/articles/about-repository-graphs)" and "[{% data variables.product.prodname_dotcom %}''s products](/articles/github-s-products)."{% endif %}'
4+
product: 'This repository insights graph is available in public repositories with {% data variables.product.prodname_free_user %} and {% data variables.product.prodname_free_team %} for organizations, and in public and private repositories with {% data variables.product.prodname_pro %}, {% data variables.product.prodname_team %}, and {% data variables.product.prodname_ghe_cloud %}.{% ifversion fpt %} For more information, see [About repository graphs](/articles/about-repository-graphs) and [{% data variables.product.prodname_dotcom %}''s products](/articles/github-s-products).{% endif %}'
55
redirect_from:
66
- /articles/viewing-traffic-to-a-repository
77
- /github/visualizing-repository-data-with-graphs/viewing-traffic-to-a-repository

data/reusables/contributing/content-linter-rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@
6565
| GHD039 | expiring-soon | Content that expires soon should be proactively addressed. | warning | expired |
6666
| [GHD040](https://github.com/github/docs/blob/main/src/content-linter/README.md) | table-liquid-versioning | Tables must use the correct liquid versioning format | error | tables |
6767
| GHD041 | third-party-action-pinning | Code examples that use third-party actions must always pin to a full length commit SHA | error | feature, actions |
68-
| GHD042 | liquid-tag-whitespace | Liquid tags should start and end with one whitespace. Liquid tag arguments should be separated by only one whitespace. | error | liquid, format |
68+
| GHD042 | liquid-tag-whitespace | Liquid tags should start and end with one whitespace. Liquid tag arguments should be separated by only one whitespace. | error | liquid, format |
69+
| GHD043 | link-quotation | Internal link titles must not be surrounded by quotations | error | links, url |

src/content-linter/lib/helpers/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export function doesStringEndWithPeriod(text) {
5050
return /^.*\.['"]?$/.test(text)
5151
}
5252

53+
export function quotePrecedesLinkOpen(text) {
54+
if (!text) return false
55+
return text.endsWith('"') || text.endsWith("'")
56+
}
57+
5358
// Filters a list of tokens by token type only when they match
5459
// a specific token type order.
5560
// For example, if a list of tokens contains:

src/content-linter/lib/linting-rules/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { expiredContent, expiringSoon } from './expired-content.js'
3232
import { tableLiquidVersioning } from './table-liquid-versioning.js'
3333
import { thirdPartyActionPinning } from './third-party-action-pinning.js'
3434
import { liquidTagWhitespace } from './liquid-tag-whitespace.js'
35+
import { linkQuotation } from './link-quotation.js'
3536

3637
const noDefaultAltText = markdownlintGitHub.find((elem) =>
3738
elem.names.includes('no-default-alt-text'),
@@ -79,5 +80,6 @@ export const gitHubDocsMarkdownlint = {
7980
tableLiquidVersioning,
8081
thirdPartyActionPinning,
8182
liquidTagWhitespace,
83+
linkQuotation,
8284
],
8385
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { addError, filterTokens } from 'markdownlint-rule-helpers'
2+
import { getRange, quotePrecedesLinkOpen } from '../helpers/utils.js'
3+
import { escapeRegExp } from 'lodash-es'
4+
5+
export const linkQuotation = {
6+
names: ['GHD043', 'link-quotation'],
7+
description: 'Internal link titles must not be surrounded by quotations',
8+
tags: ['links', 'url'],
9+
parser: 'markdownit',
10+
function: (params, onError) => {
11+
filterTokens(params, 'inline', (token) => {
12+
const { children } = token
13+
let previous_child = children[0]
14+
let inLinkWithPrecedingQuotes = false
15+
let linkUrl = ''
16+
let content = []
17+
let line = ''
18+
for (let i = 1; i < children.length; i++) {
19+
const child = children[i]
20+
if (child.type === 'link_open' && quotePrecedesLinkOpen(previous_child.content)) {
21+
inLinkWithPrecedingQuotes = true
22+
linkUrl = escapeRegExp(child.attrs[0][1])
23+
line = child.line
24+
} else if (inLinkWithPrecedingQuotes && child.type === 'text') {
25+
content.push(escapeRegExp(child.content.trim()))
26+
} else if (inLinkWithPrecedingQuotes && child.type === 'code_inline') {
27+
content.push('`' + escapeRegExp(child.content.trim()) + '`')
28+
} else if (child.type === 'link_close') {
29+
const title = content.join(' ')
30+
const regex = new RegExp(`"\\[${title}\\]\\(${linkUrl}\\)({%.*%})?(!|\\.|\\?|,)?"`)
31+
if (regex.test(child.line)) {
32+
const match = child.line.match(regex)[0]
33+
const range = getRange(child.line, match)
34+
let newLine = match
35+
if (newLine.startsWith('"')) {
36+
newLine = newLine.slice(1)
37+
}
38+
if (newLine.endsWith('"')) {
39+
newLine = newLine.slice(0, -1)
40+
}
41+
if (newLine.endsWith('".')) {
42+
newLine = newLine.slice(0, -2) + '.'
43+
}
44+
const lineNumber = child.lineNumber
45+
addError(
46+
onError,
47+
lineNumber,
48+
'Remove quotes surrounding the link title.',
49+
match,
50+
range,
51+
{
52+
lineNumber,
53+
editColumn: range[0],
54+
deleteCount: range[1],
55+
insertText: newLine,
56+
},
57+
)
58+
}
59+
inLinkWithPrecedingQuotes = false
60+
content = []
61+
line = ''
62+
linkUrl = ''
63+
}
64+
previous_child = child
65+
}
66+
})
67+
},
68+
}

0 commit comments

Comments
 (0)