scripts/document/document_check.go (325 lines of code) (raw):
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
func main() {
docsFileName := strings.TrimSpace(os.Args[1])
docsFile, err := os.OpenFile(docsFileName, os.O_RDONLY, 0644)
if err != nil {
log.Printf("open docs file %s failed. Error:%s", docsFileName, err)
os.Exit(1)
}
defer docsFile.Close()
scanner := bufio.NewScanner(docsFile)
docsFileNameParts := strings.Split(docsFileName, "/")
resourceName := "alicloud_" + strings.TrimSuffix(docsFileNameParts[len(docsFileNameParts)-1], ".html.markdown")
exitCode := 0
fmt.Printf("\n==> Checking docs content of %s ...", docsFileName)
titleCheck := false
titleDescriptionCheck := false
descriptionCheck := false
versionChecked := false
exampleCheck := false
exampleBlockOpen := false
argumentCheck := false
attributesCheck := false
importCheck := false
importBlockOpen := false
timeoutCheck := false
line := 0
for scanner.Scan() {
line += 1
text := scanner.Text()
if line == 1 {
titleCheck = true
if text != "---" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "---", text)
exitCode = 1
}
continue
}
if titleCheck {
if strings.HasPrefix(text, "layout:") && text != "layout: \"alicloud\"" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "layout: \"alicloud\"", text)
exitCode = 1
continue
}
if strings.HasPrefix(text, "page_title:") && text != "page_title: \"Alicloud: "+resourceName+"\"" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "page_title: \"Alicloud: "+resourceName, text)
exitCode = 1
continue
}
if strings.HasPrefix(text, "description:") {
titleDescriptionCheck = true
if text != "description: |-" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "description: |- ", text)
exitCode = 1
}
continue
}
if titleDescriptionCheck {
titleDescriptionCheck = false
if !strings.HasPrefix(text, " ") {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, " "+text, text)
exitCode = 1
}
continue
}
if text == "---" {
titleCheck = false
descriptionCheck = true
continue
}
}
if descriptionCheck {
if strings.HasPrefix(text, "# alicloud") || strings.HasSuffix(text, resourceName) {
if titleCheck {
fmt.Printf("\n[Error] line %d: docs title has not been closed.", line)
exitCode = 1
}
if strings.Contains(text, "\\") {
fmt.Printf("\n[WARNING] line %d: please remove the \\", line)
text = strings.Replace(text, "\\", "", -1)
}
if text != "# "+resourceName {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "# "+resourceName, text)
exitCode = 1
}
continue
}
if strings.Contains(text, "https://help.aliyun.com/") {
fmt.Printf("\n[Error] line %d: Expected: an international site link: %s. Got: %s.", line, "https://www.alibabacloud.com/help", "https://help.aliyun.com/")
exitCode = 1
continue
}
if strings.Contains(text, "Available since v") {
versionChecked = true
v := "Available since v"
if strings.Contains(text, v) && !strings.HasPrefix(text, "-> **NOTE:** "+v) {
parts := strings.Split(text, v)
fmt.Printf("\n[Error] line %d: Expected: %s Got: %s", line, "-> **NOTE:** "+v+strings.Replace(parts[1], "+", "", -1), text)
exitCode = 1
}
continue
}
if strings.Contains(text, "Available in v") {
versionChecked = true
v := "Available in v"
if strings.Contains(text, v) {
parts := strings.Split(text, v)
v = strings.Replace(strings.Replace(v, "in", "since", -1), "from", "since", -1)
fmt.Printf("\n[Error] line %d: Expected: %s Got: %s", line, parts[0]+v+strings.Replace(parts[1], "+", "", -1), text)
exitCode = 1
}
continue
}
if strings.Contains(text, "-> **DEPRECATED:**") || strings.Contains(text, "-> **NOTE:** deprecated since") ||
strings.Contains(text, "-> **NOTE:** Deprecated since") ||
(strings.Contains(text, "This resource has been") && (strings.Contains(text, "deprecated") || strings.Contains(text, "DEPRECATED"))) {
versionChecked = true
if !strings.HasPrefix(text, "-> **DEPRECATED:**") {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "-> **DEPRECATED:** This [resource|data source] has been deprecated from vxxx", text)
exitCode = 1
}
continue
}
if strings.HasPrefix(text, "## Example Usage") || strings.HasSuffix(text, "Example Usage") {
descriptionCheck = false
exampleCheck = true
if !versionChecked {
fmt.Printf("\n[Error] line %d: missing available or deprecated verison info.", line)
exitCode = 1
}
if text != "## Example Usage" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Example Usage", text)
exitCode = 1
}
continue
}
}
if exampleCheck {
if strings.HasPrefix(text, "```") {
if !exampleBlockOpen {
exampleBlockOpen = true
} else {
exampleBlockOpen = false
if text != "```" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "```", text)
exitCode = 1
}
}
if exampleBlockOpen && text != "```terraform" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "```terraform", text)
exitCode = 1
}
continue
}
if exampleBlockOpen && strings.Contains(text, "test") {
text = strings.TrimSpace(text)
parts := strings.Split(text, "=")
if key := strings.TrimSpace(parts[0]); key == "default" || strings.HasSuffix(key, "name") {
if value := strings.TrimSpace(parts[1]); strings.Contains(value, "test") {
fmt.Printf("\n[Error] line %d: avoid using 'test' as soon as possable in the example", line)
exitCode = 1
}
}
}
if exampleBlockOpen && strings.Contains(text, "depends_on") {
text = strings.TrimSpace(text)
parts := strings.Split(text, "=")
if key := strings.TrimSpace(parts[0]); key == "depends_on" {
fmt.Printf("\n[Error] line %d: avoid using 'depends_on' as soon as possable in the example", line)
exitCode = 1
}
}
if strings.HasPrefix(text, "## Argument Reference") || strings.HasSuffix(text, "Argument Reference") {
exampleCheck = false
argumentCheck = true
if exampleBlockOpen {
fmt.Printf("\n[Error] line %d: example block has not been closed.", line)
exitCode = 1
}
if text != "## Argument Reference" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Argument Reference", text)
exitCode = 1
}
continue
}
if !argumentCheck && strings.HasPrefix(text, "## Attributes Reference") || strings.HasSuffix(text, "Attributes Reference") {
exampleCheck = false
attributesCheck = true
if text != "## Attributes Reference" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Attributes Reference", text)
exitCode = 1
}
continue
}
}
if argumentCheck {
if strings.HasPrefix(text, "* `") {
parts := strings.Split(strings.Split(text, ")")[0], "(")
for i, tag := range strings.Split(parts[len(parts)-1], ",") {
if strings.HasPrefix(tag, " ") {
if i == 0 {
fmt.Printf("\n[Error] line %d: please remove redundant space prefix for %s. ", line, tag)
exitCode = 1
}
} else if i > 0 {
fmt.Printf("\n[Error] line %d: missing space prefix for %s. ", line, tag)
exitCode = 1
}
if strings.HasSuffix(tag, " ") {
fmt.Printf("\n[Error] line %d: please remove redundant space suffix for %s.", line, tag)
exitCode = 1
}
}
continue
}
if strings.HasPrefix(text, "###") {
parts := strings.Split(text, " ")
block := strings.ToLower(parts[len(parts)-1])
if len(parts) > 2 ||
(strings.HasPrefix(block, "`") && !strings.HasSuffix(block, "`")) ||
(!strings.HasPrefix(block, "`") && strings.HasSuffix(block, "`")) {
block = strings.Trim(block, "`")
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "### `"+block+"`", text)
exitCode = 1
}
block = strings.Trim(block, "`")
blockParts := strings.Split(block, "-")
blockLink := fmt.Sprintf("[`%s`](#%s)", blockParts[len(blockParts)-1], block)
docsContent, err := os.ReadFile(docsFileName)
if err != nil {
fmt.Printf("\n[Error] reading docs file %s failed. Error: %s", docsFileName, err)
exitCode = 1
} else if !strings.Contains(string(docsContent), blockLink) {
fmt.Printf("\n[Error] line %d: missing link for block `%s`. Expected link like: See %s below.", line, block, blockLink)
exitCode = 1
}
continue
}
if strings.HasPrefix(text, "## Argument Reference") || strings.HasSuffix(text, "Argument Reference") {
argumentCheck = false
attributesCheck = true
if text != "## Attributes Reference" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Attributes Reference", text)
exitCode = 1
}
continue
}
if strings.HasPrefix(text, "## Attributes Reference") || strings.HasSuffix(text, "Attributes Reference") {
argumentCheck = false
attributesCheck = true
if text != "## Attributes Reference" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Attributes Reference", text)
exitCode = 1
}
continue
}
}
if attributesCheck {
if strings.HasPrefix(text, "## Timeout") || strings.HasSuffix(text, "Timeouts") || strings.HasSuffix(text, "Timeout") {
attributesCheck = false
timeoutCheck = true
if text != "## Timeouts" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Timeouts", text)
exitCode = 1
}
continue
}
if strings.HasPrefix(text, "## Import") || strings.HasSuffix(text, "Import") {
attributesCheck = false
importCheck = true
if text != "## Import" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Import", text)
exitCode = 1
}
continue
}
}
if timeoutCheck {
if strings.HasPrefix(text, "## Import") || strings.HasSuffix(text, "Import") {
timeoutCheck = false
importCheck = true
if text != "## Import" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "## Import", text)
exitCode = 1
}
continue
}
}
if importCheck {
if !importBlockOpen && strings.Contains(text, "terraform import") {
fmt.Printf("\n[Error] line %d: Expected: %s.", line-1, "```shell")
exitCode = 1
break
}
if strings.HasPrefix(text, "```") {
if !importBlockOpen {
importBlockOpen = true
} else {
importBlockOpen = false
if text != "```" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "```", text)
exitCode = 1
}
}
if importBlockOpen && text != "```shell" {
fmt.Printf("\n[Error] line %d: Expected: %s. Got: %s.", line, "```shell", text)
exitCode = 1
}
continue
}
}
}
if importBlockOpen {
fmt.Println("\n[Error] Error: import block has not been closed.")
exitCode = 1
}
fmt.Println("\n--- Finished!\n")
os.Exit(exitCode)
}