diff --git a/assets/images/help/issues/issues-labels-button.png b/assets/images/help/issues/issues-labels-button.png new file mode 100644 index 000000000000..c4a8113f664a Binary files /dev/null and b/assets/images/help/issues/issues-labels-button.png differ diff --git a/assets/images/help/notifications/notifications-general-existence-indicator.png b/assets/images/help/notifications/notifications-general-existence-indicator.png new file mode 100644 index 000000000000..dbca6f5802a7 Binary files /dev/null and b/assets/images/help/notifications/notifications-general-existence-indicator.png differ diff --git a/content/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners.md b/content/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners.md index 99c34a13fd56..702e4d4d6be1 100644 --- a/content/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners.md +++ b/content/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners.md @@ -116,7 +116,7 @@ The following operating systems are supported for the self-hosted runner applica ### macOS -- macOS 10.13 (High Sierra) or later +- macOS 11.0 (Big Sur) or later ### Architectures diff --git a/content/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries.md b/content/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries.md index 6b331b6b4a34..b9b9631ceabb 100644 --- a/content/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries.md +++ b/content/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries.md @@ -347,7 +347,7 @@ corresponding query. Alternatively, for consistency with the standard {% data va you can write query help in the `.qhelp` format. Query help written in `.qhelp` files can’t be included in SARIF files, and they can’t be processed by code scanning so must be converted to markdown before running -the analysis. For more information, see ["Query help files"](https://codeql.github.com/docs/writing-codeql-queries/query-help-files/#query-help-files) +the analysis. For more information, see "[Query help files](https://codeql.github.com/docs/writing-codeql-queries/query-help-files/#query-help-files)" and "[AUTOTITLE](/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/testing-query-help-files)." ## Results @@ -380,4 +380,4 @@ IDE. ## Further reading -- ["Analyzing your projects in {% data variables.product.prodname_codeql %} for VS Code"](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#analyzing-your-projects) +- "[Analyzing your projects in {% data variables.product.prodname_codeql %} for VS Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#analyzing-your-projects)" diff --git a/content/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions.md b/content/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions.md index 7e132ad90464..572e687eaf46 100644 --- a/content/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions.md +++ b/content/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions.md @@ -41,7 +41,7 @@ For workflows initiated by {% data variables.product.prodname_dependabot %} (`gi {% ifversion actions-stable-actor-ids %}These restrictions apply even if the workflow is re-run by a different actor.{% endif %} -For more information, see ["Keeping your GitHub Actions and workflows secure: Preventing pwn requests"](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/). +For more information, see "[Keeping your GitHub Actions and workflows secure: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)." {% ifversion fpt or ghec or ghes %} diff --git a/content/codespaces/managing-your-codespaces/managing-repository-access-for-your-codespaces.md b/content/codespaces/managing-your-codespaces/managing-repository-access-for-your-codespaces.md index feca37f8a1fa..a71cbc333b49 100644 --- a/content/codespaces/managing-your-codespaces/managing-repository-access-for-your-codespaces.md +++ b/content/codespaces/managing-your-codespaces/managing-repository-access-for-your-codespaces.md @@ -21,7 +21,7 @@ By default, your codespace is assigned a token scoped with `read` permission or For more information, see "[AUTOTITLE](/codespaces/codespaces-reference/security-in-github-codespaces#authentication)." -If your project needs additional permissions for other repositories, you can configure this in the `devcontainer.json` file, as described in ["Setting additional repository permissions"](#setting-additional-repository-permissions) later in this article. When permissions are listed in the `devcontainer.json` file, you will be prompted to review and authorize the additional permissions as part of codespace creation for that repository. Once you've authorized the listed permissions, {% data variables.product.prodname_github_codespaces %} will remember your choice and will not prompt you for authorization unless the permissions in the `devcontainer.json` file change. +If your project needs additional permissions for other repositories, you can configure this in the `devcontainer.json` file, as described in "[Setting additional repository permissions](#setting-additional-repository-permissions)" later in this article. When permissions are listed in the `devcontainer.json` file, you will be prompted to review and authorize the additional permissions as part of codespace creation for that repository. Once you've authorized the listed permissions, {% data variables.product.prodname_github_codespaces %} will remember your choice and will not prompt you for authorization unless the permissions in the `devcontainer.json` file change. {% note %} diff --git a/content/contributing/writing-for-github-docs/style-guide.md b/content/contributing/writing-for-github-docs/style-guide.md index bee1f3dfb81f..5c7428f3e7ef 100644 --- a/content/contributing/writing-for-github-docs/style-guide.md +++ b/content/contributing/writing-for-github-docs/style-guide.md @@ -256,7 +256,7 @@ Use alt text to express the core idea of the image, without duplicating the webp > Diagram showing a five-step process by which a {% data variables.product.prodname_actions %} runner can be automatically added to named classes of runners and then requested by specific jobs. -For example, see [accompanying explanation of this diagram in the Actions documentation.](/free-pro-team@latest/actions/using-github-hosted-runners/using-larger-runners#architectural-overview-of-larger-runners). +For example, see [accompanying explanation of this diagram in the Actions documentation](/free-pro-team@latest/actions/using-github-hosted-runners/using-larger-runners#architectural-overview-of-larger-runners). #### Alt text for images of command-line interfaces diff --git a/content/migrations/using-github-enterprise-importer/understanding-github-enterprise-importer/migration-support-for-github-enterprise-importer.md b/content/migrations/using-github-enterprise-importer/understanding-github-enterprise-importer/migration-support-for-github-enterprise-importer.md index a608338f27c2..18b784e5d746 100644 --- a/content/migrations/using-github-enterprise-importer/understanding-github-enterprise-importer/migration-support-for-github-enterprise-importer.md +++ b/content/migrations/using-github-enterprise-importer/understanding-github-enterprise-importer/migration-support-for-github-enterprise-importer.md @@ -90,7 +90,7 @@ When you migrate a repository, either directly or as part of an organization mig - Active webhooks - Repository topics - Repository settings - - Branch protections (see ["Branch protections"](#branch-protections) for more details) + - Branch protections (see "[Branch protections](#branch-protections)" for more details) - {% data variables.product.prodname_pages %} settings - Autolink references - {% data variables.product.prodname_GH_advanced_security %} settings diff --git a/content/rest/enterprise-admin/ldap.md b/content/rest/enterprise-admin/ldap.md index 6394207c6a6f..98b60ee05370 100644 --- a/content/rest/enterprise-admin/ldap.md +++ b/content/rest/enterprise-admin/ldap.md @@ -13,7 +13,7 @@ autogenerated: rest ## About LDAP -You can use these endpoints to update the Distinguished Name (DN) that a user or team maps to. Note that in most cases, you must have [LDAP Sync enabled](/admin/identity-and-access-management/using-ldap-for-enterprise-iam/using-ldap) for your {% data variables.product.product_name %} appliance. The ["Update LDAP mapping for a user"](#update-ldap-mapping-for-a-user) endpoint can be used when LDAP is enabled, even if LDAP Sync is disabled. +You can use these endpoints to update the Distinguished Name (DN) that a user or team maps to. Note that in most cases, you must have [LDAP Sync enabled](/admin/identity-and-access-management/using-ldap-for-enterprise-iam/using-ldap) for your {% data variables.product.product_name %} appliance. The "[Update LDAP mapping for a user](#update-ldap-mapping-for-a-user)" endpoint can be used when LDAP is enabled, even if LDAP Sync is disabled. {% data reusables.user-settings.enterprise-admin-api-classic-pat-only %} diff --git a/content/rest/enterprise-admin/management-console.md b/content/rest/enterprise-admin/management-console.md index 37b53bedbdac..d60f2842f17c 100644 --- a/content/rest/enterprise-admin/management-console.md +++ b/content/rest/enterprise-admin/management-console.md @@ -24,7 +24,7 @@ You may also need to add the [`-k` flag](http://curl.haxx.se/docs/manpage.html#- ### Authentication {% ifversion enterprise-management-console-multi-user-auth %}as the root site administrator{% endif %} -You need to pass your [{% ifversion enterprise-management-console-multi-user-auth %}root site administrator{% else %}{% data variables.enterprise.management_console %}{% endif %} password](/admin/configuration/administering-your-instance-from-the-management-console/managing-access-to-the-management-console) as an authentication token to every endpoint in this category except ["Create a license"](#create-a-github-enterprise-server-license). +You need to pass your [{% ifversion enterprise-management-console-multi-user-auth %}root site administrator{% else %}{% data variables.enterprise.management_console %}{% endif %} password](/admin/configuration/administering-your-instance-from-the-management-console/managing-access-to-the-management-console) as an authentication token to every endpoint in this category except "[Create a license](#create-a-github-enterprise-server-license)." Use the `api_key` parameter to send this token with each request. For example: diff --git a/content/rest/guides/discovering-resources-for-a-user.md b/content/rest/guides/discovering-resources-for-a-user.md index cdb925740c04..e88395b048aa 100644 --- a/content/rest/guides/discovering-resources-for-a-user.md +++ b/content/rest/guides/discovering-resources-for-a-user.md @@ -22,7 +22,7 @@ To interact with the {% ifversion fpt or ghec %}{% data variables.product.prodna ## Getting started -If you haven't already, you should read the ["Basics of Authentication"][basics-of-authentication] guide before working through the examples below. The examples below assume that you have [registered an {% data variables.product.prodname_oauth_app %}][register-oauth-app] and that your [application has an OAuth token for a user][make-authenticated-request-for-user]. +If you haven't already, you should read the "[Basics of Authentication][basics-of-authentication]" guide before working through the examples below. The examples below assume that you have [registered an {% data variables.product.prodname_oauth_app %}][register-oauth-app] and that your [application has an OAuth token for a user][make-authenticated-request-for-user]. ## Discover the repositories that your app can access for a user diff --git a/content/rest/guides/rendering-data-as-graphs.md b/content/rest/guides/rendering-data-as-graphs.md index 0e7c1387d6a0..1962c7d7af3b 100644 --- a/content/rest/guides/rendering-data-as-graphs.md +++ b/content/rest/guides/rendering-data-as-graphs.md @@ -20,7 +20,7 @@ that we own, and the programming languages that make them up. Then, we'll visualize that information in a couple of different ways using the [D3.js][D3.js] library. To interact with the {% ifversion fpt or ghec %}{% data variables.product.prodname_dotcom %}{% else %}{% data variables.product.product_name %}{% endif %} API, we'll be using the excellent Ruby library, [Octokit][Octokit]. -If you haven't already, you should read the ["Basics of Authentication"][basics-of-authentication] +If you haven't already, you should read the "[Basics of Authentication][basics-of-authentication]" guide before starting this example. You can find the complete source code for this project in the [platform-samples][platform samples] repository. Let's jump right in! @@ -141,7 +141,7 @@ our counts into D3 to get a neat bar graph representing the popularity of the la D3.js, or just D3, is a comprehensive library for creating many kinds of charts, graphs, and interactive visualizations. Using D3 in detail is beyond the scope of this guide, but for a good introductory article, -check out ["D3 for Mortals"][D3 mortals]. +check out "[D3 for Mortals][D3 mortals]." D3 is a JavaScript library, and likes working with data as arrays. So, let's convert our Ruby hash into a JSON array for use by JavaScript in the browser. diff --git a/data/reusables/notifications/access_notifications.md b/data/reusables/notifications/access_notifications.md index 021e1947dccb..edf5d6c22a25 100644 --- a/data/reusables/notifications/access_notifications.md +++ b/data/reusables/notifications/access_notifications.md @@ -1,3 +1,3 @@ 1. In the upper-right corner of any page, click {% octicon "bell" aria-label="You have (no) unread notifications" %}. - ![Screenshot of the right corner of the header of GitHub. A bell icon with a blue dot indicating unread notifications is outlined in dark orange.](/assets/images/help/notifications/notifications_general_existence_indicator.png) + ![Screenshot of the right corner of the header of GitHub. A bell icon with a blue dot indicating unread notifications is outlined in dark orange.](/assets/images/help/notifications/notifications-general-existence-indicator.png) diff --git a/data/reusables/project-management/labels.md b/data/reusables/project-management/labels.md index 3df8c221aa06..746b2f127af2 100644 --- a/data/reusables/project-management/labels.md +++ b/data/reusables/project-management/labels.md @@ -1,3 +1,3 @@ 1. Above the list of issues or pull requests, click **Labels**. - ![Screenshot of the list of issues for a repository. Above the list, a button, labeled with a label icon and "Labels", is outlined in dark orange.](/assets/images/help/issues/issues_labels_button.png) + ![Screenshot of the list of issues for a repository. Above the list, a button, labeled with a label icon and "Labels", is outlined in dark orange.](/assets/images/help/issues/issues-labels-button.png) diff --git a/data/reusables/project-management/milestones.md b/data/reusables/project-management/milestones.md index d646cdb27693..96d0ebe71a02 100644 --- a/data/reusables/project-management/milestones.md +++ b/data/reusables/project-management/milestones.md @@ -1,3 +1,3 @@ 1. Next to the search field, click **Milestones**. - ![Screenshot of the list of issues for a repository. Above the list, a button, labeled with a signpost icon and "Milestones," is outlined in dark orange.](/assets/images/help/issues/issues_milestone_button.png) + ![Screenshot of the list of issues for a repository. Above the list, a button, labeled with a signpost icon and "Milestones," is outlined in dark orange.](/assets/images/help/issues/issues-milestone-button.png) diff --git a/data/reusables/repositories/select-items-in-issue-or-pr-list.md b/data/reusables/repositories/select-items-in-issue-or-pr-list.md index 45473d1da91d..82bebcb0a1c2 100644 --- a/data/reusables/repositories/select-items-in-issue-or-pr-list.md +++ b/data/reusables/repositories/select-items-in-issue-or-pr-list.md @@ -1,3 +1,3 @@ 1. Select the checkbox next to the items you want to apply a label to. - ![Screenshot of the first two items in a list of issues. To the left of each issue, a checkbox is checked and outlined in dark orange.](/assets/images/help/issues/issues_assign_checkbox.png) + ![Screenshot of the first two items in a list of issues. To the left of each issue, a checkbox is checked and outlined in dark orange.](/assets/images/help/issues/issues-assign-checkbox.png) diff --git a/src/content-linter/lib/helpers/liquid-utils.js b/src/content-linter/lib/helpers/liquid-utils.js new file mode 100644 index 000000000000..660c256b7e35 --- /dev/null +++ b/src/content-linter/lib/helpers/liquid-utils.js @@ -0,0 +1,43 @@ +import { Tokenizer } from 'liquidjs' + +const liquidTokenCache = new Map() + +export function getLiquidTokens(content) { + if (!content) return [] + + if (liquidTokenCache.has(content)) { + return liquidTokenCache.get(content) + } + + const tokenizer = new Tokenizer(content) + const tokens = tokenizer.readTopLevelTokens() + liquidTokenCache.set(content, tokens) + return liquidTokenCache.get(content) +} + +export const OUTPUT_OPEN = '{%' +export const OUTPUT_CLOSE = '%}' +export const TAG_OPEN = '{{' +export const TAG_CLOSE = '}}' + +export const conditionalTags = ['if', 'elseif', 'unless', 'case', 'ifversion'] + +export function getPositionData(token, lines) { + // Liquid indexes are 0-based, but we want to + // covert to the system used by Markdownlint + const begin = token.begin + 1 + const end = token.end + 1 + // Account for the newline character at the end + // of each line that is not represented in the + // `lines` array + const lineLengths = lines.map((line) => line.length + 1) + + let count = begin + let lineNumber = 1 + for (const lineLength of lineLengths) { + if (count - lineLength <= 0) break + count = count - lineLength + lineNumber++ + } + return { lineNumber, column: count, length: end - begin } +} diff --git a/src/content-linter/lib/linting-rules/image-alt-text-end-punctuation.js b/src/content-linter/lib/linting-rules/image-alt-text-end-punctuation.js index 4b477b8148e1..8693e61cc74b 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-end-punctuation.js +++ b/src/content-linter/lib/linting-rules/image-alt-text-end-punctuation.js @@ -12,7 +12,7 @@ export const imageAltTextEndPunctuation = { description: 'Alternate text for images should end with a punctuation.', tags: ['accessibility', 'images'], information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), - function: function GHD003(params, onError) { + function: function GHD002(params, onError) { forEachInlineChild(params, 'image', function forToken(token) { const imageAltText = token.content.trim() diff --git a/src/content-linter/lib/linting-rules/image-alt-text-length.js b/src/content-linter/lib/linting-rules/image-alt-text-length.js index b5689203c79b..6b4db2344e84 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-length.js +++ b/src/content-linter/lib/linting-rules/image-alt-text-length.js @@ -10,7 +10,7 @@ export const incorrectAltTextLength = { tags: ['accessibility', 'images'], asynchronous: true, information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), - function: function GHD004(params, onError) { + function: function GHD003(params, onError) { forEachInlineChild(params, 'image', async function forToken(token) { let renderedString = token.content diff --git a/src/content-linter/lib/linting-rules/image-file-kebab.js b/src/content-linter/lib/linting-rules/image-file-kebab.js index 27d41125f51a..89a545714fd7 100644 --- a/src/content-linter/lib/linting-rules/image-file-kebab.js +++ b/src/content-linter/lib/linting-rules/image-file-kebab.js @@ -7,7 +7,7 @@ export const imageFileKebab = { description: 'Image file names should always be lowercase kebab case', tags: ['accessibility', 'images'], information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), - function: function GHD005(params, onError) { + function: function GHD004(params, onError) { forEachInlineChild(params, 'image', async function forToken(token) { const imageFileName = token.attrs[0][1].split('/').pop().split('.')[0] const nonKebabRegex = /([A-Z]|_)/ diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index 5e462c649d23..a78a2e288ccb 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -11,6 +11,7 @@ import { listFirstWordCapitalization } from './list-first-word-capitalization.js import { linkPunctuation } from './link-punctuation.js' import { earlyAccessReferences } from './early-access-references.js' import { yamlScheduledJobs } from './yaml-scheduled-jobs.js' +import { liquidQuotedConditionalArg } from './liquid-quoted-conditional-arg.js' export const gitHubDocsMarkdownlint = { rules: [ @@ -26,5 +27,6 @@ export const gitHubDocsMarkdownlint = { linkPunctuation, earlyAccessReferences, yamlScheduledJobs, + liquidQuotedConditionalArg, ], } diff --git a/src/content-linter/lib/linting-rules/internal-links-lang.js b/src/content-linter/lib/linting-rules/internal-links-lang.js index c35d7d536bab..706e5f17c5a4 100644 --- a/src/content-linter/lib/linting-rules/internal-links-lang.js +++ b/src/content-linter/lib/linting-rules/internal-links-lang.js @@ -7,7 +7,7 @@ export const internalLinksLang = { description: 'Internal links must not have a hardcoded language code', tags: ['links', 'url'], information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), - function: function GHD006(params, onError) { + function: function GHD005(params, onError) { filterTokens(params, 'inline', (token) => { for (const child of token.children) { if (child.type !== 'link_open') continue diff --git a/src/content-linter/lib/linting-rules/internal-links-slash.js b/src/content-linter/lib/linting-rules/internal-links-slash.js index 52d2250f2035..a0a7fee8d284 100644 --- a/src/content-linter/lib/linting-rules/internal-links-slash.js +++ b/src/content-linter/lib/linting-rules/internal-links-slash.js @@ -7,7 +7,7 @@ export const internalLinksSlash = { description: 'Internal links must start with a /', tags: ['links', 'url'], information: new URL('https://github.com/github/docs/blob/main/src/content-linter/README.md'), - function: function GHD007(params, onError) { + function: function GHD006(params, onError) { filterTokens(params, 'inline', (token) => { for (const child of token.children) { if (child.type !== 'link_open') continue diff --git a/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js new file mode 100644 index 000000000000..e25f792b4d10 --- /dev/null +++ b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js @@ -0,0 +1,56 @@ +import { TokenKind } from 'liquidjs' +import { addError } from 'markdownlint-rule-helpers' + +import { getLiquidTokens, conditionalTags, getPositionData } from '../helpers/liquid-utils.js' +import { isStringQuoted } from '../helpers/utils.js' + +/* + Checks for instances where a Liquid conditional tag's argument is + quoted because it will always evaluate to true. + + For example, the following would be flagged: + {% if "foo" %} + {% ifversion "bar" %} +*/ +export const liquidQuotedConditionalArg = { + names: ['LQ111', 'liquid-quoted-conditional-arg'], + description: 'Liquid conditional tags should not quote the conditional argument.', + tags: ['liquid', 'formatting'], + function: function LQ111(params, onError) { + const content = params.lines.join('\n') + const tokens = getLiquidTokens(content) + .filter((token) => token.kind === TokenKind.Tag) + .filter((token) => conditionalTags.includes(token.name)) + .filter((token) => { + const tokensArray = token.args.split(/\s+/g) + if (tokensArray.some((arg) => isStringQuoted(arg))) return true + return false + }) + + if (!tokens.length) return + + for (const token of tokens) { + const lines = params.lines + const { lineNumber, column, length } = getPositionData(token, lines) + // LineNumber starts at 1, but lines is 0-based + const line = lines[lineNumber - 1].slice(column - 1, column + length) + // Trim the first and last character off of the token args + const replaceWith = token.args.slice(1, token.args.length - 1) + const replaceString = line.replace(token.args, replaceWith) + + addError( + onError, + lineNumber, + "A conditional Liquid tag's argument is quoted, meaning it will always evaluate to true. Remove the quotes to allow Liquid to evaluate variable.", + token.content, + [column, length], + { + lineNumber, + editColumn: column, + deleteCount: length, + insertText: replaceString, + }, + ) + } + }, +} diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index d7ee01043f70..fec22d625a3c 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -36,7 +36,7 @@ export const githubDocsConfig = { }, 'link-punctuation': { // GHD008 - severity: 'warning', + severity: 'error', 'partial-markdown-files': true, }, 'yaml-scheduled-jobs': { @@ -50,6 +50,12 @@ export const githubDocsConfig = { 'partial-markdown-files': true, }, 'early-access-references': { + // GH035 + severity: 'error', + 'partial-markdown-files': true, + }, + 'liquid-quoted-conditional-arg': { + // LQ111 severity: 'error', 'partial-markdown-files': true, }, diff --git a/src/content-linter/tests/lint-versioning.js b/src/content-linter/tests/lint-versioning.js index a68e3f511ea3..a53818bba44c 100644 --- a/src/content-linter/tests/lint-versioning.js +++ b/src/content-linter/tests/lint-versioning.js @@ -57,10 +57,6 @@ describe('lint feature versions', () => { const allFiles = walkFiles('content', '.md').concat(walkFiles('data', ['.yml', '.md'])) -// Quoted strings in Liquid, like {% if "foo" %}, will always evaluate true _because_ they are strings. -// Instead we need to use unquoted variables, like {% if foo %}. -const stringInLiquidRegex = /{% (?:if|ifversion|elseif|unless) (?:"|').+?%}/g - // Make sure the `if` and `ifversion` Liquid tags in content and data files are valid. describe('lint Liquid versioning', () => { describe.each(allFiles)('%s', (file) => { @@ -89,14 +85,6 @@ describe('lint Liquid versioning', () => { ${ifsForVersioning.join('\n')}` expect(ifsForVersioning.length, errorMessage).toBe(0) }) - - test('does not contain Liquid that evaluates strings (because they are always true)', async () => { - const matches = fileContents.match(stringInLiquidRegex) || [] - const message = - 'Found Liquid conditionals that evaluate a string instead of a variable. Remove the quotes around the variable!' - const errorMessage = `${message}\n - ${matches.join('\n - ')}` - expect(matches.length, errorMessage).toBe(0) - }) }) }) diff --git a/src/content-linter/tests/unit/liquid-quoted-conditional-args.js b/src/content-linter/tests/unit/liquid-quoted-conditional-args.js new file mode 100644 index 000000000000..5127eeeedd66 --- /dev/null +++ b/src/content-linter/tests/unit/liquid-quoted-conditional-args.js @@ -0,0 +1,104 @@ +import { runRule } from '../../lib/init-test.js' +import { liquidQuotedConditionalArg } from '../../lib/linting-rules/liquid-quoted-conditional-arg.js' + +describe(liquidQuotedConditionalArg.names.join(' - '), () => { + test('if conditional with quote args fails', async () => { + const markdown = [ + '---', + 'title: Good sample page', + '---', + '', + ' - One', + '{% if product.title == "Awesome Shoes" %}', + "{% elseif 'ghes' %}", + '{% elseif "ghec" %}', + '{% endif %}', + '{% data variables.stuff.foo%}', + ].join('\n') + const result = await runRule(liquidQuotedConditionalArg, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(2) + expect(errors.map((error) => error.lineNumber)).toEqual([7, 8]) + expect(errors[0].errorRange).toEqual([1, 19]) + expect(errors[1].errorRange).toEqual([1, 19]) + }) + test('ifversion conditional with quote args fails', async () => { + const markdown = [ + '---', + 'title: Good sample page', + '---', + '', + ' - One', + '{% ifversion "ghec" %}', + '{% ifversion "fpt" or ghec %}', + '{% ifversion fpt and "ghec" %}', + '{{name | capitalize}}', + '{% endif %}', + '{% data variables.stuff.foo%}', + ].join('\n') + const result = await runRule(liquidQuotedConditionalArg, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(3) + expect(errors.map((error) => error.lineNumber)).toEqual([6, 7, 8]) + expect(errors[0].errorRange).toEqual([1, 22], [1, 29], [1, 23]) + }) + test('unless conditional with quote args fails', async () => { + const markdown = [ + '---', + 'title: Good sample page', + '---', + '', + ' - One', + '{% unless "this" %}', + '- Three', + '{% data variables.stuff.foo%}', + ].join('\n') + const result = await runRule(liquidQuotedConditionalArg, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(1) + expect(errors.map((error) => error.lineNumber)).toEqual([6]) + expect(errors[0].errorRange).toEqual([1, 19]) + }) + test('case conditional with quote args fails', async () => { + const markdown = [ + '---', + 'title: Good sample page', + '---', + '', + '{% case "product.type" %}', + "{% when 'Health' %}", + 'This is a health potion.', + '{% when "Love" %}', + 'This is a love potion.', + '{% else %}', + 'This is a potion.', + '{% endcase %}', + '- Three', + '{% data variables.stuff.foo%}', + ].join('\n') + const result = await runRule(liquidQuotedConditionalArg, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(1) + expect(errors.map((error) => error.lineNumber)).toEqual([5]) + expect(errors[0].errorRange).toEqual([1, 25]) + }) + test('conditional without quote args pass', async () => { + const markdown = [ + '---', + 'title: Good sample page', + '---', + '', + '{% case product.type %}', + "{% when 'Health' %}", + '{% unless this %}', + '{% ifversion ghec %}', + '{% elseif ghes %}', + '{% if ghae %}', + '- Three', + '{% data variables.stuff.foo%}', + ].join('\n') + const result = await runRule(liquidQuotedConditionalArg, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) +})