Skip to content

Commit

Permalink
Create Linter Rule to Remove Quotes Around Internal Links (#53510)
Browse files Browse the repository at this point in the history
Co-authored-by: Rachael Sewell <rachmari@github.com>
  • Loading branch information
ashishkeshan and rachmari authored Jan 10, 2025
1 parent ecc1417 commit 3a792f8
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Adding an email address to your GitHub account
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/)."'
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/).'
redirect_from:
- /articles/adding-an-email-address-to-your-github-account
- /github/setting-up-and-managing-your-github-user-account/adding-an-email-address-to-your-github-account
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Viewing people's roles in an organization
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)."'
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).'
permissions: Organization members can see people's roles in the organization.
redirect_from:
- /articles/viewing-people-s-roles-in-an-organization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ To learn more, see [AUTOTITLE](/repositories/configuring-branches-and-merges-in-
To import a prebuilt ruleset created by {% data variables.product.company_short %}, see [`github/ruleset-recipes`](https://github.com/github/ruleset-recipes).

{% ifversion repo-rules-management %}
{% 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)."
{% 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).
{% endif %}

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

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.

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)."
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).

### Choosing which repositories to target in your enterprise

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

### Selecting branch or tag protections

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)"
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)

### Adding metadata restrictions

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

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.

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)."
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).

### Choosing which repositories to target in your enterprise

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ You can edit a ruleset to change parts of the ruleset, such as the name, bypass
1. On the "Rulesets" page, click the name of the ruleset you want to edit.
1. Change the ruleset as required.

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ For details of how to change the model for {% data variables.product.prodname_co

## Leaving feedback

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)."
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).
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Viewing traffic to a repository
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.'
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 %}'
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 %}'
redirect_from:
- /articles/viewing-traffic-to-a-repository
- /github/visualizing-repository-data-with-graphs/viewing-traffic-to-a-repository
Expand Down
3 changes: 2 additions & 1 deletion data/reusables/contributing/content-linter-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@
| GHD039 | expiring-soon | Content that expires soon should be proactively addressed. | warning | expired |
| [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 |
| GHD041 | third-party-action-pinning | Code examples that use third-party actions must always pin to a full length commit SHA | error | feature, actions |
| 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 |
| 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 |
| GHD043 | link-quotation | Internal link titles must not be surrounded by quotations | error | links, url |
5 changes: 5 additions & 0 deletions src/content-linter/lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export function doesStringEndWithPeriod(text) {
return /^.*\.['"]?$/.test(text)
}

export function quotePrecedesLinkOpen(text) {
if (!text) return false
return text.endsWith('"') || text.endsWith("'")
}

// Filters a list of tokens by token type only when they match
// a specific token type order.
// For example, if a list of tokens contains:
Expand Down
2 changes: 2 additions & 0 deletions src/content-linter/lib/linting-rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { expiredContent, expiringSoon } from './expired-content.js'
import { tableLiquidVersioning } from './table-liquid-versioning.js'
import { thirdPartyActionPinning } from './third-party-action-pinning.js'
import { liquidTagWhitespace } from './liquid-tag-whitespace.js'
import { linkQuotation } from './link-quotation.js'

const noDefaultAltText = markdownlintGitHub.find((elem) =>
elem.names.includes('no-default-alt-text'),
Expand Down Expand Up @@ -79,5 +80,6 @@ export const gitHubDocsMarkdownlint = {
tableLiquidVersioning,
thirdPartyActionPinning,
liquidTagWhitespace,
linkQuotation,
],
}
68 changes: 68 additions & 0 deletions src/content-linter/lib/linting-rules/link-quotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { addError, filterTokens } from 'markdownlint-rule-helpers'
import { getRange, quotePrecedesLinkOpen } from '../helpers/utils.js'
import { escapeRegExp } from 'lodash-es'

export const linkQuotation = {
names: ['GHD043', 'link-quotation'],
description: 'Internal link titles must not be surrounded by quotations',
tags: ['links', 'url'],
parser: 'markdownit',
function: (params, onError) => {
filterTokens(params, 'inline', (token) => {
const { children } = token
let previous_child = children[0]
let inLinkWithPrecedingQuotes = false
let linkUrl = ''
let content = []
let line = ''
for (let i = 1; i < children.length; i++) {
const child = children[i]
if (child.type === 'link_open' && quotePrecedesLinkOpen(previous_child.content)) {
inLinkWithPrecedingQuotes = true
linkUrl = escapeRegExp(child.attrs[0][1])
line = child.line
} else if (inLinkWithPrecedingQuotes && child.type === 'text') {
content.push(escapeRegExp(child.content.trim()))
} else if (inLinkWithPrecedingQuotes && child.type === 'code_inline') {
content.push('`' + escapeRegExp(child.content.trim()) + '`')
} else if (child.type === 'link_close') {
const title = content.join(' ')
const regex = new RegExp(`"\\[${title}\\]\\(${linkUrl}\\)({%.*%})?(!|\\.|\\?|,)?"`)
if (regex.test(child.line)) {
const match = child.line.match(regex)[0]
const range = getRange(child.line, match)
let newLine = match
if (newLine.startsWith('"')) {
newLine = newLine.slice(1)
}
if (newLine.endsWith('"')) {
newLine = newLine.slice(0, -1)
}
if (newLine.endsWith('".')) {
newLine = newLine.slice(0, -2) + '.'
}
const lineNumber = child.lineNumber
addError(
onError,
lineNumber,
'Remove quotes surrounding the link title.',
match,
range,
{
lineNumber,
editColumn: range[0],
deleteCount: range[1],
insertText: newLine,
},
)
}
inLinkWithPrecedingQuotes = false
content = []
line = ''
linkUrl = ''
}
previous_child = child
}
})
},
}
11 changes: 11 additions & 0 deletions src/content-linter/style/github-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ const githubDocsConfig = {
'partial-markdown-files': true,
'yml-files': true,
},
'link-quotation': {
// GHD043
severity: 'error',
'partial-markdown-files': true,
'yml-files': true,
},
}

export const githubDocsFrontmatterConfig = {
Expand Down Expand Up @@ -210,6 +216,11 @@ export const githubDocsFrontmatterConfig = {
severity: 'warning',
'partial-markdown-files': false,
},
'link-quotation': {
// GHD043
severity: 'error',
'partial-markdown-files': false,
},
}

// Configures rules from the `github/markdownlint-github` repo
Expand Down
36 changes: 36 additions & 0 deletions src/content-linter/tests/unit/link-quotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, test } from 'vitest'

