Skip to content

Commit e94dfd2

Browse files
authored
fix: add validation for Github-Flavored Markdown Alerts (#394)
No issue to link – this was a problem we discovered while updating the Registry website ## Description This PR adds (very) basic validation for the GitHub Flavored Markdown alerts that we allow contributors to add to their README files. The errors that get generated should be correct, but the error messages themselves aren't as helpful as they could be. I'm going to be handling that in a separate PR, just so we can get this one in sooner. ### Changes made - Added function for validating the core structure of all GFM alerts - Updated existing README files that were failing the new validation requirements ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other
1 parent 9125a52 commit e94dfd2

File tree

5 files changed

+84
-2
lines changed

5 files changed

+84
-2
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"
@@ -16,11 +17,16 @@ import (
1617
var (
1718
supportedResourceTypes = []string{"modules", "templates"}
1819
operatingSystems = []string{"windows", "macos", "linux"}
20+
gfmAlertTypes = []string{"NOTE", "IMPORTANT", "CAUTION", "WARNING", "TIP"}
1921

2022
// TODO: This is a holdover from the validation logic used by the Coder Modules repo. It gives us some assurance, but
2123
// realistically, we probably want to parse any Terraform code snippets, and make some deeper guarantees about how it's
2224
// structured. Just validating whether it *can* be parsed as Terraform would be a big improvement.
2325
terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`)
26+
27+
// Matches the format "> [!INFO]". Deliberately using a broad pattern to catch formatting issues that can mess up
28+
// the renderer for the Registry website
29+
gfmAlertRegex = regexp.MustCompile(`^>(\s*)\[!(\w+)\](\s*)(.*)`)
2430
)
2531

2632
type coderResourceFrontmatter struct {
@@ -277,3 +283,73 @@ func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
277283
}
278284
return allReadmeFiles, nil
279285
}
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 trailingWhitespace != "" {
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 extraContent != "" {
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+
}

cmd/readmevalidation/codertemplates.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ func validateCoderTemplateReadme(rm coderResourceReadme) []error {
7070
for _, err := range validateCoderTemplateReadmeBody(rm.body) {
7171
errs = append(errs, addFilePathToError(rm.filePath, err))
7272
}
73+
for _, err := range validateResourceGfmAlerts(rm.body) {
74+
errs = append(errs, addFilePathToError(rm.filePath, err))
75+
}
7376
if fmErrs := validateCoderResourceFrontmatter("templates", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
7477
errs = append(errs, fmErrs...)
7578
}

registry/coder/templates/azure-linux/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ This means, when the workspace restarts, any tools or files outside of the home
3535
3636
### Persistent VM
3737

38-
> [!IMPORTANT]
38+
> [!IMPORTANT]
3939
> This approach requires the [`az` CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install) to be present in the PATH of your Coder Provisioner.
4040
> You will have to do this installation manually as it is not included in our official images.
4141

registry/coder/templates/azure-windows/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ This means, when the workspace restarts, any tools or files outside of the data
3535
3636
### Persistent VM
3737

38-
> [!IMPORTANT]
38+
> [!IMPORTANT]
3939
> This approach requires the [`az` CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install) to be present in the PATH of your Coder Provisioner.
4040
> You will have to do this installation manually as it is not included in our official images.
4141

0 commit comments

Comments
 (0)