Skip to content

Commit 37a2754

Browse files
authored
Convert 21 JavaScript files to TypeScript (#57896)
1 parent 58f8bdb commit 37a2754

21 files changed

+222
-98
lines changed

src/content-linter/lib/helpers/utils.js renamed to src/content-linter/lib/helpers/utils.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
12
import { addError, filterTokens } from 'markdownlint-rule-helpers'
23
import matter from '@gr2m/gray-matter'
34

5+
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
6+
47
// Adds an error object with details conditionally via the onError callback
5-
export function addFixErrorDetail(onError, lineNumber, expected, actual, range, fixInfo) {
8+
export function addFixErrorDetail(
9+
onError: RuleErrorCallback,
10+
lineNumber: number,
11+
expected: string,
12+
actual: string,
13+
// Using flexible type to accommodate different range formats from various linting rules
14+
range: [number, number] | number[] | null,
15+
// Using any for fixInfo as markdownlint-rule-helpers accepts various fix info structures
16+
fixInfo: any,
17+
): void {
618
addError(onError, lineNumber, `Expected: ${expected}`, ` Actual: ${actual}`, range, fixInfo)
719
}
820

9-
export function forEachInlineChild(params, type, handler) {
10-
filterTokens(params, 'inline', (token) => {
11-
for (const child of token.children.filter((c) => c.type === type)) {
21+
export function forEachInlineChild(
22+
params: RuleParams,
23+
type: string,
24+
// Using any for child and token types because different linting rules pass tokens with varying structures
25+
// beyond the base MarkdownToken interface (e.g., ImageToken with additional properties)
26+
handler: (child: any, token: any) => void,
27+
): void {
28+
filterTokens(params, 'inline', (token: MarkdownToken) => {
29+
for (const child of token.children!.filter((c) => c.type === type)) {
1230
handler(child, token)
1331
}
1432
})
1533
}
1634

17-
export function getRange(line, content) {
35+
export function getRange(line: string, content: string): [number, number] | null {
1836
if (content.length === 0) {
1937
// This function assumes that the content is something. If it's an
2038
// empty string it can never produce a valid range.
@@ -24,15 +42,15 @@ export function getRange(line, content) {
2442
return startColumnIndex !== -1 ? [startColumnIndex + 1, content.length] : null
2543
}
2644

27-
export function isStringQuoted(text) {
45+
export function isStringQuoted(text: string): boolean {
2846
// String starts with either a single or double quote
2947
// ends with either a single or double quote
3048
// and optionally ends with a question mark or exclamation point
3149
// because that punctuation can exist outside of the quoted string
3250
return /^['"].*['"][?!]?$/.test(text)
3351
}
3452

35-
export function isStringPunctuated(text) {
53+
export function isStringPunctuated(text: string): boolean {
3654
// String ends with punctuation of either
3755
// . ? ! and optionally ends with single
3856
// or double quotes. This also allows
@@ -41,7 +59,7 @@ export function isStringPunctuated(text) {
4159
return /^.*[.?!]['"]?$/.test(text)
4260
}
4361

44-
export function doesStringEndWithPeriod(text) {
62+
export function doesStringEndWithPeriod(text: string): boolean {
4563
// String ends with punctuation of either
4664
// . ? ! and optionally ends with single
4765
// or double quotes. This also allows
@@ -50,7 +68,7 @@ export function doesStringEndWithPeriod(text) {
5068
return /^.*\.['"]?$/.test(text)
5169
}
5270

53-
export function quotePrecedesLinkOpen(text) {
71+
export function quotePrecedesLinkOpen(text: string | undefined): boolean {
5472
if (!text) return false
5573
return text.endsWith('"') || text.endsWith("'")
5674
}
@@ -87,12 +105,15 @@ export function quotePrecedesLinkOpen(text) {
87105
// { type: 'paragraph_close'}, <-- Index 5 - NOT INCLUDED
88106
// ]
89107
//
90-
export function filterTokensByOrder(tokens, tokenOrder) {
91-
const matches = []
108+
export function filterTokensByOrder(
109+
tokens: MarkdownToken[],
110+
tokenOrder: string[],
111+
): MarkdownToken[] {
112+
const matches: MarkdownToken[] = []
92113

93114
// Get a list of token indexes that match the
94115
// first token (root) in the tokenOrder array
95-
const tokenRootIndexes = []
116+
const tokenRootIndexes: number[] = []
96117
const firstTokenOrderType = tokenOrder[0]
97118
tokens.forEach((token, index) => {
98119
if (token.type === firstTokenOrderType) {
@@ -125,7 +146,8 @@ export const docsDomains = ['docs.github.com', 'help.github.com', 'developer.git
125146
// This is the format we get from Markdownlint.
126147
// Returns null if the lines do not contain
127148
// frontmatter properties.
128-
export function getFrontmatter(lines) {
149+
// Returns frontmatter as a Record with any values since YAML can contain various types
150+
export function getFrontmatter(lines: string[]): Record<string, any> | null {
129151
const fmString = lines.join('\n')
130152
const { data } = matter(fmString)
131153
// If there is no frontmatter or the frontmatter contains
@@ -134,7 +156,7 @@ export function getFrontmatter(lines) {
134156
return data
135157
}
136158

137-
export function getFrontmatterLines(lines) {
159+
export function getFrontmatterLines(lines: string[]): string[] {
138160
const indexStart = lines.indexOf('---')
139161
if (indexStart === -1) return []
140162
const indexEnd = lines.indexOf('---', indexStart + 1)

src/content-linter/lib/linting-rules/british-english-quotes.js renamed to src/content-linter/lib/linting-rules/british-english-quotes.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
12
import { addError } from 'markdownlint-rule-helpers'
23
import { getRange } from '../helpers/utils'
34
import frontmatter from '@/frame/lib/read-frontmatter'
45

6+
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
7+
58
export const britishEnglishQuotes = {
69
names: ['GHD048', 'british-english-quotes'],
710
description:
811
'Periods and commas should be placed inside quotation marks (American English style)',
912
tags: ['punctuation', 'quotes', 'style', 'consistency'],
1013
severity: 'warning', // Non-blocking as requested in the issue
11-
function: (params, onError) => {
14+
function: (params: RuleParams, onError: RuleErrorCallback) => {
1215
// Skip autogenerated files
1316
const frontmatterString = params.frontMatterLines.join('\n')
1417
const fm = frontmatter(frontmatterString).data
@@ -33,7 +36,7 @@ export const britishEnglishQuotes = {
3336
/**
3437
* Check if the current position is within a code context (code blocks, inline code, URLs)
3538
*/
36-
function isInCodeContext(line, allLines, lineIndex) {
39+
function isInCodeContext(line: string, allLines: string[], lineIndex: number): boolean {
3740
// Skip if line contains code fences
3841
if (line.includes('```') || line.includes('~~~')) {
3942
return true
@@ -67,18 +70,22 @@ function isInCodeContext(line, allLines, lineIndex) {
6770
/**
6871
* Find and report British English quote patterns in a line
6972
*/
70-
function findAndReportBritishQuotes(line, lineNumber, onError) {
73+
function findAndReportBritishQuotes(
74+
line: string,
75+
lineNumber: number,
76+
onError: RuleErrorCallback,
77+
): void {
7178
// Pattern to find quote followed by punctuation outside
7279
// Matches: "text". or 'text', or "text", etc.
7380
const britishPattern = /(["'])([^"']*?)\1\s*([.,])/g
7481

75-
let match
82+
let match: RegExpMatchArray | null
7683
while ((match = britishPattern.exec(line)) !== null) {
7784
const quoteChar = match[1]
7885
const quotedText = match[2]
7986
const punctuation = match[3]
8087
const fullMatch = match[0]
81-
const startIndex = match.index
88+
const startIndex = match.index ?? 0
8289

8390
// Create the corrected version (punctuation inside quotes)
8491
const correctedText = quoteChar + quotedText + punctuation + quoteChar

src/content-linter/lib/linting-rules/header-content-requirement.js renamed to src/content-linter/lib/linting-rules/header-content-requirement.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
12
import { addError, filterTokens } from 'markdownlint-rule-helpers'
23

4+
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
5+
6+
interface HeadingInfo {
7+
token: MarkdownToken
8+
lineNumber: number
9+
level: number
10+
line: string
11+
}
12+
313
export const headerContentRequirement = {
414
names: ['GHD053', 'header-content-requirement'],
515
description: 'Headers must have content between them, such as an introduction',
616
tags: ['headers', 'structure', 'content'],
7-
function: (params, onError) => {
8-
const headings = []
17+
function: (params: RuleParams, onError: RuleErrorCallback) => {
18+
const headings: HeadingInfo[] = []
919

1020
// Collect all heading tokens with their line numbers and levels
11-
filterTokens(params, 'heading_open', (token) => {
21+
filterTokens(params, 'heading_open', (token: MarkdownToken) => {
1222
headings.push({
1323
token,
1424
lineNumber: token.lineNumber,
15-
level: parseInt(token.tag.slice(1)), // Extract number from h1, h2, etc.
25+
level: parseInt(token.tag!.slice(1)), // Extract number from h1, h2, etc.
1626
line: params.lines[token.lineNumber - 1],
1727
})
1828
})
@@ -49,7 +59,11 @@ export const headerContentRequirement = {
4959
* Check if there is meaningful content between two headings
5060
* Returns true if content exists, false if only whitespace/empty lines
5161
*/
52-
function checkForContentBetweenHeadings(lines, startLineNumber, endLineNumber) {
62+
function checkForContentBetweenHeadings(
63+
lines: string[],
64+
startLineNumber: number,
65+
endLineNumber: number,
66+
): boolean {
5367
// Convert to 0-based indexes and skip the heading lines themselves
5468
const startIndex = startLineNumber // Skip the current heading line
5569
const endIndex = endLineNumber - 2 // Stop before the next heading line
@@ -82,7 +96,7 @@ function checkForContentBetweenHeadings(lines, startLineNumber, endLineNumber) {
8296
* Check if a line contains only Liquid tags that don't produce visible content
8397
* This helps avoid false positives for conditional blocks
8498
*/
85-
function isNonContentLiquidTag(line) {
99+
function isNonContentLiquidTag(line: string): boolean {
86100
// Match common non-content Liquid tags
87101
const nonContentTags = [
88102
/^{%\s*ifversion\s+.*%}$/,

src/content-linter/lib/linting-rules/index.js renamed to src/content-linter/lib/linting-rules/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
// @ts-ignore - markdownlint-rule-search-replace doesn't provide TypeScript declarations
12
import searchReplace from 'markdownlint-rule-search-replace'
3+
// @ts-ignore - @github/markdownlint-github doesn't provide TypeScript declarations
24
import markdownlintGitHub from '@github/markdownlint-github'
35

46
import { codeFenceLineLength } from '@/content-linter/lib/linting-rules/code-fence-line-length'
@@ -57,10 +59,13 @@ import { thirdPartyActionsReusable } from '@/content-linter/lib/linting-rules/th
5759
import { frontmatterLandingRecommended } from '@/content-linter/lib/linting-rules/frontmatter-landing-recommended'
5860
import { ctasSchema } from '@/content-linter/lib/linting-rules/ctas-schema'
5961

60-
const noDefaultAltText = markdownlintGitHub.find((elem) =>
62+
// Using any type because @github/markdownlint-github doesn't provide TypeScript declarations
63+
// The elements in the array have a 'names' property that contains rule identifiers
64+
const noDefaultAltText = markdownlintGitHub.find((elem: any) =>
6165
elem.names.includes('no-default-alt-text'),
6266
)
63-
const noGenericLinkText = markdownlintGitHub.find((elem) =>
67+
// Using any type because @github/markdownlint-github doesn't provide TypeScript declarations
68+
const noGenericLinkText = markdownlintGitHub.find((elem: any) =>
6469
elem.names.includes('no-generic-link-text'),
6570
)
6671

src/content-linter/lib/linting-rules/liquid-data-tags.js renamed to src/content-linter/lib/linting-rules/liquid-data-tags.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { TokenKind } from 'liquidjs'
1+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
22
import { addError } from 'markdownlint-rule-helpers'
3+
import { TokenKind } from 'liquidjs'
34

45
import { getDataByLanguage } from '@/data-directory/lib/get-data'
56
import {
@@ -9,6 +10,8 @@ import {
910
OUTPUT_CLOSE,
1011
} from '../helpers/liquid-utils'
1112

13+
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
14+
1215
/*
1316
Checks for instances where a Liquid data or indented_data_reference
1417
tag is used but is not defined.
@@ -19,11 +22,12 @@ export const liquidDataReferencesDefined = {
1922
'Liquid data or indented data references were found in content that have no value or do not exist in the data directory',
2023
tags: ['liquid'],
2124
parser: 'markdownit',
22-
function: (params, onError) => {
25+
function: (params: RuleParams, onError: RuleErrorCallback) => {
2326
const content = params.lines.join('\n')
27+
// Using any type because getLiquidTokens returns tokens from liquidjs library without complete type definitions
2428
const tokens = getLiquidTokens(content)
25-
.filter((token) => token.kind === TokenKind.Tag)
26-
.filter((token) => token.name === 'data' || token.name === 'indented_data_reference')
29+
.filter((token: any) => token.kind === TokenKind.Tag)
30+
.filter((token: any) => token.name === 'data' || token.name === 'indented_data_reference')
2731

2832
if (!tokens.length) return
2933

@@ -54,12 +58,16 @@ export const liquidDataTagFormat = {
5458
description:
5559
'Liquid data or indented data references tags must be correctly formatted and have the correct number of arguments and spacing',
5660
tags: ['liquid', 'format'],
57-
function: (params, onError) => {
61+
function: (params: RuleParams, onError: RuleErrorCallback) => {
5862
const CHECK_LIQUID_TAGS = [OUTPUT_OPEN, OUTPUT_CLOSE, '{', '}']
5963
const content = params.lines.join('\n')
60-
const tokenTags = getLiquidTokens(content).filter((token) => token.kind === TokenKind.Tag)
61-
const dataTags = tokenTags.filter((token) => token.name === 'data')
62-
const indentedDataTags = tokenTags.filter((token) => token.name === 'indented_data_reference')
64+
// Using any type because getLiquidTokens returns tokens from liquidjs library without complete type definitions
65+
// Tokens have properties like 'kind', 'name', 'args', and 'content' that aren't fully typed
66+
const tokenTags = getLiquidTokens(content).filter((token: any) => token.kind === TokenKind.Tag)
67+
const dataTags = tokenTags.filter((token: any) => token.name === 'data')
68+
const indentedDataTags = tokenTags.filter(
69+
(token: any) => token.name === 'indented_data_reference',
70+
)
6371

6472
for (const token of dataTags) {
6573
// A data tag has only one argument, the data directory path.
@@ -125,9 +133,9 @@ export const liquidDataTagFormat = {
125133
}
126134

127135
// Convenient wrapper because linting is always about English content
128-
const getData = (liquidRef) => getDataByLanguage(liquidRef, 'en')
136+
const getData = (liquidRef: string) => getDataByLanguage(liquidRef, 'en')
129137

130-
const hasData = (liquidRef) => {
138+
const hasData = (liquidRef: string): boolean => {
131139
try {
132140
// If a reusable contains a nonexistent data reference, it will
133141
// return undefined. If the data reference is inherently broken

src/content-linter/lib/linting-rules/liquid-syntax.js renamed to src/content-linter/lib/linting-rules/liquid-syntax.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
12
import { addError } from 'markdownlint-rule-helpers'
23

34
import { getFrontmatter } from '../helpers/utils'
45
import { liquid } from '@/content-render/index'
56
import { isLiquidError } from '@/languages/lib/render-with-fallback'
67

8+
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
9+
10+
interface ErrorMessageInfo {
11+
errorDescription: string
12+
lineNumber: number
13+
columnNumber: number
14+
}
15+
716
/*
817
Attempts to parse all liquid in the frontmatter of a file
918
to verify the syntax is correct.
@@ -12,7 +21,7 @@ export const frontmatterLiquidSyntax = {
1221
names: ['GHD017', 'frontmatter-liquid-syntax'],
1322
description: 'Frontmatter properties must use valid Liquid',
1423
tags: ['liquid', 'frontmatter'],
15-
function: (params, onError) => {
24+
function: (params: RuleParams, onError: RuleErrorCallback) => {
1625
const fm = getFrontmatter(params.lines)
1726
if (!fm) return
1827

@@ -31,7 +40,7 @@ export const frontmatterLiquidSyntax = {
3140
// If the error source is not a Liquid error but rather a
3241
// ReferenceError or bad type we should allow that error to be thrown
3342
if (!isLiquidError(error)) throw error
34-
const { errorDescription, columnNumber } = getErrorMessageInfo(error.message)
43+
const { errorDescription, columnNumber } = getErrorMessageInfo((error as Error).message)
3544
const lineNumber = params.lines.findIndex((line) => line.trim().startsWith(`${key}:`)) + 1
3645
// Add the key length plus 3 to the column number to account colon and
3746
// for the space after the key and column number starting at 1.
@@ -42,7 +51,7 @@ export const frontmatterLiquidSyntax = {
4251
startRange + value.length - 1 > params.lines[lineNumber - 1].length
4352
? params.lines[lineNumber - 1].length - startRange + 1
4453
: value.length
45-
const range = [startRange, endRange]
54+
const range: [number, number] = [startRange, endRange]
4655
addError(
4756
onError,
4857
lineNumber,
@@ -64,20 +73,22 @@ export const liquidSyntax = {
6473
names: ['GHD018', 'liquid-syntax'],
6574
description: 'Markdown content must use valid Liquid',
6675
tags: ['liquid'],
67-
function: function GHD018(params, onError) {
76+
function: function GHD018(params: RuleParams, onError: RuleErrorCallback) {
6877
try {
6978
liquid.parse(params.lines.join('\n'))
7079
} catch (error) {
7180
// If the error source is not a Liquid error but rather a
7281
// ReferenceError or bad type we should allow that error to be thrown
7382
if (!isLiquidError(error)) throw error
74-
const { errorDescription, lineNumber, columnNumber } = getErrorMessageInfo(error.message)
83+
const { errorDescription, lineNumber, columnNumber } = getErrorMessageInfo(
84+
(error as Error).message,
85+
)
7586
const line = params.lines[lineNumber - 1]
7687
// We don't have enough information to know the length of the full
7788
// liquid tag without doing some regex testing and making assumptions
7889
// about if the end tag is correctly formed, so we just give a
7990
// range from the start of the tag to the end of the line.
80-
const range = [columnNumber, line.slice(columnNumber - 1).length]
91+
const range: [number, number] = [columnNumber, line.slice(columnNumber - 1).length]
8192
addError(
8293
onError,
8394
lineNumber,
@@ -90,7 +101,7 @@ export const liquidSyntax = {
90101
},
91102
}
92103

93-
function getErrorMessageInfo(message) {
104+
function getErrorMessageInfo(message: string): ErrorMessageInfo {
94105
const [errorDescription, lineString, columnString] = message.split(',')
95106
// There has to be a line number so we'll default to line 1 if the message
96107
// doesn't contain a line number.

0 commit comments

Comments
 (0)