diff --git a/eslint.config.ts b/eslint.config.ts index ebbdbc052a67..e86bb633df7c 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -96,7 +96,6 @@ export default [ camelcase: 'off', // Many gh apis use underscores, 600+ uses // Disabled rules to review - '@typescript-eslint/ban-ts-comment': 'off', // 50+ 'github/array-foreach': 'off', // 250+ 'no-console': 'off', // 800+ '@typescript-eslint/no-explicit-any': 'off', // 1000+ diff --git a/src/assets/scripts/validate-asset-images.ts b/src/assets/scripts/validate-asset-images.ts index d76817c93667..9e91bd86c0cb 100755 --- a/src/assets/scripts/validate-asset-images.ts +++ b/src/assets/scripts/validate-asset-images.ts @@ -17,7 +17,6 @@ import path from 'path' import { program } from 'commander' import chalk from 'chalk' import cheerio from 'cheerio' -// @ts-ignore see https://github.com/sindresorhus/file-type/issues/652 import { fileTypeFromFile } from 'file-type' import walk from 'walk-sync' import isSVG from 'is-svg' diff --git a/src/content-linter/lib/helpers/get-rules.ts b/src/content-linter/lib/helpers/get-rules.ts index eaeaf8660aa9..c06c2c711495 100644 --- a/src/content-linter/lib/helpers/get-rules.ts +++ b/src/content-linter/lib/helpers/get-rules.ts @@ -4,7 +4,6 @@ import { customConfig } from '@/content-linter/style/github-docs' import type { Rule } from '@/content-linter/types' // Import markdownlint rules - external library without TypeScript declarations -// @ts-ignore - markdownlint doesn't provide TypeScript declarations import markdownlintRules from '../../../../node_modules/markdownlint/lib/rules' export const customRules: Rule[] = gitHubDocsMarkdownlint.rules diff --git a/src/content-linter/lib/helpers/print-annotations.ts b/src/content-linter/lib/helpers/print-annotations.ts index 0374d7a5a45a..080d551acc0c 100644 --- a/src/content-linter/lib/helpers/print-annotations.ts +++ b/src/content-linter/lib/helpers/print-annotations.ts @@ -8,7 +8,10 @@ export function printAnnotationResults( // Using 'any' type as results structure is dynamic and comes from various linting tools with different formats results: any, - { skippableRules = [], skippableFlawProperties = [] } = {}, + { + skippableRules = [], + skippableFlawProperties = [], + }: { skippableRules?: string[]; skippableFlawProperties?: string[] } = {}, ) { for (const [file, flaws] of Object.entries(results)) { // Using 'any' type for flaws as they have varying structures depending on the linting rule diff --git a/src/content-linter/lib/helpers/utils.ts b/src/content-linter/lib/helpers/utils.ts index b6279b0c69b6..8bdaa36f0dff 100644 --- a/src/content-linter/lib/helpers/utils.ts +++ b/src/content-linter/lib/helpers/utils.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import matter from '@gr2m/gray-matter' diff --git a/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.ts b/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.ts index 96c69dcb2d1e..6eaee5b9a043 100644 --- a/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.ts +++ b/src/content-linter/lib/linting-rules/code-annotation-comment-spacing.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/code-annotations.ts b/src/content-linter/lib/linting-rules/code-annotations.ts index 226e93a4d2ab..4226d8ed2070 100644 --- a/src/content-linter/lib/linting-rules/code-annotations.ts +++ b/src/content-linter/lib/linting-rules/code-annotations.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import { getFrontmatter } from '@/content-linter/lib/helpers/utils' diff --git a/src/content-linter/lib/linting-rules/ctas-schema.ts b/src/content-linter/lib/linting-rules/ctas-schema.ts index e70c924316a7..00981e0fb5a8 100644 --- a/src/content-linter/lib/linting-rules/ctas-schema.ts +++ b/src/content-linter/lib/linting-rules/ctas-schema.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import Ajv from 'ajv' diff --git a/src/content-linter/lib/linting-rules/early-access-references.ts b/src/content-linter/lib/linting-rules/early-access-references.ts index 666ae1e0ae2d..76838d95a517 100644 --- a/src/content-linter/lib/linting-rules/early-access-references.ts +++ b/src/content-linter/lib/linting-rules/early-access-references.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import yaml from 'js-yaml' diff --git a/src/content-linter/lib/linting-rules/expired-content.ts b/src/content-linter/lib/linting-rules/expired-content.ts index 152de51cbbe0..89efe85214c6 100644 --- a/src/content-linter/lib/linting-rules/expired-content.ts +++ b/src/content-linter/lib/linting-rules/expired-content.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, newLineRe } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts b/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts index e163cb641c8b..ba573884bbf5 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/frontmatter-hidden-docs.ts b/src/content-linter/lib/linting-rules/frontmatter-hidden-docs.ts index cdc9289da9cf..ef00f7cbb5ec 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-hidden-docs.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-hidden-docs.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback } from '../../types' diff --git a/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts b/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts index f07a0394c8dc..fbf6cc1e0e61 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-intro-links.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.ts b/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.ts index e3e06382d6bb..769b15926e19 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-landing-recommended.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/frontmatter-schema.ts b/src/content-linter/lib/linting-rules/frontmatter-schema.ts index d616a2021d5a..261d9d990058 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-schema.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-schema.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { intersection } from 'lodash-es' diff --git a/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts b/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts index 680873b80dd4..c97153f2f888 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '@/content-linter/lib/helpers/utils' import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts b/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts index b4ff762c1548..c4062acc5185 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import path from 'path' diff --git a/src/content-linter/lib/linting-rules/github-owned-action-references.ts b/src/content-linter/lib/linting-rules/github-owned-action-references.ts index 797e1c88c34d..f02d9c58e646 100644 --- a/src/content-linter/lib/linting-rules/github-owned-action-references.ts +++ b/src/content-linter/lib/linting-rules/github-owned-action-references.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, ellipsify } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback } from '../../types' diff --git a/src/content-linter/lib/linting-rules/hardcoded-data-variable.ts b/src/content-linter/lib/linting-rules/hardcoded-data-variable.ts index e039d3debe3f..a6d74dc22299 100644 --- a/src/content-linter/lib/linting-rules/hardcoded-data-variable.ts +++ b/src/content-linter/lib/linting-rules/hardcoded-data-variable.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, ellipsify } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback } from '../../types' diff --git a/src/content-linter/lib/linting-rules/header-content-requirement.ts b/src/content-linter/lib/linting-rules/header-content-requirement.ts index 3f0cd923a943..b68142b95151 100644 --- a/src/content-linter/lib/linting-rules/header-content-requirement.ts +++ b/src/content-linter/lib/linting-rules/header-content-requirement.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts b/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts index 769232a5e090..e8796520b4f1 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts +++ b/src/content-linter/lib/linting-rules/image-alt-text-exclude-start-words.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { forEachInlineChild, getRange } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/image-alt-text-length.ts b/src/content-linter/lib/linting-rules/image-alt-text-length.ts index 043ba270827e..a1ae7b87d134 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-length.ts +++ b/src/content-linter/lib/linting-rules/image-alt-text-length.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback } from '../../types' diff --git a/src/content-linter/lib/linting-rules/image-no-gif.ts b/src/content-linter/lib/linting-rules/image-no-gif.ts index 1e59a473050e..2863d4c97bd7 100644 --- a/src/content-linter/lib/linting-rules/image-no-gif.ts +++ b/src/content-linter/lib/linting-rules/image-no-gif.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { forEachInlineChild } from '@/content-linter/lib/helpers/utils' diff --git a/src/content-linter/lib/linting-rules/index.ts b/src/content-linter/lib/linting-rules/index.ts index a868ed9c6827..10a8ecaf49bb 100644 --- a/src/content-linter/lib/linting-rules/index.ts +++ b/src/content-linter/lib/linting-rules/index.ts @@ -1,6 +1,4 @@ -// @ts-ignore - markdownlint-rule-search-replace doesn't provide TypeScript declarations import searchReplace from 'markdownlint-rule-search-replace' -// @ts-ignore - @github/markdownlint-github doesn't provide TypeScript declarations import markdownlintGitHub from '@github/markdownlint-github' import { imageAltTextEndPunctuation } from '@/content-linter/lib/linting-rules/image-alt-text-end-punctuation' diff --git a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts index fbaabf9cb55b..f24d783a590c 100644 --- a/src/content-linter/lib/linting-rules/internal-links-no-lang.ts +++ b/src/content-linter/lib/linting-rules/internal-links-no-lang.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { filterTokens } from 'markdownlint-rule-helpers' import { addFixErrorDetail, getRange } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/internal-links-old-version.ts b/src/content-linter/lib/linting-rules/internal-links-old-version.ts index b66781ca2f93..f61b26ca7097 100644 --- a/src/content-linter/lib/linting-rules/internal-links-old-version.ts +++ b/src/content-linter/lib/linting-rules/internal-links-old-version.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import { getRange } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/internal-links-slash.ts b/src/content-linter/lib/linting-rules/internal-links-slash.ts index 39e632e1991b..0076c1531ad0 100644 --- a/src/content-linter/lib/linting-rules/internal-links-slash.ts +++ b/src/content-linter/lib/linting-rules/internal-links-slash.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { filterTokens } from 'markdownlint-rule-helpers' import { addFixErrorDetail, getRange } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/journey-tracks-guide-path-exists.ts b/src/content-linter/lib/linting-rules/journey-tracks-guide-path-exists.ts index 3d9ef099c88b..0cee7ba94039 100644 --- a/src/content-linter/lib/linting-rules/journey-tracks-guide-path-exists.ts +++ b/src/content-linter/lib/linting-rules/journey-tracks-guide-path-exists.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/journey-tracks-liquid.ts b/src/content-linter/lib/linting-rules/journey-tracks-liquid.ts index 08e932f06bac..c341b65c9340 100644 --- a/src/content-linter/lib/linting-rules/journey-tracks-liquid.ts +++ b/src/content-linter/lib/linting-rules/journey-tracks-liquid.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/journey-tracks-unique-ids.ts b/src/content-linter/lib/linting-rules/journey-tracks-unique-ids.ts index 3d70f167301b..88cba3eb405d 100644 --- a/src/content-linter/lib/linting-rules/journey-tracks-unique-ids.ts +++ b/src/content-linter/lib/linting-rules/journey-tracks-unique-ids.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/link-punctuation.ts b/src/content-linter/lib/linting-rules/link-punctuation.ts index 37fb46d00ca6..75045e2b2eb4 100644 --- a/src/content-linter/lib/linting-rules/link-punctuation.ts +++ b/src/content-linter/lib/linting-rules/link-punctuation.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, Rule } from '../../types' diff --git a/src/content-linter/lib/linting-rules/link-quotation.ts b/src/content-linter/lib/linting-rules/link-quotation.ts index 609896ec5fe3..f9f01934f007 100644 --- a/src/content-linter/lib/linting-rules/link-quotation.ts +++ b/src/content-linter/lib/linting-rules/link-quotation.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import { getRange, quotePrecedesLinkOpen } from '../helpers/utils' import { escapeRegExp } from 'lodash-es' diff --git a/src/content-linter/lib/linting-rules/liquid-data-tags.ts b/src/content-linter/lib/linting-rules/liquid-data-tags.ts index 28bf2898b403..d66d5f974700 100644 --- a/src/content-linter/lib/linting-rules/liquid-data-tags.ts +++ b/src/content-linter/lib/linting-rules/liquid-data-tags.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { TokenKind } from 'liquidjs' diff --git a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts index 8748bbca12b3..7af206e3594e 100644 --- a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts +++ b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { diff --git a/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts index 3549a7eb93bd..66b1320bf26e 100644 --- a/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts +++ b/src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts @@ -1,5 +1,4 @@ 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' diff --git a/src/content-linter/lib/linting-rules/liquid-syntax.ts b/src/content-linter/lib/linting-rules/liquid-syntax.ts index efab53c928de..debb548e7866 100644 --- a/src/content-linter/lib/linting-rules/liquid-syntax.ts +++ b/src/content-linter/lib/linting-rules/liquid-syntax.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/liquid-versioning.ts b/src/content-linter/lib/linting-rules/liquid-versioning.ts index 7a1e0e10b2aa..8af3d9cf7ad7 100644 --- a/src/content-linter/lib/linting-rules/liquid-versioning.ts +++ b/src/content-linter/lib/linting-rules/liquid-versioning.ts @@ -1,6 +1,5 @@ import semver from 'semver' import { TokenKind } from 'liquidjs' -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getRange, addFixErrorDetail } from '../helpers/utils' diff --git a/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts b/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts index cff7228581a2..7e26b8ebf125 100644 --- a/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts +++ b/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, ellipsify } from 'markdownlint-rule-helpers' import { getRange } from '@/content-linter/lib/helpers/utils' diff --git a/src/content-linter/lib/linting-rules/rai-reusable-usage.ts b/src/content-linter/lib/linting-rules/rai-reusable-usage.ts index 135c1e77a0b5..2072611f168e 100644 --- a/src/content-linter/lib/linting-rules/rai-reusable-usage.ts +++ b/src/content-linter/lib/linting-rules/rai-reusable-usage.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { TokenKind } from 'liquidjs' import path from 'path' diff --git a/src/content-linter/lib/linting-rules/table-column-integrity.ts b/src/content-linter/lib/linting-rules/table-column-integrity.ts index 5d9e7bdef91e..8df6d77a8adb 100644 --- a/src/content-linter/lib/linting-rules/table-column-integrity.ts +++ b/src/content-linter/lib/linting-rules/table-column-integrity.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getRange } from '../helpers/utils' import frontmatter from '@/frame/lib/read-frontmatter' diff --git a/src/content-linter/lib/linting-rules/table-liquid-versioning.ts b/src/content-linter/lib/linting-rules/table-liquid-versioning.ts index 12bc2dc96645..69c6ed3200e0 100644 --- a/src/content-linter/lib/linting-rules/table-liquid-versioning.ts +++ b/src/content-linter/lib/linting-rules/table-liquid-versioning.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/third-party-action-pinning.ts b/src/content-linter/lib/linting-rules/third-party-action-pinning.ts index ed3ffc88fb0f..c5a5ac0c932d 100644 --- a/src/content-linter/lib/linting-rules/third-party-action-pinning.ts +++ b/src/content-linter/lib/linting-rules/third-party-action-pinning.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import yaml from 'js-yaml' diff --git a/src/content-linter/lib/linting-rules/third-party-actions-reusable.ts b/src/content-linter/lib/linting-rules/third-party-actions-reusable.ts index 907495a9a884..b0cd8c3dada1 100644 --- a/src/content-linter/lib/linting-rules/third-party-actions-reusable.ts +++ b/src/content-linter/lib/linting-rules/third-party-actions-reusable.ts @@ -1,4 +1,3 @@ -// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types' diff --git a/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.ts b/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.ts index d3c684d68377..b241bf858657 100644 --- a/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.ts +++ b/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.ts @@ -1,5 +1,4 @@ import yaml from 'js-yaml' -// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' import { liquid } from '@/content-render/index' diff --git a/src/content-linter/scripts/lint-content.ts b/src/content-linter/scripts/lint-content.ts index df1544fa530e..ae189ba98307 100755 --- a/src/content-linter/scripts/lint-content.ts +++ b/src/content-linter/scripts/lint-content.ts @@ -2,7 +2,6 @@ * @purpose Writer tool * @description Run the Docs content linter, specifying paths and optional rules */ -// @ts-nocheck import fs from 'fs' import path from 'path' import { execSync } from 'child_process' @@ -20,6 +19,70 @@ import { prettyPrintResults } from './pretty-print-results' import { getLintableYml } from '@/content-linter/lib/helpers/get-lintable-yml' import { printAnnotationResults } from '../lib/helpers/print-annotations' import languages from '@/languages/lib/languages-server' +import type { Rule as MarkdownlintRule } from 'markdownlint' +import type { Rule, Config } from '@/content-linter/types' + +// Type definitions for Markdownlint results +interface LintError { + lineNumber: number + ruleNames: string[] + ruleDescription: string + ruleInformation: string + errorDetail: string | null + errorContext: string | null + errorRange: [number, number] | null + fixInfo?: { + editColumn?: number + deleteCount?: number + insertText?: string + lineNumber?: number + } + isYamlFile?: boolean +} + +type LintResults = Record + +interface FileList { + length: number + content: string[] + data: string[] + yml: string[] +} + +interface ConfiguredRules { + content: MarkdownlintRule[] + data: MarkdownlintRule[] + frontMatter: MarkdownlintRule[] + yml: MarkdownlintRule[] +} + +interface LintConfig { + content: Record // Markdownlint config object + data: Record + frontMatter: Record + yml: Record +} + +interface MarkdownLintConfigResult { + config: LintConfig + configuredRules: ConfiguredRules +} + +interface FormattedResult { + ruleDescription: string + ruleNames: string[] + lineNumber: number + columnNumber?: number + severity: string + errorDetail?: string + errorContext?: string + context?: string + fixable?: boolean + // Index signature allows additional properties from LintError that may vary by rule + [key: string]: any +} + +type FormattedResults = Record /** * Config that applies to all rules in all environments (CI, reports, precommit). @@ -99,20 +162,20 @@ async function main() { const start = Date.now() // Initializes the config to pass to markdownlint based on the input options - const { config, configuredRules } = getMarkdownLintConfig(errorsOnly, rules, customRules) + const { config, configuredRules } = getMarkdownLintConfig(errorsOnly, rules || []) // Run Markdownlint for content directory - const resultContent = await markdownlint.promises.markdownlint({ + const resultContent = (await markdownlint.promises.markdownlint({ files: files.content, config: config.content, customRules: configuredRules.content, - }) + })) as LintResults // Run Markdownlint for data directory - const resultData = await markdownlint.promises.markdownlint({ + const resultData = (await markdownlint.promises.markdownlint({ files: files.data, config: config.data, customRules: configuredRules.data, - }) + })) as LintResults // Run Markdownlint for content directory (frontmatter only) const resultFrontmatter = await markdownlint.promises.markdownlint({ @@ -123,20 +186,20 @@ async function main() { }) // Run Markdownlint on "lintable" Markdown strings in a YML file - const resultYml = {} + const resultYml: LintResults = {} for (const ymlFile of files.yml) { const lintableYml = await getLintableYml(ymlFile) if (!lintableYml) continue - const resultYmlFile = await markdownlint.promises.markdownlint({ + const resultYmlFile = (await markdownlint.promises.markdownlint({ strings: lintableYml, config: config.yml, customRules: configuredRules.yml, - }) + })) as LintResults Object.entries(resultYmlFile).forEach(([key, value]) => { - if (value.length) { - const errors = value.map((error) => { + if ((value as LintError[]).length) { + const errors = (value as LintError[]).map((error) => { // Autofixing would require us to write the changes back to the YML // file which Markdownlint doesn't support. So we don't support // autofixing for YML files at this time. @@ -152,13 +215,13 @@ async function main() { // There are no collisions when assigning the results to the new object // because the keys are filepaths and the individual runs of Markdownlint // are in separate directories (content and data). - const results = Object.assign({}, resultContent, resultData, resultYml) + const results: LintResults = Object.assign({}, resultContent, resultData, resultYml) // Merge in the results for frontmatter tests, which could be // in a file that already exists as a key in the `results` object. Object.entries(resultFrontmatter).forEach(([key, value]) => { - if (results[key]) results[key].push(...value) - else results[key] = value + if (results[key]) results[key].push(...(value as LintError[])) + else results[key] = value as LintError[] }) // Apply markdownlint fixes if available and rewrite the files @@ -169,7 +232,7 @@ async function main() { continue } const content = fs.readFileSync(file, 'utf8') - const applied = applyFixes(content, results[file]) + const applied = applyFixes(content, results[file] as any) if (content !== applied) { countFixedFiles++ fs.writeFileSync(file, applied, 'utf-8') @@ -191,13 +254,7 @@ async function main() { reportSummaryByRule(results, config) } else if (errorFileCount > 0 || warningFileCount > 0 || countFixedFiles > 0) { if (outputFile) { - fs.writeFileSync( - `${outputFile}`, - JSON.stringify(formattedResults, undefined, 2), - function (err) { - if (err) throw err - }, - ) + fs.writeFileSync(`${outputFile}`, JSON.stringify(formattedResults, undefined, 2), 'utf-8') console.log(`Output written to ${outputFile}`) } else { prettyPrintResults(formattedResults, { @@ -214,8 +271,8 @@ async function main() { // and columns numbers of YAML files. YAML files consist of one // or more Markdown strings that can themselves constitute an // entire "file." - 'isYamlFile', - ], + 'isYamlFile' as string, + ] as string[], }) } @@ -290,7 +347,12 @@ async function main() { } } -function pluralize(things, word, pluralForm = null) { +// Using unknown[] to accept arrays of any type (errors, warnings, files, etc.) +function pluralize( + things: unknown[] | number, + word: string, + pluralForm: string | null = null, +): string { const isPlural = Array.isArray(things) ? things.length !== 1 : things !== 1 if (isPlural) { return pluralForm || `${word}s` @@ -306,8 +368,8 @@ function pluralize(things, word, pluralForm = null) { // (e.g., heading linters) so we need to separate the // list of data files from all other files to run // through markdownlint individually -function getFilesToLint(inputPaths) { - const fileList = { +function getFilesToLint(inputPaths: string[]): FileList { + const fileList: FileList = { length: 0, content: [], data: [], @@ -347,7 +409,7 @@ function getFilesToLint(inputPaths) { const seen = new Set() - function cleanPaths(filePaths) { + function cleanPaths(filePaths: string[]): string[] { const clean = [] for (const filePath of filePaths) { if ( @@ -390,21 +452,21 @@ function getFilesToLint(inputPaths) { * isInDir('/foo', '/foo') => true * isInDir('/foo/barring', '/foo/bar') => false */ -function isInDir(child, parent) { +function isInDir(child: string, parent: string): boolean { // The simple reason why you can't use `parent.startsWith(child)` // is because the parent might be `/path/to/data` and the child // might be `/path/to/data-files`. const parentSplit = parent.split(path.sep) const childSplit = child.split(path.sep) - return parentSplit.every((dir, i) => dir === childSplit[i]) + return parentSplit.every((dir: string, i: number) => dir === childSplit[i]) } // This is a function used during development to // see how many errors we have per rule. This helps // to identify rules that can be upgraded from // warning severity to error. -function reportSummaryByRule(results, config) { - const ruleCount = {} +function reportSummaryByRule(results: LintResults, config: LintConfig): void { + const ruleCount: Record = {} // populate the list of rules with 0 occurrences for (const rule of Object.keys(config.content)) { @@ -431,16 +493,21 @@ function reportSummaryByRule(results, config) { result. Results are sorted by severity per file, with errors listed first then warnings. */ -function getFormattedResults(allResults, isInPrecommitMode) { - const output = {} +function getFormattedResults( + allResults: LintResults, + isInPrecommitMode: boolean, +): FormattedResults { + const output: FormattedResults = {} Object.entries(allResults) // Each result key always has an array value, but it may be empty .filter(([, results]) => results.length) .forEach(([key, fileResults]) => { if (verbose) { - output[key] = [...fileResults] + output[key] = fileResults.map((flaw: LintError) => formatResult(flaw, isInPrecommitMode)) } else { - const formattedResults = fileResults.map((flaw) => formatResult(flaw, isInPrecommitMode)) + const formattedResults = fileResults.map((flaw: LintError) => + formatResult(flaw, isInPrecommitMode), + ) // Only add the file to output if there are results after filtering if (formattedResults.length > 0) { @@ -458,19 +525,24 @@ function getFormattedResults(allResults, isInPrecommitMode) { // and the value being an array of errors for that filepath. // Each result has a rule name, which when looked up in `allConfig` // will give us its severity and we filter those that are 'warning'. -function getWarningCountByFile(results, fixed = false) { +function getWarningCountByFile(results: FormattedResults, fixed = false): number { return getCountBySeverity(results, 'warning', fixed) } // Results are formatted with the key being the filepath // and the value being an array of results for that filepath. // Each result in the array has a severity of error or warning. -function getErrorCountByFile(results, fixed = false) { +function getErrorCountByFile(results: FormattedResults, fixed = false): number { return getCountBySeverity(results, 'error', fixed) } -function getCountBySeverity(results, severityLookup, fixed) { - return Object.values(results).filter((fileResults) => - fileResults.some((result) => { + +function getCountBySeverity( + results: FormattedResults, + severityLookup: string, + fixed: boolean, +): number { + return Object.values(results).filter((fileResults: FormattedResult[]) => + fileResults.some((result: FormattedResult) => { // If --fix was applied, we don't want to know about files that // no longer have errors or warnings. return result.severity === severityLookup && (!fixed || !result.fixable) @@ -481,19 +553,19 @@ function getCountBySeverity(results, severityLookup, fixed) { // Removes null values and properties that are not relevant to content // writers, adds the severity to each result object, and transforms // some error and fix data into a more readable format. -function formatResult(object, isInPrecommitMode) { - const formattedResult = {} +function formatResult(object: LintError, isInPrecommitMode: boolean): FormattedResult { + const formattedResult: FormattedResult = {} as FormattedResult // Add severity to each result object const ruleName = object.ruleNames[1] || object.ruleNames[0] - if (!allConfig[ruleName]) { + const ruleConfig = allConfig[ruleName] as Config | undefined + if (!ruleConfig) { throw new Error(`Rule not found in allConfig: '${ruleName}'`) } formattedResult.severity = - allConfig[ruleName].severity || - getSearchReplaceRuleSeverity(ruleName, object, isInPrecommitMode) + ruleConfig.severity || getSearchReplaceRuleSeverity(ruleName, object, isInPrecommitMode) - formattedResult.context = allConfig[ruleName].context || '' + formattedResult.context = ruleConfig.context || '' return Object.entries(object).reduce((acc, [key, value]) => { if (key === 'fixInfo') { @@ -503,7 +575,7 @@ function formatResult(object, isInPrecommitMode) { } if (!value) return acc if (key === 'errorRange') { - acc.columnNumber = value[0] + acc.columnNumber = (value as [number, number])[0] delete acc.range return acc } @@ -545,14 +617,17 @@ function listRules() { Rules that can't be run on partials have the property `partial-markdown-files` set to false. */ -function getMarkdownLintConfig(filterErrorsOnly, runRules) { +function getMarkdownLintConfig( + filterErrorsOnly: boolean, + runRules: string[], +): MarkdownLintConfigResult { const config = { content: structuredClone(defaultConfig), data: structuredClone(defaultConfig), frontMatter: structuredClone(defaultConfig), yml: structuredClone(defaultConfig), } - const configuredRules = { + const configuredRules: ConfiguredRules = { content: [], data: [], frontMatter: [], @@ -560,12 +635,12 @@ function getMarkdownLintConfig(filterErrorsOnly, runRules) { } for (const [ruleName, ruleConfig] of Object.entries(allConfig)) { - const customRule = customConfig[ruleName] && getCustomRule(ruleName) + const customRule = (customConfig as any)[ruleName] && getCustomRule(ruleName) // search-replace is handled differently than other rules because // it has nested metadata and rules. if ( filterErrorsOnly && - getRuleSeverity(ruleConfig, isPrecommit) !== 'error' && + getSeverity(ruleConfig as any, isPrecommit) !== 'error' && ruleName !== 'search-replace' ) { continue @@ -575,8 +650,8 @@ function getMarkdownLintConfig(filterErrorsOnly, runRules) { if (runRules && !shouldIncludeRule(ruleName, runRules)) continue // There are a subset of rules run on just the frontmatter in files - if (githubDocsFrontmatterConfig[ruleName]) { - config.frontMatter[ruleName] = ruleConfig + if ((githubDocsFrontmatterConfig as any)[ruleName]) { + ;(config.frontMatter as any)[ruleName] = ruleConfig if (customRule) configuredRules.frontMatter.push(customRule) } // Handle the special case of the search-replace rule @@ -589,23 +664,23 @@ function getMarkdownLintConfig(filterErrorsOnly, runRules) { const frontmatterSearchReplaceRules = [] for (const searchRule of ruleConfig.rules) { - const searchRuleSeverity = getRuleSeverity(searchRule, isPrecommit) + const searchRuleSeverity = getSeverity(searchRule, isPrecommit) if (filterErrorsOnly && searchRuleSeverity !== 'error') continue // Add search-replace rules to frontmatter configuration for rules that make sense in frontmatter // This ensures rules like TODOCS detection work in frontmatter // Rules with applyToFrontmatter should ONLY run in the frontmatter pass (which lints the entire file) // to avoid duplicate detections if (searchRule.applyToFrontmatter) { - frontmatterSearchReplaceRules.push(searchRule) + frontmatterSearchReplaceRules.push(searchRule as any) } else { // Only add to content rules if not a frontmatter-specific rule - searchReplaceRules.push(searchRule) + searchReplaceRules.push(searchRule as any) } if (searchRule['partial-markdown-files']) { - dataSearchReplaceRules.push(searchRule) + dataSearchReplaceRules.push(searchRule as any) } if (searchRule['yml-files']) { - ymlSearchReplaceRules.push(searchRule) + ymlSearchReplaceRules.push(searchRule as any) } } @@ -645,7 +720,7 @@ function getMarkdownLintConfig(filterErrorsOnly, runRules) { // Return the severity value of a rule but keep in mind it could be // running as a precommit hook, which means the severity could be // deliberately different. -function getRuleSeverity(ruleConfig, isInPrecommitMode) { +function getSeverity(ruleConfig: Config, isInPrecommitMode: boolean): string { return isInPrecommitMode ? ruleConfig.precommitSeverity || ruleConfig.severity : ruleConfig.severity @@ -653,7 +728,7 @@ function getRuleSeverity(ruleConfig, isInPrecommitMode) { // Gets a custom rule function from the name of the rule // in the configuration file -function getCustomRule(ruleName) { +function getCustomRule(ruleName: string): Rule | MarkdownlintRule { const rule = customRules.find((r) => r.names.includes(ruleName)) if (!rule) throw new Error( @@ -664,7 +739,7 @@ function getCustomRule(ruleName) { // Check if a rule should be included based on user-specified rules // Handles both short names (e.g., GHD053, MD001) and long names (e.g., header-content-requirement, heading-increment) -export function shouldIncludeRule(ruleName, runRules) { +export function shouldIncludeRule(ruleName: string, runRules: string[]) { // First check if the rule name itself is in the list if (runRules.includes(ruleName)) { return true @@ -679,7 +754,7 @@ export function shouldIncludeRule(ruleName, runRules) { // For built-in markdownlint rules, check if any of the rule's names are in the runRules list const builtinRule = allRules.find((rule) => rule.names.includes(ruleName)) if (builtinRule) { - return builtinRule.names.some((name) => runRules.includes(name)) + return builtinRule.names.some((name: string) => runRules.includes(name)) } return false @@ -703,9 +778,15 @@ export function shouldIncludeRule(ruleName, runRules) { fixInfo: null } */ -function getSearchReplaceRuleSeverity(ruleName, object, isInPrecommitMode) { - const pluginRuleName = object.errorDetail.split(':')[0].trim() - const rule = allConfig[ruleName].rules.find((r) => r.name === pluginRuleName) +function getSearchReplaceRuleSeverity( + ruleName: string, + object: LintError, + isInPrecommitMode: boolean, +) { + const pluginRuleName = object.errorDetail?.split(':')[0].trim() + const ruleConfig = allConfig[ruleName] as Config + const rule = ruleConfig.rules?.find((r) => r.name === pluginRuleName) + if (!rule) return 'error' // Default to error if rule not found return isInPrecommitMode ? rule.precommitSeverity || rule.severity : rule.severity } @@ -745,6 +826,6 @@ function isOptionsValid() { return true } -function isAFixtureMdFile(filePath) { +function isAFixtureMdFile(filePath: string): boolean { return filePath.includes('/src') && filePath.includes('/fixtures') && filePath.endsWith('.md') } diff --git a/src/content-linter/tests/lint-files.ts b/src/content-linter/tests/lint-files.ts index e6de28e097e0..c700fc7cb08b 100755 --- a/src/content-linter/tests/lint-files.ts +++ b/src/content-linter/tests/lint-files.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { fileURLToPath } from 'url' import path from 'path' import yaml from 'js-yaml' @@ -188,19 +187,20 @@ const FbvYamlRelPaths = FbvYamlAbsPaths.map((p) => slash(path.relative(rootDir, const fbvTuples = zip(FbvYamlRelPaths, FbvYamlAbsPaths) // Put all the yaml files together -ymlToLint = [].concat( +ymlToLint = ([] as Array<[string | undefined, string | undefined]>).concat( variableYamlTuples, // These "tuples" not tested independently; they are only tested as part of ymlToLint. glossariesYamlTuples, fbvTuples, ) -function formatLinkError(message, links) { +function formatLinkError(message: string, links: string[]) { return `${message}\n - ${links.join('\n - ')}` } // Returns `content` if its a string, or `content.description` if it can. // Used for getting the nested `description` key in glossary files. -function getContent(content) { +// Using any because content can be string | { description: string } | other YAML structures +function getContent(content: any) { if (typeof content === 'string') return content if (typeof content.description === 'string') return content.description return null @@ -224,9 +224,10 @@ if (diffFiles.length > 0) { return name }), ) - const filterFiles = (tuples) => + const filterFiles = (tuples: Array<[string | undefined, string | undefined]>) => tuples.filter( - ([relativePath, absolutePath]) => only.has(relativePath) || only.has(absolutePath), + ([relativePath, absolutePath]: [string | undefined, string | undefined]) => + only.has(relativePath!) || only.has(absolutePath!), ) ymlToLint = filterFiles(ymlToLint) } @@ -239,14 +240,15 @@ if (ymlToLint.length === 0) { } else { describe('lint yaml content', () => { if (ymlToLint.length < 1) return - describe.each(ymlToLint)('%s', (yamlRelPath, yamlAbsPath) => { - let dictionary - let isEarlyAccess - let fileContents + describe.each(ymlToLint)('%s', (yamlRelPath: any, yamlAbsPath: any) => { + // Using any because Vitest's describe.each doesn't properly infer tuple types + let dictionary: any // YAML structure varies by file type (variables, glossaries, features) + let isEarlyAccess: boolean + let fileContents: string // This variable is used to determine if the file was parsed successfully. // When `yaml.load()` fails to parse the file, it is overwritten with the error message. // `false` is intentionally chosen since `null` and `undefined` are valid return values. - let dictionaryError = false + let dictionaryError: any = false // Can be false, Error, or other error types beforeAll(async () => { fileContents = await fs.readFile(yamlAbsPath, 'utf8') @@ -279,7 +281,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(relativeArticleLinkRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -297,7 +299,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(earlyAccessLinkRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -316,7 +318,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(earlyAccessImageRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -335,7 +337,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(badEarlyAccessImageRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -351,7 +353,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(languageLinkRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -367,7 +369,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(versionLinkRegEx) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -383,7 +385,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(domainLinkRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } @@ -400,7 +402,7 @@ if (ymlToLint.length === 0) { const valMatches = contentStr.match(oldVariableRegex) || [] if (valMatches.length > 0) { matches.push( - ...valMatches.map((match) => { + ...valMatches.map((match: string) => { const example = match.replace( /{{\s*?site\.data\.([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)+)\s*?}}/g, '{% data $1 %}', @@ -423,7 +425,7 @@ if (ymlToLint.length === 0) { if (!contentStr) continue const valMatches = contentStr.match(oldOcticonRegex) || [] if (valMatches.length > 0) { - matches.push(...valMatches.map((match) => `Key "${key}": ${match}`)) + matches.push(...valMatches.map((match: string) => `Key "${key}": ${match}`)) } } diff --git a/src/content-linter/tests/unit/frontmatter-search-replace.ts b/src/content-linter/tests/unit/frontmatter-search-replace.ts index 77cdbc8e2bc3..152e61e67290 100644 --- a/src/content-linter/tests/unit/frontmatter-search-replace.ts +++ b/src/content-linter/tests/unit/frontmatter-search-replace.ts @@ -1,6 +1,5 @@ import { describe, expect, test } from 'vitest' import markdownlint from 'markdownlint' -// @ts-ignore - markdownlint-rule-search-replace doesn't provide TypeScript declarations import searchReplace from 'markdownlint-rule-search-replace' import { searchReplaceConfig } from '../../style/github-docs' diff --git a/src/content-linter/types.ts b/src/content-linter/types.ts index c4feed37b124..53048ac01811 100644 --- a/src/content-linter/types.ts +++ b/src/content-linter/types.ts @@ -58,6 +58,7 @@ export type Config = { style?: string rules?: RuleDetail[] context?: string + precommitSeverity?: string } export type RuleConfig = { diff --git a/src/content-render/liquid/octicon.ts b/src/content-render/liquid/octicon.ts index 88bab333cf8d..c8ceaa3a447d 100644 --- a/src/content-render/liquid/octicon.ts +++ b/src/content-render/liquid/octicon.ts @@ -1,5 +1,4 @@ import { TokenizationError } from 'liquidjs' -// @ts-ignore - @primer/octicons doesn't provide TypeScript declarations import octicons from '@primer/octicons' // Note: Using 'any' for liquidjs-related types because liquidjs doesn't provide comprehensive TypeScript definitions diff --git a/src/content-render/liquid/prompt.ts b/src/content-render/liquid/prompt.ts index 0b5cf74b1ec3..b63b6c9b3e56 100644 --- a/src/content-render/liquid/prompt.ts +++ b/src/content-render/liquid/prompt.ts @@ -1,7 +1,6 @@ // src/content-render/liquid/prompt.ts // Defines {% prompt %}…{% endprompt %} to wrap its content in and append the Copilot icon. -// @ts-ignore - @primer/octicons doesn't provide TypeScript declarations import octicons from '@primer/octicons' interface LiquidTag { diff --git a/src/content-render/scripts/move-content.ts b/src/content-render/scripts/move-content.ts index 5454ace0634a..d092edcf1491 100755 --- a/src/content-render/scripts/move-content.ts +++ b/src/content-render/scripts/move-content.ts @@ -2,7 +2,6 @@ * @purpose Writer tool * @description Move or rename a file or a folder and automatically add redirects */ -// @ts-nocheck // [start-readme] // // Use this script to help you move or rename a single file or a folder. The script will move or rename the file or folder for you, update relevant `children` in the index.md file(s), and add a `redirect_from` to frontmatter in the renamed file(s). Note: You will still need to manually update the `title` if necessary. @@ -35,6 +34,20 @@ import escapeStringRegexp from 'escape-string-regexp' import fm from '@/frame/lib/frontmatter' import readFrontmatter from '@/frame/lib/read-frontmatter' +// Type definitions +interface MoveOptions { + verbose: boolean + undo: boolean + git: boolean +} + +type FileTuple = [string, string, string, string] // [oldPath, newPath, oldHref, newHref] + +interface PositionInfo { + childrenPosition: number + childGroupPositions: number[][] +} + // This is so you can optionally run it again the test fixtures root. const ROOT = process.env.ROOT || '.' const CONTENT_ROOT = path.resolve(path.join(ROOT, 'content')) @@ -52,13 +65,13 @@ program "DON'T use 'git mv' and 'git commit' to move the file. Just regular file moves.", ) .option('--undo', 'Reverse of moving. I.e. moving it back. Only applies to the last run.') - .arguments('old', 'old file or folder name') - .arguments('new', 'new file or folder name') + .argument('old', 'old file or folder name') + .argument('new', 'new file or folder name') .parse(process.argv) main(program.opts(), program.args) -async function main(opts, nameTuple) { +async function main(opts: MoveOptions, nameTuple: string[]) { const { verbose, undo, git } = opts if (nameTuple.length !== 2) { console.error( @@ -161,7 +174,7 @@ async function main(opts, nameTuple) { } } else { // When it's just an individual file, it's easier. - const files = [[oldPath, newPath, oldHref, newHref]] + const files: FileTuple[] = [[oldPath, newPath, oldHref, newHref]] // First take care of the `git mv` (or regular rename) part. moveFiles(files, opts) @@ -191,7 +204,7 @@ async function main(opts, nameTuple) { } } -function validateFileInputs(oldPath, newPath, isFolder) { +function validateFileInputs(oldPath: string, newPath: string, isFolder: boolean) { if (isFolder) { // Make sure that only the last portion of the path is different // and that all preceding are equal. @@ -241,17 +254,17 @@ function validateFileInputs(oldPath, newPath, isFolder) { } } -function existsAndIsDirectory(directory) { +function existsAndIsDirectory(directory: string) { return fs.existsSync(directory) && fs.lstatSync(directory).isDirectory() } -function splitDirectory(directory) { +function splitDirectory(directory: string) { return [path.dirname(directory), path.basename(directory)] } -function findFilesInFolder(oldPath, newPath, opts) { +function findFilesInFolder(oldPath: string, newPath: string, opts: MoveOptions): FileTuple[] { const { undo, verbose } = opts - const files = [] + const files: FileTuple[] = [] const allFiles = walk(oldPath, { includeBasePath: true, directories: false }) for (const filePath of allFiles) { const newFilePath = filePath.replace(oldPath, newPath) @@ -265,17 +278,17 @@ function findFilesInFolder(oldPath, newPath, opts) { return files } -function makeHref(root, filePath) { +function makeHref(root: string, filePath: string) { const nameSplit = path.relative(root, filePath).split(path.sep) if (nameSplit.slice(-1)[0] === 'index.md') { nameSplit.pop() } else { - nameSplit.push(nameSplit.pop().replace(/\.md$/, '')) + nameSplit.push(nameSplit.pop()!.replace(/\.md$/, '')) } return `/${nameSplit.join('/')}` } -function moveFolder(oldPath, newPath, files, opts) { +function moveFolder(oldPath: string, newPath: string, files: FileTuple[], opts: MoveOptions) { const { verbose, git: useGit } = opts if (useGit) { let cmd = ['mv', oldPath, newPath] @@ -297,7 +310,7 @@ function moveFolder(oldPath, newPath, files, opts) { } } -function undoFolder(oldPath, newPath, files, opts) { +function undoFolder(oldPath: string, newPath: string, files: FileTuple[], opts: MoveOptions) { const { verbose, git: useGit } = opts if (useGit) { @@ -320,12 +333,12 @@ function undoFolder(oldPath, newPath, files, opts) { } } -function getBasename(fileOrDirectory) { +function getBasename(fileOrDirectory: string) { // Note, can't use fs.lstatSync().isDirectory() because it's just a string // at this point. It might not exist. if (fileOrDirectory.endsWith('index.md')) { - return path.basename(path.directory(fileOrDirectory)) + return path.basename(path.dirname(fileOrDirectory)) } if (fileOrDirectory.endsWith('.md')) { return path.basename(fileOrDirectory).replace(/\.md$/, '') @@ -333,7 +346,7 @@ function getBasename(fileOrDirectory) { return path.basename(fileOrDirectory) } -function removeFromChildren(oldPath, opts) { +function removeFromChildren(oldPath: string, opts: MoveOptions): PositionInfo { const { verbose } = opts const parentFilePath = path.join(path.dirname(oldPath), 'index.md') @@ -342,8 +355,8 @@ function removeFromChildren(oldPath, opts) { const oldName = getBasename(oldPath) let childrenPosition = -1 - if (CHILDREN_KEY in data) { - data[CHILDREN_KEY] = data[CHILDREN_KEY].filter((entry, i) => { + if (data && CHILDREN_KEY in data) { + data[CHILDREN_KEY] = data[CHILDREN_KEY].filter((entry: any, i: number) => { if (entry === oldName || entry === `/${oldName}`) { childrenPosition = i return false @@ -355,11 +368,11 @@ function removeFromChildren(oldPath, opts) { } } - const childGroupPositions = [] + const childGroupPositions: number[][] = [] - ;(data[CHILDGROUPS_KEY] || []).forEach((group, i) => { + ;((data && data[CHILDGROUPS_KEY]) || []).forEach((group: any, i: number) => { if (group.children) { - group.children = group.children.filter((entry, j) => { + group.children = group.children.filter((entry: any, j: number) => { if (entry === oldName || entry === `/${oldName}`) { childGroupPositions.push([i, j]) return false @@ -369,11 +382,13 @@ function removeFromChildren(oldPath, opts) { } }) - fs.writeFileSync( - parentFilePath, - readFrontmatter.stringify(content, data, { lineWidth: 10000 }), - 'utf-8', - ) + if (data) { + fs.writeFileSync( + parentFilePath, + readFrontmatter.stringify(content, data, { lineWidth: 10000 } as any), + 'utf-8', + ) + } if (verbose) { console.log(`Removed 'children' (${oldName}) key in ${parentFilePath}`) } @@ -381,7 +396,7 @@ function removeFromChildren(oldPath, opts) { return { childrenPosition, childGroupPositions } } -function addToChildren(newPath, positions, opts) { +function addToChildren(newPath: string, positions: PositionInfo, opts: MoveOptions) { const { verbose } = opts const parentFilePath = path.join(path.dirname(newPath), 'index.md') const fileContent = fs.readFileSync(parentFilePath, 'utf-8') @@ -389,10 +404,10 @@ function addToChildren(newPath, positions, opts) { const newName = getBasename(newPath) const { childrenPosition, childGroupPositions } = positions - if (childrenPosition > -1) { + if (childrenPosition > -1 && data) { const children = data[CHILDREN_KEY] || [] let prefix = '' - if (children.every((entry) => entry.startsWith('/'))) { + if (children.every((entry: any) => entry.startsWith('/'))) { prefix += '/' } if (childrenPosition > -1 && childrenPosition < children.length) { @@ -403,7 +418,7 @@ function addToChildren(newPath, positions, opts) { data[CHILDREN_KEY] = children } - if (CHILDGROUPS_KEY in data) { + if (data && CHILDGROUPS_KEY in data) { for (const [groupIndex, groupChildPosition] of childGroupPositions) { if (groupIndex < data[CHILDGROUPS_KEY].length) { const group = data[CHILDGROUPS_KEY][groupIndex] @@ -416,17 +431,19 @@ function addToChildren(newPath, positions, opts) { } } - fs.writeFileSync( - parentFilePath, - readFrontmatter.stringify(content, data, { lineWidth: 10000 }), - 'utf-8', - ) + if (data) { + fs.writeFileSync( + parentFilePath, + readFrontmatter.stringify(content, data, { lineWidth: 10000 } as any), + 'utf-8', + ) + } if (verbose) { console.log(`Added 'children' (${newName}) key in ${parentFilePath}`) } } -function moveFiles(files, opts) { +function moveFiles(files: FileTuple[], opts: MoveOptions) { const { verbose, git: useGit } = opts // Before we do anything, assert that the files are valid for (const [oldPath] of files) { @@ -474,7 +491,7 @@ function moveFiles(files, opts) { } } -function editFiles(files, updateParent, opts) { +function editFiles(files: FileTuple[], updateParent: boolean, opts: MoveOptions) { const { verbose, git: useGit } = opts // Second loop. This time our only job is to edit the `redirects_from` @@ -484,13 +501,14 @@ function editFiles(files, updateParent, opts) { for (const [oldPath, newPath, oldHref, newHref] of files) { const fileContent = fs.readFileSync(newPath, 'utf-8') const { content, data } = readFrontmatter(fileContent) + if (!data) continue if (!(REDIRECT_FROM_KEY in data)) { data[REDIRECT_FROM_KEY] = [] } data[REDIRECT_FROM_KEY].push(oldHref) fs.writeFileSync( newPath, - readFrontmatter.stringify(content, data, { lineWidth: 10000 }), + readFrontmatter.stringify(content, data, { lineWidth: 10000 } as any), 'utf-8', ) if (verbose) { @@ -515,11 +533,11 @@ function editFiles(files, updateParent, opts) { const filePaths = files.map(([, newPath]) => newPath) try { const cmd = ['run', 'add-content-type', '--', '--paths', ...filePaths] - const result = execFileSync('npm', cmd, { cwd: process.cwd(), encoding: 'utf8' }) + const result = execFileSync('npm', cmd, { cwd: process.cwd(), encoding: 'utf8' }) as any if (result.trim()) { console.log(result.trim()) } - } catch (error) { + } catch (error: any) { console.warn(`Warning: Failed to add contentType frontmatter: ${error.message}`) } } @@ -538,22 +556,25 @@ function editFiles(files, updateParent, opts) { } } -function undoFiles(files, updateParent, opts) { +function undoFiles(files: FileTuple[], updateParent: boolean, opts: MoveOptions) { const { verbose, git: useGit } = opts // First undo any edits to the file for (const [oldPath, newPath, oldHref, newHref] of files) { const fileContent = fs.readFileSync(newPath, 'utf-8') const { content, data } = readFrontmatter(fileContent) + if (!data) continue - data[REDIRECT_FROM_KEY] = (data[REDIRECT_FROM_KEY] || []).filter((entry) => entry !== oldHref) + data[REDIRECT_FROM_KEY] = (data[REDIRECT_FROM_KEY] || []).filter( + (entry: any) => entry !== oldHref, + ) if (data[REDIRECT_FROM_KEY].length === 0) { delete data[REDIRECT_FROM_KEY] } fs.writeFileSync( newPath, - readFrontmatter.stringify(content, data, { lineWidth: 10000 }), + readFrontmatter.stringify(content, data, { lineWidth: 10000 } as any), 'utf-8', ) if (updateParent) { @@ -577,15 +598,18 @@ function undoFiles(files, updateParent, opts) { } } -function findInLearningTracks(href) { - const allFiles = walk(path.join(DATA_ROOT, 'learning-tracks'), { +function findInLearningTracks(href: string) { + const allFiles: string[] = walk(path.join(DATA_ROOT, 'learning-tracks'), { globs: ['*.yml'], includeBasePath: true, directories: false, }) - const found = [] + const found: string[] = [] for (const filePath of allFiles) { - const tracks = yaml.load(fs.readFileSync(filePath, 'utf-8')) + const tracks = yaml.load(fs.readFileSync(filePath, 'utf-8')) as Record< + string, + { guides?: string[] } + > if ( Object.values(tracks).find((track) => { @@ -599,7 +623,7 @@ function findInLearningTracks(href) { return found } -function changeLearningTracks(filePath, oldHref, newHref) { +function changeLearningTracks(filePath: string, oldHref: string, newHref: string) { // Can't deserialize and serialize the Yaml because it would lose // formatting and comments. So regex replace it. const regex = new RegExp(`- ${oldHref}$`, 'gm') @@ -608,7 +632,7 @@ function changeLearningTracks(filePath, oldHref, newHref) { fs.writeFileSync(filePath, newContent, 'utf-8') } -function changeHomepageLinks(oldHref, newHref, verbose) { +function changeHomepageLinks(oldHref: string, newHref: string, verbose: boolean) { // Can't deserialize and serialize the Yaml because it would lose // formatting and comments. So regex replace it. // Homepage childGroup links do not have a leading '/', so we need to remove that. @@ -625,7 +649,7 @@ function changeHomepageLinks(oldHref, newHref, verbose) { } } -function changeFeaturedLinks(oldHref, newHref) { +function changeFeaturedLinks(oldHref: string, newHref: string): void { const allFiles = walk(CONTENT_ROOT, { globs: ['**/*.md'], includeBasePath: true, @@ -638,8 +662,9 @@ function changeFeaturedLinks(oldHref, newHref) { let changed = false const fileContent = fs.readFileSync(file, 'utf-8') const { content, data } = readFrontmatter(fileContent) + if (!data) continue const featuredLinks = data.featuredLinks || {} - for (const [key, entries] of Object.entries(featuredLinks)) { + for (const [key, entries] of Object.entries(featuredLinks) as [string, string[]][]) { if (key === 'popularHeading') { continue } @@ -654,7 +679,7 @@ function changeFeaturedLinks(oldHref, newHref) { if (changed) { fs.writeFileSync( file, - readFrontmatter.stringify(content, data, { lineWidth: 10000 }), + readFrontmatter.stringify(content, data, { lineWidth: 10000 } as any), 'utf-8', ) } diff --git a/src/content-render/unified/alerts.ts b/src/content-render/unified/alerts.ts index 5fe2436a7dad..d354a774736d 100644 --- a/src/content-render/unified/alerts.ts +++ b/src/content-render/unified/alerts.ts @@ -4,7 +4,6 @@ Custom "Alerts", based on similar filter/styling in the monolith code. import { visit } from 'unist-util-visit' import { h } from 'hastscript' -// @ts-ignore - no types available for @primer/octicons import octicons from '@primer/octicons' import type { Element } from 'hast' diff --git a/src/content-render/unified/code-header.ts b/src/content-render/unified/code-header.ts index 80c670dd3131..444c2f23616e 100644 --- a/src/content-render/unified/code-header.ts +++ b/src/content-render/unified/code-header.ts @@ -7,7 +7,6 @@ import yaml from 'js-yaml' import fs from 'fs' import { visit } from 'unist-util-visit' import { h } from 'hastscript' -// @ts-ignore - no types available for @primer/octicons import octicons from '@primer/octicons' import { parse } from 'parse5' import { fromParse5 } from 'hast-util-from-parse5' @@ -17,6 +16,7 @@ import type { Element } from 'hast' interface LanguageConfig { name: string + // Using any for language properties that can vary (aliases, extensions, etc.) [key: string]: any } @@ -107,12 +107,14 @@ export function header( function btnIcon(): Element { const btnIconHtml: string = octicons.copy.toSVG() const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true }) - // @ts-ignore - fromParse5 file option typing issue - const btnIconElement = fromParse5(btnIconAst, { file: btnIconHtml }) + // Using any because fromParse5 expects VFile but we only have a string + // This is safe because parse5 only needs the string content + const btnIconElement = fromParse5(btnIconAst, { file: btnIconHtml as any }) return btnIconElement as Element } // Using any due to conflicting unist/hast type definitions between dependencies +// node can be various mdast/hast node types, return value contains meta properties from code blocks export function getPreMeta(node: any): Record { // Here's why this monstrosity works: // https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd606731c88a27dbce4bfeaab913a9589bf83/lib/handlers/code.js#L40-L42 diff --git a/src/content-render/unified/copilot-prompt.ts b/src/content-render/unified/copilot-prompt.ts index 5def4ee14052..1ce09ef679f8 100644 --- a/src/content-render/unified/copilot-prompt.ts +++ b/src/content-render/unified/copilot-prompt.ts @@ -4,14 +4,14 @@ import { find } from 'unist-util-find' import { h } from 'hastscript' -// @ts-ignore - @primer/octicons doesn't have TypeScript declarations import octicons from '@primer/octicons' import { parse } from 'parse5' import { fromParse5 } from 'hast-util-from-parse5' import { getPreMeta } from './code-header' -// node and tree are hast/unist AST nodes without proper TypeScript definitions -// Returns a hast element node for the prompt button +// Using any because node and tree are hast/unist AST nodes without proper TypeScript definitions +// node is a pre element from the AST, tree is the full document AST +// Returns a hast element node for the prompt button, or null if no prompt meta exists export function getPrompt(node: any, tree: any, code: string): any { const hasPrompt = Boolean(getPreMeta(node).prompt) if (!hasPrompt) return null @@ -31,7 +31,8 @@ export function getPrompt(node: any, tree: any, code: string): any { ) } -// node and tree are hast/unist AST nodes without proper TypeScript definitions +// Using any because node and tree are hast/unist AST nodes without proper TypeScript definitions +// node is the current code block element, tree is used to find referenced code blocks function buildPromptData( node: any, tree: any, @@ -51,7 +52,8 @@ function buildPromptData( console.warn(`Can't find referenced code block with id=${ref}`) return promptOnly(code) } - // Cast needed to access children property on untyped AST node + // Using any to access children property on untyped hast element node + // AST structure: element -> code -> text node with value property const matchingCode = (matchingCodeEl as any)?.children[0].children[0].value || null return promptAndContext(code, matchingCode) } @@ -73,18 +75,21 @@ function promptAndContext( } } -// tree and node are hast/unist AST nodes without proper TypeScript definitions +// Using any because tree and node are hast/unist AST nodes without proper TypeScript definitions +// Searches the AST tree for a code block with matching id in meta function findMatchingCode(ref: string, tree: any): any { return find(tree, (node: any) => { - // Cast needed to access tagName property on untyped element node + // Using any to access tagName property on untyped hast element node return node.type === 'element' && (node as any).tagName === 'pre' && getPreMeta(node).id === ref }) } // Returns a hast element node for the Copilot icon +// Using any return type because fromParse5 returns untyped hast nodes function copilotIcon(): any { const copilotIconHtml = octicons.copilot.toSVG() const copilotIconAst = parse(String(copilotIconHtml), { sourceCodeLocationInfo: true }) - const copilotIconElement = fromParse5(copilotIconAst, { file: copilotIconHtml }) + // Using any because fromParse5 expects VFile but we only have a string + const copilotIconElement = fromParse5(copilotIconAst, { file: copilotIconHtml as any }) return copilotIconElement } diff --git a/src/events/components/experiments/experiment.ts b/src/events/components/experiments/experiment.ts index f605c62eaba0..df264c710697 100644 --- a/src/events/components/experiments/experiment.ts +++ b/src/events/components/experiments/experiment.ts @@ -75,7 +75,7 @@ export function shouldShowExperiment( // Allow developers to override their experiment group for the current session export const controlGroupOverride = {} as { [key in ExperimentNames]: 'treatment' | 'control' } if (typeof window !== 'undefined') { - // @ts-expect-error + // @ts-expect-error globally available function window.overrideControlGroup = ( experimentKey: ExperimentNames, controlGroup: 'treatment' | 'control', diff --git a/src/graphql/scripts/sync.ts b/src/graphql/scripts/sync.ts index 772b811f051d..5cd1a5b94ec6 100755 --- a/src/graphql/scripts/sync.ts +++ b/src/graphql/scripts/sync.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import fs from 'fs/promises' import { appendFileSync } from 'fs' import path from 'path' @@ -16,8 +15,24 @@ import { getIgnoredChangesSummary, } from './build-changelog' +// Type definitions +interface GitHubRepoOptions { + owner: string + repo: string + ref?: string + path?: string +} + +interface IgnoredChange { + version: string + totalCount: number + types: Array<{ type: string }> +} + const graphqlStaticDir = 'src/graphql/data' -const dataFilenames = JSON.parse(await fs.readFile('src/graphql/scripts/utils/data-filenames.json')) +const dataFilenames = JSON.parse( + await fs.readFile('src/graphql/scripts/utils/data-filenames.json', 'utf8'), +) // check for required PAT if (!process.env.GITHUB_TOKEN) { @@ -28,7 +43,7 @@ const versionsToBuild = Object.keys(allVersions) main() -const allIgnoredChanges = [] +const allIgnoredChanges: IgnoredChange[] = [] async function main() { for (const version of versionsToBuild) { @@ -39,7 +54,11 @@ async function main() { // 1. UPDATE PREVIEWS const previewsPath = getDataFilepath('previews', graphqlVersion) - const safeForPublicPreviews = yaml.load(await getRemoteRawContent(previewsPath, graphqlVersion)) + // GraphQL preview data structure - complex nested object from YAML + // Using any because processPreviews is an external utility without type definitions + const safeForPublicPreviews = yaml.load( + await getRemoteRawContent(previewsPath, graphqlVersion), + ) as any const previewsJson = processPreviews(safeForPublicPreviews) await updateStaticFile( previewsJson, @@ -48,7 +67,10 @@ async function main() { // 2. UPDATE UPCOMING CHANGES const upcomingChangesPath = getDataFilepath('upcomingChanges', graphqlVersion) - const previousUpcomingChanges = yaml.load(await fs.readFile(upcomingChangesPath, 'utf8')) + // GraphQL upcoming changes data - contains upcoming_changes array + const previousUpcomingChanges = yaml.load(await fs.readFile(upcomingChangesPath, 'utf8')) as { + upcoming_changes: unknown[] + } const safeForPublicChanges = await getRemoteRawContent(upcomingChangesPath, graphqlVersion) await updateFile(upcomingChangesPath, safeForPublicChanges) const upcomingChangesJson = await processUpcomingChanges(safeForPublicChanges) @@ -63,9 +85,10 @@ async function main() { const previousSchemaString = await fs.readFile(previewFilePath, 'utf8') const latestSchema = await getRemoteRawContent(previewFilePath, graphqlVersion) await updateFile(previewFilePath, latestSchema) + // Using any because processSchemas returns complex GraphQL schema structures const schemaJsonPerVersion = await processSchemas(latestSchema, safeForPublicPreviews) // This is slow! await updateStaticFile( - schemaJsonPerVersion, + schemaJsonPerVersion as any, path.join(graphqlStaticDir, graphqlVersion, 'schema.json'), ) @@ -76,8 +99,9 @@ async function main() { previousSchemaString, latestSchema, safeForPublicPreviews, - previousUpcomingChanges.upcoming_changes, - yaml.load(safeForPublicChanges).upcoming_changes, + previousUpcomingChanges.upcoming_changes as any, + (yaml.load(safeForPublicChanges) as { upcoming_changes: unknown[] }) + .upcoming_changes as any, ) if (changelogEntry) { prependDatedEntry( @@ -124,31 +148,31 @@ async function main() { } // get latest from github/github -async function getRemoteRawContent(filepath, graphqlVersion) { - const options = { +async function getRemoteRawContent(filepath: string, graphqlVersion: string) { + const options: GitHubRepoOptions = { owner: 'github', repo: 'github', } // find the relevant branch in github/github and set it as options.ref - let t0 = new Date() + let t0 = new Date().getTime() options.ref = await getBranchAsRef(options, graphqlVersion) - let took = new Date() - t0 + let took = new Date().getTime() - t0 console.log(`Got ref (${options.ref}) for '${graphqlVersion}'. Took ${formatTime(took)}`) // add the filepath to the options so we can get the contents of the file options.path = `config/${path.basename(filepath)}` - t0 = new Date() - const contents = await getContents(...Object.values(options)) - took = new Date() - t0 + t0 = new Date().getTime() + const contents = await getContents(options.owner, options.repo, options.ref, options.path) + took = new Date().getTime() - t0 console.log(`Got content for '${options.path}' (in ${options.ref}). Took ${formatTime(took)}`) return contents } // find the relevant filepath in src/graphql/scripts/util/data-filenames.json -function getDataFilepath(id, graphqlVersion) { +function getDataFilepath(id: string, graphqlVersion: string) { const versionType = getVersionName(graphqlVersion) // for example, dataFilenames['schema']['ghes'] = schema.docs-enterprise.graphql @@ -157,11 +181,15 @@ function getDataFilepath(id, graphqlVersion) { return path.join(graphqlStaticDir, graphqlVersion, filename) } -async function getBranchAsRef(options, graphqlVersion, branch = false) { - const versionType = getVersionName(graphqlVersion) +async function getBranchAsRef( + options: GitHubRepoOptions, + graphqlVersion: string, + branch: string | boolean = false, +): Promise { + const versionType = getVersionName(graphqlVersion) as 'fpt' | 'ghec' | 'ghes' const defaultBranch = 'master' - const branches = { + const branches: Record = { fpt: defaultBranch, ghec: defaultBranch, ghes: `enterprise-${graphqlVersion.replace('ghes-', '')}-release`, @@ -174,7 +202,7 @@ async function getBranchAsRef(options, graphqlVersion, branch = false) { const ref = `heads/${branch}` // check whether the branch can be found in github/github - const exists = await hasMatchingRef(...Object.values(options), ref) + const exists = await hasMatchingRef(options.owner, options.repo, ref) // if ref is not found, the branch cannot be found, so try a fallback if (!exists) { @@ -186,23 +214,25 @@ async function getBranchAsRef(options, graphqlVersion, branch = false) { // given a GraphQL version like `ghes-2.22`, return `ghes`; // given a GraphQL version like `dotcom`, return as is -function getVersionName(graphqlVersion) { +function getVersionName(graphqlVersion: string) { return graphqlVersion.split('-')[0] } -async function updateFile(filepath, content) { +async function updateFile(filepath: string, content: string) { console.log(`Updating file ${filepath}`) await mkdirp(path.dirname(filepath)) return fs.writeFile(filepath, content, 'utf8') } -async function updateStaticFile(json, filepath) { +// JSON data from GraphQL schema processing - complex nested structures +// Using any because the structure varies (arrays, objects, nested schemas, etc.) +async function updateStaticFile(json: any, filepath: string) { console.log(`Updating static file ${filepath}`) const jsonString = JSON.stringify(json, null, 2) return updateFile(filepath, jsonString) } -function formatTime(ms) { +function formatTime(ms: number) { if (ms < 1000) { return `${ms.toFixed(0)}ms` } diff --git a/src/landings/components/CookBookFilter.tsx b/src/landings/components/CookBookFilter.tsx index 9e03b23e450a..d6ab5cd51d3d 100644 --- a/src/landings/components/CookBookFilter.tsx +++ b/src/landings/components/CookBookFilter.tsx @@ -67,7 +67,8 @@ export const CookBookFilter = ({ placeholder={t('search_articles')} ref={inputRef} autoComplete="false" - onChange={(e) => { + // Using any because Primer React's TextInput doesn't export proper event types + onChange={(e: any) => { const query = e.target.value || '' onSearch(query) }} diff --git a/src/landings/components/SidebarProduct.tsx b/src/landings/components/SidebarProduct.tsx index c899fff64a78..a0c4e29137cc 100644 --- a/src/landings/components/SidebarProduct.tsx +++ b/src/landings/components/SidebarProduct.tsx @@ -174,7 +174,8 @@ function RestNavListItem({ category }: { category: ProductTreeNode }) { { + // Using any because Primer React's NavList doesn't export proper event types + onClick={(event: any) => { event.preventDefault() push(childPage.href) }} diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx index f719b4cf428e..898ace65cec3 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.tsx +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -223,7 +223,7 @@ export const ArticleGrid = ({ tocItems, includedCategories, landingType }: Artic placeholder={t('article_grid.search_articles')} ref={inputRef} autoComplete="false" - onChange={(e) => { + onChange={(e: React.ChangeEvent) => { const query = e.target.value || '' handleSearch(query) }} diff --git a/src/links/lib/update-internal-links.ts b/src/links/lib/update-internal-links.ts index fde8b891d4e7..54dd3377402a 100644 --- a/src/links/lib/update-internal-links.ts +++ b/src/links/lib/update-internal-links.ts @@ -67,7 +67,9 @@ export async function updateInternalLinks(files: string[], options = {}) { async function updateFile( file: string, context: { + // Using any because page data structures vary by page type (articles, guides, etc.) pages: Record + // Using any because redirects can be strings or redirect objects with various properties redirects: any currentLanguage: string userLanguage: string @@ -92,7 +94,9 @@ async function updateFile( let newContent = content const ast = fromMarkdown(newContent) + // Using any[] because replacements can contain various mdast node types with different structures const replacements: any[] = [] + // Using any[] because warnings contain various error information depending on the issue type const warnings: any[] = [] const newData = structuredClone(data) @@ -102,6 +106,7 @@ async function updateFile( // This configuration determines which nested things to bother looking // into. + // Using any because frontmatter values can be strings, arrays, or nested objects const HAS_LINKS: Record = { featuredLinks: ['gettingStarted', 'startHere', 'guideCards', 'popular'], introLinks: ANY, @@ -215,8 +220,7 @@ async function updateFile( const hasQuotesAroundLink = content.includes(`"${asMarkdown}`) - // @ts-ignore - const xValue = node?.children?.[0]?.value + const xValue = (node?.children?.[0] as any)?.value if (opts.setAutotitle) { if (hasQuotesAroundLink) { @@ -370,9 +374,12 @@ function linkMatcher(node: Node) { } function getNewFrontmatterLinkList( + // Using any[] because frontmatter links can be strings or objects with href/title properties list: any[], context: { + // Using any because page data structures vary by page type pages: Record + // Using any because redirects can be strings or redirect objects redirects: any currentLanguage: string userLanguage: string @@ -447,6 +454,7 @@ function getNewFrontmatterLinkList( // Try to return the line in the raw content that entry was on. // It's hard to know exactly because the `entry` is the result of parsing // the YAML, most likely, from the front +// Using any because entry can be a string or an object with various link properties function findLineNumber(entry: any, rawContent: string) { let number = 0 for (const line of rawContent.split(/\n/g)) { @@ -480,6 +488,7 @@ function stripLiquid(text: string) { return text } +// Using any[] for generic array comparison - works with strings, objects, etc. function equalArray(arr1: any[], arr2: any[]) { return arr1.length === arr2.length && arr1.every((item, i) => item === arr2[i]) } @@ -487,7 +496,9 @@ function equalArray(arr1: any[], arr2: any[]) { function getNewHref( href: string, context: { + // Using any because page data structures vary by page type pages: Record + // Using any because redirects can be strings or redirect objects redirects: any currentLanguage: string userLanguage: string diff --git a/src/products/tests/products.ts b/src/products/tests/products.ts index 2fca22daf9ff..792a82ce5bf8 100644 --- a/src/products/tests/products.ts +++ b/src/products/tests/products.ts @@ -3,7 +3,6 @@ import { describe, expect, test } from 'vitest' import { getJsonValidator } from '@/tests/lib/validate-json-schema' import { productMap } from '@/products/lib/all-products' import { formatAjvErrors } from '@/tests/helpers/schemas' -// @ts-ignore - Products schema doesn't have TypeScript types yet import schema from '@/tests/helpers/schemas/products-schema' const validate = getJsonValidator(schema) diff --git a/src/rest/scripts/utils/operation.ts b/src/rest/scripts/utils/operation.ts index 2dcc78b1aadd..4c49309f1614 100644 --- a/src/rest/scripts/utils/operation.ts +++ b/src/rest/scripts/utils/operation.ts @@ -1,8 +1,6 @@ -// @ts-ignore - no types available import httpStatusCodes from 'http-status-code' import { get, isPlainObject } from 'lodash-es' import { parseTemplate } from 'url-template' -// @ts-ignore - no types available import mergeAllOf from 'json-schema-merge-allof' import { renderContent } from './render-content' @@ -178,7 +176,7 @@ export default class Operation { const mergedAllofSchema = mergeAllOf(schema) try { this.bodyParameters = isPlainObject(schema) - ? await getBodyParams(mergedAllofSchema, true) + ? await getBodyParams(mergedAllofSchema as any, true) : [] } catch (error) { console.error(error) diff --git a/src/search/components/input/SearchOverlay.tsx b/src/search/components/input/SearchOverlay.tsx index b1d0bef65a5b..250fc0317e21 100644 --- a/src/search/components/input/SearchOverlay.tsx +++ b/src/search/components/input/SearchOverlay.tsx @@ -995,7 +995,7 @@ function renderSearchGroups( tabIndex={-1} ref={(element: HTMLLIElement | null) => { if (listElementsRef.current) { - listElementsRef.current[indexWithOffset] = element + listElementsRef.current[index] = element } }} > diff --git a/src/search/components/results/SearchResults.tsx b/src/search/components/results/SearchResults.tsx index 5f9290850fbf..1f8e7a034518 100644 --- a/src/search/components/results/SearchResults.tsx +++ b/src/search/components/results/SearchResults.tsx @@ -176,7 +176,7 @@ function ResultsPagination({ page, totalPages }: { page: number; totalPages: num pageCount={Math.min(totalPages, 10)} currentPage={page} hrefBuilder={hrefBuilder} - onPageChange={(event, pageNum) => { + onPageChange={(event: React.MouseEvent, pageNum: number) => { event.preventDefault() const [pathRoot, pathQuery = ''] = router.asPath.split('#')[0].split('?') diff --git a/src/tools/components/InArticlePicker.module.scss b/src/tools/components/InArticlePicker.module.scss new file mode 100644 index 000000000000..f274c4144f23 --- /dev/null +++ b/src/tools/components/InArticlePicker.module.scss @@ -0,0 +1,9 @@ +.container { + // target the ActionList dropdown that appears when UnderlineNav overflows + ul[class*="prc-ActionList-ActionList"] { + background-color: var( + --overlay-bgColor, + var(--color-canvas-overlay, #ffffff) + ) !important; + } +} diff --git a/src/tools/components/InArticlePicker.tsx b/src/tools/components/InArticlePicker.tsx index 495d54d7fcec..e60edd961a6d 100644 --- a/src/tools/components/InArticlePicker.tsx +++ b/src/tools/components/InArticlePicker.tsx @@ -1,10 +1,12 @@ -import { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import Cookies from '@/frame/components/lib/cookies' import { UnderlineNav } from '@primer/react' import { sendEvent } from '@/events/components/events' import { EventType } from '@/events/types' import { useRouter } from 'next/router' +import styles from './InArticlePicker.module.scss' + type Option = { value: string label: string @@ -141,7 +143,7 @@ export const InArticlePicker = ({ const params = new URLSearchParams(asPathQuery) return ( -
+
{/* The key attribute is required for a bug in UnderlineNav that doesn't render the component when there are changes to the items. */} {options.map((option) => { @@ -154,7 +156,7 @@ export const InArticlePicker = ({ href={`?${params}`} key={option.value} aria-current={option.value === currentValue ? 'page' : undefined} - onSelect={(event) => { + onSelect={(event: React.MouseEvent | React.KeyboardEvent) => { event.preventDefault() onClickChoice(option.value) }} diff --git a/eslint-plugins.d.ts b/src/types/eslint-plugins.d.ts similarity index 100% rename from eslint-plugins.d.ts rename to src/types/eslint-plugins.d.ts diff --git a/src/types/github__markdownlint-github.d.ts b/src/types/github__markdownlint-github.d.ts new file mode 100644 index 000000000000..02d6f28f96a8 --- /dev/null +++ b/src/types/github__markdownlint-github.d.ts @@ -0,0 +1,15 @@ +declare module '@github/markdownlint-github' { + interface MarkdownlintRule { + names: string[] + description: string + tags: string[] + // Using any because @github/markdownlint-github doesn't provide TypeScript definitions + // params contains markdownlint parsing context with varying structures per rule + // onError is a callback function with dynamic signature + function: (params: any, onError: any) => void + } + + const markdownlintGitHub: MarkdownlintRule[] + + export default markdownlintGitHub +} diff --git a/src/types/http-status-code.d.ts b/src/types/http-status-code.d.ts new file mode 100644 index 000000000000..66e30151b2e5 --- /dev/null +++ b/src/types/http-status-code.d.ts @@ -0,0 +1,86 @@ +declare module 'http-status-code' { + /** + * HTTP status code definitions + */ + const httpStatusCodes: { + // 1xx Informational + CONTINUE: 100 + SWITCHING_PROTOCOLS: 101 + PROCESSING: 102 + EARLY_HINTS: 103 + + // 2xx Success + OK: 200 + CREATED: 201 + ACCEPTED: 202 + NON_AUTHORITATIVE_INFORMATION: 203 + NO_CONTENT: 204 + RESET_CONTENT: 205 + PARTIAL_CONTENT: 206 + MULTI_STATUS: 207 + ALREADY_REPORTED: 208 + IM_USED: 226 + + // 3xx Redirection + MULTIPLE_CHOICES: 300 + MOVED_PERMANENTLY: 301 + FOUND: 302 + SEE_OTHER: 303 + NOT_MODIFIED: 304 + USE_PROXY: 305 + TEMPORARY_REDIRECT: 307 + PERMANENT_REDIRECT: 308 + + // 4xx Client Error + BAD_REQUEST: 400 + UNAUTHORIZED: 401 + PAYMENT_REQUIRED: 402 + FORBIDDEN: 403 + NOT_FOUND: 404 + METHOD_NOT_ALLOWED: 405 + NOT_ACCEPTABLE: 406 + PROXY_AUTHENTICATION_REQUIRED: 407 + REQUEST_TIMEOUT: 408 + CONFLICT: 409 + GONE: 410 + LENGTH_REQUIRED: 411 + PRECONDITION_FAILED: 412 + PAYLOAD_TOO_LARGE: 413 + URI_TOO_LONG: 414 + UNSUPPORTED_MEDIA_TYPE: 415 + RANGE_NOT_SATISFIABLE: 416 + EXPECTATION_FAILED: 417 + IM_A_TEAPOT: 418 + MISDIRECTED_REQUEST: 421 + UNPROCESSABLE_ENTITY: 422 + LOCKED: 423 + FAILED_DEPENDENCY: 424 + TOO_EARLY: 425 + UPGRADE_REQUIRED: 426 + PRECONDITION_REQUIRED: 428 + TOO_MANY_REQUESTS: 429 + REQUEST_HEADER_FIELDS_TOO_LARGE: 431 + UNAVAILABLE_FOR_LEGAL_REASONS: 451 + + // 5xx Server Error + INTERNAL_SERVER_ERROR: 500 + NOT_IMPLEMENTED: 501 + BAD_GATEWAY: 502 + SERVICE_UNAVAILABLE: 503 + GATEWAY_TIMEOUT: 504 + HTTP_VERSION_NOT_SUPPORTED: 505 + VARIANT_ALSO_NEGOTIATES: 506 + INSUFFICIENT_STORAGE: 507 + LOOP_DETECTED: 508 + NOT_EXTENDED: 510 + NETWORK_AUTHENTICATION_REQUIRED: 511 + + // Methods + getMessage(statusCode: number, protocol?: string): string + + // Allow numeric access + [statusCode: number]: number | ((statusCode: number, protocol?: string) => string) + } + + export default httpStatusCodes +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000000..8348c4854a07 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +// Re-export all types from types.ts for backward compatibility +export * from './types' diff --git a/src/types/json-schema-merge-allof.d.ts b/src/types/json-schema-merge-allof.d.ts new file mode 100644 index 000000000000..83f021e0bf12 --- /dev/null +++ b/src/types/json-schema-merge-allof.d.ts @@ -0,0 +1,50 @@ +declare module 'json-schema-merge-allof' { + /** + * JSON Schema object that may contain allOf + */ + interface JSONSchema { + allOf?: JSONSchema[] + properties?: Record + required?: string[] + type?: string | string[] + items?: JSONSchema | JSONSchema[] + additionalProperties?: boolean | JSONSchema + [key: string]: any // JSON Schema allows arbitrary additional properties per spec + } + + /** + * Options for merging allOf schemas + */ + interface MergeAllOfOptions { + /** + * Resolvers for custom keywords + * Using any because this third-party library has dynamic schema structures + * that vary based on the JSON Schema specification + */ + resolvers?: Record< + string, + (values: any[], path: string[], mergeSchemas: any, options: any) => any + > + + /** + * Whether to ignore additional properties conflicts + */ + ignoreAdditionalProperties?: boolean + + /** + * Deep merge objects instead of replacing + */ + deep?: boolean + } + + /** + * Merges JSON schemas that use allOf into a single schema + * + * @param schema - The JSON schema containing allOf + * @param options - Merge options + * @returns The merged schema without allOf + */ + function mergeAllOf(schema: JSONSchema, options?: MergeAllOfOptions): JSONSchema + + export default mergeAllOf +} diff --git a/src/types/markdownlint-lib-rules.d.ts b/src/types/markdownlint-lib-rules.d.ts new file mode 100644 index 000000000000..bc27266bc04b --- /dev/null +++ b/src/types/markdownlint-lib-rules.d.ts @@ -0,0 +1,14 @@ +declare module '*/markdownlint/lib/rules' { + interface MarkdownlintRule { + names: string[] + description: string + tags: string[] + // Using any because markdownlint doesn't provide TypeScript definitions + // params contains parsing context with varying structures per rule + // onError is a callback function with dynamic signature + function: (params: any, onError: any) => void + } + + const rules: MarkdownlintRule[] + export default rules +} diff --git a/src/types/markdownlint-rule-helpers.d.ts b/src/types/markdownlint-rule-helpers.d.ts new file mode 100644 index 000000000000..bc785f00a315 --- /dev/null +++ b/src/types/markdownlint-rule-helpers.d.ts @@ -0,0 +1,40 @@ +declare module 'markdownlint-rule-helpers' { + /** + * Adds an error to the linting results + * Using any because this third-party library doesn't provide TypeScript definitions + * onError is a callback function with dynamic signature from markdownlint + * fixInfo contains various fix information structures depending on the error type + */ + export function addError( + onError: any, + lineNumber: number, + detail?: string, + context?: string | null, + range?: [number, number] | number[] | string | null, + fixInfo?: any, + ): void + + /** + * Filters tokens by type and calls a handler for each matching token + * Using any because markdownlint-rule-helpers has no TypeScript definitions + * params contains markdownlint parsing parameters with varying structures + * token represents markdown tokens with different properties per token type + */ + export function filterTokens(params: any, type: string, handler: (token: any) => void): void + + /** + * Truncates long strings with ellipsis for display + */ + export function ellipsify(text: string, length?: number, preferEnd?: boolean): string + + /** + * Regular expression for newline characters + */ + export const newLineRe: RegExp + + /** + * Applies fixes to markdown content + * Using any[] because error objects from markdownlint have dynamic structures + */ + export function applyFixes(content: string, errors: any[]): string +} diff --git a/src/types/markdownlint-rule-search-replace.d.ts b/src/types/markdownlint-rule-search-replace.d.ts new file mode 100644 index 000000000000..95ea43d99689 --- /dev/null +++ b/src/types/markdownlint-rule-search-replace.d.ts @@ -0,0 +1,12 @@ +declare module 'markdownlint-rule-search-replace' { + const searchReplace: { + names: string[] + description: string + tags: string[] + // Using any because this is a third-party library without proper TypeScript definitions + // params contains markdownlint-specific data structures, onError is a callback function + function: (params: any, onError: any) => void + } + + export default searchReplace +} diff --git a/src/types/primer__octicons.d.ts b/src/types/primer__octicons.d.ts new file mode 100644 index 000000000000..0ba8c8041b82 --- /dev/null +++ b/src/types/primer__octicons.d.ts @@ -0,0 +1,72 @@ +declare module '@primer/octicons' { + interface OcticonOptions { + width?: number | string + height?: number | string + 'aria-label'?: string + 'aria-hidden'?: string | boolean + class?: string + fill?: string + [key: string]: any + } + + interface Octicon { + /** + * The SVG path data for the icon + */ + path: string + + /** + * The default width of the icon + */ + width: number + + /** + * The default height of the icon + */ + height: number + + /** + * Heights-based icon data + */ + heights: { + [size: number]: { + path: string + width: number + height: number + } + } + + /** + * Convert the octicon to an SVG string + */ + toSVG(options?: OcticonOptions): string + } + + const octicons: { + [iconName: string]: Octicon + + // Common icons (non-exhaustive list for better autocomplete) + alert: Octicon + check: Octicon + 'check-circle': Octicon + chevron: Octicon + 'chevron-down': Octicon + 'chevron-left': Octicon + 'chevron-right': Octicon + 'chevron-up': Octicon + code: Octicon + copy: Octicon + copilot: Octicon + download: Octicon + gear: Octicon + info: Octicon + link: Octicon + 'link-external': Octicon + 'mark-github': Octicon + search: Octicon + 'triangle-down': Octicon + x: Octicon + } + + export default octicons +} diff --git a/src/types.ts b/src/types/types.ts similarity index 100% rename from src/types.ts rename to src/types/types.ts