Skip to content

Commit b85e067

Browse files
committed
chore: add initial version of GFM validator
1 parent 62951f1 commit b85e067

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

cmd/readmevalidation/codermodules.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ func validateCoderModuleReadme(rm coderResourceReadme) []error {
9494
for _, err := range validateCoderModuleReadmeBody(rm.body) {
9595
errs = append(errs, addFilePathToError(rm.filePath, err))
9696
}
97+
for _, err := range validateResourceGfmAlerts(rm.body) {
98+
errs = append(errs, addFilePathToError(rm.filePath, err))
99+
}
97100
if fmErrs := validateCoderResourceFrontmatter("modules", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
98101
errs = append(errs, fmErrs...)
99102
}

cmd/readmevalidation/coderresources.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bufio"
45
"errors"
56
"net/url"
67
"os"
@@ -277,3 +278,78 @@ func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
277278
}
278279
return allReadmeFiles, nil
279280
}
281+
282+
// Matches the format "> [!INFO]". Deliberately using a broad pattern to catch
283+
// formatting issues that can mess up the renderer for the Registry website
284+
var gfmAlertRegex = regexp.MustCompile(`^>(\s*)\[!(\w+)\](\s*)(.*)`)
285+
var gfmAlertTypes = []string{"NOTE", "IMPORTANT", "CAUTION", "WARNING", "TIP"}
286+
287+
func validateResourceGfmAlerts(readmeBody string) []error {
288+
trimmed := strings.TrimSpace(readmeBody)
289+
if trimmed == "" {
290+
return nil
291+
}
292+
293+
var errs []error
294+
var sourceLine string
295+
isInsideGfmQuotes := false
296+
isInsideCodeBlock := false
297+
298+
lineScanner := bufio.NewScanner(strings.NewReader(trimmed))
299+
for lineScanner.Scan() {
300+
sourceLine = lineScanner.Text()
301+
302+
if strings.HasPrefix(sourceLine, "```") {
303+
isInsideCodeBlock = !isInsideCodeBlock
304+
continue
305+
}
306+
if isInsideCodeBlock {
307+
continue
308+
}
309+
310+
isInsideGfmQuotes = isInsideGfmQuotes && strings.HasPrefix(sourceLine, "> ")
311+
312+
currentMatch := gfmAlertRegex.FindStringSubmatch(sourceLine)
313+
if currentMatch == nil {
314+
continue
315+
}
316+
317+
// Nested GFM alerts is such a weird mistake that it's probably not really safe to keep trying to process the
318+
// rest of the content, so this will prevent any other validations from happening for the given line
319+
if isInsideGfmQuotes {
320+
errs = append(errs, errors.New("registry does not support nested GFM alerts"))
321+
continue
322+
}
323+
324+
leadingWhitespace := currentMatch[1]
325+
if len(leadingWhitespace) != 1 {
326+
errs = append(errs, errors.New("GFM alerts must have one space between the '>' and the start of the GFM brackets"))
327+
}
328+
isInsideGfmQuotes = true
329+
330+
alertHeader := currentMatch[2]
331+
upperHeader := strings.ToUpper(alertHeader)
332+
if !slices.Contains(gfmAlertTypes, upperHeader) {
333+
errs = append(errs, xerrors.Errorf("GFM alert type %q is not supported", alertHeader))
334+
}
335+
if alertHeader != upperHeader {
336+
errs = append(errs, xerrors.Errorf("GFM alerts must be in all caps"))
337+
}
338+
339+
trailingWhitespace := currentMatch[3]
340+
if len(trailingWhitespace) != 0 {
341+
errs = append(errs, xerrors.Errorf("GFM alerts must not have any trailing whitespace after the closing bracket"))
342+
}
343+
344+
extraContent := currentMatch[4]
345+
if len(extraContent) != 0 {
346+
errs = append(errs, xerrors.Errorf("GFM alerts must not have any extra content on the same line"))
347+
}
348+
}
349+
350+
if gfmAlertRegex.Match([]byte(sourceLine)) {
351+
errs = append(errs, xerrors.Errorf("README has an incomplete GFM alert at the end of the file"))
352+
}
353+
354+
return errs
355+
}

0 commit comments

Comments
 (0)