diff --git a/src/content-linter/lib/helpers/print-annotations.js b/src/content-linter/lib/helpers/print-annotations.ts similarity index 78% rename from src/content-linter/lib/helpers/print-annotations.js rename to src/content-linter/lib/helpers/print-annotations.ts index 08e1c8fca5fa..0374d7a5a45a 100644 --- a/src/content-linter/lib/helpers/print-annotations.js +++ b/src/content-linter/lib/helpers/print-annotations.ts @@ -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 } @@ -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)) } diff --git a/src/content-linter/lib/linting-rules/frontmatter-schema.js b/src/content-linter/lib/linting-rules/frontmatter-schema.ts similarity index 77% rename from src/content-linter/lib/linting-rules/frontmatter-schema.js rename to src/content-linter/lib/linting-rules/frontmatter-schema.ts index aa8c331e5bb7..dc67da8449da 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-schema.js +++ b/src/content-linter/lib/linting-rules/frontmatter-schema.ts @@ -1,3 +1,4 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { intersection } from 'lodash-es' @@ -5,12 +6,13 @@ 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 @@ -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( diff --git a/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.js b/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts similarity index 77% rename from src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.js rename to src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts index 9e2bc4358829..769232a5e090 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.js +++ b/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts @@ -1,6 +1,8 @@ +// @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'] @@ -8,21 +10,21 @@ 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)) diff --git a/src/content-linter/lib/linting-rules/internal-links-no-lang.js b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts similarity index 63% rename from src/content-linter/lib/linting-rules/internal-links-no-lang.js rename to src/content-linter/lib/linting-rules/internal-links-no-lang.ts index e6746eb8c99d..d6f318aa43e1 100644 --- a/src/content-linter/lib/linting-rules/internal-links-no-lang.js +++ b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts @@ -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 @@ -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) diff --git a/src/content-linter/lib/linting-rules/internal-links-slash.js b/src/content-linter/lib/linting-rules/internal-links-slash.ts similarity index 66% rename from src/content-linter/lib/linting-rules/internal-links-slash.js rename to src/content-linter/lib/linting-rules/internal-links-slash.ts index 57fe0748e910..39e632e1991b 100644 --- a/src/content-linter/lib/linting-rules/internal-links-slash.js +++ b/src/content-linter/lib/linting-rules/internal-links-slash.ts @@ -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 @@ -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) { diff --git a/src/content-linter/lib/linting-rules/link-punctuation.ts b/src/content-linter/lib/linting-rules/link-punctuation.ts index 778a92782ade..37fb46d00ca6 100644 --- a/src/content-linter/lib/linting-rules/link-punctuation.ts +++ b/src/content-linter/lib/linting-rules/link-punctuation.ts @@ -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 diff --git a/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts similarity index 70% rename from src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js rename to src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts index bb47dda2bbc9..45e25552a7a5 100644 --- a/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.js +++ b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts @@ -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 @@ -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 }) diff --git a/src/content-linter/lib/linting-rules/octicon-aria-labels.js b/src/content-linter/lib/linting-rules/octicon-aria-labels.ts similarity index 79% rename from src/content-linter/lib/linting-rules/octicon-aria-labels.js rename to src/content-linter/lib/linting-rules/octicon-aria-labels.ts index 243167e9cde2..8d42e6a590dd 100644 --- a/src/content-linter/lib/linting-rules/octicon-aria-labels.js +++ b/src/content-linter/lib/linting-rules/octicon-aria-labels.ts @@ -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: @@ -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) diff --git a/src/content-linter/tests/unit/code-fence-line-length.js b/src/content-linter/tests/unit/code-fence-line-length.ts similarity index 100% rename from src/content-linter/tests/unit/code-fence-line-length.js rename to src/content-linter/tests/unit/code-fence-line-length.ts diff --git a/src/content-linter/tests/unit/early-access-references.js b/src/content-linter/tests/unit/early-access-references.ts similarity index 97% rename from src/content-linter/tests/unit/early-access-references.js rename to src/content-linter/tests/unit/early-access-references.ts index 00a8bf6bcc5b..cb6209e80094 100644 --- a/src/content-linter/tests/unit/early-access-references.js +++ b/src/content-linter/tests/unit/early-access-references.ts @@ -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, { diff --git a/src/content-linter/tests/unit/frontmatter-video-transcripts.js b/src/content-linter/tests/unit/frontmatter-video-transcripts.ts similarity index 100% rename from src/content-linter/tests/unit/frontmatter-video-transcripts.js rename to src/content-linter/tests/unit/frontmatter-video-transcripts.ts diff --git a/src/content-linter/tests/unit/image-alt-text-exclude-start-words.js b/src/content-linter/tests/unit/image-alt-text-exclude-start-words.ts similarity index 100% rename from src/content-linter/tests/unit/image-alt-text-exclude-start-words.js rename to src/content-linter/tests/unit/image-alt-text-exclude-start-words.ts diff --git a/src/content-linter/tests/unit/internal-links-slash.js b/src/content-linter/tests/unit/internal-links-slash.ts similarity index 100% rename from src/content-linter/tests/unit/internal-links-slash.js rename to src/content-linter/tests/unit/internal-links-slash.ts diff --git a/src/content-linter/tests/unit/link-punctuation.js b/src/content-linter/tests/unit/link-punctuation.ts similarity index 100% rename from src/content-linter/tests/unit/link-punctuation.js rename to src/content-linter/tests/unit/link-punctuation.ts diff --git a/src/content-linter/tests/unit/rule-filtering.js b/src/content-linter/tests/unit/rule-filtering.ts similarity index 100% rename from src/content-linter/tests/unit/rule-filtering.js rename to src/content-linter/tests/unit/rule-filtering.ts diff --git a/src/content-linter/tests/unit/table-column-integrity-simple.js b/src/content-linter/tests/unit/table-column-integrity-simple.ts similarity index 89% rename from src/content-linter/tests/unit/table-column-integrity-simple.js rename to src/content-linter/tests/unit/table-column-integrity-simple.ts index 6d45e01a3f07..de8eb55724a9 100644 --- a/src/content-linter/tests/unit/table-column-integrity-simple.js +++ b/src/content-linter/tests/unit/table-column-integrity-simple.ts @@ -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 { @@ -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 { diff --git a/src/content-render/tests/data.js b/src/content-render/tests/data.ts similarity index 89% rename from src/content-render/tests/data.js rename to src/content-render/tests/data.ts index 6cc104d4d2e8..322509b2ba63 100644 --- a/src/content-render/tests/data.js +++ b/src/content-render/tests/data.ts @@ -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(() => { @@ -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. @@ -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", ) }) diff --git a/src/content-render/tests/liquid-helpers.js b/src/content-render/tests/liquid-helpers.ts similarity index 90% rename from src/content-render/tests/liquid-helpers.js rename to src/content-render/tests/liquid-helpers.ts index ee4bdce298a6..34042034ecaf 100644 --- a/src/content-render/tests/liquid-helpers.js +++ b/src/content-render/tests/liquid-helpers.ts @@ -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(() => { diff --git a/src/content-render/tests/octicon.js b/src/content-render/tests/octicon.ts similarity index 100% rename from src/content-render/tests/octicon.js rename to src/content-render/tests/octicon.ts diff --git a/src/data-directory/tests/data-schemas.js b/src/data-directory/tests/data-schemas.ts similarity index 82% rename from src/data-directory/tests/data-schemas.js rename to src/data-directory/tests/data-schemas.ts index e7a3ae2bb00a..d2966023a63e 100644 --- a/src/data-directory/tests/data-schemas.js +++ b/src/data-directory/tests/data-schemas.ts @@ -4,6 +4,7 @@ import { extname, basename } from 'path' import walk from 'walk-sync' import { beforeAll, describe, expect, test } from 'vitest' +import type { ValidateFunction, SchemaObject } from 'ajv' import { getJsonValidator, validateJson } from '@/tests/lib/validate-json-schema' import { formatAjvErrors } from '@/tests/helpers/schemas' @@ -19,7 +20,8 @@ const yamlWalkOptions = { } for (const dataDir of directorySchemas) { - let schema, validate + let schema: SchemaObject + let validate: ValidateFunction const dataDirectoryName = basename(dataDir) const yamlFileList = walk(dataDir, yamlWalkOptions).sort() @@ -32,7 +34,7 @@ for (const dataDir of directorySchemas) { test.each(yamlFileList)('%p', async (yamlAbsPath) => { const yamlContent = yaml.load(readFileSync(yamlAbsPath, 'utf8')) const isValid = validate(yamlContent) - const formattedErrors = isValid ? validate.errors : formatAjvErrors(validate.errors) + const formattedErrors = isValid ? undefined : formatAjvErrors(validate.errors || []) expect(isValid, formattedErrors).toBe(true) }) }) @@ -43,6 +45,7 @@ describe('single data files', () => { const ymlData = yaml.load(readFileSync(filepath, 'utf8')) const schema = (await import(dataSchemas[filepath])).default const { isValid, errors } = validateJson(schema, ymlData) - expect(isValid, errors).toBe(true) + const formattedErrors = isValid ? undefined : formatAjvErrors(errors || []) + expect(isValid, formattedErrors).toBe(true) }) }) diff --git a/src/data-directory/tests/index.js b/src/data-directory/tests/index.ts similarity index 93% rename from src/data-directory/tests/index.js rename to src/data-directory/tests/index.ts index f33ca9b5cbfd..6ba9dde7b2a1 100644 --- a/src/data-directory/tests/index.js +++ b/src/data-directory/tests/index.ts @@ -20,7 +20,7 @@ describe('data-directory', () => { }) test('option: preprocess function', async () => { - const preprocess = function (content) { + const preprocess = function (content: string) { return content.replace('markdown', 'MARKDOWN') } const data = dataDirectory(fixturesDir, { preprocess }) @@ -35,7 +35,7 @@ describe('data-directory', () => { }) test('option: ignorePatterns', async () => { - const ignorePatterns = [] + const ignorePatterns: RegExp[] = [] // README is ignored by default expect('README' in dataDirectory(fixturesDir)).toBe(false) diff --git a/src/frame/middleware/set-fastly-surrogate-key.js b/src/frame/middleware/set-fastly-surrogate-key.ts similarity index 60% rename from src/frame/middleware/set-fastly-surrogate-key.js rename to src/frame/middleware/set-fastly-surrogate-key.ts index fd72df15abb9..1fa0befa5b94 100644 --- a/src/frame/middleware/set-fastly-surrogate-key.js +++ b/src/frame/middleware/set-fastly-surrogate-key.ts @@ -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 // 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. @@ -14,7 +16,8 @@ export const SURROGATE_ENUMS = { 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 (!isCustomKey && !Object.values(SURROGATE_ENUMS).includes(enumKey)) { throw new Error( @@ -27,17 +30,21 @@ export function setFastlySurrogateKey(res, enumKey, isCustomKey = false) { 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()}`) 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)}`) return next() } -export function makeLanguageSurrogateKey(langCode) { +export function makeLanguageSurrogateKey(langCode?: string) { if (!langCode) { return 'no-language' } diff --git a/src/frame/tests/secure-files.js b/src/frame/tests/secure-files.ts similarity index 100% rename from src/frame/tests/secure-files.js rename to src/frame/tests/secure-files.ts diff --git a/src/frame/tests/toc-links.js b/src/frame/tests/toc-links.ts similarity index 86% rename from src/frame/tests/toc-links.js rename to src/frame/tests/toc-links.ts index 73a7f1776a32..3288fb21799b 100644 --- a/src/frame/tests/toc-links.js +++ b/src/frame/tests/toc-links.ts @@ -3,6 +3,7 @@ import { describe, expect, test, vi } from 'vitest' import { loadPageMap, loadPages } from '@/frame/lib/page-data' import { renderContent } from '@/content-render/index' import { allVersions } from '@/versions/lib/all-versions' +import type { Permalink } from '@/types' describe('toc links', () => { vi.setConfig({ testTimeout: 3 * 60 * 1000 }) @@ -20,7 +21,8 @@ describe('toc links', () => { for (const pageVersion of Object.keys(allVersions)) { for (const page of englishIndexPages) { // 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 const context = { @@ -38,7 +40,7 @@ describe('toc links', () => { } catch (err) { issues.push({ 'TOC path': page.relativePath, - error: err.message, + error: err instanceof Error ? err.message : String(err), pageVersion, }) } diff --git a/src/graphql/tests/get-schema-files.js b/src/graphql/tests/get-schema-files.ts similarity index 85% rename from src/graphql/tests/get-schema-files.js rename to src/graphql/tests/get-schema-files.ts index d3479a5b1dc5..bf228be99a50 100644 --- a/src/graphql/tests/get-schema-files.js +++ b/src/graphql/tests/get-schema-files.ts @@ -10,10 +10,15 @@ import { getPreviews, } from '../lib/index' +interface GraphqlType { + kind: string + type: string +} + describe('graphql schema', () => { - const graphqlTypes = JSON.parse(readFileSync('src/graphql/lib/types.json')).map( - (item) => item.kind, - ) + const graphqlTypes = ( + JSON.parse(readFileSync('src/graphql/lib/types.json', 'utf-8')) as GraphqlType[] + ).map((item) => item.kind) for (const version in allVersions) { for (const type of graphqlTypes) { test(`getting the GraphQL ${type} schema works for ${version}`, async () => { diff --git a/src/graphql/tests/server-rendering.js b/src/graphql/tests/server-rendering.ts similarity index 91% rename from src/graphql/tests/server-rendering.js rename to src/graphql/tests/server-rendering.ts index f79e388cab31..9219b28ce902 100644 --- a/src/graphql/tests/server-rendering.js +++ b/src/graphql/tests/server-rendering.ts @@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest' import { getDOM } from '@/tests/helpers/e2etest' import { loadPages } from '@/frame/lib/page-data' +import type { Permalink } from '@/types' const pageList = await loadPages(undefined, ['en']) @@ -27,7 +28,9 @@ describe('server rendering certain GraphQL pages', () => { ) const nonFPTPermalinks = autogeneratedPages .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) const nonFPTPermalinksHrefs = nonFPTPermalinks.map((permalink) => { diff --git a/src/tests/helpers/check-url.js b/src/tests/helpers/check-url.ts similarity index 84% rename from src/tests/helpers/check-url.js rename to src/tests/helpers/check-url.ts index ef8766094760..ad878eb1657b 100644 --- a/src/tests/helpers/check-url.js +++ b/src/tests/helpers/check-url.ts @@ -13,7 +13,7 @@ const liquidEndRex = /{%-?\s*endif\s*-?%}$/ // {% ifversion ghes%}/foo/bar{%endif %} // // And if no liquid, just return as is. -function stripLiquid(text) { +function stripLiquid(text: string): string { if (liquidStartRex.test(text) && liquidEndRex.test(text)) { return text.replace(liquidStartRex, '').replace(liquidEndRex, '').trim() } else if (text.includes('{')) { @@ -26,7 +26,9 @@ function stripLiquid(text) { // return undefined if it can found as a known page. // Otherwise, return an object with information that is used to // 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]}` if (!(url in redirectsContext.pages)) { // Some are written without a version, but don't work with the diff --git a/src/tests/helpers/schemas/products-schema.js b/src/tests/helpers/schemas/products-schema.ts similarity index 100% rename from src/tests/helpers/schemas/products-schema.js rename to src/tests/helpers/schemas/products-schema.ts diff --git a/src/versions/lib/all-versions.d.ts b/src/versions/lib/all-versions.d.ts deleted file mode 100644 index ea2ad0058c78..000000000000 --- a/src/versions/lib/all-versions.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { AllVersions } from '@/types' - -export const allVersionKeys: string[] - -export const allVersionShortnames: Record - -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 diff --git a/src/versions/tests/versions.js b/src/versions/tests/versions.ts similarity index 80% rename from src/versions/tests/versions.js rename to src/versions/tests/versions.ts index 51fac5285997..3bf575cc3334 100644 --- a/src/versions/tests/versions.js +++ b/src/versions/tests/versions.ts @@ -6,6 +6,7 @@ import { latest } from '@/versions/lib/enterprise-server-releases' import schema from '@/tests/helpers/schemas/versions-schema' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version' import { formatAjvErrors } from '@/tests/helpers/schemas' +import type { Version } from '@/types' const validate = getJsonValidator(schema) @@ -16,12 +17,13 @@ describe('versions module', () => { }) test('every version is valid', () => { - Object.values(allVersions).forEach((versionObj) => { + Object.values(allVersions).forEach((versionObj: Version) => { + const versionName = versionObj.version const isValid = validate(versionObj) - let errors + let errors: string | undefined if (!isValid) { - errors = `version '${versionObj.version}': ${formatAjvErrors(validate.errors)}` + errors = `version '${versionName}': ${formatAjvErrors(validate.errors || [])}` } expect(isValid, errors).toBe(true) @@ -29,7 +31,7 @@ describe('versions module', () => { }) 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) { const latestApiVersion = versionObj.latestApiVersion const apiVersions = versionObj.apiVersions