Skip to content

Commit

Permalink
Enhance autoformat logic for Groovy files (#4009)
Browse files Browse the repository at this point in the history
* Enhance autoformat logic for Groovy files

* Fix failing Gradle tests
  • Loading branch information
shanman190 authored Feb 16, 2024
1 parent 0727b0a commit 2055f3f
Show file tree
Hide file tree
Showing 10 changed files with 666 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
*/
package org.openrewrite.groovy;

import org.openrewrite.Cursor;
import org.openrewrite.SourceFile;
import org.openrewrite.groovy.format.MinimumViableSpacingVisitor;
import org.openrewrite.groovy.format.OmitParenthesesForLastArgumentLambdaVisitor;
import org.openrewrite.groovy.tree.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.lang.Nullable;
Expand Down Expand Up @@ -173,16 +170,4 @@ public <J2 extends J> JContainer<J2> visitContainer(JContainer<J2> container,
GContainer.Location loc, P p) {
return super.visitContainer(container, JContainer.Location.LANGUAGE_EXTENSION, p);
}

@Override
public <J2 extends J> J2 autoFormat(J2 j, @Nullable J stopAfter, P p, Cursor cursor) {
J after = super.autoFormat(j, stopAfter, p, cursor.fork());

after = new OmitParenthesesForLastArgumentLambdaVisitor<>(stopAfter).visitNonNull(after, p, cursor.fork());

after = new MinimumViableSpacingVisitor<>(stopAfter).visitNonNull(after, p, cursor.fork());

//noinspection unchecked
return (J2) after;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.groovy.format;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;

public class AutoFormat extends Recipe {
@Override
public String getDisplayName() {
return "Format Groovy code";
}

@Override
public String getDescription() {
return "Format Groovy code using a standard comprehensive set of Groovy formatting recipes.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new AutoFormatVisitor<>(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.groovy.format;

import org.openrewrite.Cursor;
import org.openrewrite.Tree;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.format.*;
import org.openrewrite.java.style.*;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaSourceFile;
import org.openrewrite.style.GeneralFormatStyle;

import java.util.Optional;

import static org.openrewrite.java.format.AutodetectGeneralFormatStyle.autodetectGeneralFormatStyle;

public class AutoFormatVisitor<P> extends GroovyIsoVisitor<P> {
@Nullable
private final Tree stopAfter;

@SuppressWarnings("unused")
public AutoFormatVisitor() {
this(null);
}

public AutoFormatVisitor(@Nullable Tree stopAfter) {
this.stopAfter = stopAfter;
}

@Override
public J visit(@Nullable Tree tree, P p, Cursor cursor) {
JavaSourceFile cu = (tree instanceof JavaSourceFile) ?
(JavaSourceFile) tree :
cursor.firstEnclosingOrThrow(JavaSourceFile.class);

J t = new NormalizeFormatVisitor<>(stopAfter).visit(tree, p, cursor.fork());

t = new BlankLinesVisitor<>(Optional.ofNullable(cu.getStyle(BlankLinesStyle.class))
.orElse(IntelliJ.blankLines()), stopAfter)
.visit(t, p, cursor.fork());

t = new WrappingAndBracesVisitor<>(Optional.ofNullable(cu.getStyle(WrappingAndBracesStyle.class))
.orElse(IntelliJ.wrappingAndBraces()), stopAfter)
.visit(t, p, cursor.fork());

t = new SpacesVisitor<>(
Optional.ofNullable(cu.getStyle(SpacesStyle.class)).orElse(IntelliJ.spaces()),
cu.getStyle(EmptyForInitializerPadStyle.class),
cu.getStyle(EmptyForIteratorPadStyle.class),
stopAfter
).visit(t, p, cursor.fork());

t = new NormalizeTabsOrSpacesVisitor<>(Optional.ofNullable(cu.getStyle(TabsAndIndentsStyle.class))
.orElse(IntelliJ.tabsAndIndents()), stopAfter)
.visit(t, p, cursor.fork());

t = new TabsAndIndentsVisitor<>(Optional.ofNullable(cu.getStyle(TabsAndIndentsStyle.class))
.orElse(IntelliJ.tabsAndIndents()), stopAfter)
.visit(t, p, cursor.fork());

t = new NormalizeLineBreaksVisitor<>(Optional.ofNullable(cu.getStyle(GeneralFormatStyle.class))
.orElse(autodetectGeneralFormatStyle(cu)), stopAfter)
.visit(t, p, cursor.fork());

t = new RemoveTrailingWhitespaceVisitor<>(stopAfter).visit(t, p, cursor.fork());

t = new OmitParenthesesForLastArgumentLambdaVisitor<>(stopAfter).visitNonNull(t, p, cursor.fork());

t = new MinimumViableSpacingVisitor<>(stopAfter).visitNonNull(t, p, cursor.fork());

return t;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@
*/
package org.openrewrite.groovy.format;

import lombok.RequiredArgsConstructor;
import org.openrewrite.Tree;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.marker.OmitParentheses;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaSourceFile;

@RequiredArgsConstructor
public class MinimumViableSpacingVisitor<P> extends GroovyIsoVisitor<P> {
public class MinimumViableSpacingVisitor<P> extends org.openrewrite.java.format.MinimumViableSpacingVisitor<P> {
@Nullable
private final Tree stopAfter;

public MinimumViableSpacingVisitor(@Nullable Tree stopAfter) {
super(stopAfter);
this.stopAfter = stopAfter;
}

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P p) {
J.MethodInvocation m = super.visitMethodInvocation(method, p);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.groovy.service;

import org.openrewrite.Tree;
import org.openrewrite.groovy.format.AutoFormatVisitor;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.service.AutoFormatService;

public class GroovyAutoFormatService extends AutoFormatService {
@Override
public <P> JavaVisitor<P> autoFormatVisitor(@Nullable Tree stopAfter) {
return new AutoFormatVisitor<>(stopAfter);
}
}
48 changes: 43 additions & 5 deletions rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import org.openrewrite.groovy.GroovyPrinter;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.internal.GroovyWhitespaceValidationService;
import org.openrewrite.groovy.service.GroovyAutoFormatService;
import org.openrewrite.internal.WhitespaceValidationService;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.internal.TypesInUse;
import org.openrewrite.java.service.AutoFormatService;
import org.openrewrite.java.tree.*;
import org.openrewrite.marker.Markers;

Expand Down Expand Up @@ -141,10 +143,22 @@ public G.CompilationUnit withPackageDeclaration(Package packageDeclaration) {
@SuppressWarnings("unchecked")
@Override
public <S, T extends S> T service(Class<S> service) {
if(WhitespaceValidationService.class.getName().equals(service.getName())) {
return (T) new GroovyWhitespaceValidationService();
String serviceName = service.getName();
try {
Class<S> serviceClass;
if (GroovyAutoFormatService.class.getName().equals(serviceName)) {
serviceClass = service;
} else if (AutoFormatService.class.getName().equals(serviceName)) {
serviceClass = (Class<S>) service.getClassLoader().loadClass(GroovyAutoFormatService.class.getName());
} else if (WhitespaceValidationService.class.getName().equals(serviceName)) {
serviceClass = (Class<S>) service.getClassLoader().loadClass(GroovyWhitespaceValidationService.class.getName());
} else {
return JavaSourceFile.super.service(service);
}
return (T) serviceClass.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
return JavaSourceFile.super.service(service);
}

List<JRightPadded<Statement>> statements;
Expand Down Expand Up @@ -271,7 +285,19 @@ public G.CompilationUnit withClasses(List<JRightPadded<ClassDeclaration>> classe
.map(i -> (JRightPadded<Statement>) (Object) i)
.collect(Collectors.toList()));

return t.getPadding().getClasses() == classes ? t : new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
List<JRightPadded<ClassDeclaration>> originalClasses = t.getPadding().getClasses();
if (originalClasses.size() != classes.size()) {
return new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
} else {
boolean hasChanges = false;
for (int i = 0; i < originalClasses.size(); i++) {
if (originalClasses.get(i) != classes.get(i)) {
hasChanges = true;
break;
}
}
return !hasChanges ? t : new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
}
}

@Transient
Expand All @@ -294,7 +320,19 @@ public G.CompilationUnit withImports(List<JRightPadded<Import>> imports) {
.map(i -> (JRightPadded<Statement>) (Object) i)
.collect(Collectors.toList()));

return t.getPadding().getImports() == imports ? t : new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
List<JRightPadded<Import>> originalImports = t.getPadding().getImports();
if (originalImports.size() != imports.size()) {
return new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
} else {
boolean hasChanges = false;
for (int i = 0; i < originalImports.size(); i++) {
if (originalImports.get(i) != imports.get(i)) {
hasChanges = true;
break;
}
}
return !hasChanges ? t : new G.CompilationUnit(t.id, t.shebang, t.prefix, t.markers, t.sourcePath, t.fileAttributes, t.charsetName, t.charsetBomMarked, t.checksum, t.packageDeclaration, statements, t.eof);
}
}

public List<JRightPadded<Statement>> getStatements() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.groovy.format;

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.groovy.Assertions.groovy;

class AutoFormatVisitorTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new AutoFormat());
}

@DocumentExample
@Test
void keepMaximumBetweenHeaderAndPackage() {
rewriteRun(
groovy(
"""
/*
* This is a sample file.
*/
package com.intellij.samples
class Test {
}
""",
"""
/*
* This is a sample file.
*/
package com.intellij.samples
class Test {
}
"""
)
);
}
}
Loading

0 comments on commit 2055f3f

Please sign in to comment.