Skip to content

Commit 47e08a2

Browse files
authored
Migrate 18 JavaScript files to TypeScript (#57857)
1 parent bac4fdd commit 47e08a2

20 files changed

+212
-97
lines changed

src/content-linter/lib/linting-rules/image-alt-text-end-punctuation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import {
55
isStringQuoted,
66
isStringPunctuated,
77
} from '../helpers/utils'
8-
import type { RuleParams, RuleErrorCallback } from '../../types'
8+
import type { RuleParams, RuleErrorCallback, Rule, MarkdownToken } from '../../types'
99

10-
export const imageAltTextEndPunctuation = {
10+
export const imageAltTextEndPunctuation: Rule = {
1111
names: ['GHD032', 'image-alt-text-end-punctuation'],
1212
description: 'Alternate text for images should end with punctuation',
1313
tags: ['accessibility', 'images'],
1414
parser: 'markdownit',
1515
function: (params: RuleParams, onError: RuleErrorCallback) => {
16-
forEachInlineChild(params, 'image', function forToken(token: any) {
17-
const imageAltText = token.content.trim()
16+
forEachInlineChild(params, 'image', function forToken(token: MarkdownToken) {
17+
const imageAltText = token.content?.trim()
1818

1919
// If the alt text is empty, there is nothing to check and you can't
2020
// produce a valid range.

src/content-linter/lib/linting-rules/internal-links-old-version.js renamed to src/content-linter/lib/linting-rules/internal-links-old-version.ts

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

34
import { getRange } from '../helpers/utils'
5+
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'
46

5-
export const internalLinksOldVersion = {
7+
export const internalLinksOldVersion: Rule = {
68
names: ['GHD006', 'internal-links-old-version'],
79
description: 'Internal links must not have a hardcoded version using old versioning syntax',
810
tags: ['links', 'url', 'versioning'],
911
parser: 'markdownit',
10-
function: (params, onError) => {
11-
filterTokens(params, 'inline', (token) => {
12+
function: (params: RuleParams, onError: RuleErrorCallback) => {
13+
filterTokens(params, 'inline', (token: MarkdownToken) => {
1214
if (
1315
params.name.endsWith('migrating-from-github-enterprise-1110x-to-2123.md') ||
1416
params.name.endsWith('all-releases.md')
1517
)
1618
return
17-
for (const child of token.children) {
19+
for (const child of token.children || []) {
1820
if (child.type !== 'link_open') continue
21+
if (!child.attrs) continue
1922
// Things matched by this RegExp:
2023
// - /enterprise/2.19/admin/blah
2124
// - https://docs.github.com/enterprise/11.10.340/admin/blah

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
12
import { addError, filterTokens } from 'markdownlint-rule-helpers'
23
import { getRange, quotePrecedesLinkOpen } from '../helpers/utils'
34
import { escapeRegExp } from 'lodash-es'
5+
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'
46

5-
export const linkQuotation = {
7+
export const linkQuotation: Rule = {
68
names: ['GHD043', 'link-quotation'],
79
description: 'Internal link titles must not be surrounded by quotations',
810
tags: ['links', 'url'],
911
parser: 'markdownit',
10-
function: (params, onError) => {
11-
filterTokens(params, 'inline', (token) => {
12+
function: (params: RuleParams, onError: RuleErrorCallback) => {
13+
filterTokens(params, 'inline', (token: MarkdownToken) => {
1214
const { children } = token
13-
let previous_child = children[0]
15+
if (!children) return
16+
let previous_child: MarkdownToken = children[0]
1417
let inLinkWithPrecedingQuotes = false
1518
let linkUrl = ''
16-
let content = []
17-
let line = ''
19+
let content: string[] = []
1820
for (let i = 1; i < children.length; i++) {
1921
const child = children[i]
20-
if (child.type === 'link_open' && quotePrecedesLinkOpen(previous_child.content)) {
22+
if (child.type === 'link_open' && quotePrecedesLinkOpen(previous_child.content || '')) {
23+
if (!child.attrs) continue
2124
inLinkWithPrecedingQuotes = true
2225
linkUrl = escapeRegExp(child.attrs[0][1])
23-
line = child.line
2426
} else if (inLinkWithPrecedingQuotes && child.type === 'text') {
25-
content.push(escapeRegExp(child.content.trim()))
27+
content.push(escapeRegExp((child.content || '').trim()))
2628
} else if (inLinkWithPrecedingQuotes && child.type === 'code_inline') {
27-
content.push('`' + escapeRegExp(child.content.trim()) + '`')
29+
content.push('`' + escapeRegExp((child.content || '').trim()) + '`')
2830
} else if (child.type === 'link_close') {
2931
const title = content.join(' ')
3032
const regex = new RegExp(`"\\[${title}\\]\\(${linkUrl}\\)({%.*%})?(!|\\.|\\?|,)?"`)
3133
if (regex.test(child.line)) {
32-
const match = child.line.match(regex)[0]
34+
const matchResult = child.line.match(regex)
35+
if (!matchResult) continue
36+
const match = matchResult[0]
3337
const range = getRange(child.line, match)
38+
if (!range) continue
3439
let newLine = match
3540
if (newLine.startsWith('"')) {
3641
newLine = newLine.slice(1)
@@ -58,7 +63,6 @@ export const linkQuotation = {
5863
}
5964
inLinkWithPrecedingQuotes = false
6065
content = []
61-
line = ''
6266
linkUrl = ''
6367
}
6468
previous_child = child

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import { TokenKind } from 'liquidjs'
22

33
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
44
import { addFixErrorDetail } from '../helpers/utils'
5+
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'
6+
7+
interface LiquidToken {
8+
kind: number
9+
content: string
10+
contentRange: [number, number]
11+
begin: number
12+
end: number
13+
}
514

615
/*
716
Liquid tags should start and end with one whitespace. For example:
@@ -16,14 +25,16 @@ Liquid tags should start and end with one whitespace. For example:
1625
{%data arg1 arg2 %}
1726
*/
1827

19-
export const liquidTagWhitespace = {
28+
export const liquidTagWhitespace: Rule = {
2029
names: ['GHD042', 'liquid-tag-whitespace'],
2130
description:
2231
'Liquid tags should start and end with one whitespace. Liquid tag arguments should be separated by only one whitespace.',
2332
tags: ['liquid', 'format'],
24-
function: (params, onError) => {
33+
function: (params: RuleParams, onError: RuleErrorCallback) => {
2534
const content = params.lines.join('\n')
26-
const tokens = getLiquidTokens(content).filter((token) => token.kind === TokenKind.Tag)
35+
const tokens = (getLiquidTokens(content) as LiquidToken[]).filter(
36+
(token: LiquidToken) => token.kind === TokenKind.Tag,
37+
)
2738
for (const token of tokens) {
2839
const { lineNumber, column, length } = getPositionData(token, params.lines)
2940

src/content-linter/lib/linting-rules/list-first-word-capitalization.js renamed to src/content-linter/lib/linting-rules/list-first-word-capitalization.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
import { addFixErrorDetail, getRange, filterTokensByOrder } from '../helpers/utils'
2+
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'
23

3-
export const listFirstWordCapitalization = {
4+
export const listFirstWordCapitalization: Rule = {
45
names: ['GHD034', 'list-first-word-capitalization'],
56
description: 'First word of list item should be capitalized',
67
tags: ['ul', 'ol'],
7-
function: (params, onError) => {
8+
function: (params: RuleParams, onError: RuleErrorCallback) => {
89
// Skip site-policy directory as these are legal documents with specific formatting requirements
910
if (params.name && params.name.includes('content/site-policy/')) return
1011

1112
// We're going to look for a sequence of 3 tokens. If the markdown
1213
// is a really small string, it might not even have that many tokens
1314
// in it. Can bail early.
14-
if (params.tokens.length < 3) return
15+
if (!params.tokens || params.tokens.length < 3) return
1516

1617
const inlineListItems = filterTokensByOrder(params.tokens, [
1718
'list_item_open',
1819
'paragraph_open',
1920
'inline',
20-
]).filter((token) => token.type === 'inline')
21+
]).filter((token: MarkdownToken) => token.type === 'inline')
2122

22-
inlineListItems.forEach((token) => {
23+
inlineListItems.forEach((token: MarkdownToken) => {
2324
// Only proceed if all of the token's children start with a text
2425
// node that is not empty.
2526
// This filters out cases where the list item is inline code, or
2627
// a link, or an image, etc.
2728
// This also avoids cases like `- **bold** text` where the first
2829
// child is a text node string but the text node content is empty.
2930
const firstWordTextNode =
31+
token.children &&
3032
token.children.length > 0 &&
3133
token.children[0].type === 'text' &&
3234
token.children[0].content !== ''
3335
if (!firstWordTextNode) return
3436

35-
const content = token.content.trim()
37+
const content = (token.content || '').trim()
3638
const firstWord = content.trim().split(' ')[0]
3739

3840
// If the first character in the first word is not an alphanumeric,
@@ -49,6 +51,7 @@ export const listFirstWordCapitalization = {
4951

5052
const lineNumber = token.lineNumber
5153
const range = getRange(token.line, firstWord)
54+
if (!range) return
5255
addFixErrorDetail(
5356
onError,
5457
lineNumber,

src/content-linter/lib/linting-rules/rai-reusable-usage.js renamed to src/content-linter/lib/linting-rules/rai-reusable-usage.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
1+
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
12
import { addError } from 'markdownlint-rule-helpers'
23
import { TokenKind } from 'liquidjs'
34
import path from 'path'
45

56
import { getFrontmatter } from '../helpers/utils'
67
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
8+
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'
79

8-
export const raiReusableUsage = {
10+
interface Frontmatter {
11+
type?: string
12+
// Allow any additional frontmatter properties since we only care about 'type'
13+
[key: string]: any
14+
}
15+
16+
interface LiquidToken {
17+
kind: number
18+
name?: string
19+
args: string
20+
content: string
21+
begin: number
22+
end: number
23+
}
24+
25+
export const raiReusableUsage: Rule = {
926
names: ['GHD035', 'rai-reusable-usage'],
1027
description:
1128
'RAI articles and reusables can only reference reusable content in the data/reusables/rai directory',
1229
tags: ['feature', 'rai'],
13-
function: (params, onError) => {
30+
function: (params: RuleParams, onError: RuleErrorCallback) => {
1431
if (!isFileRai(params)) return
1532

1633
const content = params.lines.join('\n')
17-
const tokens = getLiquidTokens(content)
18-
.filter((token) => token.kind === TokenKind.Tag)
19-
.filter((token) => token.name === 'data' || token.name === 'indented_data_reference')
34+
const tokens = (getLiquidTokens(content) as LiquidToken[])
35+
.filter((token: LiquidToken) => token.kind === TokenKind.Tag)
36+
.filter(
37+
(token: LiquidToken) => token.name === 'data' || token.name === 'indented_data_reference',
38+
)
2039
// It's ok to reference variables from rai content
21-
.filter((token) => !token.args.startsWith('variables'))
40+
.filter((token: LiquidToken) => !token.args.startsWith('variables'))
2241

2342
for (const token of tokens) {
2443
// if token is 'data foo.bar` or `indented_data_reference foo.bar depth=3`
@@ -42,7 +61,7 @@ export const raiReusableUsage = {
4261

4362
// Rai file content can be in either the data/reusables/rai directory
4463
// or anywhere in the content directory
45-
function isFileRai(params) {
64+
function isFileRai(params: RuleParams): boolean {
4665
// ROOT is set in the test environment to src/fixtures/fixtures otherwise
4766
// it is set to the root of the project.
4867
const ROOT = process.env.ROOT || '.'
@@ -53,6 +72,6 @@ function isFileRai(params) {
5372
return params.name.startsWith(dataRai)
5473
}
5574

56-
const fm = getFrontmatter(params.frontMatterLines) || {}
75+
const fm: Frontmatter = (getFrontmatter(params.frontMatterLines) as Frontmatter) || {}
5776
return fm.type === 'rai'
5877
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
11
import yaml from 'js-yaml'
2+
// @ts-ignore - markdownlint-rule-helpers doesn't have TypeScript declarations
23
import { addError, filterTokens } from 'markdownlint-rule-helpers'
34

45
import { liquid } from '@/content-render/index'
56
import { allVersions } from '@/versions/lib/all-versions'
7+
import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '../../types'
68

7-
const scheduledYamlJobs = []
9+
interface YamlSchedule {
10+
cron: string
11+
}
12+
13+
interface YamlWorkflow {
14+
on?: {
15+
schedule?: YamlSchedule[]
16+
}
17+
}
18+
19+
const scheduledYamlJobs: string[] = []
820

9-
export const yamlScheduledJobs = {
21+
export const yamlScheduledJobs: Rule = {
1022
names: ['GHD021', 'yaml-scheduled-jobs'],
1123
description:
1224
'YAML snippets that include scheduled workflows must not run on the hour and must be unique',
1325
tags: ['feature', 'actions'],
1426
parser: 'markdownit',
1527
asynchronous: true,
16-
function: (params, onError) => {
17-
filterTokens(params, 'fence', async (token) => {
18-
const lang = token.info.trim().split(/\s+/u).shift().toLowerCase()
28+
function: (params: RuleParams, onError: RuleErrorCallback) => {
29+
filterTokens(params, 'fence', async (token: MarkdownToken) => {
30+
if (!token.info) return
31+
if (!token.content) return
32+
const lang = token.info.trim().split(/\s+/u).shift()?.toLowerCase()
1933
if (lang !== 'yaml' && lang !== 'yml') return
2034
if (!token.content.includes('schedule:')) return
2135
if (!token.content.includes('- cron:')) return
@@ -26,15 +40,15 @@ export const yamlScheduledJobs = {
2640
}
2741
// If we don't parse the Liquid first, yaml loading chokes on {% raw %} tags
2842
const renderedYaml = await liquid.parseAndRender(token.content, context)
29-
const yamlObj = yaml.load(renderedYaml)
43+
const yamlObj = yaml.load(renderedYaml) as YamlWorkflow
3044
if (!yamlObj.on) return
3145
if (!yamlObj.on.schedule) return
3246

33-
yamlObj.on.schedule.forEach((schedule) => {
47+
yamlObj.on.schedule.forEach((schedule: YamlSchedule) => {
3448
if (schedule.cron.split(' ')[0] === '0') {
3549
addError(
3650
onError,
37-
getLineNumber(token.content, schedule.cron) + token.lineNumber,
51+
getLineNumber(token.content!, schedule.cron) + token.lineNumber,
3852
`YAML scheduled workflow must not run on the hour`,
3953
schedule.cron,
4054
)
@@ -43,7 +57,7 @@ export const yamlScheduledJobs = {
4357
if (scheduledYamlJobs.includes(schedule.cron)) {
4458
addError(
4559
onError,
46-
getLineNumber(token.content, schedule.cron) + token.lineNumber,
60+
getLineNumber(token.content!, schedule.cron) + token.lineNumber,
4761
`YAML scheduled workflow must be unique`,
4862
schedule.cron,
4963
)
@@ -55,7 +69,7 @@ export const yamlScheduledJobs = {
5569
},
5670
}
5771

58-
function getLineNumber(tokenContent, schedule) {
72+
function getLineNumber(tokenContent: string, schedule: string): number {
5973
const contentLines = tokenContent.split('\n')
6074
return contentLines.findIndex((line) => line.includes(schedule)) + 1
6175
}

src/content-linter/tests/site-data-references.js renamed to src/content-linter/tests/site-data-references.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const getDataPathRegex =
1717

1818
const rawLiquidPattern = /{%\s*raw\s*%}.*?{%\s*endraw\s*%}/gs
1919

20-
const getDataReferences = (content) => {
20+
const getDataReferences = (content: string): string[] => {
2121
// When looking for things like `{% data reusables.foo %}` in the
2222
// content, we first have to exclude any Liquid that isn't real.
2323
// E.g.
@@ -26,14 +26,15 @@ const getDataReferences = (content) => {
2626
// {% endraw %}
2727
const withoutRawLiquidBlocks = content.replace(rawLiquidPattern, '')
2828
const refs = withoutRawLiquidBlocks.match(patterns.dataReference) || []
29-
return refs.map((ref) => ref.replace(getDataPathRegex, '$1'))
29+
return refs.map((ref: string) => ref.replace(getDataPathRegex, '$1'))
3030
}
3131

3232
describe('data references', () => {
3333
vi.setConfig({ testTimeout: 60 * 1000 })
3434

3535
test('every data reference found in English variable files is defined and has a value', async () => {
36-
let errors = []
36+
// value can be any type returned by getDataByLanguage - we check if it's a string
37+
let errors: Array<{ key: string; value: unknown; variableFile: string }> = []
3738
const allVariables = getDeepDataByLanguage('variables', 'en')
3839
const variables = Object.values(allVariables)
3940
expect(variables.length).toBeGreaterThan(0)
@@ -42,13 +43,11 @@ describe('data references', () => {
4243
variables.map(async (variablesPerFile) => {
4344
const variableRefs = getDataReferences(JSON.stringify(variablesPerFile))
4445

45-
variableRefs.forEach((key) => {
46+
variableRefs.forEach((key: string) => {
4647
const value = getDataByLanguage(key, 'en')
4748
if (typeof value !== 'string') {
48-
const variableFile = path.join(
49-
'data/variables',
50-
getFilenameByValue(allVariables, variablesPerFile),
51-
)
49+
const filename = getFilenameByValue(allVariables, variablesPerFile)
50+
const variableFile = path.join('data/variables', filename || '')
5251
errors.push({ key, value, variableFile })
5352
}
5453
})
@@ -60,6 +59,7 @@ describe('data references', () => {
6059
})
6160
})
6261

63-
function getFilenameByValue(object, value) {
62+
// object is the allVariables object with dynamic keys, value is the nested object we're searching for
63+
function getFilenameByValue(object: Record<string, unknown>, value: unknown): string | undefined {
6464
return Object.keys(object).find((key) => object[key] === value)
6565
}

0 commit comments

Comments
 (0)