-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathutils.ts
More file actions
163 lines (149 loc) · 5.63 KB
/
utils.ts
File metadata and controls
163 lines (149 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { addError, filterTokens } from 'markdownlint-rule-helpers'
import matter from '@gr2m/gray-matter'
import type { RuleParams, RuleErrorCallback, MarkdownToken } from '@/content-linter/types'
// Adds an error object with details conditionally via the onError callback
export function addFixErrorDetail(
onError: RuleErrorCallback,
lineNumber: number,
expected: string,
actual: string,
// Using flexible type to accommodate different range formats from various linting rules
range: [number, number] | number[] | null,
// Using any for fixInfo as markdownlint-rule-helpers accepts various fix info structures
fixInfo: any,
): void {
addError(onError, lineNumber, `Expected: ${expected}`, ` Actual: ${actual}`, range, fixInfo)
}
export function forEachInlineChild(
params: RuleParams,
type: string,
// Using any for child and token types because different linting rules pass tokens with varying structures
// beyond the base MarkdownToken interface (e.g., ImageToken with additional properties)
handler: (child: any, token: any) => void,
): void {
filterTokens(params, 'inline', (token: MarkdownToken) => {
for (const child of token.children!.filter((c) => c.type === type)) {
handler(child, token)
}
})
}
export function getRange(line: string, content: string): [number, number] | null {
if (content.length === 0) {
// This function assumes that the content is something. If it's an
// empty string it can never produce a valid range.
throw new Error('invalid content (empty)')
}
const startColumnIndex = line.indexOf(content)
return startColumnIndex !== -1 ? [startColumnIndex + 1, content.length] : null
}
export function isStringQuoted(text: string): boolean {
// String starts with either a single or double quote
// ends with either a single or double quote
// and optionally ends with a question mark or exclamation point
// because that punctuation can exist outside of the quoted string
return /^['"].*['"][?!]?$/.test(text)
}
export function isStringPunctuated(text: string): boolean {
// String ends with punctuation of either
// . ? ! and optionally ends with single
// or double quotes. This also allows
// for single or double quotes before
// the punctuation.
return /^.*[.?!]['"]?$/.test(text)
}
export function doesStringEndWithPeriod(text: string): boolean {
// String ends with punctuation of either
// . ? ! and optionally ends with single
// or double quotes. This also allows
// for single or double quotes before
// the punctuation.
return /^.*\.['"]?$/.test(text)
}
export function quotePrecedesLinkOpen(text: string | undefined): boolean {
if (!text) return false
return text.endsWith('"') || text.endsWith("'")
}
// Filters a list of tokens by token type only when they match
// a specific token type order.
// For example, if a list of tokens contains:
//
// [
// { type: 'inline'},
// { type: 'list_item_close'},
// { type: 'list_item_open'},
// { type: 'paragraph_open'},
// { type: 'inline'},
// { type: 'paragraph_close'},
// ]
//
// And if the `tokenOrder` being looked for is:
//
// [
// 'list_item_open',
// 'paragraph_open',
// 'inline'
// ]
//
// Then the return value would be the items that match that sequence:
// Index 2-4:
// [
// { type: 'inline'}, <-- Index 0 - NOT INCLUDED
// { type: 'list_item_close'}, <-- Index 1 - NOT INCLUDED
// { type: 'list_item_open'}, <-- Index 2 - INCLUDED
// { type: 'paragraph_open'}, <-- Index 3 - INCLUDED
// { type: 'inline'}, <-- Index 4 - INCLUDED
// { type: 'paragraph_close'}, <-- Index 5 - NOT INCLUDED
// ]
//
export function filterTokensByOrder(
tokens: MarkdownToken[],
tokenOrder: string[],
): MarkdownToken[] {
const matches: MarkdownToken[] = []
// Get a list of token indexes that match the
// first token (root) in the tokenOrder array
const tokenRootIndexes: number[] = []
const firstTokenOrderType = tokenOrder[0]
tokens.forEach((token, index) => {
if (token.type === firstTokenOrderType) {
tokenRootIndexes.push(index)
}
})
// Loop through each root token index and check if
// the order matches the tokenOrder array
for (const tokenRootIndex of tokenRootIndexes) {
for (let i = 1; i < tokenOrder.length; i++) {
if (tokens[tokenRootIndex + i].type !== tokenOrder[i]) {
// This tokenRootIndex was a possible start,
// but doesn't match the tokenOrder perfectly, so break out
// of the inner loop before it reaches the end.
break
}
if (i === tokenOrder.length - 1) {
matches.push(...tokens.slice(tokenRootIndex, tokenRootIndex + i + 1))
}
}
}
return matches
}
export const docsDomains = ['docs.github.com', 'help.github.com', 'developer.github.com']
// Lines is an array of strings read from a
// Markdown file a split around new lines.
// This is the format we get from Markdownlint.
// Returns null if the lines do not contain
// frontmatter properties.
// Returns frontmatter as a Record with any values since YAML can contain various types
export function getFrontmatter(lines: string[]): Record<string, any> | null {
const fmString = lines.join('\n')
const { data } = matter(fmString)
// If there is no frontmatter or the frontmatter contains
// no keys, matter will return an empty object.
if (Object.keys(data).length === 0) return null
return data
}
export function getFrontmatterLines(lines: string[]): string[] {
const indexStart = lines.indexOf('---')
if (indexStart === -1) return []
const indexEnd = lines.indexOf('---', indexStart + 1)
return lines.slice(indexStart, indexEnd + 1)
}