Java: add "Layout on-demand import from the same package first" option (IDEA-204441)

enabled by default for consistency with other tools

(cherry picked from commit df5a4b65c5f31b195bd091a91d16ea0bc7bcc36c)

GitOrigin-RevId: 3b669ce15e9ad037bcff9c1e2638e2b823424ef7
This commit is contained in:
Bas Leijdekkers
2024-10-22 20:10:14 +02:00
committed by intellij-monorepo-bot
parent f4cdf03f94
commit 2ec44412eb
12 changed files with 108 additions and 49 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.codeStyle;
import com.intellij.application.options.CodeStyle;
@@ -57,7 +57,7 @@ public class JavaCodeStyleSettings extends CustomCodeStyleSettings implements Im
public boolean GENERATE_FINAL_PARAMETERS;
@PsiModifier.ModifierConstant
public String VISIBILITY = "public";
public String VISIBILITY = PsiModifier.PUBLIC;
public boolean USE_EXTERNAL_ANNOTATIONS;
public boolean GENERATE_USE_TYPE_ANNOTATION_BEFORE_TYPE = true;
@@ -170,6 +170,7 @@ public class JavaCodeStyleSettings extends CustomCodeStyleSettings implements Im
// Imports
public boolean LAYOUT_STATIC_IMPORTS_SEPARATELY = true;
public boolean LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = true;
public boolean USE_FQ_CLASS_NAMES;
public boolean USE_SINGLE_CLASS_IMPORTS = true;
public boolean INSERT_INNER_CLASS_IMPORTS;
@@ -288,7 +289,16 @@ public class JavaCodeStyleSettings extends CustomCodeStyleSettings implements Im
@Override
public void setLayoutStaticImportsSeparately(boolean value) {
LAYOUT_STATIC_IMPORTS_SEPARATELY = value;
}
@Override
public boolean isLayoutOnDemandImportFromSamePackageFirst() {
return LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST;
}
@Override
public void setLayoutOnDemandImportFromSamePackageFirst(boolean value) {
this.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = value;
}
@Override

View File

