Skip to content
36 changes: 35 additions & 1 deletion impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ func stripPaths(in string) string {
out := make([]rune, 0, len(runes))
for len(runes) > 0 {
// Find extent of path-like segment
isQuotedPath := false
Comment thread
EliSauder marked this conversation as resolved.
Outdated
if runes[0] == '"' {
runes = runes[1:]
isQuotedPath = true
}
n := slices.IndexFunc(runes, isNonPathRune)
seg := runes
if n >= 0 {
Expand All @@ -98,13 +103,18 @@ func stripPaths(in string) string {
break
}
runes = runes[n:]
if isQuotedPath && len(runes) > 0 {
Comment thread
EliSauder marked this conversation as resolved.
Outdated
runes = runes[1:]
}

// Copy non-path runes verbatim
n = slices.IndexFunc(runes, isPathRune)
seg = runes
if n >= 0 {
seg = seg[:n]
}
seg = trimPathPrefix(seg)
Comment thread
EliSauder marked this conversation as resolved.
Outdated
seg = trimPathSuffix(seg)
out = append(out, seg...)
if n == -1 {
break
Expand All @@ -114,6 +124,30 @@ func stripPaths(in string) string {
return string(out)
}

func trimPathSuffix(p []rune) []rune {
if len(p) == 0 {
return p
}

if p[len(p)-1] != '"' {
return p
}

return p[:len(p)-1]
}

func trimPathPrefix(p []rune) []rune {
if len(p) == 0 {
return p
}

if p[0] != '"' {
return p
}

return p[1:]
}

// lastIndex returns the index of the last occurrence of v in s, or -1 if not present.
func lastIndex[S ~[]E, E comparable](s S, v E) int {
for i := len(s) - 1; i >= 0; i-- {
Expand Down Expand Up @@ -175,7 +209,7 @@ func findInterface(input string, srcDir string) (path string, iface Type, err er
if dot <= slash {
return "", Type{}, fmt.Errorf("invalid interface name: %s", input)
}
path = baseInput[:dot]
path = strings.Trim(baseInput[:dot], "\"")
id := stripPaths(input[dot+1:])
iface, err = parseType(id)
if err != nil {
Expand Down
16 changes: 11 additions & 5 deletions impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func TestFindInterface(t *testing.T) {
{input: "a/b/c/pkg", wantErr: true},
{input: "a/b/c/pkg.", wantErr: true},
{input: "a/b/c/pkg.Typ", path: "a/b/c/pkg", typ: Type{Name: "Typ"}},
{input: `"a/b/c/pkg".Typ`, path: "a/b/c/pkg", typ: Type{Name: "Typ"}},
{input: "gopkg.in/yaml.v2.Unmarshaler", path: "gopkg.in/yaml.v2", typ: Type{Name: "Unmarshaler"}},
{input: "github.com/josharian/impl/testdata.GenericInterface1[string]", path: "github.com/josharian/impl/testdata", typ: Type{Name: "GenericInterface1", Params: []string{"string"}}},
{input: "github.com/josharian/impl/testdata.GenericInterface1[*string]", path: "github.com/josharian/impl/testdata", typ: Type{Name: "GenericInterface1", Params: []string{"*string"}}},
Expand Down Expand Up @@ -693,35 +694,35 @@ func TestStubGenerationForImplemented(t *testing.T) {
want string
}{
{
desc: "without implemeted methods",
desc: "without implemented methods",
iface: "github.com/josharian/impl/testdata.Interface3",
recv: "r *Implemented",
recvPkg: "testdata",
want: testdata.Interface4Output,
},
{
desc: "without implemeted methods with trailing space",
desc: "without implemented methods with trailing space",
iface: "github.com/josharian/impl/testdata.Interface3",
recv: "r *Implemented ",
recvPkg: "testdata",
want: testdata.Interface4Output,
},
{
desc: "without implemeted methods, with generic receiver",
desc: "without implemented methods, with generic receiver",
iface: "github.com/josharian/impl/testdata.Interface3",
recv: "r *ImplementedGeneric[Type1]",
recvPkg: "testdata",
want: testdata.Interface4GenericOutput,
},
{
desc: "without implemeted methods, with generic receiver with multiple params",
desc: "without implemented methods, with generic receiver with multiple params",
iface: "github.com/josharian/impl/testdata.Interface3",
recv: "r *ImplementedGenericMultipleParams[Type1, Type2]",
recvPkg: "testdata",
want: testdata.Interface4GenericMultipleParamsOutput,
},
{
desc: "without implemeted methods and receiver variable",
desc: "without implemented methods and receiver variable",
iface: "github.com/josharian/impl/testdata.Interface3",
recv: "*Implemented",
recvPkg: "testdata",
Expand Down Expand Up @@ -914,8 +915,13 @@ func TestStripPaths(t *testing.T) {
}{
{desc: "no path", input: "Iface", want: "Iface"},
{desc: "simple path", input: "a/b.T", want: "b.T"},
{desc: "simple quoted path", input: `"a/b".T`, want: "b.T"},
{desc: "deep path", input: "github.com/foo/bar.T", want: "bar.T"},
{desc: "deep quoted path", input: `"github.com/foo/bar".T`, want: "bar.T"},
{desc: "generic with path param", input: "Iface[github.com/foo/bar.T]", want: "Iface[bar.T]"},
{desc: "generic and interface with path param", input: "github.com/foo.Iface[github.com/foo/bar.T]", want: "foo.Iface[bar.T]"},
{desc: "generic and interface with quoted path param", input: `"github.com/foo".Iface["github.com/foo/bar".T]`, want: "foo.Iface[bar.T]"},
{desc: "generic with quoted path param", input: `Iface["github.com/foo/bar".T]`, want: "Iface[bar.T]"},
{desc: "multiple path params", input: "Iface[a/b.T, c/d.U]", want: "Iface[b.T, d.U]"},
{desc: "nested generic with paths", input: "Iface[a/b.Other[c/d.T]]", want: "Iface[b.Other[d.T]]"},
{desc: "pointer to path type", input: "Iface[*a/b.T]", want: "Iface[*b.T]"},
Expand Down