Skip to content

Commit 1f0df74

Browse files
authored
Migrate 7 JavaScript files to TypeScript (#57816)
1 parent 42240a6 commit 1f0df74

13 files changed

+207
-100
lines changed

src/content-render/index.js renamed to src/content-render/index.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
import { renderLiquid } from './liquid/index'
22
import { renderMarkdown, renderUnified } from './unified/index'
33
import { engine } from './liquid/engine'
4+
import type { Context } from '@/types'
45

5-
const globalCache = new Map()
6+
interface RenderOptions {
7+
cache?: boolean | ((template: string, context: Context) => string)
8+
filename?: string
9+
textOnly?: boolean
10+
}
11+
12+
const globalCache = new Map<string, string>()
613

714
// parse multiple times because some templates contain more templates. :]
8-
export async function renderContent(template = '', context = {}, options = {}) {
15+
export async function renderContent(
16+
template = '',
17+
context: Context = {} as Context,
18+
options: RenderOptions = {},
19+
): Promise<string> {
920
// If called with a falsy template, it can't ever become something
1021
// when rendered. We can exit early to save some pointless work.
1122
if (!template) return template
12-
let cacheKey = null
23+
let cacheKey: string | null = null
1324
if (options && options.cache) {
1425
if (!context) throw new Error("If setting 'cache' in options, the 'context' must be set too")
1526
if (typeof options.cache === 'function') {
@@ -21,13 +32,13 @@ export async function renderContent(template = '', context = {}, options = {}) {
2132
throw new Error('cache option must return a string if truthy')
2233
}
2334
if (globalCache.has(cacheKey)) {
24-
return globalCache.get(cacheKey)
35+
return globalCache.get(cacheKey) as string
2536
}
2637
}
2738
try {
2839
template = await renderLiquid(template, context)
2940
if (context.markdownRequested) {
30-
const md = await renderMarkdown(template, context, options)
41+
const md = await renderMarkdown(template, context)
3142

3243
return md
3344
}
@@ -45,7 +56,7 @@ export async function renderContent(template = '', context = {}, options = {}) {
4556
}
4657
}
4758

48-
function getDefaultCacheKey(template, context) {
59+
function getDefaultCacheKey(template: string, context: Context): string {
4960
return `${template}:${context.currentVersion}:${context.currentLanguage}`
5061
}
5162

src/content-render/liquid/engine.js

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Liquid } from 'liquidjs'
2+
import GithubSlugger from 'github-slugger'
3+
// Copilot: When converting data.js to TypeScript, remove the 'as any' assertion for anyData below
4+
import Data from './data'
5+
import Octicon from './octicon'
6+
// Copilot: When converting ifversion.js to TypeScript, remove the 'as any' assertion for anyIfversion below
7+
import Ifversion from './ifversion'
8+
// Copilot: When converting tool.js to TypeScript, remove the 'as any' assertion for anyTool below
9+
import { Tool, tags as toolTags } from './tool'
10+
import { Spotlight, tags as spotlightTags } from './spotlight'
11+
import { Prompt } from './prompt'
12+
import IndentedDataReference from './indented-data-reference'
13+
14+
// Type assertions for .js files without type definitions
15+
// Copilot: Remove these assertions when the corresponding .js files are converted to TypeScript
16+
const anyData = Data as any
17+
const anyIfversion = Ifversion as any
18+
const anyTool = Tool as any
19+
const anySpotlight = Spotlight as any
20+
const anyPrompt = Prompt as any
21+
const anyIndentedDataReference = IndentedDataReference as any
22+
23+
export const engine = new Liquid({
24+
extname: '.html',
25+
dynamicPartials: false,
26+
})
27+
28+
engine.registerTag('indented_data_reference', anyIndentedDataReference)
29+
engine.registerTag('data', anyData)
30+
engine.registerTag('octicon', Octicon)
31+
engine.registerTag('ifversion', anyIfversion)
32+
33+
for (const tag of toolTags) {
34+
engine.registerTag(tag, anyTool)
35+
}
36+
37+
for (const tag in spotlightTags) {
38+
engine.registerTag(tag, anySpotlight)
39+
}
40+
41+
engine.registerTag('prompt', anyPrompt)
42+
43+
/**
44+
* Like the `size` filter, but specifically for
45+
* getting the number of keys in an object
46+
*/
47+
engine.registerFilter('obj_size', (input: Record<string, unknown> | null | undefined): number => {
48+
if (!input) return 0
49+
return Object.keys(input).length
50+
})
51+
52+
/**
53+
* Returns the version number of a GHES version string
54+
* ex: [email protected] => 2.22
55+
*/
56+
engine.registerFilter('version_num', (input: string): string => {
57+
return input.split('@')[1]
58+
})
59+
60+
/**
61+
* Convert the input to a slug
62+
*/
63+
engine.registerFilter('slugify', (input: string): string => {
64+
const slugger = new GithubSlugger()
65+
return slugger.slug(input)
66+
})

src/content-render/liquid/indented-data-reference.js renamed to src/content-render/liquid/indented-data-reference.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@ import assert from 'assert'
33
import { THROW_ON_EMPTY, IndentedDataReferenceError } from './error-handling'
44
import { getDataByLanguage } from '@/data-directory/lib/get-data'
55

6+
// Note: Using 'any' for liquidjs-related types because liquidjs doesn't provide comprehensive TypeScript definitions
7+
interface LiquidTag {
8+
markup: string
9+
liquid: any
10+
parse(tagToken: any): void
11+
render(scope: any): Promise<string | undefined>
12+
}
13+
14+
interface LiquidScope {
15+
environments: {
16+
currentLanguage: string
17+
[key: string]: any
18+
}
19+
}
20+
621
// This class supports a tag that expects two parameters, a data reference and `spaces=NUMBER`:
722
//
823
// {% indented_data_reference foo.bar spaces=NUMBER %}
@@ -13,12 +28,15 @@ import { getDataByLanguage } from '@/data-directory/lib/get-data'
1328
// reference is used inside a block element (like a list or nested list) without
1429
// affecting the formatting when the reference is used elsewhere via {{ site.data.foo.bar }}.
1530

16-
export default {
17-
parse(tagToken) {
31+
const IndentedDataReference: LiquidTag = {
32+
markup: '',
33+
liquid: null as any,
34+
35+
parse(tagToken: any): void {
1836
this.markup = tagToken.args.trim()
1937
},
2038

21-
async render(scope) {
39+
async render(scope: LiquidScope): Promise<string | undefined> {
2240
// obfuscate first legit space, remove all other spaces, then restore legit space
2341
// this way we can support spaces=NUMBER as well as spaces = NUMBER
2442
const input = this.markup
@@ -29,12 +47,15 @@ export default {
2947
const [dataReference, spaces] = input.split(' ')
3048

3149
// if no spaces are specified, default to 2
32-
const numSpaces = spaces ? spaces.replace(/spaces=/, '') : '2'
50+
const numSpaces: string = spaces ? spaces.replace(/spaces=/, '') : '2'
3351

3452
assert(parseInt(numSpaces) || numSpaces === '0', '"spaces=NUMBER" must include a number')
3553

3654
// Get the referenced value from the context
37-
const text = getDataByLanguage(dataReference, scope.environments.currentLanguage)
55+
const text: string | undefined = getDataByLanguage(
56+
dataReference,
57+
scope.environments.currentLanguage,
58+
)
3859
if (text === undefined) {
3960
if (scope.environments.currentLanguage === 'en') {
4061
const message = `Can't find the key 'indented_data_reference ${dataReference}' in the scope.`
@@ -47,8 +68,10 @@ export default {
4768
}
4869

4970
// add spaces to each line
50-
const renderedReferenceWithIndent = text.replace(/^/gm, ' '.repeat(numSpaces))
71+
const renderedReferenceWithIndent: string = text.replace(/^/gm, ' '.repeat(parseInt(numSpaces)))
5172

5273
return this.liquid.parseAndRender(renderedReferenceWithIndent, scope.environments)
5374
},
5475
}
76+
77+
export default IndentedDataReference

src/content-render/liquid/octicon.js renamed to src/content-render/liquid/octicon.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
import { TokenizationError } from 'liquidjs'
2+
// @ts-ignore - @primer/octicons doesn't provide TypeScript declarations
23
import octicons from '@primer/octicons'
34

5+
// Note: Using 'any' for liquidjs-related types because liquidjs doesn't provide comprehensive TypeScript definitions
6+
interface LiquidTag {
7+
icon: string
8+
options: Record<string, string>
9+
parse(tagToken: any): void
10+
render(): Promise<string>
11+
}
12+
13+
interface OcticonsMatch {
14+
groups: {
15+
icon: string
16+
options?: string
17+
}
18+
}
19+
420
const OptionsSyntax = /([a-zA-Z-]+)="([\w\s-]+)"*/g
521
const Syntax = new RegExp('"(?<icon>[a-zA-Z-]+)"(?<options>(?:\\s' + OptionsSyntax.source + ')*)')
622
const SyntaxHelp = 'Syntax Error in tag \'octicon\' - Valid syntax: octicon "<name>" <key="value">'
@@ -12,9 +28,12 @@ const SyntaxHelp = 'Syntax Error in tag \'octicon\' - Valid syntax: octicon "<na
1228
* {% octicon "check" %}
1329
* {% octicon "check" width="64" aria-label="Example label" %}
1430
*/
15-
export default {
16-
parse(tagToken) {
17-
const match = tagToken.args.match(Syntax)
31+
const Octicon: LiquidTag = {
32+
icon: '',
33+
options: {},
34+
35+
parse(tagToken: any): void {
36+
const match: OcticonsMatch | null = tagToken.args.match(Syntax)
1837
if (!match) {
1938
throw new TokenizationError(SyntaxHelp, tagToken)
2039
}
@@ -32,7 +51,7 @@ export default {
3251

3352
// Memoize any options passed
3453
if (match.groups.options) {
35-
let optionsMatch
54+
let optionsMatch: RegExpExecArray | null
3655

3756
// Loop through each option matching the OptionsSyntax regex
3857
while ((optionsMatch = OptionsSyntax.exec(match.groups.options))) {
@@ -46,13 +65,15 @@ export default {
4665
}
4766
},
4867

49-
async render() {
68+
async render(): Promise<string> {
5069
// Throw an error if the requested octicon does not exist.
5170
if (!Object.prototype.hasOwnProperty.call(octicons, this.icon)) {
5271
throw new Error(`Octicon ${this.icon} does not exist`)
5372
}
5473

55-
const result = octicons[this.icon].toSVG(this.options)
74+
const result: string = octicons[this.icon].toSVG(this.options)
5675
return result
5776
},
5877
}
78+
79+
export default Octicon

src/github-apps/lib/index.js renamed to src/github-apps/lib/index.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,31 @@ import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
55
import { getOpenApiVersion } from '@/versions/lib/all-versions'
66
import { categoriesWithoutSubcategories } from '../../rest/lib/index'
77

8+
interface AppsConfig {
9+
pages: Record<string, unknown>
10+
}
11+
12+
// Note: Using 'any' for AppsData to maintain compatibility with existing consumers that expect different shapes
13+
type AppsData = any
14+
815
const ENABLED_APPS_DIR = 'src/github-apps/data'
9-
const githubAppsData = new Map()
16+
const githubAppsData = new Map<string, Map<string, AppsData>>()
1017

1118
// Initialize the Map with the page type keys listed under `pages`
1219
// in the config.json file.
13-
const appsDataConfig = JSON.parse(fs.readFileSync('src/github-apps/lib/config.json', 'utf8'))
20+
const appsDataConfig: AppsConfig = JSON.parse(
21+
fs.readFileSync('src/github-apps/lib/config.json', 'utf8'),
22+
)
1423
for (const pageType of Object.keys(appsDataConfig.pages)) {
15-
githubAppsData.set(pageType, new Map())
24+
githubAppsData.set(pageType, new Map<string, AppsData>())
1625
}
1726

18-
export async function getAppsData(pageType, docsVersion, apiVersion) {
19-
const pageTypeMap = githubAppsData.get(pageType)
27+
export async function getAppsData(
28+
pageType: string,
29+
docsVersion: string,
30+
apiVersion?: string,
31+
): Promise<AppsData> {
32+
const pageTypeMap = githubAppsData.get(pageType)!
2033
const filename = `${pageType}.json`
2134
const openApiVersion = getOpenApiVersion(docsVersion) + (apiVersion ? `-${apiVersion}` : '')
2235
if (!pageTypeMap.has(openApiVersion)) {
@@ -27,26 +40,34 @@ export async function getAppsData(pageType, docsVersion, apiVersion) {
2740
pageTypeMap.set(openApiVersion, data)
2841
}
2942

30-
return pageTypeMap.get(openApiVersion)
43+
return pageTypeMap.get(openApiVersion)!
3144
}
3245

33-
export async function getAppsServerSideProps(context, pageType, { useDisplayTitle = false }) {
46+
export async function getAppsServerSideProps(
47+
context: any,
48+
pageType: string,
49+
{ useDisplayTitle = false }: { useDisplayTitle?: boolean },
50+
): Promise<{
51+
currentVersion: string
52+
appsItems: AppsData
53+
categoriesWithoutSubcategories: string[]
54+
}> {
3455
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items')
3556
const { getAutomatedPageContextFromRequest } = await import(
3657
'@/automated-pipelines/components/AutomatedPageContext'
3758
)
38-
const currentVersion = context.query.versionId
59+
const currentVersion: string = context.query.versionId
3960
const allVersions = context.req.context.allVersions
40-
const queryApiVersion = context.query.apiVersion
41-
const apiVersion = allVersions[currentVersion].apiVersions.includes(queryApiVersion)
61+
const queryApiVersion: string = context.query.apiVersion
62+
const apiVersion: string = allVersions[currentVersion].apiVersions.includes(queryApiVersion)
4263
? queryApiVersion
4364
: allVersions[currentVersion].latestApiVersion
4465

45-
const appsItems = await getAppsData(pageType, currentVersion, apiVersion)
66+
const appsItems: AppsData = await getAppsData(pageType, currentVersion, apiVersion)
4667
// Create minitoc
4768
const { miniTocItems } = getAutomatedPageContextFromRequest(context.req)
48-
const titles = useDisplayTitle
49-
? Object.values(appsItems).map((item) => item.displayTitle)
69+
const titles: string[] = useDisplayTitle
70+
? Object.values(appsItems).map((item: any) => item.displayTitle!)
5071
: Object.keys(appsItems)
5172
const appMiniToc = await getAutomatedPageMiniTocItems(titles, context)
5273
appMiniToc && miniTocItems.push(...appMiniToc)

0 commit comments

Comments
 (0)