Skip to content

Commit

Permalink
check: Add experimental contents checking (#42)
Browse files Browse the repository at this point in the history
Reference: #6
Reference: #8
Reference: #9
Reference: #10
Reference: #12
Reference: #21
Reference: #22
Reference: #39

Currently, this only supports schema ordering for section level lists and not sub-section lists.
  • Loading branch information
bflad authored Oct 26, 2020
1 parent a33eea5 commit 3c0f427
Show file tree
Hide file tree
Showing 59 changed files with 1,657 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v0.8.0

ENHANCEMENTS

* check: Add experimental `-enable-contents-check` and `-require-schema-ordering` options

# v0.7.0

ENHANCEMENTS
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ The validity of files is checked with the following rules:

The YAML frontmatter checks include some defaults (e.g. no `layout` field for Terraform Registry), but there are some useful flags that can be passed to the command to tune the behavior, especially for larger Terraform Providers.

The validity of files can also be experimentally checked (via the `-enable-contents-check` flag) with the following rules:

- Ensures all expected headings are present.
- Verifies heading levels and text.
- Verifies schema attribute lists are ordered (if `-require-schema-ordering` is provided). Only supports section level lists (not sub-section level lists) currently.
- Verifies resource type is present in code blocks (e.g. examples and import sections).

For additional information about check flags, you can run `tfproviderdocs check -help`.

## Development and Testing
Expand Down
63 changes: 63 additions & 0 deletions check/contents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package check

import (
"fmt"

"github.com/bflad/tfproviderdocs/check/contents"
)

type ContentsCheck struct {
Options *ContentsOptions
}

// ContentsOptions represents configuration options for Contents.
type ContentsOptions struct {
*FileOptions

Enable bool
ProviderName string
RequireSchemaOrdering bool
}

func NewContentsCheck(opts *ContentsOptions) *ContentsCheck {
check := &ContentsCheck{
Options: opts,
}

if check.Options == nil {
check.Options = &ContentsOptions{}
}

if check.Options.FileOptions == nil {
check.Options.FileOptions = &FileOptions{}
}

return check
}

func (check *ContentsCheck) Run(path string) error {
if !check.Options.Enable {
return nil
}

checkOpts := &contents.CheckOptions{
ArgumentsSection: &contents.CheckArgumentsSectionOptions{
RequireSchemaOrdering: check.Options.RequireSchemaOrdering,
},
AttributesSection: &contents.CheckAttributesSectionOptions{
RequireSchemaOrdering: check.Options.RequireSchemaOrdering,
},
}

doc := contents.NewDocument(path, check.Options.ProviderName)

if err := doc.Parse(); err != nil {
return fmt.Errorf("error parsing file: %w", err)
}

if err := doc.Check(checkOpts); err != nil {
return err
}

return nil
}
36 changes: 36 additions & 0 deletions check/contents/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package contents

type CheckOptions struct {
ArgumentsSection *CheckArgumentsSectionOptions
AttributesSection *CheckAttributesSectionOptions
}

func (d *Document) Check(opts *CheckOptions) error {
d.CheckOptions = opts

if err := d.checkTitleSection(); err != nil {
return err
}

if err := d.checkExampleSection(); err != nil {
return err
}

if err := d.checkArgumentsSection(); err != nil {
return err
}

if err := d.checkAttributesSection(); err != nil {
return err
}

if err := d.checkTimeoutsSection(); err != nil {
return err
}

if err := d.checkImportSection(); err != nil {
return err
}

return nil
}
47 changes: 47 additions & 0 deletions check/contents/check_arguments_section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package contents

import (
"fmt"
"sort"
)

type CheckArgumentsSectionOptions struct {
RequireSchemaOrdering bool
}

func (d *Document) checkArgumentsSection() error {
checkOpts := &CheckArgumentsSectionOptions{}

if d.CheckOptions != nil && d.CheckOptions.ArgumentsSection != nil {
checkOpts = d.CheckOptions.ArgumentsSection
}

section := d.Sections.Arguments

if section == nil {
return fmt.Errorf("missing arguments section: ## Argument Reference")
}

heading := section.Heading

if heading.Level != 2 {
return fmt.Errorf("arguments section heading level (%d) should be: 2", heading.Level)
}

headingText := string(heading.Text(d.source))
expectedHeadingText := "Argument Reference"

if headingText != expectedHeadingText {
return fmt.Errorf("arguments section heading (%s) should be: %s", headingText, expectedHeadingText)
}

if checkOpts.RequireSchemaOrdering {
for _, list := range section.SchemaAttributeLists {
if !sort.IsSorted(SchemaAttributeListItemByName(list.Items)) {
return fmt.Errorf("arguments section is not sorted by name")
}
}
}

return nil
}
77 changes: 77 additions & 0 deletions check/contents/check_arguments_section_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package contents

import (
"testing"
)

func TestCheckArgumentsSection(t *testing.T) {
testCases := []struct {
Name string
Path string
ProviderName string
CheckOptions *CheckOptions
ExpectError bool
}{
{
Name: "passing",
Path: "testdata/arguments/passing.md",
ProviderName: "test",
},
{
Name: "missing heading",
Path: "testdata/arguments/missing_heading.md",
ProviderName: "test",
ExpectError: true,
},
{
Name: "wrong heading level",
Path: "testdata/arguments/wrong_heading_level.md",
ProviderName: "test",
ExpectError: true,
},
{
Name: "wrong heading text",
Path: "testdata/arguments/wrong_heading_text.md",
ProviderName: "test",
ExpectError: true,
},
{
Name: "wrong list order",
Path: "testdata/arguments/wrong_list_order.md",
ProviderName: "test",
},
{
Name: "wrong list order",
Path: "testdata/arguments/wrong_list_order.md",
ProviderName: "test",
CheckOptions: &CheckOptions{
ArgumentsSection: &CheckArgumentsSectionOptions{
RequireSchemaOrdering: true,
},
},
ExpectError: true,
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
doc := NewDocument(testCase.Path, testCase.ProviderName)

if err := doc.Parse(); err != nil {
t.Fatalf("unexpected error: %s", err)
}

doc.CheckOptions = testCase.CheckOptions

got := doc.checkArgumentsSection()

if got == nil && testCase.ExpectError {
t.Errorf("expected error, got no error")
}

if got != nil && !testCase.ExpectError {
t.Errorf("expected no error, got error: %s", got)
}
})
}
}
61 changes: 61 additions & 0 deletions check/contents/check_attributes_section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package contents

