From aa9938e770d1b7ebd65900fcdfe240ac6706687d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 16 Oct 2025 12:46:36 -0500 Subject: [PATCH 1/3] feat: go docs to be formatted as JSDocs --- bindings/bindings.go | 42 ++++++++++++++++++++++++++++- bindings/comments.go | 5 ++++ testdata/comments/comments.ts | 26 ++++++++++++------ testdata/enums/enums.ts | 4 ++- testdata/external/external.ts | 4 ++- testdata/generics/generics.ts | 8 ++++-- testdata/inheritance/inheritance.ts | 6 +++-- testdata/lintignore/lintignore.ts | 8 ++++-- testdata/nonids/nonids.ts | 4 ++- testdata/nullable/nullable.ts | 4 ++- testdata/union/union.ts | 4 ++- 11 files changed, 95 insertions(+), 20 deletions(-) diff --git a/bindings/bindings.go b/bindings/bindings.go index ede3a01..517fef1 100644 --- a/bindings/bindings.go +++ b/bindings/bindings.go @@ -2,6 +2,7 @@ package bindings import ( "fmt" + "strings" "github.com/dop251/goja" "golang.org/x/xerrors" @@ -733,8 +734,47 @@ func (b *Bindings) CommentGojaObject(comments []SyntheticComment, object *goja.O return nil, err } - node := object + // Group all comments that should be included into a JSDoc block. + jsDoc := make([]SyntheticComment, 0) + rest := make([]SyntheticComment, 0) + for _, c := range comments { + if !c.DoNotFormat && c.Leading && c.SingleLine { + jsDoc = append(jsDoc, c) + continue + } + rest = append(rest, c) + } + + // JSDoc comments should be blocked together + node := object + if len(jsDoc) > 0 { + var jsDocComment strings.Builder + // JSDoc requires '/**' start and ' */' end. The default synthetic comment only places 1 '*'. + // So include the second '*', and start the comment line. + jsDocComment.WriteString("*\n *") + sep := "" + for _, cmt := range jsDoc { + jsDocComment.WriteString(sep) + jsDocComment.WriteString(cmt.Text) + sep = "\n *" + } + jsDocComment.WriteString("\n ") + + res, err := commentF(goja.Undefined(), + node, + b.vm.ToValue(true), + b.vm.ToValue(false), + b.vm.ToValue(jsDocComment.String()), + b.vm.ToValue(true), + ) + if err != nil { + return nil, xerrors.Errorf("call addSyntheticComment for JSDoc: %w", err) + } + node = res.ToObject(b.vm) + } + + for _, c := range rest { res, err := commentF(goja.Undefined(), node, b.vm.ToValue(c.Leading), diff --git a/bindings/comments.go b/bindings/comments.go index 599c13f..7ac6cd6 100644 --- a/bindings/comments.go +++ b/bindings/comments.go @@ -19,6 +19,10 @@ type SyntheticComment struct { SingleLine bool Text string TrailingNewLine bool + + // DoNotFormat indicates this comment should not be attempted to be reformatted. + // Guts will attempt to format comments into JSDoc style comments where possible. + DoNotFormat bool } type SupportComments struct { @@ -66,5 +70,6 @@ func (s Source) SourceComment() (SyntheticComment, bool) { SingleLine: true, Text: fmt.Sprintf(" From %s", s.File), TrailingNewLine: false, + DoNotFormat: true, }, s.File != "" } diff --git a/testdata/comments/comments.ts b/testdata/comments/comments.ts index 0c7cf9c..5c4abfe 100644 --- a/testdata/comments/comments.ts +++ b/testdata/comments/comments.ts @@ -8,18 +8,26 @@ export interface BlockComment { } // From comments/comments.go -// CommentedStructure is a struct with a comment. -// -// It actually has 2 comments?! -// TODO: Maybe add a third comment! +/** + * CommentedStructure is a struct with a comment. + * + * It actually has 2 comments?! + * TODO: Maybe add a third comment! + */ export interface CommentedStructure { readonly Inline: string; // Field comment - // Leading comment + /** + * Leading comment + */ readonly Leading: string; readonly Trailing: string; - // Leading comment + /** + * Leading comment + */ readonly All: string; // Inline comment - // Another leading comment + /** + * Another leading comment + */ readonly Block: string; /* Multi Line @@ -29,7 +37,9 @@ export interface CommentedStructure { } // From comments/comments.go -// Constant is just a value +/** + * Constant is just a value + */ export const Constant = "value"; // An inline note diff --git a/testdata/enums/enums.ts b/testdata/enums/enums.ts index 5d438c2..dd5d223 100644 --- a/testdata/enums/enums.ts +++ b/testdata/enums/enums.ts @@ -14,7 +14,9 @@ export enum EnumInt { } // From enums/enums.go -// EnumSliceType is a slice of string-based enums +/** + * EnumSliceType is a slice of string-based enums + */ export type EnumSliceType = readonly EnumString[]; // From enums/enums.go diff --git a/testdata/external/external.ts b/testdata/external/external.ts index d4b6fc4..75038f6 100644 --- a/testdata/external/external.ts +++ b/testdata/external/external.ts @@ -2,6 +2,8 @@ // From external/external.go export interface ExternalField { - // external type "github.com/fatih/structtag.Tag", to include this type the package must be explicitly included in the parsing + /** + * external type "github.com/fatih/structtag.Tag", to include this type the package must be explicitly included in the parsing + */ readonly GojaField: unknown; } diff --git a/testdata/generics/generics.ts b/testdata/generics/generics.ts index 9c9dbc3..c43a217 100644 --- a/testdata/generics/generics.ts +++ b/testdata/generics/generics.ts @@ -25,7 +25,9 @@ export interface Complex { readonly dynamic: Fields; readonly comparable: boolean | null; @@ -49,7 +51,9 @@ export interface FieldsDiffOrder; } diff --git a/testdata/inheritance/inheritance.ts b/testdata/inheritance/inheritance.ts index ccfd3f6..1a2a739 100644 --- a/testdata/inheritance/inheritance.ts +++ b/testdata/inheritance/inheritance.ts @@ -21,8 +21,10 @@ export interface FooBarPtr extends Bar, GenBar { } // From codersdk/inheritance.go -// FooBuzz has a json tag for the embedded -// See: https://go.dev/play/p/-p6QYmY8mtR +/** + * FooBuzz has a json tag for the embedded + * See: https://go.dev/play/p/-p6QYmY8mtR + */ export interface FooBuzz { readonly foo: Buzz; // Json tag changes the inheritance readonly bazz: string; diff --git a/testdata/lintignore/lintignore.ts b/testdata/lintignore/lintignore.ts index 53c8d2e..393b6d5 100644 --- a/testdata/lintignore/lintignore.ts +++ b/testdata/lintignore/lintignore.ts @@ -4,9 +4,13 @@ export type AnyThing = any; // From lintignore/lintignore.go -// biome-ignore lint lint/complexity/noUselessTypeConstraint: golang does 'any' for generics, typescript does not like it +/** + * biome-ignore lint lint/complexity/noUselessTypeConstraint: golang does 'any' for generics, typescript does not like it + */ export interface LintIgnore { readonly Foo: T; - // empty interface{} type, falling back to unknown + /** + * empty interface{} type, falling back to unknown + */ readonly AnyFoo: unknown; } diff --git a/testdata/nonids/nonids.ts b/testdata/nonids/nonids.ts index 795759b..b369b5a 100644 --- a/testdata/nonids/nonids.ts +++ b/testdata/nonids/nonids.ts @@ -2,7 +2,9 @@ // From nonids/nonids.go export interface Foo { - // Hyphen is an odd case, but this field is not ignored + /** + * Hyphen is an odd case, but this field is not ignored + */ readonly "-": string; readonly "hyphenated-string": string; readonly "1numbered": number; diff --git a/testdata/nullable/nullable.ts b/testdata/nullable/nullable.ts index 8c5ec8e..94db9d8 100644 --- a/testdata/nullable/nullable.ts +++ b/testdata/nullable/nullable.ts @@ -2,7 +2,9 @@ // From nullable/nullable.go export interface EmptyFields { - // empty interface{} type, falling back to unknown + /** + * empty interface{} type, falling back to unknown + */ readonly empty: unknown; } diff --git a/testdata/union/union.ts b/testdata/union/union.ts index 326de9c..dadd9e4 100644 --- a/testdata/union/union.ts +++ b/testdata/union/union.ts @@ -1,7 +1,9 @@ // Code generated by 'guts'. DO NOT EDIT. // From union/union.go -// Repeated constraints are redundant +/** + * Repeated constraints are redundant + */ export interface Repeated { readonly Value: T; } From 04c6ba20fa2bbddf71664a8af94879845a8fd5d4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 16 Oct 2025 12:57:31 -0500 Subject: [PATCH 2/3] system generated messages to not appear in JSDoc --- bindings/comments.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/comments.go b/bindings/comments.go index 7ac6cd6..cf60b19 100644 --- a/bindings/comments.go +++ b/bindings/comments.go @@ -29,7 +29,6 @@ type SupportComments struct { comments []SyntheticComment } -// LeadingComment is a helper function for the most common type of comment. func (s *SupportComments) LeadingComment(text string) { s.AppendComment(SyntheticComment{ Leading: true, @@ -37,6 +36,7 @@ func (s *SupportComments) LeadingComment(text string) { // All go comments are `// ` prefixed, so add a space. Text: " " + text, TrailingNewLine: false, + DoNotFormat: true, }) } From aa336436b1028dc4f8825fe7ad533c95db500a4e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 16 Oct 2025 13:02:51 -0500 Subject: [PATCH 3/3] chore: add a mutation to not format jsdocs --- config/mutations.go | 21 +++++++++++++++++++++ convert_test.go | 2 ++ testdata/external/external.ts | 4 +--- testdata/lintignore/lintignore.ts | 8 ++------ testdata/nojsdoc/mutations | 1 + testdata/nojsdoc/nojsdoc.go | 7 +++++++ testdata/nojsdoc/nojsdoc.ts | 8 ++++++++ testdata/nullable/nullable.ts | 4 +--- 8 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 testdata/nojsdoc/mutations create mode 100644 testdata/nojsdoc/nojsdoc.go create mode 100644 testdata/nojsdoc/nojsdoc.ts diff --git a/config/mutations.go b/config/mutations.go index 7035516..ae26b43 100644 --- a/config/mutations.go +++ b/config/mutations.go @@ -417,3 +417,24 @@ func isGoEnum(n bindings.Node) (*bindings.Alias, *bindings.UnionType, bool) { return al, union, true } + +// NoJSDocTransform prevents `guts` from reformatting Golang comments to JSDoc. +// JSDoc comments use `/** */` style multi-line comments. +func NoJSDocTransform(ts *guts.Typescript) { + ts.ForEach(func(key string, node bindings.Node) { + walk.Walk(&noJSDocTransformWalker{}, node) + }) +} + +type noJSDocTransformWalker struct{} + +func (v *noJSDocTransformWalker) Visit(node bindings.Node) walk.Visitor { + if commentedNode, ok := node.(bindings.Commentable); ok { + comments := commentedNode.Comments() + for i := range comments { + comments[i].DoNotFormat = true + } + } + + return v +} diff --git a/convert_test.go b/convert_test.go index c6f4d42..ee3988f 100644 --- a/convert_test.go +++ b/convert_test.go @@ -130,6 +130,8 @@ func TestGeneration(t *testing.T) { mutations = append(mutations, config.InterfaceToType) case "BiomeLintIgnoreAnyTypeParameters": mutations = append(mutations, config.BiomeLintIgnoreAnyTypeParameters) + case "NoJSDocTransform": + mutations = append(mutations, config.NoJSDocTransform) default: t.Fatal("unknown mutation, add it to the list:", m) } diff --git a/testdata/external/external.ts b/testdata/external/external.ts index 75038f6..d4b6fc4 100644 --- a/testdata/external/external.ts +++ b/testdata/external/external.ts @@ -2,8 +2,6 @@ // From external/external.go export interface ExternalField { - /** - * external type "github.com/fatih/structtag.Tag", to include this type the package must be explicitly included in the parsing - */ + // external type "github.com/fatih/structtag.Tag", to include this type the package must be explicitly included in the parsing readonly GojaField: unknown; } diff --git a/testdata/lintignore/lintignore.ts b/testdata/lintignore/lintignore.ts index 393b6d5..53c8d2e 100644 --- a/testdata/lintignore/lintignore.ts +++ b/testdata/lintignore/lintignore.ts @@ -4,13 +4,9 @@ export type AnyThing = any; // From lintignore/lintignore.go -/** - * biome-ignore lint lint/complexity/noUselessTypeConstraint: golang does 'any' for generics, typescript does not like it - */ +// biome-ignore lint lint/complexity/noUselessTypeConstraint: golang does 'any' for generics, typescript does not like it export interface LintIgnore { readonly Foo: T; - /** - * empty interface{} type, falling back to unknown - */ + // empty interface{} type, falling back to unknown readonly AnyFoo: unknown; } diff --git a/testdata/nojsdoc/mutations b/testdata/nojsdoc/mutations new file mode 100644 index 0000000..77c1593 --- /dev/null +++ b/testdata/nojsdoc/mutations @@ -0,0 +1 @@ +NoJSDocTransform \ No newline at end of file diff --git a/testdata/nojsdoc/nojsdoc.go b/testdata/nojsdoc/nojsdoc.go new file mode 100644 index 0000000..cc2511d --- /dev/null +++ b/testdata/nojsdoc/nojsdoc.go @@ -0,0 +1,7 @@ +package nojsdoc + +// Commented is a struct that does nothing +type Commented struct { + // Nothing is not very helpful + Nothing string +} diff --git a/testdata/nojsdoc/nojsdoc.ts b/testdata/nojsdoc/nojsdoc.ts new file mode 100644 index 0000000..77cc54a --- /dev/null +++ b/testdata/nojsdoc/nojsdoc.ts @@ -0,0 +1,8 @@ +// Code generated by 'guts'. DO NOT EDIT. + +// From nojsdoc/nojsdoc.go +// Commented is a struct that does nothing +interface Commented { + // Nothing is not very helpful + Nothing: string; +} diff --git a/testdata/nullable/nullable.ts b/testdata/nullable/nullable.ts index 94db9d8..8c5ec8e 100644 --- a/testdata/nullable/nullable.ts +++ b/testdata/nullable/nullable.ts @@ -2,9 +2,7 @@ // From nullable/nullable.go export interface EmptyFields { - /** - * empty interface{} type, falling back to unknown - */ + // empty interface{} type, falling back to unknown readonly empty: unknown; }