-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathliquid-syntax.ts
More file actions
112 lines (104 loc) · 4.32 KB
/
liquid-syntax.ts
File metadata and controls
112 lines (104 loc) · 4.32 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
import { addError } from 'markdownlint-rule-helpers'
import { getFrontmatter } from '../helpers/utils'
import { liquid } from '@/content-render/index'
import { isLiquidError } from '@/languages/lib/render-with-fallback'
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
interface ErrorMessageInfo {
errorDescription: string
lineNumber: number
columnNumber: number
}
/*
Attempts to parse all liquid in the frontmatter of a file
to verify the syntax is correct.
*/
export const frontmatterLiquidSyntax = {
names: ['GHD017', 'frontmatter-liquid-syntax'],
description: 'Frontmatter properties must use valid Liquid',
tags: ['liquid', 'frontmatter'],
function: (params: RuleParams, onError: RuleErrorCallback) => {
const fm = getFrontmatter(params.lines)
if (!fm) return
// Currently this list is hardcoded, but in the future we plan to
// use a custom key in the frontmatter to determine which keys
// contain Liquid.
const keysWithLiquid = ['title', 'shortTitle', 'intro', 'product', 'permissions'].filter(
(key) => Boolean(fm[key]),
)
for (const key of keysWithLiquid) {
const value = fm[key]
try {
liquid.parse(value)
} catch (error) {
// If the error source is not a Liquid error but rather a
// ReferenceError or bad type we should allow that error to be thrown
if (!isLiquidError(error)) throw error
const { errorDescription, columnNumber } = getErrorMessageInfo((error as Error).message)
const lineNumber = params.lines.findIndex((line) => line.trim().startsWith(`${key}:`)) + 1
// Add the key length plus 3 to the column number to account colon and
// for the space after the key and column number starting at 1.
// If there is no space after the colon, a YAMLException will be thrown.
const startRange = columnNumber + key.length + 3
// If the range is greater than the length of the line, we need to adjust the range to the end of the line
const endRange =
startRange + value.length - 1 > params.lines[lineNumber - 1].length
? params.lines[lineNumber - 1].length - startRange + 1
: value.length
const range: [number, number] = [startRange, endRange]
addError(
onError,
lineNumber,
`Liquid syntax error: ${errorDescription}`,
value,
range,
null, // No fix possible
)
}
}
},
}
/*
Attempts to parse all liquid in the Markdown content of a file
to verify the syntax is correct.
*/
export const liquidSyntax = {
names: ['GHD018', 'liquid-syntax'],
description: 'Markdown content must use valid Liquid',
tags: ['liquid'],
function: function GHD018(params: RuleParams, onError: RuleErrorCallback) {
try {
liquid.parse(params.lines.join('\n'))
} catch (error) {
// If the error source is not a Liquid error but rather a
// ReferenceError or bad type we should allow that error to be thrown
if (!isLiquidError(error)) throw error
const { errorDescription, lineNumber, columnNumber } = getErrorMessageInfo(
(error as Error).message,
)
const line = params.lines[lineNumber - 1]
// We don't have enough information to know the length of the full
// liquid tag without doing some regex testing and making assumptions
// about if the end tag is correctly formed, so we just give a
// range from the start of the tag to the end of the line.
const range: [number, number] = [columnNumber, line.slice(columnNumber - 1).length]
addError(
onError,
lineNumber,
`Liquid syntax error: ${errorDescription}`,
line,
range,
null, // No fix possible
)
}
},
}
function getErrorMessageInfo(message: string): ErrorMessageInfo {
const [errorDescription, lineString, columnString] = message.split(',')
// There has to be a line number so we'll default to line 1 if the message
// doesn't contain a line number.
if (!columnString || !lineString)
throw new Error('Liquid error message does not contain line or column number')
const lineNumber = parseInt(lineString.trim().replace('line:', ''), 10)
const columnNumber = parseInt(columnString.trim().replace('col:', ''), 10)
return { errorDescription, lineNumber, columnNumber }
}