Skip to content

Commit c510680

Browse files
committed
handle edge cases
1 parent 6b2b284 commit c510680

File tree

1 file changed

+157
-28
lines changed

1 file changed

+157
-28
lines changed

plugins/frontmatter-validation/customParseFrontMatter.js

Lines changed: 157 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const filesWithIssues = [];
66

77
/**
88
* Custom frontmatter parser that enforces specific formatting rules
9+
* with support for multi-line values
910
*
1011
* @param {Object} params - The parameters provided by Docusaurus
1112
* @param {string} params.filePath - Path to the markdown file
@@ -83,42 +84,170 @@ async function customParseFrontMatter(params) {
8384
const yamlContent = frontmatterMatch[1];
8485
const yamlLines = yamlContent.split('\n');
8586

86-
for (const line of yamlLines) {
87+
// Track multi-line values
88+
let inMultiLineValue = false;
89+
let currentFieldName = '';
90+
91+
for (let i = 0; i < yamlLines.length; i++) {
92+
const line = yamlLines[i];
8793
if (line.trim() === '') continue;
8894

89-
// Check for single space between key and value
90-
if (!/^[a-zA-Z_]+: /.test(line) && !line.includes(': [')) {
91-
issues.push(`incorrect spacing in line: "${line.trim()}"`);
92-
}
95+
// Check if this line starts a new field
96+
const fieldMatch = line.match(/^([a-zA-Z_]+):/);
97+
98+
if (fieldMatch) {
99+
// This is a new field, not a continuation
100+
inMultiLineValue = false;
101+
currentFieldName = fieldMatch[1];
102+
103+
// Check for single space between key and value
104+
if (!/^[a-zA-Z_]+: /.test(line) && !line.includes(': [')) {
105+
issues.push(`incorrect spacing in line: "${line.trim()}"`);
106+
}
107+
108+
// Check for block style arrays (should be flow style with brackets)
109+
if (line.trim().match(/^[a-zA-Z_]+: ?$/)) {
110+
// This field has no value on the same line, check if next line starts with a dash
111+
const nextLine = (i + 1 < yamlLines.length) ? yamlLines[i + 1].trim() : '';
112+
if (nextLine.startsWith('-')) {
113+
issues.push(`field '${currentFieldName}' should use flow style array with square brackets`);
114+
}
115+
}
116+
117+
// Check for single quotes on regular single-line values
118+
const fieldValue = line.substring(line.indexOf(':') + 1).trim();
119+
120+
// Check if this might be the start of a multi-line value
121+
if (fieldValue.startsWith("'") && !fieldValue.endsWith("'")) {
122+
inMultiLineValue = true;
123+
continue;
124+
}
125+
126+
// Special check for keywords array - items should be in single quotes
127+
if (currentFieldName === 'keywords' && line.includes('[')) {
128+
// Check if this is the start of a multi-line array
129+
if (line.includes('[') && !line.includes(']')) {
130+
// This is the start of a multi-line array
131+
inMultiLineValue = true;
132+
continue;
133+
}
134+
135+
// For single-line arrays
136+
if (line.includes('[') && line.includes(']')) {
137+
const arrayContent = line.substring(line.indexOf('[') + 1, line.lastIndexOf(']'));
138+
if (arrayContent.trim()) { // Only check if array is not empty
139+
const items = arrayContent.split(',').map(item => item.trim());
140+
for (const item of items) {
141+
// Check if the item is not wrapped in single quotes
142+
if (item && (!item.startsWith("'") || !item.endsWith("'"))) {
143+
issues.push(`keywords array item '${item}' should be wrapped in single quotes`);
144+
}
145+
}
146+
}
147+
}
148+
}
149+
150+
const isExcludedField = currentFieldName === 'slug' ||
151+
currentFieldName === 'id' ||
152+
currentFieldName === 'pagination_next' ||
153+
currentFieldName === 'pagination_prev';
93154

94-
// Special check for keywords array - items should be in single quotes
95-
if (line.trim().startsWith('keywords:') && line.includes('[')) {
96-
const arrayContent = line.substring(line.indexOf('[') + 1, line.lastIndexOf(']'));
97-
if (arrayContent.trim()) { // Only check if array is not empty
98-
const items = arrayContent.split(',').map(item => item.trim());
155+
if (!isExcludedField && !inMultiLineValue && (
156+
line.includes(': "') || (
157+
line.includes(': ') &&
158+
!line.includes(': \'') &&
159+
!line.includes(': [') &&
160+
!line.includes(': {') &&
161+
!line.includes(': true') &&
162+
!line.includes(': false') &&
163+
!/: \d+/.test(line)
164+
)
165+
)) {
166+
issues.push(`value should use single quotes in line: "${line.trim()}"`);
167+
}
168+
} else if (inMultiLineValue) {
169+
// This is a continuation of a multi-line value
170+
171+
// For multi-line arrays (keywords)
172+
if (currentFieldName === 'keywords') {
173+
// Check individual array items if this is a line in a multi-line array
174+
const trimmedLine = line.trim();
175+
176+
// Handle closing bracket properly
177+
if (trimmedLine === ']') {
178+
inMultiLineValue = false;
179+
continue;
180+
}
181+
182+
// Parse items carefully to handle the closing bracket
183+
let items = [];
184+
let currentItem = '';
185+
let inQuotes = false;
186+
187+
for (let j = 0; j < trimmedLine.length; j++) {
188+
const char = trimmedLine[j];
189+
190+
if (char === "'" && (j === 0 || trimmedLine[j-1] !== '\\')) {
191+
inQuotes = !inQuotes;
192+
currentItem += char;
193+
} else if (char === ',' && !inQuotes) {
194+
items.push(currentItem.trim());
195+
currentItem = '';
196+
} else if (char === ']' && !inQuotes) {
197+
// End of array - don't include the closing bracket in the item
198+
if (currentItem.trim()) {
199+
items.push(currentItem.trim());
200+
}
201+
inMultiLineValue = false;
202+
break;
203+
} else {
204+
currentItem += char;
205+
}
206+
}
207+
208+
// Add the last item if we didn't end with a bracket
209+
if (currentItem.trim() && !trimmedLine.endsWith(']')) {
210+
items.push(currentItem.trim());
211+
}
212+
213+
// Check items for proper quoting
99214
for (const item of items) {
100-
// Check if the item is not wrapped in single quotes
101-
if (item && !item.startsWith("'") || !item.endsWith("'")) {
102-
issues.push(`keywords array item '${item}' should be wrapped in single quotes`);
215+
if (item && item !== ']') {
216+
if (!item.startsWith("'") || !item.endsWith("'")) {
217+
issues.push(`keywords array item '${item}' should be wrapped in single quotes`);
218+
}
103219
}
104220
}
221+
222+
// Array end is now handled in the item parsing loop above
105223
}
106-
}
224+
// For regular multi-line strings
225+
else if (line.trim().endsWith("'")) {
226+
inMultiLineValue = false;
227+
}
228+
} else {
229+
// This is not a new field nor a continuation of a multi-line value
107230

108-
const lineStart = line.trim();
109-
const isExcludedField = lineStart.startsWith('slug:') ||
110-
lineStart.startsWith('id:') ||
111-
lineStart.startsWith('pagination_next:') ||
112-
lineStart.startsWith('pagination_prev:');
113-
if (!isExcludedField && (
114-
line.includes(': "') || (line.includes(': ') &&
115-
!line.includes(': \'') &&
116-
!line.includes(': [') &&
117-
!line.includes(': {') &&
118-
!line.includes(': true') &&
119-
!line.includes(': false') &&
120-
!/: \d+/.test(line)))) {
121-
issues.push(`value should use single quotes in line: "${line.trim()}"`);
231+
// Check for block style array items that should be flow style
232+
if (line.trim().startsWith('-')) {
233+
// Find the previous field to associate with this block array item
234+
let j = i - 1;
235+
while (j >= 0) {
236+
const prevLine = yamlLines[j].trim();
237+
if (prevLine.match(/^[a-zA-Z_]+: ?$/)) {
238+
const fieldName = prevLine.split(':')[0].trim();
239+
// Only report once per field to avoid multiple errors
240+
if (!issues.some(issue => issue.includes(`field '${fieldName}'`) && issue.includes('flow style array'))) {
241+
issues.push(`field '${fieldName}' should use flow style array with square brackets`);
242+
}
243+
break;
244+
} else if (prevLine.match(/^[a-zA-Z_]+:/)) {
245+
// Found a different field, so stop looking
246+
break;
247+
}
248+
j--;
249+
}
250+
}
122251
}
123252
}
124253
}

0 commit comments

Comments
 (0)