diff --git a/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 b/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 index f1c5c01c5dfd..b4baa9dfde01 100644 --- a/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 +++ b/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 @@ -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 diff --git a/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 b/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 index 15ef0ddab8f1..aba42992ca38 100644 --- a/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 +++ b/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 @@ -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 diff --git a/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-governance.md b/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-governance.md index d6bb07cae274..82863cbb6955 100644 --- a/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-governance.md +++ b/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-governance.md @@ -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? @@ -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 @@ -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 @@ -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 diff --git a/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/managing-policies-for-code-governance.md b/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/managing-policies-for-code-governance.md index ea36de1701f6..84b87f29c3b1 100644 --- a/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/managing-policies-for-code-governance.md +++ b/content/admin/enforcing-policies/enforcing-policies-for-your-enterprise/managing-policies-for-code-governance.md @@ -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**. diff --git a/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md b/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md index 4b435beebeda..58a41a587a48 100644 --- a/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md +++ b/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md @@ -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). diff --git a/content/repositories/viewing-activity-and-data-for-your-repository/viewing-traffic-to-a-repository.md b/content/repositories/viewing-activity-and-data-for-your-repository/viewing-traffic-to-a-repository.md index 4b238fd8964f..7e7d647adc1b 100644 --- a/content/repositories/viewing-activity-and-data-for-your-repository/viewing-traffic-to-a-repository.md +++ b/content/repositories/viewing-activity-and-data-for-your-repository/viewing-traffic-to-a-repository.md @@ -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 diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index 82d8de234160..f471bf450f22 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -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 | \ No newline at end of file +| 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 | \ No newline at end of file diff --git a/src/content-linter/lib/helpers/utils.js b/src/content-linter/lib/helpers/utils.js index c6538fe1f98d..25d009c0df50 100644 --- a/src/content-linter/lib/helpers/utils.js +++ b/src/content-linter/lib/helpers/utils.js @@ -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: diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index 6c37c8f6016a..0c8ab9b0c2d4 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -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'), @@ -79,5 +80,6 @@ export const gitHubDocsMarkdownlint = { tableLiquidVersioning, thirdPartyActionPinning, liquidTagWhitespace, + linkQuotation, ], } diff --git a/src/content-linter/lib/linting-rules/link-quotation.js b/src/content-linter/lib/linting-rules/link-quotation.js new file mode 100644 index 000000000000..6d6db6bcf8cd --- /dev/null +++ b/src/content-linter/lib/linting-rules/link-quotation.js @@ -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 + } + }) + }, +} diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index ca9119db3510..6c92e6154f40 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -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 = { @@ -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 diff --git a/src/content-linter/tests/unit/link-quotation.js b/src/content-linter/tests/unit/link-quotation.js new file mode 100644 index 000000000000..d76ef0266d34 --- /dev/null +++ b/src/content-linter/tests/unit/link-quotation.js @@ -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)?') + }) +})