diff --git a/pkg/linters/doc_sync_test.go b/pkg/linters/doc_sync_test.go index 18d13003338..10c654f945e 100644 --- a/pkg/linters/doc_sync_test.go +++ b/pkg/linters/doc_sync_test.go @@ -11,10 +11,16 @@ import ( "strings" "testing" + "github.com/github/gh-aw/pkg/linters" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +var ( + docBulletRe = regexp.MustCompile(`^//\s+-\s+([a-z0-9-]+)\s+—`) + readmeTableRe = regexp.MustCompile(`^\|\s+` + "`" + `([a-z0-9-]+)` + "`" + `\s+\|`) +) + // TestDocGo_CountMatchesBullets validates that the "All N active analyzers:" // header count in doc.go matches the actual number of bullet entries. // This prevents the header from silently drifting from the bullet list @@ -52,23 +58,58 @@ func TestDocGo_CountMatchesBullets(t *testing.T) { } func TestDocGo_AnalyzersMatchREADME(t *testing.T) { + docSet := parseDocBulletSet(t) + readmeSet := parseReadmeSubpackageSet(t) + assert.Equal(t, sortedKeys(docSet), sortedKeys(readmeSet), + "doc.go analyzer bullets and README Subpackages table must list the same analyzers; update both files together") +} + +func TestDocSurfacesMatchRegistryAndSpecList(t *testing.T) { + registrySet := make(map[string]struct{}) + for _, analyzer := range linters.All() { + registrySet[analyzer.Name] = struct{}{} + } + + docAsAnalyzerNames := normalizedDocSlugSetToAnalyzerNames(parseDocBulletSet(t)) + readmeSlugs := parseReadmeSubpackageSet(t) + readmeAsAnalyzerNames := normalizedDocSlugSetToAnalyzerNames(readmeSlugs) + + assert.Equal(t, sortedKeys(registrySet), sortedKeys(docAsAnalyzerNames), + "doc.go analyzer bullets must match linters.All(); update pkg/linters/doc.go when adding/removing linters") + assert.Equal(t, sortedKeys(registrySet), sortedKeys(readmeAsAnalyzerNames), + "README Subpackages analyzer entries must match linters.All(); update pkg/linters/README.md when adding/removing linters") + + documentedLabels := make(map[string]struct{}) + for _, d := range documentedAnalyzers() { + documentedLabels[d.label] = struct{}{} + } + assert.Equal(t, sortedKeys(readmeSlugs), sortedKeys(documentedLabels), + "documentedAnalyzers() labels in spec_test.go must match README Subpackages table labels exactly") +} + +func parseDocBulletSet(t *testing.T) map[string]struct{} { + t.Helper() + docBytes, err := os.ReadFile("doc.go") require.NoError(t, err, "doc.go must be present in pkg/linters") - readmeBytes, err := os.ReadFile("README.md") - require.NoError(t, err, "README.md must be present in pkg/linters") - docSet := make(map[string]struct{}) - readmeSet := make(map[string]struct{}) - - docBulletRe := regexp.MustCompile(`^//\s+-\s+([a-z0-9-]+)\s+—`) for line := range strings.SplitSeq(string(docBytes), "\n") { if m := docBulletRe.FindStringSubmatch(line); m != nil { docSet[m[1]] = struct{}{} } } - readmeTableRe := regexp.MustCompile(`^\|\s+` + "`" + `([a-z0-9-]+)` + "`" + `\s+\|`) + return docSet +} + +func parseReadmeSubpackageSet(t *testing.T) map[string]struct{} { + t.Helper() + + readmeBytes, err := os.ReadFile("README.md") + require.NoError(t, err, "README.md must be present in pkg/linters") + + readmeSet := make(map[string]struct{}) for line := range strings.SplitSeq(string(readmeBytes), "\n") { if !strings.HasPrefix(line, "| `") { continue @@ -83,8 +124,24 @@ func TestDocGo_AnalyzersMatchREADME(t *testing.T) { readmeSet[m[1]] = struct{}{} } - assert.Equal(t, sortedKeys(docSet), sortedKeys(readmeSet), - "doc.go analyzer bullets and README Subpackages table must list the same analyzers; update both files together") + return readmeSet +} + +func normalizedDocSlugSetToAnalyzerNames(slugSet map[string]struct{}) map[string]struct{} { + normalized := make(map[string]struct{}, len(slugSet)) + for slug := range slugSet { + normalized[docSlugToAnalyzerName(slug)] = struct{}{} + } + return normalized +} + +func docSlugToAnalyzerName(slug string) string { + switch slug { + case "panic-in-library-code": + return "panicinlibrarycode" + default: + return slug + } } func sortedKeys(set map[string]struct{}) []string {