import { runRule } from '../../lib/init-test.js'
import { linkQuotation } from '../../lib/linting-rules/link-quotation.js'

describe(linkQuotation.names.join(' - '), () => {
test('links that are formatted correctly should not generate an error', async () => {
const markdown = [
'Random stuff [A title](./image.png)',
'"This is a direct quote" [A title](./image.png)',
].join('\n')
const result = await runRule(linkQuotation, { strings: { markdown } })
const errors = result.markdown
expect(errors.length).toBe(0)
})

test('links with quotes around them should error out', async () => {
const markdown = [
'Random stuff "[A title](./image.png)."',
'Random stuff "[A title](./image.png)?"',
'Random stuff "[A title](./image.png)!"',
'Random stuff "[A title](./image.png)".',
'Random stuff "[A title](./image.png)"?',
'Random stuff "[A title](./image.png)"!',
'See "[AUTOTITLE](/foo/bar){% ifversion fpt %}."{% elsif ghes or ghec %}" and "[AUTOTITLE](/foo/bar)."{% endif %}',
'See "[AUTOTITLE](/foo/bar)," "[AUTOTITLE](/foo/bar2)," "[AUTOTITLE](/foo/bar3)," and "[AUTOTITLE](/foo/bar4)."',
'See "[Anchor link](#anchor-link)."',
].join('\n')
const result = await runRule(linkQuotation, { strings: { markdown } })
const errors = result.markdown
expect(errors.length).toBe(13)
expect(errors[0].errorRange).toEqual([14, 25])
expect(errors[0].fixInfo.insertText).toBe('[A title](./image.png).')
expect(errors[1].fixInfo.insertText).toBe('[A title](./image.png)?')
})
})

0 comments on commit 3a792f8

Please sign in to comment.