From 6dde733975a3994f9dff556bf40f0eed5a450da8 Mon Sep 17 00:00:00 2001 From: Bart Van Bos Date: Sat, 7 Nov 2026 13:28:25 +0100 Subject: [PATCH] fix: rollout findings of v0.3.0 (v0.3.1) - DS-SEC accepts "Latest release (v1.4.x)"; the parenthesised version must be the latest minor, as a bare "v1.4.x" must. - goapi: addTests parses comments, so a //go:build ignore _test.go file is skipped and its test functions and imports are not recorded. - goapi: the ExportedNames doc comment no longer claims struct fields. Co-Authored-By: Claude Opus 5.5 --- RELEASE.md | 13 +++++++++++++ internal/check/versions.go | 25 +++++++++++++++++++++---- internal/cli/cli_test.go | 14 ++++++++++++++ internal/goapi/goapi.go | 9 +++++---- internal/goapi/goapi_test.go | 15 ++++++++++++++- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index ff485a0..a622f28 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,18 @@ # docsync Releases +## v0.3.1 + +**Date:** unreleased +**Previous release:** v0.3.0 + +### Fixed + +- DS-SEC accepts a "Latest release" supported-versions row that names the version in parentheses (`Latest release (v1.4.x)`); the version must be the latest minor, as a bare `v1.4.x` must. +- A build-ignored `_test.go` file (`//go:build ignore`) is skipped like a build-ignored non-test file: its test functions no longer count for DS-REF and its imports are no longer recorded. +- The `goapi.ExportedNames` doc comment no longer claims struct fields; it returns funcs, values, types and the methods of exported types. + +--- + ## v0.3.0 **Date:** 2026-11-07 diff --git a/internal/check/versions.go b/internal/check/versions.go index 4d11021..4e9816a 100644 --- a/internal/check/versions.go +++ b/internal/check/versions.go @@ -110,7 +110,7 @@ func normGo(v string) string { var ( emailRE = regexp.MustCompile(`[A-Za-z0-9._%+-]+@otfabric\.[a-z]+`) supportedRE = regexp.MustCompile(`(?i)^v?(\d+)\.(\d+)\.x(?:$|[\s(,;:])`) - latestRE = regexp.MustCompile(`(?i)^(?:the )?(?:latest|current|newest|most recent)(?: (?:release|version|minor))?$`) + latestRE = regexp.MustCompile(`(?i)^(?:the )?(?:latest|current|newest|most recent)(?: (?:release|version|minor))?(?:\s*\(([^()]*)\))?$`) yesRE = regexp.MustCompile(`^(?:(?i:yes|supported)\b|✓|✅|✔|:white_check_mark:)`) ) @@ -144,6 +144,24 @@ func cellText(c string) string { return strings.TrimSpace(strings.NewReplacer("`", "", "*", "", "_", "").Replace(c)) } +// supportsMinor reports whether ver starts with the minor want ("0.8.x"). +func supportsMinor(ver, want string) bool { + m := supportedRE.FindStringSubmatch(ver) + return m != nil && m[1]+"."+m[2]+".x" == want +} + +// latestRow reports whether ver is a "Latest release" cell: bare, it names +// the latest minor, whatever it is; with a parenthesised version ("Latest +// release (v1.4.x)") that version must be the latest minor, as a bare +// "v1.4.x" must. +func latestRow(ver, want string) bool { + m := latestRE.FindStringSubmatch(ver) + if m == nil { + return false + } + return !strings.Contains(ver, "(") || supportsMinor(strings.TrimSpace(m[1]), want) +} + // SecurityAddress is the only address for private security reports. const SecurityAddress = "security@otfabric.io" @@ -192,9 +210,8 @@ func Security(r *Repo) []finding.Finding { continue } ver := cellText(row.Cells[0]) // "0.8.x", "0.8.x (latest: v0.8.2)" - m := supportedRE.FindStringSubmatch(ver) - if (m != nil && m[1]+"."+m[2]+".x" == want || latestRE.MatchString(ver) || openEnded(ver, latest)) && yesRE.MatchString(strings.TrimSpace(row.Cells[1])) { - found = true // "Latest release | Yes" names the latest minor, whatever it is + if (supportsMinor(ver, want) || latestRow(ver, want) || openEnded(ver, latest)) && yesRE.MatchString(strings.TrimSpace(row.Cells[1])) { + found = true } } } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 4dcb97f..0b55614 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -208,6 +208,20 @@ func TestGitChecks(t *testing.T) { if _, out, _ := run(t, "check", dir); !strings.Contains(out, "does not support 0.3.x") { t.Errorf("0.3.xy is not 0.3.x:\n%s", out) } + // A "Latest release" row may name the version in parentheses; it must + // then be the latest minor. + for _, cell := range []string{"Latest release (v0.3.x)", "Latest release (0.3.x)", "Latest release(v0.3.x)"} { + writeFile(t, dir, "SECURITY.md", "Report to security@otfabric.io.\n\n| Version | Supported |\n|---|---|\n| "+cell+" | Yes |\n") + if code, out, _ := run(t, "check", dir); code != ExitOK { + t.Errorf("%s: exit %d\n%s", cell, code, out) + } + } + for _, cell := range []string{"Latest release (v0.2.x)", "Latest release (v0.3.xy)", "Latest release (soon)", "Latest release ()"} { + writeFile(t, dir, "SECURITY.md", "Report to security@otfabric.io.\n\n| Version | Supported |\n|---|---|\n| "+cell+" | Yes |\n") + if _, out, _ := run(t, "check", dir); !strings.Contains(out, "does not support 0.3.x") { + t.Errorf("%s does not name 0.3.x:\n%s", cell, out) + } + } // A "Latest release" row supports whichever minor is the latest. writeFile(t, dir, "SECURITY.md", "Report to security@otfabric.io.\n\n| Version | Supported |\n|---|---|\n| Latest release | Yes |\n| Older releases | No |\n") if code, out, _ := run(t, "check", dir); code != ExitOK { diff --git a/internal/goapi/goapi.go b/internal/goapi/goapi.go index 503d906..e5a01e0 100644 --- a/internal/goapi/goapi.go +++ b/internal/goapi/goapi.go @@ -225,7 +225,7 @@ var testFuncRE = regexp.MustCompile(`^(?:Test|Fuzz|Benchmark|Example)(?:$|[^a-z] // addTests records the test functions of a _test.go file. func (m *Module) addTests(fset *token.FileSet, p string) { - f, err := parser.ParseFile(fset, p, nil, parser.SkipObjectResolution) + f, err := parser.ParseFile(fset, p, nil, parser.SkipObjectResolution|parser.ParseComments) // ignored reads the comments if err != nil || ignored(f) { return } @@ -547,9 +547,10 @@ func NewIndex(m *Module) *Index { return ix } -// ExportedNames returns the exported top-level names (and the methods and -// fields of exported types) of the package in dir, a dependency's: its -// non-test Go files, whatever their build constraints. +// ExportedNames returns the exported top-level names (funcs, values and +// types) and the methods of exported types of the package in dir, a +// dependency's: its non-test Go files, whatever their build constraints. +// Struct fields are not included. func ExportedNames(dir string) map[string]bool { out := map[string]bool{} files, _ := filepath.Glob(filepath.Join(dir, "*.go")) diff --git a/internal/goapi/goapi_test.go b/internal/goapi/goapi_test.go index 9dc3697..f864f8c 100644 --- a/internal/goapi/goapi_test.go +++ b/internal/goapi/goapi_test.go @@ -77,6 +77,19 @@ import "testing" func TestExt(t *testing.T) {} `, "bad_test.go": "package m\n\nfunc TestBroken( {\n", + // A build-ignored test file is not part of the package's tests. + "ignored_test.go": `//go:build ignore + +package m + +import ( + "testing" + + "github.com/dep/ignoredonly" +) + +func TestIgnored(t *testing.T) { _ = ignoredonly.X } +`, }) m, err := Load(dir) if err != nil { @@ -165,7 +178,7 @@ type I interface{ IM() } t.Errorf("%s is missing from %v", n, got) } } - for _, n := range []string{"unexported", "f", "TestOnly", "X", "Broken"} { + for _, n := range []string{"unexported", "f", "TestOnly", "X", "Broken", "Field"} { // no struct fields if got[n] { t.Errorf("%s is in %v", n, got) }