Skip to content

ImportLayoutStyle breaking format #6107

@Pankraz76

Description

@Pankraz76

What version of OpenRewrite are you using?

I am using

  • OpenRewrite v1.2.3
  • Maven/Gradle plugin v1.2.3
  • rewrite-module v1.2.3

How are you running OpenRewrite?

I am using the Maven plugin, and my project is a single module project.

---
type: specs.openrewrite.org/v1beta/recipe
name: com.diffplug.spotless.openrewrite.SanityCheck
displayName: Apply all Java & Gradle best practices
description: Comprehensive code quality recipe combining modernization, security, and best practices.
tags:
  - java
  - gradle
  - static-analysis
  - cleanup
recipeList:
#  - org.openrewrite.gradle.EnableGradleBuildCache
#  - org.openrewrite.gradle.EnableGradleParallelExecution
#  - org.openrewrite.gradle.GradleBestPractices
#  - org.openrewrite.java.RemoveUnusedImports
  - org.openrewrite.java.OrderImports
#  - org.openrewrite.java.format.NormalizeFormat
#  - org.openrewrite.java.format.NormalizeLineBreaks
#  - org.openrewrite.java.format.RemoveTrailingWhitespace
#  - org.openrewrite.java.migrate.UpgradeToJava17
#  - org.openrewrite.java.migrate.util.JavaUtilAPIs
#  - org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose
#  - org.openrewrite.java.migrate.util.ReplaceStreamCollectWithToList
#  - org.openrewrite.java.migrate.util.SequencedCollection
#  - org.openrewrite.java.recipes.JavaRecipeBestPractices
#  - org.openrewrite.java.recipes.RecipeTestingBestPractices
#  - org.openrewrite.java.security.JavaSecurityBestPractices
#  - org.openrewrite.recipes.rewrite.OpenRewriteRecipeBestPractices
#  - org.openrewrite.staticanalysis.CommonStaticAnalysis
#  - org.openrewrite.staticanalysis.EqualsAvoidsNull
#  - org.openrewrite.staticanalysis.JavaApiBestPractices
#  - org.openrewrite.staticanalysis.LowercasePackage
#  - org.openrewrite.staticanalysis.MissingOverrideAnnotation
#  - org.openrewrite.staticanalysis.ModifierOrder
#  - org.openrewrite.staticanalysis.NoFinalizer
#  - org.openrewrite.staticanalysis.NoToStringOnStringType
#  - org.openrewrite.staticanalysis.NoValueOfOnStringType
#  - org.openrewrite.staticanalysis.RemoveUnusedLocalVariables
#  - org.openrewrite.staticanalysis.RemoveUnusedPrivateFields
#  - org.openrewrite.staticanalysis.RemoveUnusedPrivateMethods
#  - org.openrewrite.staticanalysis.UnnecessaryCloseInTryWithResources
#  - org.openrewrite.staticanalysis.UnnecessaryExplicitTypeArguments
#  - org.openrewrite.staticanalysis.UnnecessaryParentheses
#  - org.openrewrite.staticanalysis.UnnecessaryReturnAsLastStatement
#  - tech.picnic.errorprone.refasterrules.BigDecimalRulesRecipes
#  - tech.picnic.errorprone.refasterrules.CharSequenceRulesRecipes
#  - tech.picnic.errorprone.refasterrules.ClassRulesRecipes
#  - tech.picnic.errorprone.refasterrules.CollectionRulesRecipes
#  - tech.picnic.errorprone.refasterrules.ComparatorRulesRecipes
#  - tech.picnic.errorprone.refasterrules.EqualityRulesRecipes
#  - tech.picnic.errorprone.refasterrules.FileRulesRecipes
#  - tech.picnic.errorprone.refasterrules.MapRulesRecipes
#  - tech.picnic.errorprone.refasterrules.MicrometerRulesRecipes
#  - tech.picnic.errorprone.refasterrules.MockitoRulesRecipes
#  - tech.picnic.errorprone.refasterrules.NullRulesRecipes
#  - tech.picnic.errorprone.refasterrules.OptionalRulesRecipes
#  - tech.picnic.errorprone.refasterrules.PatternRulesRecipes
#  - tech.picnic.errorprone.refasterrules.PreconditionsRulesRecipes
#  - tech.picnic.errorprone.refasterrules.PrimitiveRulesRecipes
#  - tech.picnic.errorprone.refasterrules.StreamRulesRecipes
#  - tech.picnic.errorprone.refasterrules.StringRulesRecipes
#  - tech.picnic.errorprone.refasterrules.TimeRulesRecipes
---
name: com.diffplug.spotless.openrewrite.SpotlessFormat
styleConfigs:
  - org.openrewrite.java.style.ImportLayoutStyle:
      classCountToUseStarImport: 999
      nameCountToUseStarImport: 999
      layout:
        - import java.*
        - <blank line>
        - import javax.*
        - <blank line>
        - import org.*
        - <blank line>
        - import com.*
        - <blank line>
        - import com.diffplug.*
        - <blank line>
        - import static all other imports
        - <blank line>
        - import all other imports
        - <blank line>
