package validation import ( "fmt" "regexp" "strings" ) func ValidateConventionalCommit(commit string) error { // Regex to match the commit format // type(scope)!: description // or // type!: description // or // type: description re := regexp.MustCompile(`^(?P[a-z]+)(?P\([a-z]+\))?(?P!)?: (?P[a-z].+)$`) match := re.FindStringSubmatch(commit) if len(match) == 0 { return fmt.Errorf("Invalid PR title") } typeIndex := re.SubexpIndex("type") scopeIndex := re.SubexpIndex("scope") breakingIndex := re.SubexpIndex("breaking") descriptionIndex := re.SubexpIndex("description") commitType := match[typeIndex] scope := match[scopeIndex] breaking := match[breakingIndex] description := match[descriptionIndex] // Type MUST be lowercase if commitType != strings.ToLower(commitType) { return fmt.Errorf("type must be lowercase") } // Description MUST start with lowercase if description != strings.ToLower(description) { return fmt.Errorf("description must start with lowercase") } // Scope MUST be lowercase if scope != "" && scope != strings.ToLower(scope) { return fmt.Errorf("scope must be lowercase") } // Check for breaking change indicator hasBreakingChangeFooter := strings.Contains(commit, "BREAKING CHANGE:") if breaking == "!" && hasBreakingChangeFooter { return fmt.Errorf("breaking change indicator and footer are mutually exclusive") } lines := strings.Split(commit, "\n") if len(lines) > 1 { body := strings.Join(lines[1:], "\n") // Check if body is separated from description by a blank line if !strings.HasPrefix(body, "\n") { return fmt.Errorf("body must be separated from description by a blank line") } } // Check for footers footerRegex := regexp.MustCompile(`(?m)^(?P[A-Za-z-]+|BREAKING CHANGE): (?P.*)$`) footerMatches := footerRegex.FindAllStringSubmatch(commit, -1) for _, footerMatch := range footerMatches { tokenIndex := footerRegex.SubexpIndex("token") token := footerMatch[tokenIndex] // BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE if token == "BREAKING-CHANGE" { continue } // Token MUST use - in place of whitespace characters, except for BREAKING CHANGE if token != "BREAKING CHANGE" && strings.Contains(token, " ") { return fmt.Errorf("footer token must use - in place of whitespace characters") } } return nil }