Skip to content

Commit 8307b09

Browse files
authored
Merge pull request #40508 from github/repo-sync
Repo sync
2 parents 3037c30 + 8f5f64a commit 8307b09

File tree

15 files changed

+221
-116
lines changed

15 files changed

+221
-116
lines changed

src/content-linter/lib/init-test.js

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import markdownlint from 'markdownlint'
2+
import type { Configuration, Options } from 'markdownlint'
3+
4+
import { defaultConfig } from '@/content-linter/lib/default-markdownlint-options'
5+
import type { Rule } from '@/content-linter/types'
6+
7+
interface RunRuleOptions {
8+
strings?: { [key: string]: string }
9+
files?: string[]
10+
ruleConfig?: boolean | object
11+
markdownlintOptions?: Partial<Options>
12+
}
13+
14+
export async function runRule(
15+
module: Rule,
16+
{ strings, files, ruleConfig, markdownlintOptions = {} }: RunRuleOptions,
17+
) {
18+
if ((!strings && !files) || (strings && files))
19+
throw new Error('Must provide either Markdown strings or files to run a rule')
20+
21+
const testConfig: Configuration = {
22+
[module.names[0]]: ruleConfig || true,
23+
}
24+
25+
const testOptions: Partial<Options> = {
26+
customRules: [module as any],
27+
config: { ...defaultConfig, ...testConfig },
28+
}
29+
if (strings) testOptions.strings = strings
30+
if (files) testOptions.files = files
31+
32+
const options: Options = { ...markdownlintOptions, ...testOptions }
33+
return await markdownlint.promises.markdownlint(options)
34+
}

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

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

3-
import { getFrontmatter } from '../helpers/utils'
4+
import { getFrontmatter } from '@/content-linter/lib/helpers/utils'
5+
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
6+
7+
interface Frontmatter {
8+
layout?: string
9+
[key: string]: any
10+
}
411