@@ -63,6 +63,7 @@ public abstract class CodeStyleImportsPanelBase extends JPanel {
myPackageList.copyFrom(settings.getPackagesToUseImportOnDemand());
myImportLayoutPanel.getCbLayoutStaticImportsSeparately().setSelected(settings.isLayoutStaticImportsSeparately());
myImportLayoutPanel.getCbLayoutOnDemandImportsFromSamePackageFirst().setSelected(settings.isLayoutOnDemandImportFromSamePackageFirst());
final JBTable importLayoutTable = myImportLayoutPanel.getImportLayoutTable();
AbstractTableModel model = (AbstractTableModel)importLayoutTable.getModel();
@@ -83,6 +84,7 @@ public abstract class CodeStyleImportsPanelBase extends JPanel {
stopTableEditing();
settings.setLayoutStaticImportsSeparately(myImportLayoutPanel.areStaticImportsEnabled());
settings.setLayoutOnDemandImportFromSamePackageFirst(myImportLayoutPanel.isLayoutOnDemandImportsFromSamePackageFirst());
kotlinUI.apply(settings);
PackageEntryTable list = myImportLayoutPanel.getImportLayoutList();
settings.getImportLayoutTable().copyFrom(getCopyWithoutEmptyPackages(list));
@@ -91,6 +93,7 @@ public abstract class CodeStyleImportsPanelBase extends JPanel {
public boolean isModifiedLayoutSettings(ImportsLayoutSettings settings) {
boolean isModified = isModified(myImportLayoutPanel.getCbLayoutStaticImportsSeparately(), settings.isLayoutStaticImportsSeparately());
isModified |= isModified(myImportLayoutPanel.getCbLayoutOnDemandImportsFromSamePackageFirst(), settings.isLayoutOnDemandImportFromSamePackageFirst());
isModified |= kotlinUI.isModified(settings);
PackageEntryTable list = myImportLayoutPanel.getImportLayoutList();

View File

@@ -37,6 +37,8 @@ import java.awt.*;
public abstract class ImportLayoutPanel extends JPanel {
private final JBCheckBox myCbLayoutStaticImportsSeparately =
new JBCheckBox(JavaBundle.message("import.layout.static.imports.separately"));
private final JBCheckBox myCbLayoutOnDemandImportsFromSamePackageFirst =
new JBCheckBox(JavaBundle.message("import.layout.on.demand.import.from.same.package.first"));
private final JBTable myImportLayoutTable;
private final PackageEntryTable myImportLayoutList = new PackageEntryTable();
@@ -53,6 +55,10 @@ public abstract class ImportLayoutPanel extends JPanel {
return myCbLayoutStaticImportsSeparately;
}
public JBCheckBox getCbLayoutOnDemandImportsFromSamePackageFirst() {
return myCbLayoutOnDemandImportsFromSamePackageFirst;
}
public ImportLayoutPanel() {
super(new BorderLayout());
@@ -108,7 +114,9 @@ public abstract class ImportLayoutPanel extends JPanel {
.setPreferredSize(new Dimension(-1, JBUI.scale(180)))
.createPanel();
final ImportLayoutPanelUI UI = new ImportLayoutPanelUI(myCbLayoutStaticImportsSeparately, importLayoutPanel);
final ImportLayoutPanelUI UI = new ImportLayoutPanelUI(myCbLayoutStaticImportsSeparately,
myCbLayoutOnDemandImportsFromSamePackageFirst,
importLayoutPanel);
add(UI.getPanel(), BorderLayout.CENTER);
}
@@ -215,6 +223,10 @@ public abstract class ImportLayoutPanel extends JPanel {
return myCbLayoutStaticImportsSeparately.isSelected();
}
public boolean isLayoutOnDemandImportsFromSamePackageFirst() {
return myCbLayoutOnDemandImportsFromSamePackageFirst.isSelected();
}
public static JBTable createTableForPackageEntries(final PackageEntryTable packageTable, final ImportLayoutPanel panel) {
final String[] names = {
JavaBundle.message("listbox.import.package"),

View File

@@ -7,9 +7,10 @@ import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.panel
import javax.swing.JPanel
internal class ImportLayoutPanelUI(staticImportsCb: JBCheckBox, importLayoutPanel: JPanel) {
internal class ImportLayoutPanelUI(staticImportsCb: JBCheckBox, onDemandBeforeSingleClassCb: JBCheckBox, importLayoutPanel: JPanel) {
val panel = panel {
group(JavaBundle.message("title.import.layout")) {
row { cell(onDemandBeforeSingleClassCb) }
row { cell(staticImportsCb) }
row { cell(importLayoutPanel).align(Align.FILL) }.resizableRow()
}.resizableRow()

View File

@@ -51,7 +51,7 @@ import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class ImportHelper{
public final class ImportHelper {
private static final Logger LOG = Logger.getInstance(ImportHelper.class);
private final JavaCodeStyleSettings mySettings;
@@ -124,7 +124,9 @@ public final class ImportHelper{
classesToUseSingle.addAll(toReimport);
try {
StringBuilder text = buildImportListText(resultList, classesOrPackagesToImportOnDemand.keySet(), classesToUseSingle, checker);
boolean onDemandFirst = mySettings.isLayoutOnDemandImportFromSamePackageFirst();
StringBuilder text =
buildImportListText(resultList, classesOrPackagesToImportOnDemand.keySet(), classesToUseSingle, checker, onDemandFirst);
for (PsiElement nonImport : nonImports) {
text.append("\n").append(nonImport.getText());
}
@@ -378,43 +380,40 @@ public final class ImportHelper{
private static @NotNull StringBuilder buildImportListText(@NotNull List<Import> imports,
@NotNull Set<String> packagesOrClassesToImportOnDemand,
@NotNull Set<String> namesToUseSingle,
@NotNull ImportUtils.ImplicitImportChecker implicitImportContext) {
Set<Import> importedPackagesOrClasses = new HashSet<>();
@NotNull ImportUtils.ImplicitImportChecker implicitImportContext,
boolean onDemandImportsFirst) {
Set<String> importedPackagesOrClasses = new HashSet<>();
@NonNls StringBuilder buffer = new StringBuilder();
for (Import importedName : imports) {
String name = importedName.name();
boolean isStatic = importedName.isStatic();
String packageOrClassName = StringUtil.getPackageName(name);
boolean implicitlyImported = implicitImportContext.isImplicitlyImported(name, isStatic);
boolean useOnDemand = implicitlyImported || packagesOrClassesToImportOnDemand.contains(packageOrClassName);
Import current = new Import(packageOrClassName, isStatic);
if (namesToUseSingle.remove(name)) {
if (useOnDemand && importedPackagesOrClasses.contains(current)) {
if (!implicitlyImported && packagesOrClassesToImportOnDemand.remove(packageOrClassName)) {
appendImportStatement(packageOrClassName + ".*", isStatic, buffer);
importedPackagesOrClasses.add(packageOrClassName);
}
if (namesToUseSingle.contains(name)) {
if (!implicitlyImported && !onDemandImportsFirst && importedPackagesOrClasses.contains(packageOrClassName)) {
buffer.insert(buffer.lastIndexOf("import "), "import " + (isStatic ? "static " : "") + name + ";\n");
continue;
}
useOnDemand = false;
else {
appendImportStatement(name, isStatic, buffer);
}
}
if (useOnDemand && (importedPackagesOrClasses.contains(current) || implicitlyImported)) continue;
buffer.append("import ");
if (isStatic) buffer.append("static ");
if (useOnDemand) {
importedPackagesOrClasses.add(current);
buffer.append(packageOrClassName).append(".*");
else if (!implicitlyImported && !importedPackagesOrClasses.contains(packageOrClassName)) {
appendImportStatement(name, isStatic, buffer);
}
else {
buffer.append(name);
}
buffer.append(";\n");
}
for (String remainingSingle : namesToUseSingle) {
buffer.append("import ").append(remainingSingle).append(";\n");
}
return buffer;
}
private static void appendImportStatement(String name, boolean isStatic, StringBuilder buffer) {
buffer.append("import ");
if (isStatic) buffer.append("static ");
buffer.append(name).append(";\n");
}
/**
* Adds import if it is needed.
* @return false when the FQN has to be used in code (e.g. when conflicting imports already exist)

View File

@@ -135,6 +135,7 @@
"label_indent_absolute": false,
"label_indent_size": 0,
"lambda_brace_style": "end_of_line",
"layout_on_demand_import_from_same_package_first": true,
"layout_static_imports_separately": true,
"line_comment_add_space": false,
"line_comment_add_space_on_reformat": false,

View File

@@ -1,7 +1,7 @@
package p2;
import p1.List;
import p1.*;
import p1.List;
A1 a1;

View File

@@ -13,6 +13,46 @@ import org.intellij.lang.annotations.Language;
@SuppressWarnings("ALL")
public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCase {
public void testLayoutOnDemandImportsFromTheSamePackageFirst() {
myFixture.addClass("package a.a; public class A {}");
myFixture.addClass("package a.b; public class A {}");
myFixture.addClass("package a.b; public class B {}");
myFixture.addClass("package a.b; public class Boolean {}");
@Language("JAVA") String code = """
package a;
import a.a.A;
import a.b.B;
import a.b.Boolean;
class Main {
A a;
B b;
Boolean bool;
}""";
myFixture.configureByText(JavaFileType.INSTANCE, code);
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
javaSettings.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = true;
javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 1;
WriteCommandAction.runWriteCommandAction(getProject(), () -> JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()));
@Language("JAVA") String result = """
package a;
import a.a.*;
import a.a.A;
import a.b.*;
import a.b.Boolean;
class Main {
A a;
B b;
Boolean bool;
}""";
myFixture.checkResult(result);
}
public void testImportLayoutStaticAndNonStaticImportsTogether() {
myFixture.addClass("package aaa; public class AAA {}");
myFixture.addClass("package aaa; public class BBB {" +
@@ -223,6 +263,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
javaSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = 1;
javaSettings.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = false;
WriteCommandAction.runWriteCommandAction(getProject(), () -> JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()));
@Language("JAVA")
@@ -246,7 +287,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
public void testConflictInPackageWithImportInName() {
myFixture.addClass("package a.importb; public class A {}");
myFixture.addClass("package a.importb; public class B {}");
myFixture.addClass("package a.importb; public class Boolean {}"); // conflict with java.lang.Process
myFixture.addClass("package a.importb; public class Boolean {}"); // conflict with java.lang.Boolean
@Language("JAVA")
String text = """
@@ -267,6 +308,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 1;
javaSettings.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = false;
WriteCommandAction.runWriteCommandAction(getProject(), () -> JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()));
@Language("JAVA")
@@ -362,6 +404,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.addEntry(new PackageEntry(false, "p", true));
javaSettings.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = false;
WriteCommandAction.runWriteCommandAction(getProject(), () -> JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()));
@Language("JAVA") String result = """

View File

@@ -364,6 +364,7 @@ icon.preview=Icon preview
ignore.imports.and.formatting=Ignore imports and formatting
illegal.name.validation.info=Illegal name: {0}
import.layout.static.imports.separately=Layout static imports separately
import.layout.on.demand.import.from.same.package.first=Place on-demand import before single-class imports from the same package
import.statically=Import statically
include.accessors=&Include Accessors
infer.nullity.progress=Post-processing results\u2026

View File

@@ -974,11 +974,13 @@ com.intellij.psi.codeStyle.ImportsLayoutSettings
- a:getNamesCountToUseImportOnDemand():I
- a:getPackagesToUseImportOnDemand():com.intellij.psi.codeStyle.PackageEntryTable
- a:isInsertInnerClassImports():Z
- isLayoutOnDemandImportFromSamePackageFirst():Z
- a:isLayoutStaticImportsSeparately():Z
- a:isUseFqClassNames():Z
- a:isUseSingleClassImports():Z
- a:setClassCountToUseImportOnDemand(I):V
- a:setInsertInnerClassImports(Z):V
- setLayoutOnDemandImportFromSamePackageFirst(Z):V
- a:setLayoutStaticImportsSeparately(Z):V
- a:setNamesCountToUseImportOnDemand(I):V
- a:setUseFqClassNames(Z):V

View File

@@ -1,23 +1,11 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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.
*/
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.codeStyle;
public interface ImportsLayoutSettings {
boolean isLayoutStaticImportsSeparately();
void setLayoutStaticImportsSeparately(boolean value);
default boolean isLayoutOnDemandImportFromSamePackageFirst() { return false; }
default void setLayoutOnDemandImportFromSamePackageFirst(boolean value) {}
int getNamesCountToUseImportOnDemand();
void setNamesCountToUseImportOnDemand(int value);
int getClassCountToUseImportOnDemand();

View File

@@ -1,6 +1,4 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.eclipse.importer;
import com.intellij.openapi.options.SchemeImportException;
@@ -34,6 +32,7 @@ public class EclipseCodeStylePropertiesImporter implements EclipseFormatterOptio
importOrderOfImports(uiPreferences, javaSettings);
importStarImportThresholds(uiPreferences, javaSettings);
javaSettings.LAYOUT_STATIC_IMPORTS_SEPARATELY = true;
javaSettings.LAYOUT_ON_DEMAND_IMPORT_FROM_SAME_PACKAGE_FIRST = true;
javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.copyFrom(new PackageEntryTable());
}