Skip to content

Commit 893ea88

Browse files
authored
Migrate 6 files from JavaScript to TypeScript (#57884)
1 parent 6d3f74a commit 893ea88

File tree

11 files changed

+279
-162
lines changed

11 files changed

+279
-162
lines changed

src/content-linter/lib/linting-rules/code-annotation-comment-spacing.js renamed to src/content-linter/lib/linting-rules/code-annotation-comment-spacing.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
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+
36
export const codeAnnotationCommentSpacing = {
47
names: ['GHD045', 'code-annotation-comment-spacing'],
58
description:
69
'Code comments in annotation blocks must have exactly one space after the comment character(s)',
710
tags: ['code', 'comments', 'annotate', 'spacing'],
811
parser: 'markdownit',
9-
function: (params, onError) => {
10-
filterTokens(params, 'fence', (token) => {
11-
if (!token.info.includes('annotate')) return
12+
function: (params: RuleParams, onError: RuleErrorCallback) => {
13+
filterTokens(params, 'fence', (token: MarkdownToken) => {
14+
if (!token.info?.includes('annotate')) return
15+
16+
const content = token.content
17+
if (!content) return
1218

13-
const lines = token.content.split('\n')
19+
const lines = content.split('\n')
1420

15-
lines.forEach((line, index) => {
21+
lines.forEach((line: string, index: number) => {
1622
const trimmedLine = line.trim()
1723
if (!trimmedLine) return
1824

1925
// Define a map of comment patterns
20-
const commentPatterns = {
26+
const commentPatterns: Record<string, RegExp> = {
2127
'//': /^(\/\/)(.*)/, // JavaScript/TypeScript/Java/C# style comments
2228
'#': /^(#)(.*)/, // Python/Ruby/Shell/YAML style comments
2329
'--': /^(--)(.*)/, // SQL/Lua style comments
2430
}
2531

2632
// Check for different comment patterns
27-
let commentMatch = null
28-
let commentChar = null
29-
let restOfLine = null
33+
let commentMatch: RegExpMatchArray | null = null
34+
let commentChar: string | null = null
35+
let restOfLine: string | null = null
3036

3137
// Iterate over the map to find a matching comment style
3238
for (const [char, pattern] of Object.entries(commentPatterns)) {
@@ -38,7 +44,7 @@ export const codeAnnotationCommentSpacing = {
3844
}
3945
}
4046

41-
if (commentMatch && restOfLine !== null) {
47+
if (commentMatch && restOfLine !== null && commentChar !== null) {
4248
// Skip shebang lines (#!/...)
4349
if (trimmedLine.startsWith('#!')) {
4450
return
@@ -49,8 +55,8 @@ export const codeAnnotationCommentSpacing = {
4955
// If it starts with a space, make sure it's exactly one space
5056
if (restOfLine.startsWith(' ') && restOfLine.length > 1 && restOfLine[1] === ' ') {
5157
// Multiple spaces - this is an error
52-
const lineNumber = token.lineNumber + index + 1
53-
const fixedLine = line.replace(
58+
const lineNumber: number = token.lineNumber + index + 1
59+
const fixedLine: string = line.replace(
5460
new RegExp(`^(\\s*${commentChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})\\s+`),
5561
`$1 `,
5662
)
@@ -73,9 +79,9 @@ export const codeAnnotationCommentSpacing = {
7379
return
7480
} else {
7581
// No space after comment character - this is an error
76-
const lineNumber = token.lineNumber + index + 1
77-
const leadingWhitespace = line.match(/^\s*/)[0]
78-
const fixedLine = leadingWhitespace + commentChar + ' ' + restOfLine
82+
const lineNumber: number = token.lineNumber + index + 1
83+
const leadingWhitespace: string = line.match(/^\s*/)![0]
84+
const fixedLine: string = leadingWhitespace + commentChar + ' ' + restOfLine
7985

8086
addError(
8187
onError,

src/content-linter/lib/linting-rules/frontmatter-landing-recommended.js renamed to src/content-linter/lib/linting-rules/frontmatter-landing-recommended.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import fs from 'fs'
22
import path from 'path'
3+
// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations
34
import { addError } from 'markdownlint-rule-helpers'
45

56
import { getFrontmatter } from '../helpers/utils'
7+
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
68

7-
function isValidArticlePath(articlePath, currentFilePath) {
9+
function isValidArticlePath(articlePath: string, currentFilePath: string): boolean {
810
const ROOT = process.env.ROOT || '.'
911

1012
// Strategy 1: Always try as an absolute path from content root first
1113
const contentDir = path.join(ROOT, 'content')
1214
const normalizedPath = articlePath.startsWith('/') ? articlePath.substring(1) : articlePath
13-
const absolutePath = path.join(contentDir, `${normalizedPath}.md`)
15+
const absolutePath: string = path.join(contentDir, `${normalizedPath}.md`)
1416

1517
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
1618
return true
1719
}
1820

1921
// Strategy 2: Fall back to relative path from current file's directory
20-
const currentDir = path.dirname(currentFilePath)
21-
const relativePath = path.join(currentDir, `${normalizedPath}.md`)
22+
const currentDir: string = path.dirname(currentFilePath)
23+
const relativePath: string = path.join(currentDir, `${normalizedPath}.md`)
2224

2325
try {
2426
return fs.existsSync(relativePath) && fs.statSync(relativePath).isFile()
@@ -32,15 +34,18 @@ export const frontmatterLandingRecommended = {
3234
description:
3335
'Only landing pages can have recommended articles, there should be no duplicate recommended articles, and all recommended articles must exist',
3436
tags: ['frontmatter', 'landing', 'recommended'],
35-
function: (params, onError) => {
36-
const fm = getFrontmatter(params.lines)
37+
function: (params: RuleParams, onError: RuleErrorCallback) => {
38+
// Using any for frontmatter as it's a dynamic YAML object with varying properties
39+
const fm: any = getFrontmatter(params.lines)
3740
if (!fm || !fm.recommended) return
3841

39-
const recommendedLine = params.lines.find((line) => line.startsWith('recommended:'))
42+
const recommendedLine: string | undefined = params.lines.find((line) =>
43+
line.startsWith('recommended:'),
44+
)
4045

4146
if (!recommendedLine) return
4247

43-
const lineNumber = params.lines.indexOf(recommendedLine) + 1
48+
const lineNumber: number = params.lines.indexOf(recommendedLine) + 1
4449

4550
if (!fm.layout || !fm.layout.includes('landing')) {
4651
addError(
@@ -55,11 +60,11 @@ export const frontmatterLandingRecommended = {
5560

5661
// Check for duplicate recommended items and invalid paths
5762
if (Array.isArray(fm.recommended)) {
58-
const seen = new Set()
59-
const duplicates = []
60-
const invalidPaths = []
63+
const seen = new Set<string>()
64+
const duplicates: string[] = []
65+
const invalidPaths: string[] = []
6166

62-
fm.recommended.forEach((item) => {
67+
fm.recommended.forEach((item: string) => {
6368
if (seen.has(item)) {
6469
duplicates.push(item)
6570
} else {

src/content-linter/lib/linting-rules/third-party-actions-reusable.js renamed to src/content-linter/lib/linting-rules/third-party-actions-reusable.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
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+
36
export const thirdPartyActionsReusable = {
47
names: ['GHD054', 'third-party-actions-reusable'],
58
description: 'Code examples with third-party actions must include disclaimer reusable',
69
tags: ['actions', 'reusable', 'third-party'],
7-
function: (params, onError) => {
10+
function: (params: RuleParams, onError: RuleErrorCallback) => {
811
// Find all code fence blocks
9-
filterTokens(params, 'fence', (token) => {
12+
filterTokens(params, 'fence', (token: MarkdownToken) => {
1013
// Only check YAML code blocks (GitHub Actions workflows)
1114
if (token.info !== 'yaml' && token.info !== 'yaml copy') return
1215

1316
const codeContent = token.content
17+
if (!codeContent) return
18+
1419
const lineNumber = token.lineNumber
1520

1621
// Find third-party actions in the code block
@@ -41,8 +46,8 @@ export const thirdPartyActionsReusable = {
4146
* Third-party actions are identified by the pattern: owner/action@version
4247
* where owner is not 'actions' or 'github'
4348
*/
44-
function findThirdPartyActions(yamlContent) {
45-
const thirdPartyActions = []
49+
function findThirdPartyActions(yamlContent: string): string[] {
50+
const thirdPartyActions: string[] = []
4651

4752
// Pattern to match 'uses: owner/action@version' where owner is not actions or github
4853
const actionPattern = /uses:\s+([^{\s]+\/[^@\s]+@[^\s]+)/g
@@ -70,7 +75,11 @@ function findThirdPartyActions(yamlContent) {
7075
* Check if the disclaimer reusable is present before the given line number or inside the code block
7176
* Looks backward from the code block and also inside the code block content
7277
*/
73-
function checkForDisclaimer(lines, codeBlockLineNumber, codeContent) {
78+
function checkForDisclaimer(
79+
lines: string[],
80+
codeBlockLineNumber: number,
81+
codeContent: string,
82+
): boolean {
7483
const disclaimerPattern = /{% data reusables\.actions\.actions-not-certified-by-github-comment %}/
7584

7685
// First, check inside the code block content

src/content-render/unified/code-header.js renamed to src/content-render/unified/code-header.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@ import yaml from 'js-yaml'
77
import fs from 'fs'
88
import { visit } from 'unist-util-visit'
99
import { h } from 'hastscript'
10+
// @ts-ignore - no types available for @primer/octicons
1011
import octicons from '@primer/octicons'
1112
import { parse } from 'parse5'
1213
import { fromParse5 } from 'hast-util-from-parse5'
1314
import murmur from 'imurmurhash'
1415
import { getPrompt } from './copilot-prompt'
16+
import type { Element } from 'hast'
1517

16-
const languages = yaml.load(fs.readFileSync('./data/code-languages.yml', 'utf8'))
18+
interface LanguageConfig {
19+
name: string
20+
[key: string]: any
21+
}
22+
23+
type Languages = Record<string, LanguageConfig>
24+
25+
const languages = yaml.load(fs.readFileSync('./data/code-languages.yml', 'utf8')) as Languages
1726

18-
const matcher = (node) =>
27+
// Using any due to conflicting unist/hast type definitions between dependencies
28+
const matcher = (node: any): boolean =>
1929
node.type === 'element' &&
2030
node.tagName === 'pre' &&
2131
// For now, limit to ones with the copy or prompt meta,
@@ -25,28 +35,39 @@ const matcher = (node) =>
2535
!getPreMeta(node).annotate
2636

2737
export default function codeHeader() {
28-
return (tree) => {
29-
visit(tree, matcher, (node, index, parent) => {
30-
parent.children[index] = wrapCodeExample(node, tree)
38+
// Using any due to conflicting unist/hast type definitions between dependencies
39+
return (tree: any) => {
40+
// Using any due to conflicting unist/hast type definitions between dependencies
41+
visit(tree, matcher, (node: any, index: number | undefined, parent: any) => {
42+
if (index !== undefined && parent) {
43+
parent.children[index] = wrapCodeExample(node, tree)
44+
}
3145
})
3246
}
3347
}
3448

35-
function wrapCodeExample(node, tree) {
36-
const lang = node.children[0].properties.className?.[0].replace('language-', '')
37-
const code = node.children[0].children[0].value
49+
// Using any due to conflicting unist/hast type definitions between dependencies
50+
function wrapCodeExample(node: any, tree: any): Element {
51+
const lang: string = node.children[0].properties.className?.[0].replace('language-', '')
52+
const code: string = node.children[0].children[0].value
3853

3954
const subnav = null // getSubnav() lives in annotate.js, not needed for normal code blocks
4055
const prompt = getPrompt(node, tree, code) // returns null if there's no prompt
41-
const hasCopy = Boolean(getPreMeta(node).copy) // defaults to true
56+
const hasCopy: boolean = Boolean(getPreMeta(node).copy) // defaults to true
4257

4358
const headerHast = header(lang, code, subnav, prompt, hasCopy)
4459

4560
return h('div', { className: 'code-example' }, [headerHast, node])
4661
}
4762

48-
export function header(lang, code, subnav = null, prompt = null, hasCopy = true) {
49-
const codeId = murmur('js-btn-copy').hash(code).result()
63+
export function header(
64+
lang: string,
65+
code: string,
66+
subnav: Element | null = null,
67+
prompt: Element | null = null,
68+
hasCopy: boolean = true,
69+
): Element {
70+
const codeId: string = murmur('js-btn-copy').hash(code).result().toString()
5071

5172
return h(
5273
'header',
@@ -83,14 +104,16 @@ export function header(lang, code, subnav = null, prompt = null, hasCopy = true)
83104
)
84105
}
85106

86-
function btnIcon() {
87-
const btnIconHtml = octicons.copy.toSVG()
107+
function btnIcon(): Element {
108+
const btnIconHtml: string = octicons.copy.toSVG()
88109
const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true })
110+
// @ts-ignore - fromParse5 file option typing issue
89111
const btnIcon = fromParse5(btnIconAst, { file: btnIconHtml })
90-
return btnIcon
112+
return btnIcon as Element
91113
}
92114

93-
export function getPreMeta(node) {
115+
// Using any due to conflicting unist/hast type definitions between dependencies
116+
export function getPreMeta(node: any): Record<string, any> {
94117
// Here's why this monstrosity works:
95118
// https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd606731c88a27dbce4bfeaab913a9589bf83/lib/handlers/code.js#L40-L42
96119
return node.children[0]?.data?.meta || {}

0 commit comments

Comments
 (0)