Convert 28 JavaScript files to TypeScript (#57753)
This commit is contained in:
@@ -6,11 +6,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function printAnnotationResults(
|
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 = [] } = {},
|
{ skippableRules = [], skippableFlawProperties = [] } = {},
|
||||||
) {
|
) {
|
||||||
for (const [file, flaws] of Object.entries(results)) {
|
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)) {
|
if (intersection(flaw.ruleNames, skippableRules)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,7 @@ export function printAnnotationResults(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function intersection(arr1, arr2) {
|
// Using 'any' types for generic array intersection utility function
|
||||||
return arr1.some((item) => arr2.includes(item))
|
function intersection(arr1: any[], arr2: any[]) {
|
||||||
|
return arr1.some((item: any) => arr2.includes(item))
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { addError } from 'markdownlint-rule-helpers'
|
import { addError } from 'markdownlint-rule-helpers'
|
||||||
import { intersection } from 'lodash-es'
|
import { intersection } from 'lodash-es'
|
||||||
|
|
||||||
@@ -5,12 +6,13 @@ import { getFrontmatter } from '../helpers/utils'
|
|||||||
import { formatAjvErrors } from '../helpers/schema-utils'
|
import { formatAjvErrors } from '../helpers/schema-utils'
|
||||||
import { frontmatter, deprecatedProperties } from '@/frame/lib/frontmatter'
|
import { frontmatter, deprecatedProperties } from '@/frame/lib/frontmatter'
|
||||||
import readFrontmatter from '@/frame/lib/read-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'],
|
names: ['GHD012', 'frontmatter-schema'],
|
||||||
description: 'Frontmatter must conform to the schema',
|
description: 'Frontmatter must conform to the schema',
|
||||||
tags: ['frontmatter', 'schema'],
|
tags: ['frontmatter', 'schema'],
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
const fm = getFrontmatter(params.lines)
|
const fm = getFrontmatter(params.lines)
|
||||||
if (!fm) return
|
if (!fm) return
|
||||||
|
|
||||||
@@ -22,25 +24,25 @@ export const frontmatterSchema = {
|
|||||||
for (const key of deprecatedKeys) {
|
for (const key of deprecatedKeys) {
|
||||||
// Early access articles are allowed to have deprecated properties
|
// Early access articles are allowed to have deprecated properties
|
||||||
if (params.name.includes('early-access')) continue
|
if (params.name.includes('early-access')) continue
|
||||||
const line = params.lines.find((line) => line.trim().startsWith(key))
|
const line = params.lines.find((line: string) => line.trim().startsWith(key))
|
||||||
const lineNumber = params.lines.indexOf(line) + 1
|
const lineNumber = params.lines.indexOf(line!) + 1
|
||||||
addError(
|
addError(
|
||||||
onError,
|
onError,
|
||||||
lineNumber,
|
lineNumber,
|
||||||
`The frontmatter property '${key}' is deprecated. Please remove the property from your article's frontmatter.`,
|
`The frontmatter property '${key}' is deprecated. Please remove the property from your article's frontmatter.`,
|
||||||
line,
|
line!,
|
||||||
[1, line.length],
|
[1, line!.length],
|
||||||
null, // No fix possible
|
null, // No fix possible
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the frontmatter matches the schema
|
// Check that the frontmatter matches the schema
|
||||||
const { errors } = readFrontmatter(params.lines.join('\n'), { schema: frontmatter.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) {
|
for (const error of formattedErrors) {
|
||||||
// If the missing property is at the top level, we don't have a line
|
// 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.
|
// 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 line = error.searchProperty === '' ? null : params.lines.find(query)
|
||||||
const lineNumber = line ? params.lines.indexOf(line) + 1 : 1
|
const lineNumber = line ? params.lines.indexOf(line) + 1 : 1
|
||||||
addError(
|
addError(
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { addError } from 'markdownlint-rule-helpers'
|
import { addError } from 'markdownlint-rule-helpers'
|
||||||
|
|
||||||
import { forEachInlineChild, getRange } from '../helpers/utils'
|
import { forEachInlineChild, getRange } from '../helpers/utils'
|
||||||
|
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'
|
||||||
|
|
||||||
const excludeStartWords = ['image', 'graphic']
|
const excludeStartWords = ['image', 'graphic']
|
||||||
|
|
||||||
@@ -8,21 +10,21 @@ const excludeStartWords = ['image', 'graphic']
|
|||||||
Images should have meaningful alternative text (alt text)
|
Images should have meaningful alternative text (alt text)
|
||||||
and should not begin with words like "image" or "graphic".
|
and should not begin with words like "image" or "graphic".
|
||||||
*/
|
*/
|
||||||
export const imageAltTextExcludeStartWords = {
|
export const imageAltTextExcludeStartWords: Rule = {
|
||||||
names: ['GHD031', 'image-alt-text-exclude-words'],
|
names: ['GHD031', 'image-alt-text-exclude-words'],
|
||||||
description: 'Alternate text for images should not begin with words like "image" or "graphic"',
|
description: 'Alternate text for images should not begin with words like "image" or "graphic"',
|
||||||
tags: ['accessibility', 'images'],
|
tags: ['accessibility', 'images'],
|
||||||
parser: 'markdownit',
|
parser: 'markdownit',
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
forEachInlineChild(params, 'image', function forToken(token) {
|
forEachInlineChild(params, 'image', function forToken(token: MarkdownToken) {
|
||||||
const imageAltText = token.content.trim()
|
|
||||||
|
|
||||||
// If the alt text is empty, there is nothing to check and you can't
|
// If the alt text is empty, there is nothing to check and you can't
|
||||||
// produce a valid range.
|
// produce a valid range.
|
||||||
// We can safely return early because the image-alt-text-length rule
|
// We can safely return early because the image-alt-text-length rule
|
||||||
// will fail this one.
|
// will fail this one.
|
||||||
if (!token.content) return
|
if (!token.content) return
|
||||||
|
|
||||||
|
const imageAltText = token.content.trim()
|
||||||
|
|
||||||
const range = getRange(token.line, imageAltText)
|
const range = getRange(token.line, imageAltText)
|
||||||
if (
|
if (
|
||||||
excludeStartWords.some((excludeWord) => imageAltText.toLowerCase().startsWith(excludeWord))
|
excludeStartWords.some((excludeWord) => imageAltText.toLowerCase().startsWith(excludeWord))
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { filterTokens } from 'markdownlint-rule-helpers'
|
import { filterTokens } from 'markdownlint-rule-helpers'
|
||||||
|
|
||||||
import { addFixErrorDetail, getRange } from '../helpers/utils'
|
import { addFixErrorDetail, getRange } from '../helpers/utils'
|
||||||
import { allLanguageKeys } from '@/languages/lib/languages'
|
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'],
|
names: ['GHD002', 'internal-links-no-lang'],
|
||||||
description: 'Internal links must not have a hardcoded language code',
|
description: 'Internal links must not have a hardcoded language code',
|
||||||
tags: ['links', 'url'],
|
tags: ['links', 'url'],
|
||||||
parser: 'markdownit',
|
parser: 'markdownit',
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
filterTokens(params, 'inline', (token) => {
|
// 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) {
|
for (const child of token.children) {
|
||||||
if (child.type !== 'link_open') continue
|
if (child.type !== 'link_open') continue
|
||||||
|
|
||||||
@@ -18,14 +21,17 @@ export const internalLinksNoLang = {
|
|||||||
// ['href', 'get-started'], ['target', '_blank'],
|
// ['href', 'get-started'], ['target', '_blank'],
|
||||||
// ['rel', 'canonical'],
|
// ['rel', 'canonical'],
|
||||||
// ]
|
// ]
|
||||||
|
// Attribute arrays are tuples of [attributeName, attributeValue] from markdownit parser
|
||||||
const hrefsMissingSlashes = child.attrs
|
const hrefsMissingSlashes = child.attrs
|
||||||
// The attribute could also be `target` or `rel`
|
// The attribute could also be `target` or `rel`
|
||||||
.filter((attr) => attr[0] === 'href')
|
.filter((attr: [string, string]) => attr[0] === 'href')
|
||||||
.filter((attr) => attr[1].startsWith('/') || !attr[1].startsWith('//'))
|
.filter((attr: [string, string]) => attr[1].startsWith('/') || !attr[1].startsWith('//'))
|
||||||
// Filter out link paths that start with language code
|
// 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
|
// 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
|
// Create errors for each link path that includes a language code
|
||||||
for (const linkPath of hrefsMissingSlashes) {
|
for (const linkPath of hrefsMissingSlashes) {
|
||||||
const range = getRange(child.line, linkPath)
|
const range = getRange(child.line, linkPath)
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { filterTokens } from 'markdownlint-rule-helpers'
|
import { filterTokens } from 'markdownlint-rule-helpers'
|
||||||
|
|
||||||
import { addFixErrorDetail, getRange } from '../helpers/utils'
|
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'],
|
names: ['GHD003', 'internal-links-slash'],
|
||||||
description: 'Internal links must start with a /',
|
description: 'Internal links must start with a /',
|
||||||
tags: ['links', 'url'],
|
tags: ['links', 'url'],
|
||||||
parser: 'markdownit',
|
parser: 'markdownit',
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
filterTokens(params, 'inline', (token) => {
|
// 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) {
|
for (const child of token.children) {
|
||||||
if (child.type !== 'link_open') continue
|
if (child.type !== 'link_open') continue
|
||||||
|
|
||||||
@@ -17,20 +20,21 @@ export const internalLinksSlash = {
|
|||||||
// ['href', '/get-started'], ['target', '_blank'],
|
// ['href', '/get-started'], ['target', '_blank'],
|
||||||
// ['rel', 'canonical'],
|
// ['rel', 'canonical'],
|
||||||
// ]
|
// ]
|
||||||
|
// Attribute arrays are tuples of [attributeName, attributeValue] from markdownit parser
|
||||||
const hrefsMissingSlashes = child.attrs
|
const hrefsMissingSlashes = child.attrs
|
||||||
// The attribute could also be `target` or `rel`
|
// 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 out prefixes we don't want to check
|
||||||
.filter(
|
.filter(
|
||||||
(attr) =>
|
(attr: [string, string]) =>
|
||||||
!['http', 'mailto', '#', '/'].some((ignorePrefix) =>
|
!['http', 'mailto', '#', '/'].some((ignorePrefix) =>
|
||||||
attr[1].startsWith(ignorePrefix),
|
attr[1].startsWith(ignorePrefix),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// We can ignore empty links because MD042 from markdownlint catches empty links
|
// 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
|
// 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 /
|
// Create errors for each link path that doesn't start with a /
|
||||||
for (const linkPath of hrefsMissingSlashes) {
|
for (const linkPath of hrefsMissingSlashes) {
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { addError, filterTokens } from 'markdownlint-rule-helpers'
|
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'
|
import { doesStringEndWithPeriod, getRange, isStringQuoted } from '../helpers/utils'
|
||||||
|
|
||||||
export const linkPunctuation = {
|
export const linkPunctuation: Rule = {
|
||||||
names: ['GHD001', 'link-punctuation'],
|
names: ['GHD001', 'link-punctuation'],
|
||||||
description: 'Internal link titles must not contain punctuation',
|
description: 'Internal link titles must not contain punctuation',
|
||||||
tags: ['links', 'url'],
|
tags: ['links', 'url'],
|
||||||
parser: 'markdownit',
|
parser: 'markdownit',
|
||||||
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
|
// Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types
|
||||||
filterTokens(params, 'inline', (token: any) => {
|
filterTokens(params, 'inline', (token: any) => {
|
||||||
const { children, line } = token
|
const { children, line } = token
|
||||||
let inLink = false
|
let inLink = false
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { TokenKind } from 'liquidjs'
|
import { TokenKind } from 'liquidjs'
|
||||||
|
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
|
||||||
import { addError } from 'markdownlint-rule-helpers'
|
import { addError } from 'markdownlint-rule-helpers'
|
||||||
|
|
||||||
import { getLiquidTokens, conditionalTags, getPositionData } from '../helpers/liquid-utils'
|
import { getLiquidTokens, conditionalTags, getPositionData } from '../helpers/liquid-utils'
|
||||||
import { isStringQuoted } from '../helpers/utils'
|
import { isStringQuoted } from '../helpers/utils'
|
||||||
|
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Checks for instances where a Liquid conditional tag's argument is
|
Checks for instances where a Liquid conditional tag's argument is
|
||||||
@@ -12,18 +14,20 @@ import { isStringQuoted } from '../helpers/utils'
|
|||||||
{% if "foo" %}
|
{% if "foo" %}
|
||||||
{% ifversion "bar" %}
|
{% ifversion "bar" %}
|
||||||
*/
|
*/
|
||||||
export const liquidQuotedConditionalArg = {
|
export const liquidQuotedConditionalArg: Rule = {
|
||||||
names: ['GHD016', 'liquid-quoted-conditional-arg'],
|
names: ['GHD016', 'liquid-quoted-conditional-arg'],
|
||||||
description: 'Liquid conditional tags should not quote the conditional argument',
|
description: 'Liquid conditional tags should not quote the conditional argument',
|
||||||
tags: ['liquid', 'format'],
|
tags: ['liquid', 'format'],
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
const content = params.lines.join('\n')
|
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)
|
const tokens = getLiquidTokens(content)
|
||||||
.filter((token) => token.kind === TokenKind.Tag)
|
.filter((token: any) => token.kind === TokenKind.Tag)
|
||||||
.filter((token) => conditionalTags.includes(token.name))
|
.filter((token: any) => conditionalTags.includes(token.name))
|
||||||
.filter((token) => {
|
.filter((token: any) => {
|
||||||
const tokensArray = token.args.split(/\s+/g)
|
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
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TokenKind } from 'liquidjs'
|
import { TokenKind } from 'liquidjs'
|
||||||
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
|
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
|
||||||
import { addFixErrorDetail } from '../helpers/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:
|
Octicons should always have an aria-label attribute even if aria hidden. For example:
|
||||||
|
|
||||||
@@ -13,16 +14,17 @@ Octicons should always have an aria-label attribute even if aria hidden. For exa
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const octiconAriaLabels = {
|
export const octiconAriaLabels: Rule = {
|
||||||
names: ['GHD044', 'octicon-aria-labels'],
|
names: ['GHD044', 'octicon-aria-labels'],
|
||||||
description: 'Octicons should always have an aria-label attribute even if aria-hidden.',
|
description: 'Octicons should always have an aria-label attribute even if aria-hidden.',
|
||||||
tags: ['accessibility', 'octicons'],
|
tags: ['accessibility', 'octicons'],
|
||||||
parser: 'markdownit',
|
parser: 'markdownit',
|
||||||
function: (params, onError) => {
|
function: (params: RuleParams, onError: RuleErrorCallback) => {
|
||||||
const content = params.lines.join('\n')
|
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)
|
const tokens = getLiquidTokens(content)
|
||||||
.filter((token) => token.kind === TokenKind.Tag)
|
.filter((token: any) => token.kind === TokenKind.Tag)
|
||||||
.filter((token) => token.name === 'octicon')
|
.filter((token: any) => token.name === 'octicon')
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
const { lineNumber, column, length } = getPositionData(token, params.lines)
|
const { lineNumber, column, length } = getPositionData(token, params.lines)
|
||||||
@@ -41,7 +41,7 @@ describe(frontmatterEarlyAccessReferences.names.join(' - '), () => {
|
|||||||
expect(lineNumbers.includes(4)).toBe(false)
|
expect(lineNumbers.includes(4)).toBe(false)
|
||||||
expect(lineNumbers.includes(5)).toBe(false)
|
expect(lineNumbers.includes(5)).toBe(false)
|
||||||
expect(errors[0].errorRange).toEqual([8, 12])
|
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 () => {
|
test('early access file with early access references passes', async () => {
|
||||||
const result = await runRule(frontmatterEarlyAccessReferences, {
|
const result = await runRule(frontmatterEarlyAccessReferences, {
|
||||||
@@ -22,8 +22,8 @@ describe(tableColumnIntegrity.names.join(' - '), () => {
|
|||||||
const errors = result.markdown
|
const errors = result.markdown
|
||||||
expect(errors.length).toBe(1)
|
expect(errors.length).toBe(1)
|
||||||
expect(errors[0].lineNumber).toBe(3)
|
expect(errors[0].lineNumber).toBe(3)
|
||||||
if (errors[0].detail) {
|
if ((errors[0] as any).detail) {
|
||||||
expect(errors[0].detail).toContain('Table row has 3 columns but header has 2')
|
expect((errors[0] as any).detail).toContain('Table row has 3 columns but header has 2')
|
||||||
} else if (errors[0].errorDetail) {
|
} else if (errors[0].errorDetail) {
|
||||||
expect(errors[0].errorDetail).toContain('Table row has 3 columns but header has 2')
|
expect(errors[0].errorDetail).toContain('Table row has 3 columns but header has 2')
|
||||||
} else {
|
} else {
|
||||||
@@ -38,8 +38,8 @@ describe(tableColumnIntegrity.names.join(' - '), () => {
|
|||||||
const errors = result.markdown
|
const errors = result.markdown
|
||||||
expect(errors.length).toBe(1)
|
expect(errors.length).toBe(1)
|
||||||
expect(errors[0].lineNumber).toBe(3)
|
expect(errors[0].lineNumber).toBe(3)
|
||||||
if (errors[0].detail) {
|
if ((errors[0] as any).detail) {
|
||||||
expect(errors[0].detail).toContain('Table row has 2 columns but header has 3')
|
expect((errors[0] as any).detail).toContain('Table row has 2 columns but header has 3')
|
||||||
} else if (errors[0].errorDetail) {
|
} else if (errors[0].errorDetail) {
|
||||||
expect(errors[0].errorDetail).toContain('Table row has 2 columns but header has 3')
|
expect(errors[0].errorDetail).toContain('Table row has 2 columns but header has 3')
|
||||||
} else {
|
} else {
|
||||||
@@ -6,7 +6,8 @@ import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-v
|
|||||||
import { DataDirectory } from '@/tests/helpers/data-directory'
|
import { DataDirectory } from '@/tests/helpers/data-directory'
|
||||||
|
|
||||||
describe('data tag', () => {
|
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
|
const enDirBefore = languages.en.dir
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -41,7 +42,7 @@ describe('data tag', () => {
|
|||||||
currentLanguage: 'en',
|
currentLanguage: 'en',
|
||||||
currentPath: '/en/liquid-tags/good-data-variable',
|
currentPath: '/en/liquid-tags/good-data-variable',
|
||||||
}
|
}
|
||||||
const rendered = await page.render(context)
|
const rendered = await page!.render(context)
|
||||||
// The test fixture contains:
|
// The test fixture contains:
|
||||||
// {% data variables.stuff.foo %}
|
// {% data variables.stuff.foo %}
|
||||||
// which we control the value of here in the test.
|
// which we control the value of here in the test.
|
||||||
@@ -57,7 +58,7 @@ describe('data tag', () => {
|
|||||||
currentPath: '/en/liquid-tags/bad-data-variable',
|
currentPath: '/en/liquid-tags/bad-data-variable',
|
||||||
currentLanguage: 'en',
|
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",
|
"Can't find the key 'foo.bar.tipu' in the scope., line:2, col:1",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -7,8 +7,10 @@ import { DataDirectory } from '@/tests/helpers/data-directory'
|
|||||||
describe('liquid helper tags', () => {
|
describe('liquid helper tags', () => {
|
||||||
vi.setConfig({ testTimeout: 60 * 1000 })
|
vi.setConfig({ testTimeout: 60 * 1000 })
|
||||||
|
|
||||||
const context = {}
|
// Using 'any' type as context is a test fixture with dynamic properties set in beforeAll
|
||||||
let dd
|
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
|
const enDirBefore = languages.en.dir
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -4,6 +4,7 @@ import { extname, basename } from 'path'
|
|||||||
|
|
||||||
import walk from 'walk-sync'
|
import walk from 'walk-sync'
|
||||||
import { beforeAll, describe, expect, test } from 'vitest'
|
import { beforeAll, describe, expect, test } from 'vitest'
|
||||||
|
import type { ValidateFunction, SchemaObject } from 'ajv'
|
||||||
|
|
||||||
import { getJsonValidator, validateJson } from '@/tests/lib/validate-json-schema'
|
import { getJsonValidator, validateJson } from '@/tests/lib/validate-json-schema'
|
||||||
import { formatAjvErrors } from '@/tests/helpers/schemas'
|
import { formatAjvErrors } from '@/tests/helpers/schemas'
|
||||||
@@ -19,7 +20,8 @@ const yamlWalkOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const dataDir of directorySchemas) {
|
for (const dataDir of directorySchemas) {
|
||||||
let schema, validate
|
let schema: SchemaObject
|
||||||
|
let validate: ValidateFunction
|
||||||
const dataDirectoryName = basename(dataDir)
|
const dataDirectoryName = basename(dataDir)
|
||||||
const yamlFileList = walk(dataDir, yamlWalkOptions).sort()
|
const yamlFileList = walk(dataDir, yamlWalkOptions).sort()
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ for (const dataDir of directorySchemas) {
|
|||||||
test.each(yamlFileList)('%p', async (yamlAbsPath) => {
|
test.each(yamlFileList)('%p', async (yamlAbsPath) => {
|
||||||
const yamlContent = yaml.load(readFileSync(yamlAbsPath, 'utf8'))
|
const yamlContent = yaml.load(readFileSync(yamlAbsPath, 'utf8'))
|
||||||
const isValid = validate(yamlContent)
|
const isValid = validate(yamlContent)
|
||||||
const formattedErrors = isValid ? validate.errors : formatAjvErrors(validate.errors)
|
const formattedErrors = isValid ? undefined : formatAjvErrors(validate.errors || [])
|
||||||
expect(isValid, formattedErrors).toBe(true)
|
expect(isValid, formattedErrors).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -43,6 +45,7 @@ describe('single data files', () => {
|
|||||||
const ymlData = yaml.load(readFileSync(filepath, 'utf8'))
|
const ymlData = yaml.load(readFileSync(filepath, 'utf8'))
|
||||||
const schema = (await import(dataSchemas[filepath])).default
|
const schema = (await import(dataSchemas[filepath])).default
|
||||||
const { isValid, errors } = validateJson(schema, ymlData)
|
const { isValid, errors } = validateJson(schema, ymlData)
|
||||||
expect(isValid, errors).toBe(true)
|
const formattedErrors = isValid ? undefined : formatAjvErrors(errors || [])
|
||||||
|
expect(isValid, formattedErrors).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -20,7 +20,7 @@ describe('data-directory', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('option: preprocess function', async () => {
|
test('option: preprocess function', async () => {
|
||||||
const preprocess = function (content) {
|
const preprocess = function (content: string) {
|
||||||
return content.replace('markdown', 'MARKDOWN')
|
return content.replace('markdown', 'MARKDOWN')
|
||||||
}
|
}
|
||||||
const data = dataDirectory(fixturesDir, { preprocess })
|
const data = dataDirectory(fixturesDir, { preprocess })
|
||||||
@@ -35,7 +35,7 @@ describe('data-directory', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('option: ignorePatterns', async () => {
|
test('option: ignorePatterns', async () => {
|
||||||
const ignorePatterns = []
|
const ignorePatterns: RegExp[] = []
|
||||||
|
|
||||||
// README is ignored by default
|
// README is ignored by default
|
||||||
expect('README' in dataDirectory(fixturesDir)).toBe(false)
|
expect('README' in dataDirectory(fixturesDir)).toBe(false)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { NextFunction } from 'express'
|
||||||
|
|
||||||
// Fastly provides a Soft Purge feature that allows you to mark content as outdated (stale) instead of permanently
|
// Fastly provides a Soft Purge feature that allows you to mark content as outdated (stale) instead of permanently
|
||||||
// purging and thereby deleting it from Fastly's caches. Objects invalidated with Soft Purge will be treated as
|
// purging and thereby deleting it from Fastly's caches. Objects invalidated with Soft Purge will be treated as
|
||||||
// outdated (stale) while Fastly fetches a new version from origin.
|
// outdated (stale) while Fastly fetches a new version from origin.
|
||||||
@@ -14,7 +16,8 @@ export const SURROGATE_ENUMS = {
|
|||||||
MANUAL: 'manual-purge',
|
MANUAL: 'manual-purge',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFastlySurrogateKey(res, enumKey, isCustomKey = false) {
|
// Using 'any' type for res parameter to maintain compatibility with Express Response objects
|
||||||
|
export function setFastlySurrogateKey(res: any, enumKey: string, isCustomKey = false) {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (!isCustomKey && !Object.values(SURROGATE_ENUMS).includes(enumKey)) {
|
if (!isCustomKey && !Object.values(SURROGATE_ENUMS).includes(enumKey)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -27,17 +30,21 @@ export function setFastlySurrogateKey(res, enumKey, isCustomKey = false) {
|
|||||||
res.set(KEY, enumKey)
|
res.set(KEY, enumKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDefaultFastlySurrogateKey(req, res, next) {
|
// Using 'any' type for req and res parameters to maintain backward compatibility with test mock objects
|
||||||
|
// that don't fully implement ExtendedRequest and Response interfaces
|
||||||
|
export function setDefaultFastlySurrogateKey(req: any, res: any, next: NextFunction) {
|
||||||
res.set(KEY, `${SURROGATE_ENUMS.DEFAULT} ${makeLanguageSurrogateKey()}`)
|
res.set(KEY, `${SURROGATE_ENUMS.DEFAULT} ${makeLanguageSurrogateKey()}`)
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setLanguageFastlySurrogateKey(req, res, next) {
|
// Using 'any' type for req and res parameters to maintain backward compatibility with test mock objects
|
||||||
|
// that don't fully implement ExtendedRequest and Response interfaces
|
||||||
|
export function setLanguageFastlySurrogateKey(req: any, res: any, next: NextFunction) {
|
||||||
res.set(KEY, `${SURROGATE_ENUMS.DEFAULT} ${makeLanguageSurrogateKey(req.language)}`)
|
res.set(KEY, `${SURROGATE_ENUMS.DEFAULT} ${makeLanguageSurrogateKey(req.language)}`)
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeLanguageSurrogateKey(langCode) {
|
export function makeLanguageSurrogateKey(langCode?: string) {
|
||||||
if (!langCode) {
|
if (!langCode) {
|
||||||
return 'no-language'
|
return 'no-language'
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import { describe, expect, test, vi } from 'vitest'
|
|||||||
import { loadPageMap, loadPages } from '@/frame/lib/page-data'
|
import { loadPageMap, loadPages } from '@/frame/lib/page-data'
|
||||||
import { renderContent } from '@/content-render/index'
|
import { renderContent } from '@/content-render/index'
|
||||||
import { allVersions } from '@/versions/lib/all-versions'
|
import { allVersions } from '@/versions/lib/all-versions'
|
||||||
|
import type { Permalink } from '@/types'
|
||||||
|
|
||||||
describe('toc links', () => {
|
describe('toc links', () => {
|
||||||
vi.setConfig({ testTimeout: 3 * 60 * 1000 })
|
vi.setConfig({ testTimeout: 3 * 60 * 1000 })
|
||||||
@@ -20,7 +21,8 @@ describe('toc links', () => {
|
|||||||
for (const pageVersion of Object.keys(allVersions)) {
|
for (const pageVersion of Object.keys(allVersions)) {
|
||||||
for (const page of englishIndexPages) {
|
for (const page of englishIndexPages) {
|
||||||
// skip page if it doesn't have a permalink for the current product version
|
// skip page if it doesn't have a permalink for the current product version
|
||||||
if (!page.permalinks.some((permalink) => permalink.pageVersion === pageVersion)) continue
|
if (!page.permalinks.some((permalink: Permalink) => permalink.pageVersion === pageVersion))
|
||||||
|
continue
|
||||||
|
|
||||||
// build fake context object for rendering the page
|
// build fake context object for rendering the page
|
||||||
const context = {
|
const context = {
|
||||||
@@ -38,7 +40,7 @@ describe('toc links', () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
issues.push({
|
issues.push({
|
||||||
'TOC path': page.relativePath,
|
'TOC path': page.relativePath,
|
||||||
error: err.message,
|
error: err instanceof Error ? err.message : String(err),
|
||||||
pageVersion,
|
pageVersion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,15 @@ import {
|
|||||||
getPreviews,
|
getPreviews,
|
||||||
} from '../lib/index'
|
} from '../lib/index'
|
||||||
|
|
||||||
|
interface GraphqlType {
|
||||||
|
kind: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
describe('graphql schema', () => {
|
describe('graphql schema', () => {
|
||||||
const graphqlTypes = JSON.parse(readFileSync('src/graphql/lib/types.json')).map(
|
const graphqlTypes = (
|
||||||
(item) => item.kind,
|
JSON.parse(readFileSync('src/graphql/lib/types.json', 'utf-8')) as GraphqlType[]
|
||||||
)
|
).map((item) => item.kind)
|
||||||
for (const version in allVersions) {
|
for (const version in allVersions) {
|
||||||
for (const type of graphqlTypes) {
|
for (const type of graphqlTypes) {
|
||||||
test(`getting the GraphQL ${type} schema works for ${version}`, async () => {
|
test(`getting the GraphQL ${type} schema works for ${version}`, async () => {
|
||||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'
|
|||||||
|
|
||||||
import { getDOM } from '@/tests/helpers/e2etest'
|
import { getDOM } from '@/tests/helpers/e2etest'
|
||||||
import { loadPages } from '@/frame/lib/page-data'
|
import { loadPages } from '@/frame/lib/page-data'
|
||||||
|
import type { Permalink } from '@/types'
|
||||||
|
|
||||||
const pageList = await loadPages(undefined, ['en'])
|
const pageList = await loadPages(undefined, ['en'])
|
||||||
|
|
||||||
@@ -27,7 +28,9 @@ describe('server rendering certain GraphQL pages', () => {
|
|||||||
)
|
)
|
||||||
const nonFPTPermalinks = autogeneratedPages
|
const nonFPTPermalinks = autogeneratedPages
|
||||||
.map((page) =>
|
.map((page) =>
|
||||||
page.permalinks.find((permalink) => permalink.pageVersion !== 'free-pro-team@latest'),
|
page.permalinks.find(
|
||||||
|
(permalink: Permalink) => permalink.pageVersion !== 'free-pro-team@latest',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
const nonFPTPermalinksHrefs = nonFPTPermalinks.map((permalink) => {
|
const nonFPTPermalinksHrefs = nonFPTPermalinks.map((permalink) => {
|
||||||
@@ -13,7 +13,7 @@ const liquidEndRex = /{%-?\s*endif\s*-?%}$/
|
|||||||
// {% ifversion ghes%}/foo/bar{%endif %}
|
// {% ifversion ghes%}/foo/bar{%endif %}
|
||||||
//
|
//
|
||||||
// And if no liquid, just return as is.
|
// And if no liquid, just return as is.
|
||||||
function stripLiquid(text) {
|
function stripLiquid(text: string): string {
|
||||||
if (liquidStartRex.test(text) && liquidEndRex.test(text)) {
|
if (liquidStartRex.test(text) && liquidEndRex.test(text)) {
|
||||||
return text.replace(liquidStartRex, '').replace(liquidEndRex, '').trim()
|
return text.replace(liquidStartRex, '').replace(liquidEndRex, '').trim()
|
||||||
} else if (text.includes('{')) {
|
} else if (text.includes('{')) {
|
||||||
@@ -26,7 +26,9 @@ function stripLiquid(text) {
|
|||||||
// return undefined if it can found as a known page.
|
// return undefined if it can found as a known page.
|
||||||
// Otherwise, return an object with information that is used to
|
// Otherwise, return an object with information that is used to
|
||||||
// print a useful test error message in the assertion.
|
// print a useful test error message in the assertion.
|
||||||
export function checkURL(uri, index, redirectsContext) {
|
// Using 'any' type for redirectsContext parameter as it's a complex context object
|
||||||
|
// with dynamic structure that would require extensive type definitions
|
||||||
|
export function checkURL(uri: string, index: number, redirectsContext: any) {
|
||||||
const url = `/en${stripLiquid(uri).split('#')[0]}`
|
const url = `/en${stripLiquid(uri).split('#')[0]}`
|
||||||
if (!(url in redirectsContext.pages)) {
|
if (!(url in redirectsContext.pages)) {
|
||||||
// Some are written without a version, but don't work with the
|
// Some are written without a version, but don't work with the
|
||||||
13
src/versions/lib/all-versions.d.ts
vendored
13
src/versions/lib/all-versions.d.ts
vendored
@@ -1,13 +0,0 @@
|
|||||||
import type { AllVersions } from '@/types'
|
|
||||||
|
|
||||||
export const allVersionKeys: string[]
|
|
||||||
|
|
||||||
export const allVersionShortnames: Record<string, string>
|
|
||||||
|
|
||||||
export declare function isApiVersioned(version: string): boolean
|
|
||||||
|
|
||||||
export declare function getDocsVersion(openApiVersion: string): string
|
|
||||||
|
|
||||||
export declare function getOpenApiVersion(version: string): string
|
|
||||||
|
|
||||||
export const allVersions: AllVersions
|
|
||||||
@@ -6,6 +6,7 @@ import { latest } from '@/versions/lib/enterprise-server-releases'
|
|||||||
import schema from '@/tests/helpers/schemas/versions-schema'
|
import schema from '@/tests/helpers/schemas/versions-schema'
|
||||||
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
|
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
|
||||||
import { formatAjvErrors } from '@/tests/helpers/schemas'
|
import { formatAjvErrors } from '@/tests/helpers/schemas'
|
||||||
|
import type { Version } from '@/types'
|
||||||
|
|
||||||
const validate = getJsonValidator(schema)
|
const validate = getJsonValidator(schema)
|
||||||
|
|
||||||
@@ -16,12 +17,13 @@ describe('versions module', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('every version is valid', () => {
|
test('every version is valid', () => {
|
||||||
Object.values(allVersions).forEach((versionObj) => {
|
Object.values(allVersions).forEach((versionObj: Version) => {
|
||||||
|
const versionName = versionObj.version
|
||||||
const isValid = validate(versionObj)
|
const isValid = validate(versionObj)
|
||||||
let errors
|
let errors: string | undefined
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
errors = `version '${versionObj.version}': ${formatAjvErrors(validate.errors)}`
|
errors = `version '${versionName}': ${formatAjvErrors(validate.errors || [])}`
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(isValid, errors).toBe(true)
|
expect(isValid, errors).toBe(true)
|
||||||
@@ -29,7 +31,7 @@ describe('versions module', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('check REST api calendar date versioned versions set to correct latestApiVersion', () => {
|
test('check REST api calendar date versioned versions set to correct latestApiVersion', () => {
|
||||||
Object.values(allVersions).forEach((versionObj) => {
|
Object.values(allVersions).forEach((versionObj: Version) => {
|
||||||
if (versionObj.apiVersions.length > 0) {
|
if (versionObj.apiVersions.length > 0) {
|
||||||
const latestApiVersion = versionObj.latestApiVersion
|
const latestApiVersion = versionObj.latestApiVersion
|
||||||
const apiVersions = versionObj.apiVersions
|
const apiVersions = versionObj.apiVersions
|
||||||
Reference in New Issue
Block a user