Skip to content
Closed
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
2 changes: 1 addition & 1 deletion rewrite-go/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rewrite/test-classpath.txt
test-classpath.txt
50 changes: 41 additions & 9 deletions rewrite-go/pkg/parser/go_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ func (ctx *parseContext) mapFieldListAsParams(fl *ast.FieldList) tree.Container[
} else {
closeParen := ctx.prefix(fl.Closing)
ctx.skip(1) // ")"
if len(closeParen.Comments) > 0 {
if !closeParen.IsEmpty() {
elements = append(elements, tree.RightPadded[tree.Statement]{
Element: &tree.Empty{ID: uuid.New()},
After: closeParen,
Expand Down Expand Up @@ -2193,8 +2193,14 @@ func (ctx *parseContext) mapSliceExpr(expr *ast.SliceExpr) tree.Expression {
// mapMapType maps a map type expression like `map[K]V`.
func (ctx *parseContext) mapMapType(expr *ast.MapType) tree.Expression {
prefix := ctx.prefixAndSkip(expr.Map, len("map"))
lbrackPrefix := ctx.prefix(expr.Map + token.Pos(len("map")))
ctx.skip(1) // "["
lbrackOff := ctx.findNext('[')
var lbrackPrefix tree.Space
if lbrackOff >= 0 {
lbrackPrefix = ctx.prefix(ctx.file.Pos(lbrackOff))
ctx.skip(1) // "["
} else {
ctx.skip(1) // "["
}
key := ctx.mapTypeExpr(expr.Key)
rbrackOff := ctx.findNext(']')
var rbrackPrefix tree.Space
Expand All @@ -2216,26 +2222,52 @@ func (ctx *parseContext) mapMapType(expr *ast.MapType) tree.Expression {
// mapChanType maps a channel type expression.
func (ctx *parseContext) mapChanType(expr *ast.ChanType) tree.Expression {
prefix := ctx.prefix(expr.Begin)
var markers tree.Markers

var dir tree.ChanDir
switch expr.Dir {
case ast.SEND:
dir = tree.ChanSendOnly
ctx.skip(len("chan<-"))
ctx.skip(len("chan"))
// Capture space before the arrow
arrowOff := ctx.findNext('<')
var dirMarkerBefore tree.Space
if arrowOff >= 0 {
dirMarkerBefore = ctx.prefix(ctx.file.Pos(arrowOff))
ctx.cursor = arrowOff
}
ctx.skip(2) // "<-"
// Create marker to store the space
if !dirMarkerBefore.IsEmpty() {
markers = tree.Markers{
ID: uuid.New(),
Entries: []tree.Marker{tree.ChanDirMarker{
Ident: uuid.New(),
Before: dirMarkerBefore,
}},
}
}
case ast.RECV:
dir = tree.ChanRecvOnly
ctx.skip(len("<-chan"))
ctx.skip(2) // "<-"
// Skip any whitespace/comments before "chan"
chanOff := ctx.findNext('c')
if chanOff >= 0 && chanOff+4 <= len(ctx.src) && string(ctx.src[chanOff:chanOff+4]) == "chan" {
ctx.cursor = chanOff
}
ctx.skip(len("chan"))
default:
dir = tree.ChanBidi
ctx.skip(len("chan"))
}

value := ctx.mapTypeExpr(expr.Value)
return &tree.Channel{
ID: uuid.New(),
Prefix: prefix,
Dir: dir,
Value: value,
ID: uuid.New(),
Prefix: prefix,
Markers: markers,
Dir: dir,
Value: value,
}
}

Expand Down
12 changes: 11 additions & 1 deletion rewrite-go/pkg/printer/go_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,12 +800,22 @@ func (p *GoPrinter) VisitPointerType(pt *tree.PointerType, param any) tree.J {
func (p *GoPrinter) VisitChannel(ch *tree.Channel, param any) tree.J {
out := param.(*PrintOutputCapture)
p.beforeSyntax(ch.Prefix, ch.Markers, out)

// Check for ChanDirMarker in markers for space before direction operator
var dirMarker tree.Space
if marker := tree.FindMarker[tree.ChanDirMarker](ch.Markers); marker != nil {
dirMarker = marker.Before
}

switch ch.Dir {
case tree.ChanBidi:
out.Append("chan")
case tree.ChanSendOnly:
out.Append("chan<-")
out.Append("chan")
p.visitSpace(dirMarker, out)
out.Append("<-")
case tree.ChanRecvOnly:
p.visitSpace(dirMarker, out)
out.Append("<-chan")
}
p.Visit(ch.Value, out)
Expand Down
9 changes: 9 additions & 0 deletions rewrite-go/pkg/tree/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,15 @@ type ImportBlock struct {

func (b ImportBlock) ID() uuid.UUID { return b.Ident }

// ChanDirMarker is a marker on a Channel that stores whitespace around the direction operator.
// For SEND channels, it stores the space before `<-`. For RECV channels, it's unused (space is in Prefix).
type ChanDirMarker struct {
Ident uuid.UUID
Before Space // space before the direction operator (<- or ->)
}

func (c ChanDirMarker) ID() uuid.UUID { return c.Ident }

// MultiAssignment represents a multi-value assignment: `x, y = 1, 2` or `x, y := f()`.
type MultiAssignment struct {
ID uuid.UUID
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.golang.tree;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.marker.Marker;
import org.openrewrite.java.tree.Space;

import java.util.UUID;

@Value
@EqualsAndHashCode(callSuper = false)
public class ChanDirMarker implements Marker {
UUID id;
Space before;

public ChanDirMarker(UUID id, Space before) {
this.id = id;
this.before = before;
}

@Override
public UUID getId() {
return id;
}

@Override
public ChanDirMarker withId(UUID id) {
return new ChanDirMarker(id, this.before);
}
}
136 changes: 136 additions & 0 deletions rewrite-go/src/test/java/org/openrewrite/golang/ChanDirMarkerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.golang;

import org.junit.jupiter.api.Test;
import org.openrewrite.golang.tree.ChanDirMarker;
import org.openrewrite.golang.tree.Go;
import org.openrewrite.test.RewriteTest;
import org.openrewrite.test.SourceSpec;

import java.util.function.Consumer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.openrewrite.golang.Assertions.go;

class ChanDirMarkerTest implements RewriteTest {

@Test
void sendChannelWithSpaceBeforeArrow() {
rewriteRun(
go(
"""
package main

func test() {
ch := make(chan <- int)
}
""",
spec -> spec.afterRecipe(cu -> {
// Verify that ChanDirMarker is present and captures the space
var channelType = findChannelType(cu);
assertThat(channelType).isNotNull();

var marker = channelType.getMarkers()
.findFirst(ChanDirMarker.class);
assertThat(marker).isPresent()
.hasValueSatisfying(m -> {
assertThat(m.getBefore().getWhitespace()).contains(" ");
});
})
)
);
}

@Test
void receiveChannelWithSpaceBeforeArrow() {
rewriteRun(
go(
"""
package main

func test() {
ch := make(<- chan int)
}
""",
spec -> spec.afterRecipe(cu -> {
var channelType = findChannelType(cu);
assertThat(channelType).isNotNull();

// For receive channels, the space is typically in the prefix
// but may also be in ChanDirMarker depending on implementation
var marker = channelType.getMarkers()
.findFirst(ChanDirMarker.class);
// Just verify the marker can be found if present
assertThat(marker).isPresent();
})
)
);
}

@Test
void channelWithCommentBeforeArrow() {
rewriteRun(
go(
"""
package main

func test() {
ch := make(chan /* send */ <- int)
}
"""
)
);
}

@Test
void biDirectionalChannelNoArrow() {
rewriteRun(
go(
"""
package main

func test() {
ch := make(chan int)
}
""",
spec -> spec.afterRecipe(cu -> {
var channelType = findChannelType(cu);
assertThat(channelType).isNotNull();

var marker = channelType.getMarkers()
.findFirst(ChanDirMarker.class);
// Bi-directional channels may not have a ChanDirMarker
assertThat(marker).isEmpty();
})
)
);
}

private Go.Channel findChannelType(Go.CompilationUnit cu) {
final Go.Channel[] result = {null};
new org.openrewrite.golang.GolangVisitor<Integer>() {
@Override
public org.openrewrite.java.tree.J visitChannel(Go.Channel channel, Integer p) {
if (result[0] == null) {
result[0] = channel;
}
return super.visitChannel(channel, p);
}
}.visit(cu, 0);
return result[0];
}
}
13 changes: 13 additions & 0 deletions rewrite-go/test/array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,16 @@ func TestParseSliceExpressionOpenEnd(t *testing.T) {
}
`))
}

func TestParseArrayIntenseWhitespace(t *testing.T) {
NewRecipeSpec().RewriteRun(t,
Golang(`
package main

func f(items []int,/*c1*/fixed [5]int) []int {
_ = fixed[0]
return items[1:
/*c2*/3]
}
`))
}
16 changes: 16 additions & 0 deletions rewrite-go/test/assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,19 @@ func TestParseMultiAssignFromFunc(t *testing.T) {
}
`))
}

func TestParseAssignmentIntenseWhitespace(t *testing.T) {
NewRecipeSpec().RewriteRun(t,
Golang(`
package main

func f() {
x := 1
x +=/*c1*/3
x++
a, b := 1,
/*c2*/2
_, _, _ = x, a, b
}
`))
}
17 changes: 17 additions & 0 deletions rewrite-go/test/branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,20 @@ func TestParseGotoStatement(t *testing.T) {
}
`))
}

func TestParseBranchIntenseWhitespace(t *testing.T) {
NewRecipeSpec().RewriteRun(t,
Golang(`
package main

func f() {
outer:
for {
break/*c1*/outer
continue outer
/*c2*/goto end
}
end:
}
`))
}
17 changes: 17 additions & 0 deletions rewrite-go/test/closure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,20 @@ func TestParseClosureWithReturn(t *testing.T) {
}
`))
}

func TestParseClosureIntenseWhitespace(t *testing.T) {
NewRecipeSpec().RewriteRun(t,
Golang(`
package main

func run() {
apply(func(
x int,
/*c1*/y int,
) int {
return x +
/*c2*/y
})
}
`))
}
Loading
Loading