Skip to content

Commit a89743b

Browse files
authored
Replace 'any' types with 'unknown' or specific types (#58694)
1 parent cfb053c commit a89743b

File tree

26 files changed

+222
-149
lines changed

26 files changed

+222
-149
lines changed

src/content-linter/lib/helpers/liquid-utils.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import { Tokenizer, TokenKind } from 'liquidjs'
2+
import type { TopLevelToken, TagToken } from 'liquidjs'
23

34
import { deprecated } from '@/versions/lib/enterprise-server-releases'
45

5-
// Using `any` for the cache because TopLevelToken is a complex union type from liquidjs
6-
// that includes TagToken, OutputToken, and HTMLToken with different properties.
7-
// The cache is private to this module and we control all access to it.
8-
const liquidTokenCache = new Map<string, any>()
6+
// Cache for liquid tokens to improve performance
7+
const liquidTokenCache = new Map<string, TopLevelToken[]>()
98

10-
// Returns `any[]` instead of `TopLevelToken[]` because TopLevelToken is a union type
11-
// (TagToken | OutputToken | HTMLToken) and consumers of this function access properties
12-
// like `name` and `args` that only exist on TagToken. Using `any` here avoids complex
13-
// type narrowing throughout the codebase.
9+
// Returns TopLevelToken array from liquidjs which is a union of TagToken, OutputToken, and HTMLToken
1410
export function getLiquidTokens(
1511
content: string,
1612
{ noCache = false }: { noCache?: boolean } = {},
17-
): any[] {
13+
): TopLevelToken[] {
1814
if (!content) return []
1915

2016
if (noCache) {
@@ -23,13 +19,13 @@ export function getLiquidTokens(
2319
}
2420

2521
if (liquidTokenCache.has(content)) {
26-
return liquidTokenCache.get(content)
22+
return liquidTokenCache.get(content)!
2723
}
2824

2925
const tokenizer = new Tokenizer(content)
3026
const tokens = tokenizer.readTopLevelTokens()
3127
liquidTokenCache.set(content, tokens)
32-
return liquidTokenCache.get(content)
28+
return liquidTokenCache.get(content)!
3329
}
3430

3531
export const OUTPUT_OPEN = '{%'
@@ -40,10 +36,9 @@ export const TAG_CLOSE = '}}'
4036
export const conditionalTags = ['if', 'elseif', 'unless', 'case', 'ifversion']
4137
const CONDITIONAL_TAG_NAMES = ['if', 'ifversion', 'elsif', 'else', 'endif']
4238

43-
// Token is `any` because it's used with different token types from liquidjs
44-
// that all have `begin` and `end` properties but are part of complex union types.
39+
// Token parameter uses TopLevelToken which has begin and end properties
4540
export function getPositionData(
46-
token: any,
41+
token: TopLevelToken,
4742
lines: string[],
4843
): { lineNumber: number; column: number; length: number } {
4944
// Liquid indexes are 0-based, but we want to
@@ -77,9 +72,9 @@ export function getPositionData(
7772
* by Markdownlint:
7873
* [ { lineNumber: 1, column: 1, deleteCount: 3, }]
7974
*/
80-
// Token is `any` because it's used with different token types from liquidjs.
75+
// Token parameter uses TopLevelToken from liquidjs
8176
export function getContentDeleteData(
82-
token: any,
77+
token: TopLevelToken,
8378
tokenEnd: number,
8479
lines: string[],
8580
): Array<{ lineNumber: number; column: number; deleteCount: number }> {
@@ -123,15 +118,14 @@ export function getContentDeleteData(
123118
// related elsif, else, and endif tags).
124119
// Docs doesn't use the standard `if` tag for versioning, instead the
125120
// `ifversion` tag is used.
126-
// Returns `any[]` because the tokens need to be accessed as TagToken with `name` and `args` properties,
127-
// but TopLevelToken union type would require complex type narrowing.
128-
export function getLiquidIfVersionTokens(content: string): any[] {
121+
// Returns TagToken array since we filter to only Tag tokens
122+
export function getLiquidIfVersionTokens(content: string): TagToken[] {
129123
const tokens = getLiquidTokens(content)
130-
.filter((token) => token.kind === TokenKind.Tag)
124+
.filter((token): token is TagToken => token.kind === TokenKind.Tag)
131125
.filter((token) => CONDITIONAL_TAG_NAMES.includes(token.name))
132126

133127
let inIfStatement = false
134-
const ifVersionTokens: any[] = []
128+
const ifVersionTokens: TagToken[] = []
135129
for (const token of tokens) {
136130
if (token.name === 'if') {
137131
inIfStatement = true

src/content-linter/lib/helpers/utils.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ export function addFixErrorDetail(
1111
actual: string,
1212
// Using flexible type to accommodate different range formats from various linting rules
1313
range: [number, number] | number[] | null,
14-
// Using any for fixInfo as markdownlint-rule-helpers accepts various fix info structures
15-
fixInfo: any,
14+
// Using unknown for fixInfo as markdownlint-rule-helpers accepts various fix info structures
15+
fixInfo: unknown,
1616
): void {
1717
addError(onError, lineNumber, `Expected: ${expected}`, ` Actual: ${actual}`, range, fixInfo)
1818
}
1919

2020
export function forEachInlineChild(
2121
params: RuleParams,
2222
type: string,
23-
// Using any for child and token types because different linting rules pass tokens with varying structures
24-
// beyond the base MarkdownToken interface (e.g., ImageToken with additional properties)
25-
handler: (child: any, token: any) => void,
23+
// Handler uses `any` for function parameter variance reasons. TypeScript's contravariance rules for function
24+
// parameters mean that a function accepting a specific type cannot be assigned to a parameter of type `unknown`.
25+
// Therefore, `unknown` cannot be used here, as different linting rules pass tokens with varying structures
26+
// beyond the base MarkdownToken interface, and some handlers are async.
27+
handler: (child: any, token?: any) => void | Promise<void>,
2628
): void {
2729
filterTokens(params, 'inline', (token: MarkdownToken) => {
2830
for (const child of token.children!.filter((c) => c.type === type)) {
@@ -146,8 +148,8 @@ export const docsDomains = ['docs.github.com', 'help.github.com', 'developer.git
146148
// This is the format we get from Markdownlint.
147149
// Returns null if the lines do not contain
148150
// frontmatter properties.
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 {
151+
// Returns frontmatter as a Record with unknown values since YAML can contain various types
152+
export function getFrontmatter(lines: string[]): Record<string, unknown> | null {
151153
const fmString = lines.join('\n')
152154
const { data } = matter(fmString)
153155
// If there is no frontmatter or the frontmatter contains

src/content-linter/lib/linting-rules/internal-links-slash.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { filterTokens } from 'markdownlint-rule-helpers'
22

33
import { addFixErrorDetail, getRange } from '../helpers/utils'
4-
import type { RuleParams, RuleErrorCallback, Rule } from '../../types'
4+
import type { RuleParams, RuleErrorCallback, Rule, MarkdownToken } from '../../types'
55

66
export const internalLinksSlash: Rule = {
77
names: ['GHD003', 'internal-links-slash'],
88
description: 'Internal links must start with a /',
99
tags: ['links', 'url'],
1010
parser: 'markdownit',
1111
function: (params: RuleParams, onError: RuleErrorCallback) => {
12-
// Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types
13-
filterTokens(params, 'inline', (token: any) => {
12+
filterTokens(params, 'inline', (token: MarkdownToken) => {
13+
if (!token.children) return
1414
for (const child of token.children) {
1515
if (child.type !== 'link_open') continue
1616

@@ -20,6 +20,7 @@ export const internalLinksSlash: Rule = {
2020
// ['rel', 'canonical'],
2121
// ]
2222
// Attribute arrays are tuples of [attributeName, attributeValue] from markdownit parser
23+
if (!child.attrs) continue
2324
const hrefsMissingSlashes = child.attrs
2425
// The attribute could also be `target` or `rel`
2526
.filter((attr: [string, string]) => attr[0] === 'href')

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { addError } from 'markdownlint-rule-helpers'
22
import { TokenKind } from 'liquidjs'
3+
import type { TagToken } from 'liquidjs'
34

45
import { getDataByLanguage } from '@/data-directory/lib/get-data'
56
import {
@@ -23,10 +24,9 @@ export const liquidDataReferencesDefined = {
2324
parser: 'markdownit',
2425
function: (params: RuleParams, onError: RuleErrorCallback) => {
2526
const content = params.lines.join('\n')
26-
// Using any type because getLiquidTokens returns tokens from liquidjs library without complete type definitions
2727
const tokens = getLiquidTokens(content)
28-
.filter((token: any) => token.kind === TokenKind.Tag)
29-
.filter((token: any) => token.name === 'data' || token.name === 'indented_data_reference')
28+
.filter((token): token is TagToken => token.kind === TokenKind.Tag)
29+
.filter((token) => token.name === 'data' || token.name === 'indented_data_reference')
3030

3131
if (!tokens.length) return
3232

@@ -60,13 +60,11 @@ export const liquidDataTagFormat = {
6060
function: (params: RuleParams, onError: RuleErrorCallback) => {
6161
const CHECK_LIQUID_TAGS = [OUTPUT_OPEN, OUTPUT_CLOSE, '{', '}']
6262
const content = params.lines.join('\n')
63-
// Using any type because getLiquidTokens returns tokens from liquidjs library without complete type definitions
64-
// Tokens have properties like 'kind', 'name', 'args', and 'content' that aren't fully typed
65-
const tokenTags = getLiquidTokens(content).filter((token: any) => token.kind === TokenKind.Tag)
66-
const dataTags = tokenTags.filter((token: any) => token.name === 'data')
67-
const indentedDataTags = tokenTags.filter(
68-
(token: any) => token.name === 'indented_data_reference',
63+
const tokenTags = getLiquidTokens(content).filter(
64+
(token): token is TagToken => token.kind === TokenKind.Tag,
6965
)
66+
const dataTags = tokenTags.filter((token) => token.name === 'data')
67+
const indentedDataTags = tokenTags.filter((token) => token.name === 'indented_data_reference')
7068

7169
for (const token of dataTags) {
7270
// A data tag has only one argument, the data directory path.

src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { addError } from 'markdownlint-rule-helpers'
2+
import type { TopLevelToken } from 'liquidjs'
23

34
import {
45
getLiquidIfVersionTokens,
@@ -35,8 +36,11 @@ export const liquidIfversionVersions = {
3536
const fileVersionsFm = params.name.startsWith('data')
3637
? { ghec: '*', ghes: '*', fpt: '*' }
3738
: fm
38-
? fm.versions
39-
: getFrontmatter(params.frontMatterLines)?.versions
39+
? (fm.versions as string | Record<string, string> | undefined)
40+
: (getFrontmatter(params.frontMatterLines)?.versions as
41+
| string
42+
| Record<string, string>
43+
| undefined)
4044
// This will only contain valid (non-deprecated) and future versions
4145
const fileVersions = getApplicableVersions(fileVersionsFm, '', {
4246
doNotThrow: true,
@@ -134,7 +138,7 @@ function setLiquidErrors(condTagItems: any[], onError: RuleErrorCallback, lines:
134138
{
135139
begin: item.begin,
136140
end: item.end,
137-
},
141+
} as TopLevelToken,
138142
lines,
139143
)
140144
const deleteCount = length - column + 1 === lines[lineNumber - 1].length ? -1 : length
@@ -159,7 +163,7 @@ function setLiquidErrors(condTagItems: any[], onError: RuleErrorCallback, lines:
159163
{
160164
begin: item.contentrange[0],
161165
end: item.contentrange[1],
162-
},
166+
} as TopLevelToken,
163167
lines,
164168
)
165169
const insertText = `${item.action.name || item.name} ${item.action.cond || item.cond}`

src/content-linter/lib/linting-rules/liquid-quoted-conditional-arg.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TokenKind } from 'liquidjs'
2+
import type { TagToken } from 'liquidjs'
23
import { addError } from 'markdownlint-rule-helpers'
34

45
import { getLiquidTokens, conditionalTags, getPositionData } from '../helpers/liquid-utils'
@@ -19,14 +20,12 @@ export const liquidQuotedConditionalArg: Rule = {
1920
tags: ['liquid', 'format'],
2021
function: (params: RuleParams, onError: RuleErrorCallback) => {
2122
const content = params.lines.join('\n')
22-
// Using 'any' type for tokens as getLiquidTokens returns tokens from liquid-utils.ts which lacks type definitions
2323
const tokens = getLiquidTokens(content)
24-
.filter((token: any) => token.kind === TokenKind.Tag)
25-
.filter((token: any) => conditionalTags.includes(token.name))
26-
.filter((token: any) => {
24+
.filter((token): token is TagToken => token.kind === TokenKind.Tag)
25+
.filter((token) => conditionalTags.includes(token.name))
26+
.filter((token) => {
2727
const tokensArray = token.args.split(/\s+/g)
28-
// Using 'any' for args as they come from the untyped liquid token structure
29-
if (tokensArray.some((arg: any) => isStringQuoted(arg))) return true
28+
if (tokensArray.some((arg) => isStringQuoted(arg))) return true
3029
return false
3130
})
3231

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const frontmatterLiquidSyntax = {
3333

3434
for (const key of keysWithLiquid) {
3535
const value = fm[key]
36+
if (typeof value !== 'string') continue
3637
try {
3738
liquid.parse(value)
3839
} catch (error) {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TokenKind } from 'liquidjs'
2+
import type { TopLevelToken } from 'liquidjs'
23

34
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils'
45
import { addFixErrorDetail } from '../helpers/utils'
@@ -36,7 +37,10 @@ export const liquidTagWhitespace: Rule = {
3637
(token: LiquidToken) => token.kind === TokenKind.Tag,
3738
)
3839
for (const token of tokens) {
39-
const { lineNumber, column, length } = getPositionData(token, params.lines)
40+
const { lineNumber, column, length } = getPositionData(
41+
token as unknown as TopLevelToken,
42+
params.lines,
43+
)
4044

4145
const range = [column, length]
4246
const tag = params.lines[lineNumber - 1].slice(column - 1, column - 1 + length)

src/content-linter/lib/linting-rules/liquid-versioning.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import semver from 'semver'
22
import { TokenKind } from 'liquidjs'
3+
import type { TagToken } from 'liquidjs'
34
import { addError } from 'markdownlint-rule-helpers'
45

56
import { getRange, addFixErrorDetail } from '../helpers/utils'
@@ -13,7 +14,7 @@ import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
1314

1415
interface Feature {
1516
versions: Record<string, string>
16-
[key: string]: any
17+
[key: string]: unknown
1718
}
1819

1920
type AllFeatures = Record<string, Feature>
@@ -60,12 +61,13 @@ export const liquidIfTags = {
6061
function: (params: RuleParams, onError: RuleErrorCallback) => {
6162
const content = params.lines.join('\n')
6263

63-
const tokens = getLiquidTokens(content).filter(
64-
(token) =>
65-
token.kind === TokenKind.Tag &&
66-
token.name === 'if' &&
67-
token.args.split(/\s+/).some((arg: string) => getAllPossibleVersionNames().has(arg)),
68-
)
64+
const tokens = getLiquidTokens(content)
65+
.filter((token): token is TagToken => token.kind === TokenKind.Tag)
66+
.filter(
67+
(token) =>
68+
token.name === 'if' &&
69+
token.args.split(/\s+/).some((arg: string) => getAllPossibleVersionNames().has(arg)),
70+
)
6971

7072
for (const token of tokens) {
7173
const args = token.args
@@ -90,7 +92,7 @@ export const liquidIfVersionTags = {
9092
function: (params: RuleParams, onError: RuleErrorCallback) => {
9193
const content = params.lines.join('\n')
9294
const tokens = getLiquidTokens(content)
93-
.filter((token) => token.kind === TokenKind.Tag)
95+
.filter((token): token is TagToken => token.kind === TokenKind.Tag)
9496
.filter((token) => token.name === 'ifversion' || token.name === 'elsif')
9597

9698
for (const token of tokens) {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { addError } from 'markdownlint-rule-helpers'
22
import { TokenKind } from 'liquidjs'
3+
import type { TopLevelToken } from 'liquidjs'
34
import path from 'path'
45

56
import { getFrontmatter } from '../helpers/utils'
@@ -45,7 +46,10 @@ export const raiReusableUsage: Rule = {
4546
if (dataDirectoryReference.startsWith('reusables.rai')) continue
4647

4748
const lines = params.lines
48-
const { lineNumber, column, length } = getPositionData(token, lines)
49+
const { lineNumber, column, length } = getPositionData(
50+
token as unknown as TopLevelToken,
51+
lines,
52+
)
4953
addError(
5054
onError,
5155
lineNumber,

0 commit comments

Comments
 (0)