diff --git a/review/docmap.go b/review/docmap.go index 8916b6f..7317936 100644 --- a/review/docmap.go +++ b/review/docmap.go @@ -66,6 +66,18 @@ func ParseDocMapConfig(localPath string) (*DocMapConfig, error) { return &cfg, nil } +// FileCoveredByDocMap reports whether at least one paths: glob in any mapping +// of cfg matches the given file path. It is used by static validation tooling +// (e.g. the validate-docmap subcommand) to check per-file docmap coverage. +func FileCoveredByDocMap(cfg *DocMapConfig, file string) bool { + for _, mapping := range cfg.Mappings { + if mappingMatches(mapping.Paths, []string{file}) { + return true + } + } + return false +} + // MatchDocs returns deduplicated doc paths for the given changed file paths. // A mapping matches if any of its path globs matches any of the changed files. func MatchDocs(cfg *DocMapConfig, changedFiles []string) []string { diff --git a/review/docmap_test.go b/review/docmap_test.go index 7ad51c0..5e42be7 100644 --- a/review/docmap_test.go +++ b/review/docmap_test.go @@ -436,3 +436,52 @@ func writeTempYAML(t *testing.T, content string) string { } return filepath.Clean(f.Name()) } + +// ============================================================ +// FileCoveredByDocMap +// ============================================================ + +func TestFileCoveredByDocMap(t *testing.T) { + cfg := &DocMapConfig{ + Mappings: []DocMapping{ + { + Paths: []string{"lib/foo/**", "lib/bar/*.go"}, + Docs: []string{"docs/foo.md"}, + }, + { + Paths: []string{"cmd/**"}, + Docs: []string{"docs/cmd.md"}, + }, + }, + } + + cases := []struct { + file string + covered bool + }{ + {"lib/foo/baz.ex", true}, + {"lib/foo/sub/deep.ex", true}, + {"lib/bar/util.go", true}, + {"lib/bar/sub/util.go", false}, // *.go only matches one level + {"cmd/main.go", true}, + {"cmd/sub/main.go", true}, + {"internal/secret.go", false}, + {"", false}, + } + + for _, tc := range cases { + t.Run(tc.file, func(t *testing.T) { + got := FileCoveredByDocMap(cfg, tc.file) + if got != tc.covered { + t.Errorf("FileCoveredByDocMap(%q) = %v, want %v", tc.file, got, tc.covered) + } + }) + } +} + +func TestFileCoveredByDocMap_EmptyConfig(t *testing.T) { + cfg := &DocMapConfig{} + if FileCoveredByDocMap(cfg, "lib/foo/bar.go") { + t.Error("expected false for empty config, got true") + } +}