Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
*/

export function printAnnotationResults(
results,
// Using 'any' type as results structure is dynamic and comes from various linting tools with different formats
results: any,
{ skippableRules = [], skippableFlawProperties = [] } = {},
) {
for (const [file, flaws] of Object.entries(results)) {
for (const flaw of flaws) {
// Using 'any' type for flaws as they have varying structures depending on the linting rule
for (const flaw of flaws as any) {
if (intersection(flaw.ruleNames, skippableRules)) {
continue
}
Expand Down Expand Up @@ -52,6 +54,7 @@ export function printAnnotationResults(
}
}

function intersection(arr1, arr2) {
return arr1.some((item) => arr2.includes(item))
// Using 'any' types for generic array intersection utility function
function intersection(arr1: any[], arr2: any[]) {
return arr1.some((item: any) => arr2.includes(item))
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { addError } from 'markdownlint-rule-helpers'
import { intersection } from 'lodash-es'

import { getFrontmatter } from '../helpers/utils'
import { formatAjvErrors } from '../helpers/schema-utils'
import { frontmatter, deprecatedProperties } from '@/frame/lib/frontmatter'
import readFrontmatter from '@/frame/lib/read-frontmatter'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'

export const frontmatterSchema = {
export const frontmatterSchema: Rule = {
names: ['GHD012', 'frontmatter-schema'],
description: 'Frontmatter must conform to the schema',
tags: ['frontmatter', 'schema'],
function: (params, onError) => {
function: (params: RuleParams, onError: RuleErrorCallback) => {
const fm = getFrontmatter(params.lines)
if (!fm) return

Expand All @@ -22,25 +24,25 @@ export const frontmatterSchema = {
for (const key of deprecatedKeys) {
// Early access articles are allowed to have deprecated properties
if (params.name.includes('early-access')) continue
const line = params.lines.find((line) => line.trim().startsWith(key))
const lineNumber = params.lines.indexOf(line) + 1
const line = params.lines.find((line: string) => line.trim().startsWith(key))
const lineNumber = params.lines.indexOf(line!) + 1
addError(
onError,
lineNumber,
`The frontmatter property '${key}' is deprecated. Please remove the property from your article's frontmatter.`,
line,
[1, line.length],
line!,
[1, line!.length],
null, // No fix possible
)
}

// Check that the frontmatter matches the schema
const { errors } = readFrontmatter(params.lines.join('\n'), { schema: frontmatter.schema })
const formattedErrors = formatAjvErrors(errors)
const formattedErrors = formatAjvErrors(errors as any)
for (const error of formattedErrors) {
// If the missing property is at the top level, we don't have a line
// to point to. In that case, the error will be added to line 1.
const query = (line) => line.trim().startsWith(`${error.searchProperty}:`)
const query = (line: string) => line.trim().startsWith(`${error.searchProperty}:`)
const line = error.searchProperty === '' ? null : params.lines.find(query)
const lineNumber = line ? params.lines.indexOf(line) + 1 : 1
addError(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { addError } from 'markdownlint-rule-helpers'

import { forEachInlineChild, getRange } from '../helpers/utils'
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'

const excludeStartWords = ['image', 'graphic']

/*
Images should have meaningful alternative text (alt text)
and should not begin with words like "image" or "graphic".
*/
export const imageAltTextExcludeStartWords = {
export const imageAltTextExcludeStartWords: Rule = {
names: ['GHD031', 'image-alt-text-exclude-words'],
description: 'Alternate text for images should not begin with words like "image" or "graphic"',
tags: ['accessibility', 'images'],
parser: 'markdownit',
function: (params, onError) => {
forEachInlineChild(params, 'image', function forToken(token) {
const imageAltText = token.content.trim()

function: (params: RuleParams, onError: RuleErrorCallback) => {
forEachInlineChild(params, 'image', function forToken(token: MarkdownToken) {
// If the alt text is empty, there is nothing to check and you can't
// produce a valid range.
// We can safely return early because the image-alt-text-length rule
// will fail this one.
if (!token.content) return

const imageAltText = token.content.trim()

const range = getRange(token.line, imageAltText)
if (
excludeStartWords.some((excludeWord) => imageAltText.toLowerCase().startsWith(excludeWord))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { filterTokens } from 'markdownlint-rule-helpers'

import { addFixErrorDetail, getRange } from '../helpers/utils'
import { allLanguageKeys } from '@/languages/lib/languages'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'

export const internalLinksNoLang = {
export const internalLinksNoLang: Rule = {
names: ['GHD002', 'internal-links-no-lang'],
description: 'Internal links must not have a hardcoded language code',
tags: ['links', 'url'],
parser: 'markdownit',
function: (params, onError) => {
filterTokens(params, 'inline', (token) => {
function: (params: RuleParams, onError: RuleErrorCallback) => {
// Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types
filterTokens(params, 'inline', (token: any) => {
for (const child of token.children) {
if (child.type !== 'link_open') continue

Expand All @@ -18,14 +21,17 @@ export const internalLinksNoLang = {
// ['href', 'get-started'], ['target', '_blank'],
// ['rel', 'canonical'],
// ]
// Attribute arrays are tuples of [attributeName, attributeValue] from markdownit parser
const hrefsMissingSlashes = child.attrs
// The attribute could also be `target` or `rel`
.filter((attr) => attr[0] === 'href')
.filter((attr) => attr[1].startsWith('/') || !attr[1].startsWith('//'))
.filter((attr: [string, string]) => attr[0] === 'href')
.filter((attr: [string, string]) => attr[1].startsWith('/') || !attr[1].startsWith('//'))
// Filter out link paths that start with language code
.filter((attr) => allLanguageKeys.some((lang) => attr[1].split('/')[1] === lang))
.filter((attr: [string, string]) =>
allLanguageKeys.some((lang) => attr[1].split('/')[1] === lang),
)
// Get the link path from the attribute
.map((attr) => attr[1])
.map((attr: [string, string]) => attr[1])
// Create errors for each link path that includes a language code
for (const linkPath of hrefsMissingSlashes) {
const range = getRange(child.line, linkPath)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { filterTokens } from 'markdownlint-rule-helpers'

import { addFixErrorDetail, getRange } from '../helpers/utils'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'

export const internalLinksSlash = {
export const internalLinksSlash: Rule = {
names: ['GHD003', 'internal-links-slash'],
description: 'Internal links must start with a /',
tags: ['links', 'url'],
parser: 'markdownit',
function: (params, onError) => {
filterTokens(params, 'inline', (token) => {
function: (params: RuleParams, onError: RuleErrorCallback) => {
// Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types
filterTokens(params, 'inline', (token: any) => {
for (const child of token.children) {
if (child.type !== 'link_open') continue

Expand All @@ -17,20 +20,21 @@ export const internalLinksSlash = {
// ['href', '/get-started'], ['target', '_blank'],
// ['rel', 'canonical'],
// ]
// Attribute arrays are tuples of [attributeName, attributeValue] from markdownit parser
const hrefsMissingSlashes = child.attrs
// The attribute could also be `target` or `rel`
.filter((attr) => attr[0] === 'href')
.filter((attr: [string, string]) => attr[0] === 'href')
// Filter out prefixes we don't want to check
.filter(
(attr) =>
(attr: [string, string]) =>
!['http', 'mailto', '#', '/'].some((ignorePrefix) =>
attr[1].startsWith(ignorePrefix),
),
)
// We can ignore empty links because MD042 from markdownlint catches empty links
.filter((attr) => attr[1] !== '')
.filter((attr: [string, string]) => attr[1] !== '')
// Get the link path from the attribute
.map((attr) => attr[1])
.map((attr: [string, string]) => attr[1])

// Create errors for each link path that doesn't start with a /
for (const linkPath of hrefsMissingSlashes) {
Expand Down
5 changes: 3 additions & 2 deletions src/content-linter/lib/linting-rules/link-punctuation.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { addError, filterTokens } from 'markdownlint-rule-helpers'
import type { RuleParams, RuleErrorCallback } from '../../types'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'

import { doesStringEndWithPeriod, getRange, isStringQuoted } from '../helpers/utils'

export const linkPunctuation = {
export const linkPunctuation: Rule = {
names: ['GHD001', 'link-punctuation'],
description: 'Internal link titles must not contain punctuation',
tags: ['links', 'url'],
parser: 'markdownit',
function: (params: RuleParams, onError: RuleErrorCallback) => {
// Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types
filterTokens(params, 'inline', (token: any) => {
const { children, line } = token
let inLink = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { TokenKind } from 'liquidjs'
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
import { addError } from 'markdownlint-rule-helpers'

import { getLiquidTokens, conditionalTags, getPositionData } from '../helpers/liquid-utils'
import { isStringQuoted } from '../helpers/utils'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'

/*
Checks for instances where a Liquid conditional tag's argument is
Expand All @@ -12,18 +14,20 @@ import { isStringQuoted } from '../helpers/utils'
{% if "foo" %}
{% ifversion "bar" %}
*/
export const liquidQuotedConditionalArg = {
export const liquidQuotedConditionalArg: Rule = {
names: ['GHD016', 'liquid-quoted-conditional-arg'],
description: 'Liquid conditional tags should not quote the conditional argument',
tags: ['liquid', 'format'],
function: (params, onError) => {
function: (params: RuleParams, onError: RuleErrorCallback) => {
const content = params.lines.join('\n')
// Using 'any' type for tokens as getLiquidTokens returns tokens from liquid-utils.js which lacks type definitions
const tokens = getLiquidTokens(content)
.filter((token) => token.kind === TokenKind.Tag)
.filter((token) => conditionalTags.includes(token.name))
.filter((token) => {
.filter((token: any) => token.kind === TokenKind.Tag)
.filter((token: any) => conditionalTags.includes(token.name))
.filter((token: any) => {
const tokensArray = token.args.split(/\s+/g)
if (tokensArray.some((arg) => isStringQuoted(arg))) return true
// Using 'any' for args as they come from the untyped liquid token structure
if (tokensArray.some((arg: any) => isStringQuoted(arg))) return true
return false
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TokenKind } from 'liquidjs'
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
import { addFixErrorDetail } from '../helpers/utils'
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'
/*
Octicons should always have an aria-label attribute even if aria hidden. For example:

Expand All @@ -9,20 +10,21 @@ Octicons should always have an aria-label attribute even if aria hidden. For exa
{% octicon "alert" aria-label="alert" aria-hidden="true" %}
{% octicon "alert" aria-label="alert" aria-hidden="true" class="foo" %}

This is necessary for copilot to be able to recognize the svgs correctly when using our API.
This is necessary for copilot to be able to recognize the svgs correctly when using our API.

*/

export const octiconAriaLabels = {
export const octiconAriaLabels: Rule = {
names: ['GHD044', 'octicon-aria-labels'],
description: 'Octicons should always have an aria-label attribute even if aria-hidden.',
tags: ['accessibility', 'octicons'],
parser: 'markdownit',
function: (params, onError) => {
function: (params: RuleParams, onError: RuleErrorCallback) => {
const content = params.lines.join('\n')
// Using 'any' type for tokens as getLiquidTokens returns tokens from liquid-utils.js which lacks type definitions
const tokens = getLiquidTokens(content)
.filter((token) => token.kind === TokenKind.Tag)
.filter((token) => token.name === 'octicon')
.filter((token: any) => token.kind === TokenKind.Tag)
.filter((token: any) => token.name === 'octicon')

for (const token of tokens) {
const { lineNumber, column, length } = getPositionData(token, params.lines)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe(frontmatterEarlyAccessReferences.names.join(' - '), () => {
expect(lineNumbers.includes(4)).toBe(false)
expect(lineNumbers.includes(5)).toBe(false)
expect(errors[0].errorRange).toEqual([8, 12])
expect(errors[1].errorRange).toEqual([15, 12], [28, 12])
expect(errors[1].errorRange).toEqual([15, 12])
})
test('early access file with early access references passes', async () => {
const result = await runRule(frontmatterEarlyAccessReferences, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe(tableColumnIntegrity.names.join(' - '), () => {
const errors = result.markdown
expect(errors.length).toBe(1)
expect(errors[0].lineNumber).toBe(3)
if (errors[0].detail) {
expect(errors[0].detail).toContain('Table row has 3 columns but header has 2')
if ((errors[0] as any).detail) {
expect((errors[0] as any).detail).toContain('Table row has 3 columns but header has 2')
} else if (errors[0].errorDetail) {
expect(errors[0].errorDetail).toContain('Table row has 3 columns but header has 2')
} else {
Expand All @@ -38,8 +38,8 @@ describe(tableColumnIntegrity.names.join(' - '), () => {
const errors = result.markdown
expect(errors.length).toBe(1)
expect(errors[0].lineNumber).toBe(3)
if (errors[0].detail) {
expect(errors[0].detail).toContain('Table row has 2 columns but header has 3')
if ((errors[0] as any).detail) {
expect((errors[0] as any).detail).toContain('Table row has 2 columns but header has 3')
} else if (errors[0].errorDetail) {
expect(errors[0].errorDetail).toContain('Table row has 2 columns but header has 3')
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-v
import { DataDirectory } from '@/tests/helpers/data-directory'

describe('data tag', () => {
let dd
// Using 'any' type as DataDirectory is from data-directory.js which lacks type definitions
let dd: any
const enDirBefore = languages.en.dir

beforeAll(() => {
Expand Down Expand Up @@ -41,7 +42,7 @@ describe('data tag', () => {
currentLanguage: 'en',
currentPath: '/en/liquid-tags/good-data-variable',
}
const rendered = await page.render(context)
const rendered = await page!.render(context)
// The test fixture contains:
// {% data variables.stuff.foo %}
// which we control the value of here in the test.
Expand All @@ -57,7 +58,7 @@ describe('data tag', () => {
currentPath: '/en/liquid-tags/bad-data-variable',
currentLanguage: 'en',
}
await expect(page.render(context)).rejects.toThrow(
await expect(page!.render(context)).rejects.toThrow(
"Can't find the key 'foo.bar.tipu' in the scope., line:2, col:1",
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { DataDirectory } from '@/tests/helpers/data-directory'
describe('liquid helper tags', () => {
vi.setConfig({ testTimeout: 60 * 1000 })

const context = {}
let dd
// Using 'any' type as context is a test fixture with dynamic properties set in beforeAll
const context: any = {}
// Using 'any' type as DataDirectory is from data-directory.js which lacks type definitions
let dd: any
const enDirBefore = languages.en.dir

beforeAll(() => {
Expand Down
Loading
Loading