512
export const codeAnnotations = {
613
names: ['GHD007', 'code-annotations'],
714
description:
815
'Code annotations defined in Markdown must contain a specific layout frontmatter property',
916
tags: ['code', 'feature', 'annotate', 'frontmatter'],
10-
parser: 'markdownit',
11-
function: (params, onError) => {
12-
filterTokens(params, 'fence', (token) => {
13-
if (!token.info.includes('annotate')) return
14-
const fm = getFrontmatter(params.frontMatterLines)
17+
parser: 'markdownit' as const,
18+
function: (params: RuleParams, onError: RuleErrorCallback) => {
19+
filterTokens(params, 'fence', (token: MarkdownToken) => {
20+
if (!token.info?.includes('annotate')) return
21+
const fm: Frontmatter | null = getFrontmatter(params.frontMatterLines)
1522
if (!fm || (fm.layout && fm.layout === 'inline')) return
1623

1724
addError(

src/content-linter/lib/linting-rules/image-file-kebab-case.js renamed to src/content-linter/lib/linting-rules/image-file-kebab-case.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { addFixErrorDetail, forEachInlineChild } from '../helpers/utils'
1+
import { addFixErrorDetail, forEachInlineChild } from '@/content-linter/lib/helpers/utils'
2+
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
23

34
export const imageFileKebabCase = {
45
names: ['GHD004', 'image-file-kebab-case'],
56
description: 'Image file names must use kebab-case',
67
tags: ['images'],
7-
parser: 'markdownit',
8-
function: (params, onError) => {
9-
forEachInlineChild(params, 'image', async function forToken(token) {
10-
const imageFileName = token.attrs[0][1].split('/').pop().split('.')[0]
8+
parser: 'markdownit' as const,
9+
function: (params: RuleParams, onError: RuleErrorCallback) => {
10+
forEachInlineChild(params, 'image', async function forToken(token: MarkdownToken) {
11+
const imageFileName = token.attrs?.[0]?.[1]?.split('/').pop()?.split('.')[0] || ''
1112
const nonKebabRegex = /([A-Z]|_)/
1213
const suggestedFileName = imageFileName.toLowerCase().replace(/_/g, '-')
1314
if (imageFileName.match(nonKebabRegex)) {

src/content-linter/lib/linting-rules/image-no-gif.js renamed to src/content-linter/lib/linting-rules/image-no-gif.ts

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

3-
import { forEachInlineChild } from '../helpers/utils'
4+
import { forEachInlineChild } from '@/content-linter/lib/helpers/utils'
5+
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
46

57
export const imageNoGif = {
68
names: ['GHD036', 'image-no-gif'],
79
description:
810
'Image must not be a gif, styleguide reference: contributing/style-guide-and-content-model/style-guide.md#images',
911
tags: ['images'],
10-
parser: 'markdownit',
11-
function: (params, onError) => {
12-
forEachInlineChild(params, 'image', function forToken(token) {
13-
const imageFileName = token.attrs[0][1]
14-
if (imageFileName.endsWith('.gif')) {
12+
parser: 'markdownit' as const,
13+
function: (params: RuleParams, onError: RuleErrorCallback) => {
14+
forEachInlineChild(params, 'image', function forToken(token: MarkdownToken) {
15+
const imageFileName = token.attrs?.[0]?.[1]
16+
if (imageFileName && imageFileName.endsWith('.gif')) {
1517
addError(
1618
onError,
1719
token.lineNumber,

src/content-linter/tests/unit/hardcoded-data-variable.js renamed to src/content-linter/tests/unit/hardcoded-data-variable.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { describe, expect, test } from 'vitest'
22

3-
import { runRule } from '../../lib/init-test'
4-
import { hardcodedDataVariable } from '../../lib/linting-rules/hardcoded-data-variable'
3+
import { runRule } from '@/content-linter/lib/init-test'
4+
import { hardcodedDataVariable } from '@/content-linter/lib/linting-rules/hardcoded-data-variable'
55

66
describe(hardcodedDataVariable.names.join(' - '), () => {
7-
test('Using hardcoded personal access token string causes error', async () => {
8-
const markdown = [
7+
test('Using hardcoded personal access token string causes error', async (): Promise<void> => {
8+
const markdown: string = [
99
'Hello personal access token for apps.',
1010
'A Personal access token for apps.',
1111
'Lots of PERSONAL ACCESS TOKENS for apps.',
1212
'access tokens for apps.',
1313
'personal access token and a Personal Access Tokens',
1414
].join('\n')
15-
const result = await runRule(hardcodedDataVariable, { strings: { markdown } })
15+
const result = await runRule(hardcodedDataVariable, {
16+
strings: { markdown },
17+
files: undefined,
18+
ruleConfig: true,
19+
})
1620
const errors = result.markdown
1721
expect(errors.length).toBe(5)
1822
expect(errors[errors.length - 2].lineNumber).toBe(5)

src/content-linter/types.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,44 @@
1+
// Interfaces for content linter rule parameters and callbacks
2+
export interface MarkdownToken {
3+
type: string
4+
tag?: string
5+
attrs?: [string, string][]
6+
content?: string
7+
info?: string
8+
lineNumber: number
9+
line: string
10+
children?: MarkdownToken[]
11+
}
12+
13+
export interface RuleParams {
14+
name: string // file path
15+
lines: string[] // array of lines from the file
16+
frontMatterLines: string[] // array of frontmatter lines
17+
tokens?: MarkdownToken[] // markdown tokens (when using markdownit parser)
18+
config?: {
19+
[key: string]: any // rule-specific configuration
20+
}
21+
}
22+
23+
export interface RuleErrorCallback {
24+
(
25+
lineNumber: number,
26+
detail?: string,
27+
context?: string,
28+
range?: [number, number],
29+
fixInfo?: any,
30+
): void
31+
}
32+
133
export type Rule = {
234
names: string[]
335
description: string
436
tags: string[]
537
information?: URL
6-
function: Function[]
38+
parser?: 'markdownit' | 'micromark' | 'none'
39+
asynchronous?: boolean
40+
severity?: string
41+
function: (params: RuleParams, onError: RuleErrorCallback) => void | Promise<void>
742
}
843

944
type RuleDetail = Rule & {

src/content-render/unified/heading-links.js

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { visit } from 'unist-util-visit'
2+
import { h } from 'hastscript'
3+
4+
interface HeadingNode {
5+
type: 'element'
6+
tagName: string
7+
properties: {
8+
id?: string
9+
tabIndex?: number
10+
[key: string]: any
11+
}
12+
children: any[]
13+
}
14+
15+
const matcher = (node: any): node is HeadingNode =>
16+
node.type === 'element' &&
17+
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName) &&
18+
node.properties?.id
19+
20+
export default function headingLinks() {
21+
return (tree: any) => {
22+
visit(tree, matcher, (node: HeadingNode) => {
23+
const { id } = node.properties
24+
const text = node.children
25+
node.properties.tabIndex = -1
26+
node.children = [
27+
h('a', { className: 'heading-link', href: `#${id}` }, [
28+
...text,
29+
h('span', { className: 'heading-link-symbol', ariaHidden: 'true' }),
30+
]),
31+
]
32+
})
33+
}
34+
}

src/content-render/unified/index.js

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)