#  - org.openrewrite.java.style.TabsAndIndentsStyle:
#      useTabCharacter: true
#      tabSize: 4
---
#Organize Import Order
#Fri Apr 24 02:36:28 PDT 2015
#5=
#4=com.diffplug
#3=com
#2=org
#1=javax
#0=java

What is the smallest, simplest way to reproduce the problem?

/*
 * Copyright 2023-2024 DiffPlug
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.diffplug.spotless.glue.java;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.FormatterFunc;

import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties;
import eu.solven.cleanthat.config.pojo.SourceCodeProperties;
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties;
import eu.solven.cleanthat.formatter.LineEnding;
import eu.solven.cleanthat.formatter.PathAndContent;

/**
 * The glue for CleanThat: it is build over the version in build.gradle, but at runtime it will be executed over
 * the version loaded in JarState, which is by default defined in com.diffplug.spotless.java.CleanthatJavaStep#JVM_SUPPORT
 */
public class JavaCleanthatRefactorerFunc implements FormatterFunc.NeedsFile {
	private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class);

	private String jdkVersion;
	private List<String> included;
	private List<String> excluded;
	private boolean includeDraft;

	public JavaCleanthatRefactorerFunc(String jdkVersion, List<String> included, List<String> excluded, boolean includeDraft) {
		this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion;
		this.included = included == null ? Collections.emptyList() : included;
		this.excluded = excluded == null ? Collections.emptyList() : excluded;
		this.includeDraft = includeDraft;
	}

	public JavaCleanthatRefactorerFunc() {
		this(IJdkVersionConstants.JDK_8, Arrays.asList("SafeAndConsensual"), Arrays.asList(), false);
	}

	@Override
	public String applyWithFile(String unix, File file) throws Exception {
		// https://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader
		ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			// Ensure CleanThat main Thread has its custom classLoader while executing its refactoring
			Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
			return doApply(unix, file);
		} finally {
			// Restore the originalClassLoader
			Thread.currentThread().setContextClassLoader(originalClassLoader);
		}
	}

	private String doApply(String input, File file) throws IOException {
		// call some API that uses reflection without taking ClassLoader param
		CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build();

		// Spotless will push us LF content
		engineProperties.setSourceCode(SourceCodeProperties.builder().lineEnding(LineEnding.LF).build());

		JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties();

		refactorerProperties.setIncluded(included);
		refactorerProperties.setExcluded(excluded);

		refactorerProperties.setIncludeDraft(includeDraft);

		JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties);

		LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded, includeDraft);
		LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded());

		PathAndContent pathAndContent = new PathAndContent(file.toPath(), input);

		return refactorer.doFormat(pathAndContent);
	}

}

What did you expect to see?

/*
 * Copyright 2023-2024 DiffPlug
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.diffplug.spotless.glue.java;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.FormatterFunc;

import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties;
import eu.solven.cleanthat.config.pojo.SourceCodeProperties;
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties;
import eu.solven.cleanthat.formatter.LineEnding;
import eu.solven.cleanthat.formatter.PathAndContent;

/**
 * The glue for CleanThat: it is build over the version in build.gradle, but at runtime it will be executed over
 * the version loaded in JarState, which is by default defined in com.diffplug.spotless.java.CleanthatJavaStep#JVM_SUPPORT
 */