import (
"fmt"
"sort"
)

type CheckAttributesSectionOptions struct {
RequireSchemaOrdering bool
}

func (d *Document) checkAttributesSection() error {
checkOpts := &CheckAttributesSectionOptions{}

if d.CheckOptions != nil && d.CheckOptions.AttributesSection != nil {
checkOpts = d.CheckOptions.AttributesSection
}

section := d.Sections.Attributes

if section == nil {
return fmt.Errorf("missing attributes section: ## Attributes Reference")
}

heading := section.Heading

if heading.Level != 2 {
return fmt.Errorf("attributes section heading level (%d) should be: 2", heading.Level)
}

headingText := string(heading.Text(d.source))
expectedHeadingText := "Attributes Reference"

if headingText != expectedHeadingText {
return fmt.Errorf("attributes section heading (%s) should be: %s", headingText, expectedHeadingText)
}

paragraphs := section.Paragraphs
expectedBylineText := "In addition to all arguments above, the following attributes are exported:"

switch len(paragraphs) {
case 0:
return fmt.Errorf("attributes section byline should be: %s", expectedBylineText)
case 1:
paragraphText := string(paragraphs[0].Text(d.source))

if paragraphText != expectedBylineText {
return fmt.Errorf("attributes section byline (%s) should be: %s", paragraphText, expectedBylineText)
}
}

if checkOpts.RequireSchemaOrdering {
for _, list := range section.SchemaAttributeLists {
if !sort.IsSorted(SchemaAttributeListItemByName(list.Items)) {
return fmt.Errorf("attributes section is not sorted by name")
}
}
}

return nil
}
Loading

0 comments on commit 3c0f427

Please sign in to comment.