docs(sdk): document Coords scope normalisation and accept-both contract

Adds a doc comment to ModFile.Coords explaining the leading-@ trim and a
note on ModPlugin.Scope clarifying that consumers should trim "@" before
comparing. Locks in the contract with a test asserting both call shapes
produce the same display string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-06-03 08:55:46 +08:00
parent 7af42c1c83
commit 20a7b35e50
2 changed files with 26 additions and 1 deletions

View File

@ -14,6 +14,10 @@ type ModFile struct {
type ModPlugin struct { type ModPlugin struct {
Name string `toml:"name"` Name string `toml:"name"`
// Scope is the plugin owner namespace as it appears in plugin.mod. It may
// include the leading "@" (e.g. "@themes") or omit it (e.g. "themes") —
// both forms are accepted. Consumers comparing scopes should trim the "@"
// before comparing; use ModFile.Coords() for a normalised display string.
Scope string `toml:"scope"` Scope string `toml:"scope"`
Version string `toml:"version"` Version string `toml:"version"`
Kind string `toml:"kind,omitempty"` Kind string `toml:"kind,omitempty"`
@ -37,6 +41,13 @@ func ParseModFull(b []byte) (*ModFile, error) {
return &m, nil return &m, nil
} }
// Coords returns the canonical display coordinate for the plugin in the form
// "@scope/name@version" (or "name@version" when no scope is set).
//
// The leading "@" on m.Plugin.Scope is intentionally trimmed before
// re-prefixing so that authors may write either "@themes" or "themes" in
// plugin.mod and get the same output. Callers that need the raw scope as
// written should read m.Plugin.Scope directly.
func (m *ModFile) Coords() string { func (m *ModFile) Coords() string {
if m == nil { if m == nil {
return "" return ""

View File

@ -100,6 +100,20 @@ version = "0.1.0"
} }
} }
func TestCoords_AcceptsScopeWithOrWithoutAt(t *testing.T) {
want := "@themes/foo@1.0.0"
withAt := &ModFile{Plugin: ModPlugin{Name: "foo", Scope: "@themes", Version: "1.0.0"}}
if got := withAt.Coords(); got != want {
t.Errorf("Coords() with leading @ = %q, want %q", got, want)
}
withoutAt := &ModFile{Plugin: ModPlugin{Name: "foo", Scope: "themes", Version: "1.0.0"}}
if got := withoutAt.Coords(); got != want {
t.Errorf("Coords() without leading @ = %q, want %q", got, want)
}
}
func TestParseModFull_RequiresAndCompat(t *testing.T) { func TestParseModFull_RequiresAndCompat(t *testing.T) {
src := []byte(` src := []byte(`
[plugin] [plugin]