public class JavaCleanthatRefactorerFunc implements FormatterFunc.NeedsFile {
	private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class);

	private String jdkVersion;
	private List<String> included;
	private List<String> excluded;
	private boolean includeDraft;

	public JavaCleanthatRefactorerFunc(String jdkVersion, List<String> included, List<String> excluded, boolean includeDraft) {
		this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion;
		this.included = included == null ? Collections.emptyList() : included;
		this.excluded = excluded == null ? Collections.emptyList() : excluded;
		this.includeDraft = includeDraft;
	}

	public JavaCleanthatRefactorerFunc() {
		this(IJdkVersionConstants.JDK_8, Arrays.asList("SafeAndConsensual"), Arrays.asList(), false);
	}

	@Override
	public String applyWithFile(String unix, File file) throws Exception {
		// https://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader
		ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			// Ensure CleanThat main Thread has its custom classLoader while executing its refactoring
			Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
			return doApply(unix, file);
		} finally {
			// Restore the originalClassLoader
			Thread.currentThread().setContextClassLoader(originalClassLoader);
		}
	}

	private String doApply(String input, File file) throws IOException {
		// call some API that uses reflection without taking ClassLoader param
		CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build();

		// Spotless will push us LF content
		engineProperties.setSourceCode(SourceCodeProperties.builder().lineEnding(LineEnding.LF).build());

		JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties();

		refactorerProperties.setIncluded(included);
		refactorerProperties.setExcluded(excluded);

		refactorerProperties.setIncludeDraft(includeDraft);

		JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties);

		LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded, includeDraft);
		LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded());

		PathAndContent pathAndContent = new PathAndContent(file.toPath(), input);

		return refactorer.doFormat(pathAndContent);
	}

}

What did you see instead?

/*
 * Copyright 2023-2024 DiffPlug
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.diffplug.spotless.glue.java;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties;
import eu.solven.cleanthat.config.pojo.SourceCodeProperties;
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties;
import eu.solven.cleanthat.formatter.LineEnding;
import eu.solven.cleanthat.formatter.PathAndContent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.FormatterFunc;

/**
 * The glue for CleanThat: it is build over the version in build.gradle, but at runtime it will be executed over
 * the version loaded in JarState, which is by default defined in com.diffplug.spotless.java.CleanthatJavaStep#JVM_SUPPORT
 */
public class JavaCleanthatRefactorerFunc implements FormatterFunc.NeedsFile {
	private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class);

	private String jdkVersion;
	private List<String> included;
	private List<String> excluded;
	private boolean includeDraft;

	public JavaCleanthatRefactorerFunc(String jdkVersion, List<String> included, List<String> excluded, boolean includeDraft) {
		this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion;
		this.included = included == null ? Collections.emptyList() : included;
		this.excluded = excluded == null ? Collections.emptyList() : excluded;
		this.includeDraft = includeDraft;
	}

	public JavaCleanthatRefactorerFunc() {
		this(IJdkVersionConstants.JDK_8, Arrays.asList("SafeAndConsensual"), Arrays.asList(), false);
	}

	@Override
	public String applyWithFile(String unix, File file) throws Exception {
		// https://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader
		ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			// Ensure CleanThat main Thread has its custom classLoader while executing its refactoring
			Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
			return doApply(unix, file);
		} finally {
			// Restore the originalClassLoader
			Thread.currentThread().setContextClassLoader(originalClassLoader);
		}
	}

	private String doApply(String input, File file) throws IOException {
		// call some API that uses reflection without taking ClassLoader param
		CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build();

		// Spotless will push us LF content
		engineProperties.setSourceCode(SourceCodeProperties.builder().lineEnding(LineEnding.LF).build());

		JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties();

		refactorerProperties.setIncluded(included);
		refactorerProperties.setExcluded(excluded);

		refactorerProperties.setIncludeDraft(includeDraft);

		JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties);

		LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded, includeDraft);
		LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded());

		PathAndContent pathAndContent = new PathAndContent(file.toPath(), input);

		return refactorer.doFormat(pathAndContent);
	}

}
Image

What is the full stack trace of any errors you encountered?

stacktrace output here

Are you interested in contributing a fix to OpenRewrite?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions