1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Convert 28 JavaScript files to TypeScript (#57753)

This commit is contained in:
Kevin Heis
2025-10-06 09:07:33 -07:00
committed by GitHub
parent 140a3f7768
commit 822bdfff67
30 changed files with 126 additions and 88 deletions

View File

@@ -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))
} }

View File

@@ -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(

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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
}) })

View File

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

View File

@@ -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, {

View File

@@ -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 {

View File

@@ -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",
) )
}) })

View File

@@ -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(() => {

View File

@@ -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)
}) })
}) })

View File

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

View File

@@ -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'
} }

View File

@@ -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,
}) })
} }

View File

@@ -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 () => {

View File

@@ -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) => {

View File

@@ -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

View File

@@ -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

View File

@@ -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