Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions interfaces/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,44 @@ func Generate(clients []any, dir string, opts ...Option) error {
return nil
}

func normalizeFullTypeName(typeName string) string {
// Preserve pointer prefix
prefix := ""
if strings.HasPrefix(typeName, "*") {
prefix = "*"
typeName = typeName[1:]
}

versionPattern := regexp.MustCompile(`/v\d+\.`)
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern is compiled on every function call. Since this function may be called multiple times during code generation (once per type parameter), consider moving the regex compilation to a package-level variable to improve performance. For example, declare var versionPattern = regexp.MustCompile(\/v\d+\.`)` at the package level and reuse it in this function.

Copilot uses AI. Check for mistakes.
parts := strings.Split(typeName, "/")
importName := parts[len(parts)-1]
if versionPattern.MatchString(typeName) {
// Example typeName: github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientCreateResponse
importName = parts[len(parts)-2] + "." + strings.Split(parts[len(parts)-1], ".")[1]
Comment on lines +170 to +172
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential index out of bounds error. The code assumes that when versionPattern matches, parts[len(parts)-1] (the last path segment) will contain a dot and can be safely split to access index [1]. While this is true for well-formed versioned import paths like armappconfiguration/v2.ConfigurationStoresClientListResponse, malformed input could cause a panic. Consider adding a bounds check or using a more defensive approach, such as checking the length of the split result before accessing index [1].

Suggested change
if versionPattern.MatchString(typeName) {
// Example typeName: github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientCreateResponse
importName = parts[len(parts)-2] + "." + strings.Split(parts[len(parts)-1], ".")[1]
if versionPattern.MatchString(typeName) && len(parts) >= 2 {
// Example typeName: github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientCreateResponse
lastParts := strings.Split(parts[len(parts)-1], ".")
if len(lastParts) >= 2 {
importName = parts[len(parts)-2] + "." + lastParts[1]
}

Copilot uses AI. Check for mistakes.
}
return prefix + importName
}

func normalizedGenericTypeName(str string) string {
// Generic output types have the full import path in the string value, so we need to normalize it
pattern := regexp.MustCompile(`\[(.*?)\]`)
groups := pattern.FindStringSubmatch((str))
pattern := regexp.MustCompile(`\[(.*)\]`)
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern is compiled on every function call. Since this function is called for each return type in method signatures, consider moving the regex compilation to a package-level variable to improve performance. For example, declare var genericTypePattern = regexp.MustCompile(\\[(.*)\]`)` at the package level and reuse it in this function.

Copilot uses AI. Check for mistakes.
groups := pattern.FindStringSubmatch(str)
if len(groups) < 2 {
return str
}

typeName := groups[1]
normalizedGenericTypeName := strings.Split(typeName, "/")
importName := normalizedGenericTypeName[len(normalizedGenericTypeName)-1]
versionPattern := regexp.MustCompile(`/v\d+\.`)
if versionPattern.MatchString(typeName) {
// Example typeName: github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientCreateResponse
importName = normalizedGenericTypeName[len(normalizedGenericTypeName)-2] + "." + strings.Split(normalizedGenericTypeName[len(normalizedGenericTypeName)-1], ".")[1]
// Handle multiple type parameters (e.g. iter.Seq2[*github.com/.../github.Artifact,error])
typeParams := strings.Split(groups[1], ",")
normalized := make([]string, len(typeParams))
for i, tp := range typeParams {
tp = strings.TrimSpace(tp)
if strings.Contains(tp, "/") {
normalized[i] = normalizeFullTypeName(tp)
} else {
normalized[i] = tp
}
}
return pattern.ReplaceAllString(str, "["+importName+"]")
return pattern.ReplaceAllString(str, "["+strings.Join(normalized, ", ")+"]")
}

// Adapted from https://stackoverflow.com/a/54129236
Expand Down
53 changes: 53 additions & 0 deletions interfaces/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,59 @@ type ConfigurationStoresClient interface {
`,
}

func TestNormalizedGenericTypeName(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "no generics",
input: "github.Response",
want: "github.Response",
},
{
name: "single type param without pointer (existing behavior)",
input: "interfaces.Pager[github.com/cloudquery/codegen/interfaces.Response]",
want: "interfaces.Pager[interfaces.Response]",
},
{
name: "single type param with versioned import",
input: "runtime.Pager[github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientListResponse]",
want: "runtime.Pager[armappconfiguration.ConfigurationStoresClientListResponse]",
},
{
name: "two type params with pointer (iter.Seq2 style)",
input: "iter.Seq2[*github.com/google/go-github/v83/github.Artifact,error]",
want: "iter.Seq2[*github.Artifact, error]",
},
{
name: "two type params without pointer",
input: "iter.Seq2[github.com/google/go-github/v83/github.Artifact,error]",
want: "iter.Seq2[github.Artifact, error]",
},
{
name: "two type params both with full paths",
input: "iter.Seq2[*github.com/google/go-github/v83/github.Artifact,*github.com/google/go-github/v83/github.Response]",
want: "iter.Seq2[*github.Artifact, *github.Response]",
},
{
name: "single type param with pointer and versioned import",
input: "runtime.Pager[*github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration/v2.ConfigurationStoresClientListResponse]",
want: "runtime.Pager[*armappconfiguration.ConfigurationStoresClientListResponse]",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := normalizedGenericTypeName(tt.input)
if diff := cmp.Diff(got, tt.want); diff != "" {
t.Errorf("normalizedGenericTypeName(%q) mismatch (-got +want):\n%s", tt.input, diff)
}
})
}
}

func TestGenerate(t *testing.T) {
dir := t.TempDir()
err := Generate([]any{&Client{}}, dir,
Expand Down