├── .github ├── .lycheeignore ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.yml │ ├── enhancement.yml │ └── bug_report.yml ├── workflows │ ├── sync-labels.yml │ ├── test-deploy.yml │ ├── broken-links.yml │ ├── check-ide-compatibility.yml │ └── check-build.yml └── labels.yml ├── .gitattributes ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── assets ├── intellij-save-actions-plugin-settings-page.png └── intellij-save-actions-plugin-settings-page-java.png ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── externalDependencies.xml ├── inspectionProfiles │ └── Project_Default.xml ├── PMDPlugin.xml ├── checkstyle-idea.xml └── saveactions_settings.xml ├── src ├── test │ ├── java │ │ ├── org │ │ │ └── junit │ │ │ │ ├── rules │ │ │ │ ├── TestRule.java │ │ │ │ └── ExternalResource.java │ │ │ │ ├── runners │ │ │ │ └── model │ │ │ │ │ └── Statement.java │ │ │ │ ├── ComparisonFailure.java │ │ │ │ └── AssumptionViolatedException.java │ │ ├── junit │ │ │ └── framework │ │ │ │ ├── AssertionFailedError.java │ │ │ │ └── TestCase.java │ │ └── software │ │ │ └── xdev │ │ │ └── saveactions │ │ │ ├── core │ │ │ ├── action │ │ │ │ ├── BatchActionConstants.java │ │ │ │ └── ShortcutActionConstants.java │ │ │ └── component │ │ │ │ ├── SaveActionManagerConstants.java │ │ │ │ └── PsiFileTest.java │ │ │ ├── model │ │ │ └── java │ │ │ │ ├── EpfTestConstants.java │ │ │ │ ├── EpfActionTest.java │ │ │ │ └── EpfKeyTest.java │ │ │ ├── junit │ │ │ └── JUnit5Utils.java │ │ │ ├── processors │ │ │ ├── BuildProcessorTest.java │ │ │ ├── GlobalProcessorTest.java │ │ │ └── java │ │ │ │ └── JavaProcessorTest.java │ │ │ └── integration │ │ │ ├── ActionTestFile.java │ │ │ ├── IntegrationTest.java │ │ │ └── GlobalIntegrationTest.java │ └── resources │ │ └── software │ │ └── xdev │ │ └── saveactions │ │ ├── integration │ │ ├── FinalPrivateMethod_OK.java │ │ ├── GenerateSerialVersionUID_KO.java │ │ ├── FinalPrivateMethod_KO.java │ │ ├── UnnecessarySemicolon_OK.java │ │ ├── UnnecessarySemicolon_KO.java │ │ ├── MethodMayBeStatic_KO.java │ │ ├── MethodMayBeStatic_OK.java │ │ ├── GenerateSerialVersionUID_OK.java │ │ ├── UnnecessaryFinalOnLocalVariableOrParameter_OK.java │ │ ├── AccessCanBeTightened_KO.java │ │ ├── UnnecessaryFinalOnLocalVariableOrParameter_KO.java │ │ ├── AccessCanBeTightened_OK.java │ │ ├── UnqualifiedFieldAccess_KO.java │ │ ├── UnqualifiedFieldAccess_OK.java │ │ ├── UnqualifiedMethodAccess_KO.java │ │ ├── InspectionsAll_KO.java │ │ ├── InspectionsAll_OK.java │ │ ├── UnqualifiedMethodAccess_OK.java │ │ ├── ExplicitTypeCanBeDiamond_OK.java │ │ ├── ExplicitTypeCanBeDiamond_KO.java │ │ ├── FieldCanBeFinal_KO.java │ │ ├── FieldCanBeFinal_OK.java │ │ ├── UnnecessaryThis_OK.java │ │ ├── UnnecessaryThis_KO.java │ │ ├── MissingOverrideAnnotation_KO.java │ │ ├── MissingOverrideAnnotation_OK.java │ │ ├── SingleStatementInBlock_OK.java │ │ ├── UnqualifiedStaticMemberAccess_KO.java │ │ ├── UnqualifiedStaticMemberAccess_OK.java │ │ ├── CustomUnqualifiedStaticMemberAccess_KO.java │ │ ├── CustomUnqualifiedStaticMemberAccess_OK.java │ │ ├── UseBlocks_KO.java │ │ ├── UseBlocks_OK.java │ │ ├── SingleStatementInBlock_KO.java │ │ ├── LocalCanBeFinal_KO.java │ │ ├── LocalCanBeFinal_OK.java │ │ ├── LocalCanBeFinalExceptImplicit_KO.java │ │ ├── LocalCanBeFinalExceptImplicit_OK.java │ │ ├── Reformat_KO_Rearrange_OK.java │ │ ├── Reformat_KO_Import_KO.java │ │ ├── Reformat_KO_Import_OK.java │ │ ├── Reformat_KO_Rearrange_KO.java │ │ ├── Reformat_OK_Rearrange_OK.java │ │ ├── Reformat_OK_Import_KO.java │ │ └── Reformat_OK_Import_OK.java │ │ └── model │ │ ├── example0.epf │ │ ├── example1.epf │ │ └── example2.epf └── main │ ├── java │ └── software │ │ └── xdev │ │ └── saveactions │ │ ├── model │ │ ├── ActionType.java │ │ ├── StorageFactory.java │ │ ├── java │ │ │ ├── EpfKey.java │ │ │ ├── EpfAction.java │ │ │ └── EpfStorage.java │ │ ├── Storage.java │ │ └── Action.java │ │ ├── processors │ │ ├── ResultCode.java │ │ ├── Result.java │ │ ├── Processor.java │ │ ├── SaveReadCommand.java │ │ ├── SaveWriteCommand.java │ │ ├── SaveCommand.java │ │ ├── java │ │ │ ├── inspection │ │ │ │ ├── AccessibleVisibilityInspection.java │ │ │ │ ├── CustomLocalCanBeFinal.java │ │ │ │ ├── CustomSerializableHasSerialVersionUidFieldInspection.java │ │ │ │ ├── SerializableHasSerialVersionUIDFieldInspectionWrapper.java │ │ │ │ └── style │ │ │ │ │ └── CustomUnqualifiedStaticUsageInspection.java │ │ │ └── InspectionRunnable.java │ │ ├── GlobalProcessor.java │ │ └── BuildProcessor.java │ │ ├── utils │ │ └── Helper.java │ │ ├── core │ │ ├── ExecutionMode.java │ │ ├── service │ │ │ ├── SaveActionsService.java │ │ │ ├── impl │ │ │ │ ├── SaveActionsDefaultService.java │ │ │ │ └── SaveActionsJavaService.java │ │ │ └── SaveActionsServiceManager.java │ │ ├── action │ │ │ ├── ToggleAnAction.java │ │ │ ├── ShortcutAction.java │ │ │ └── BatchAction.java │ │ └── listener │ │ │ └── SaveActionsDocumentManagerListener.java │ │ └── ui │ │ ├── java │ │ ├── InspectionPanel.java │ │ └── IdeSupportPanel.java │ │ ├── FormattingPanel.java │ │ ├── FileMaskExclusionPanel.java │ │ ├── GeneralPanel.java │ │ ├── FileMaskInclusionPanel.java │ │ └── FileMaskPanel.java │ └── resources │ └── META-INF │ ├── plugin-java.xml │ ├── pluginIcon.svg │ ├── pluginIcon_dark.svg │ └── plugin.xml ├── SECURITY.md ├── renovate.json5 ├── .config └── checkstyle │ └── suppressions.xml ├── .gitignore ├── .run ├── Run Plugin.run.xml ├── Run Tests.run.xml └── Run Verifications.run.xml ├── gradle.properties ├── gradlew.bat ├── CHANGELOG.md ├── README.md └── CONTRIBUTING.md /.github/.lycheeignore: -------------------------------------------------------------------------------- 1 | # Ignorefile for broken link check 2 | localhost 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Force sh files to have LF 5 | *.sh text eol=lf 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdev-software/intellij-plugin-save-actions/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /assets/intellij-save-actions-plugin-settings-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdev-software/intellij-plugin-save-actions/HEAD/assets/intellij-save-actions-plugin-settings-page.png -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /assets/intellij-save-actions-plugin-settings-page-java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdev-software/intellij-plugin-save-actions/HEAD/assets/intellij-save-actions-plugin-settings-page-java.png -------------------------------------------------------------------------------- /src/test/java/org/junit/rules/TestRule.java: -------------------------------------------------------------------------------- 1 | package org.junit.rules; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public interface TestRule 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 💬 Contact support 3 | url: https://xdev.software/en/services/support 4 | about: "If you need support as soon as possible or/and you can't wait for any pull request" 5 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/intellij-plugin-save-actions/security/advisories/new). 6 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/ActionType.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model; 2 | 3 | public enum ActionType 4 | { 5 | 6 | activation, 7 | 8 | global, 9 | 10 | build, 11 | 12 | java 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/FinalPrivateMethod_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private void removeFinal() { 6 | 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/GenerateSerialVersionUID_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Class implements Serializable { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/org/junit/runners/model/Statement.java: -------------------------------------------------------------------------------- 1 | package org.junit.runners.model; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public class Statement 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/FinalPrivateMethod_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private final void removeFinal() { 6 | 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/org/junit/ComparisonFailure.java: -------------------------------------------------------------------------------- 1 | package org.junit; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public class ComparisonFailure extends AssertionError 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /.idea/externalDependencies.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/java/junit/framework/AssertionFailedError.java: -------------------------------------------------------------------------------- 1 | package junit.framework; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public class AssertionFailedError extends AssertionError 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/junit/rules/ExternalResource.java: -------------------------------------------------------------------------------- 1 | package org.junit.rules; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public abstract class ExternalResource implements TestRule 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/ResultCode.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | /** 4 | * The result code for the run of {@link SaveWriteCommand}. 5 | */ 6 | public enum ResultCode 7 | { 8 | 9 | OK, 10 | FAILED 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/org/junit/AssumptionViolatedException.java: -------------------------------------------------------------------------------- 1 | package org.junit; 2 | 3 | /** 4 | * @deprecated Compat for IJPL-159134 5 | */ 6 | @SuppressWarnings("all") 7 | @Deprecated 8 | public class AssumptionViolatedException extends RuntimeException 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/model/example0.epf: -------------------------------------------------------------------------------- 1 | # @title Save Actions 2 | # @description Save Actions 3 | # @task_type LASTMOD 4 | file_export_version=3.0 5 | /instance/org.eclipse.jdt.ui/editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true 6 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessarySemicolon_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private void method() { 6 | System.out.println("many semicolon"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/junit/framework/TestCase.java: -------------------------------------------------------------------------------- 1 | package junit.framework; 2 | 3 | import org.junit.Assert; 4 | 5 | 6 | /** 7 | * @deprecated Compat for IJPL-159134 8 | */ 9 | @SuppressWarnings("all") 10 | @Deprecated 11 | public abstract class TestCase extends Assert 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessarySemicolon_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private void method() { 6 | System.out.println("many semicolon");; 7 | ; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/MethodMayBeStatic_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public void canBeStatic() { 6 | 7 | } 8 | 9 | public void method() { 10 | canBeStatic(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/MethodMayBeStatic_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void canBeStatic() { 6 | 7 | } 8 | 9 | public void method() { 10 | canBeStatic(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/GenerateSerialVersionUID_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Class implements Serializable { 6 | 7 | private static final long serialVersionUID = -4531004116562853912L; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin-java.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessaryFinalOnLocalVariableOrParameter_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class 4 | { 5 | 6 | private final String field; 7 | 8 | private void method(String arg1, String arg2) 9 | { 10 | String localVariable; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/AccessCanBeTightened_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public int field; 6 | 7 | public void method() { 8 | 9 | } 10 | 11 | public void method1() { 12 | method(); 13 | field = 0; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessaryFinalOnLocalVariableOrParameter_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class 4 | { 5 | 6 | private final String field; 7 | 8 | private void method(final String arg1, final String arg2) 9 | { 10 | final String localVariable; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/AccessCanBeTightened_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private int field; 6 | 7 | private void method() { 8 | 9 | } 10 | 11 | public void method1() { 12 | method(); 13 | field = 0; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedFieldAccess_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private String theFieldToAccess = ""; 6 | 7 | public void method() { 8 | String noThisToAdd = this.theFieldToAccess + ""; 9 | String thisToAdd = theFieldToAccess + ""; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedFieldAccess_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private String theFieldToAccess = ""; 6 | 7 | public void method() { 8 | String noThisToAdd = this.theFieldToAccess + ""; 9 | String thisToAdd = this.theFieldToAccess + ""; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedMethodAccess_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public void method() { 6 | thisToAddMethod(); 7 | noThisToAddMethod(); 8 | } 9 | 10 | public void thisToAddMethod() { 11 | 12 | } 13 | 14 | public static void noThisToAddMethod() { 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/InspectionsAll_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public int field; 6 | 7 | public void method() { 8 | if (true) 9 | System.out.println("sout"); 10 | } 11 | 12 | public void method1() { 13 | method(); 14 | field = 0; 15 | ; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/InspectionsAll_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private int field; 6 | 7 | private void method() { 8 | if (true) { 9 | System.out.println("sout"); 10 | } 11 | } 12 | 13 | public void method1() { 14 | method(); 15 | field = 0; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedMethodAccess_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public void method() { 6 | this.thisToAddMethod(); 7 | noThisToAddMethod(); 8 | } 9 | 10 | public void thisToAddMethod() { 11 | 12 | } 13 | 14 | public static void noThisToAddMethod() { 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/ExplicitTypeCanBeDiamond_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Class { 9 | 10 | private void method() { 11 | List list = new ArrayList<>(); 12 | Map map = new HashMap<>(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/ExplicitTypeCanBeDiamond_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Class { 9 | 10 | private void method() { 11 | List list = new ArrayList(); 12 | Map map = new HashMap(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/FieldCanBeFinal_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private String canAddFinal1 = ""; 6 | private String cannotAddFinal1 = ""; 7 | 8 | private String canAddFinal2 = ""; 9 | private String cannotAddFinal2 = ""; 10 | 11 | public void someMethod() { 12 | cannotAddFinal1 = ""; 13 | cannotAddFinal2 = ""; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/FieldCanBeFinal_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private final String canAddFinal1 = ""; 6 | private String cannotAddFinal1 = ""; 7 | 8 | private final String canAddFinal2 = ""; 9 | private String cannotAddFinal2 = ""; 10 | 11 | public void someMethod() { 12 | cannotAddFinal1 = ""; 13 | cannotAddFinal2 = ""; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "rebaseWhen": "behind-base-branch", 4 | "packageRules": [ 5 | { 6 | "description": "Workaround, see https://github.com/gradle/gradle/issues/27035", 7 | "matchPackagePatterns": [ 8 | "^com.google.guava:guava" 9 | ], 10 | "datasources": [ 11 | "maven" 12 | ], 13 | "matchCurrentVersion": "0", 14 | "enabled": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/utils/Helper.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.psi.PsiFile; 7 | 8 | 9 | public final class Helper 10 | { 11 | private Helper() 12 | { 13 | } 14 | 15 | public static VirtualFile[] toVirtualFiles(final PsiFile[] psiFiles) 16 | { 17 | return Arrays.stream(psiFiles).map(PsiFile::getVirtualFile).toArray(VirtualFile[]::new); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessaryThis_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private String canRemoveThis = ""; 6 | private String cannotRemoveThis = ""; 7 | 8 | public void method() { 9 | canRemoveThis = ""; 10 | 11 | String cannotRemoveThis = ""; 12 | this.cannotRemoveThis = ""; 13 | 14 | canRemoveThis = ""; 15 | } 16 | 17 | public void otherMethod() { 18 | method(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /.idea/PMDPlugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 16 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnnecessaryThis_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private String canRemoveThis = ""; 6 | private String cannotRemoveThis = ""; 7 | 8 | public void method() { 9 | this.canRemoveThis = ""; 10 | 11 | String cannotRemoveThis = ""; 12 | this.cannotRemoveThis = ""; 13 | 14 | canRemoveThis = ""; 15 | } 16 | 17 | public void otherMethod() { 18 | this.method(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/Result.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | /** 4 | * Composite of {@link com.intellij.openapi.application.Result} and {@link com.intellij.openapi.application.RunResult}. 5 | */ 6 | public class Result 7 | { 8 | private final T result; 9 | 10 | Result(final T result) 11 | { 12 | this.result = result; 13 | } 14 | 15 | public T getResult() 16 | { 17 | return this.result; 18 | } 19 | 20 | @Override 21 | public String toString() 22 | { 23 | return this.result.toString(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync labels 2 | 3 | on: 4 | push: 5 | branches: develop 6 | paths: 7 | - .github/labels.yml 8 | 9 | workflow_dispatch: 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | labels: 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 10 18 | steps: 19 | - uses: actions/checkout@v5 20 | with: 21 | sparse-checkout: .github/labels.yml 22 | 23 | - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2 24 | with: 25 | config-file: .github/labels.yml 26 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/MissingOverrideAnnotation_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public void methodToOverride() { 6 | 7 | } 8 | 9 | private static class Children extends Class { 10 | 11 | public void methodToOverride() { 12 | 13 | } 14 | 15 | // This is a known limitation of the plugin: override is no added to methods from external jars and jdk 16 | public String toString() { 17 | return super.toString(); 18 | } 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/MissingOverrideAnnotation_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public void methodToOverride() { 6 | 7 | } 8 | 9 | private static class Children extends Class { 10 | 11 | @java.lang.Override 12 | public void methodToOverride() { 13 | 14 | } 15 | 16 | // This is a known limitation of the plugin: override is no added to methods from external jars and jdk 17 | public String toString() { 18 | return super.toString(); 19 | } 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/SingleStatementInBlock_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(String[] args) { 6 | if (true) System.out.println(""); 7 | else System.out.println(""); 8 | 9 | while (true) System.out.println(""); 10 | 11 | for (int i = 0; i < 10; i++) System.out.println(""); 12 | 13 | if (true) System.out.println(""); 14 | else System.out.println(""); 15 | 16 | while (true) System.out.println(""); 17 | 18 | for (int i = 0; i < 10; i++) System.out.println(""); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedStaticMemberAccess_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | // https://github.com/dubreuia/intellij-plugin-save-actions/issues/155 4 | 5 | 6 | class Hello 7 | { 8 | static final String STR = "Hello"; 9 | 10 | void sayIt() 11 | { 12 | println(STR); // should qualify 13 | } 14 | 15 | class Other 16 | { 17 | String method() 18 | { 19 | final String s = STR; // should qualify 20 | return s.replace("l", "y"); 21 | } 22 | } 23 | } 24 | 25 | 26 | class World extends Hello 27 | { 28 | @Override 29 | void sayIt() 30 | { 31 | println(STR + " World!"); // should qualify 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UnqualifiedStaticMemberAccess_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | // https://github.com/dubreuia/intellij-plugin-save-actions/issues/155 4 | 5 | 6 | class Hello 7 | { 8 | static final String STR = "Hello"; 9 | 10 | void sayIt() 11 | { 12 | println(Hello.STR); // should qualify 13 | } 14 | 15 | class Other 16 | { 17 | String method() 18 | { 19 | final String s = Hello.STR; // should qualify 20 | return s.replace("l", "y"); 21 | } 22 | } 23 | } 24 | 25 | 26 | class World extends Hello 27 | { 28 | @Override 29 | void sayIt() 30 | { 31 | println(Hello.STR + " World!"); // should qualify 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/CustomUnqualifiedStaticMemberAccess_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | // https://github.com/dubreuia/intellij-plugin-save-actions/issues/155 4 | 5 | 6 | class Hello 7 | { 8 | static final String STR = "Hello"; 9 | 10 | void sayIt() 11 | { 12 | println(STR); // should not qualify it as Hello.STR 13 | } 14 | 15 | class Other 16 | { 17 | String method() 18 | { 19 | final String s = STR; // should qualify 20 | return s.replace("l", "y"); 21 | } 22 | } 23 | } 24 | 25 | 26 | class World extends Hello 27 | { 28 | @Override 29 | void sayIt() 30 | { 31 | println(STR + " World!"); // should qualify 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/CustomUnqualifiedStaticMemberAccess_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | // https://github.com/dubreuia/intellij-plugin-save-actions/issues/155 4 | 5 | 6 | class Hello 7 | { 8 | static final String STR = "Hello"; 9 | 10 | void sayIt() 11 | { 12 | println(STR); // should not qualify it as Hello.STR 13 | } 14 | 15 | class Other 16 | { 17 | String method() 18 | { 19 | final String s = Hello.STR; // should qualify 20 | return s.replace("l", "y"); 21 | } 22 | } 23 | } 24 | 25 | 26 | class World extends Hello 27 | { 28 | @Override 29 | void sayIt() 30 | { 31 | println(Hello.STR + " World!"); // should qualify 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | .idea/* 3 | *.iml 4 | *.ipr 5 | *.iws 6 | 7 | # Generated java classes 8 | out 9 | classes 10 | 11 | # Plugins 12 | *.idea/checkstyle-idea.xml 13 | 14 | # Gradle 15 | .gradle/ 16 | build/ 17 | 18 | # IntelliJ Platform Plugin 19 | .intellijPlatform 20 | 21 | 22 | # Some files are user/installation independent and are used for configuring the IDE 23 | # See also https://stackoverflow.com/a/35279076 24 | 25 | .idea/* 26 | !.idea/saveactions_settings.xml 27 | !.idea/checkstyle-idea.xml 28 | !.idea/externalDependencies.xml 29 | !.idea/PMDPlugin.xml 30 | 31 | !.idea/inspectionProfiles/ 32 | .idea/inspectionProfiles/* 33 | !.idea/inspectionProfiles/Project_Default.xml 34 | 35 | !.idea/codeStyles/ 36 | .idea/codeStyles/* 37 | !.idea/codeStyles/codeStyleConfig.xml 38 | !.idea/codeStyles/Project.xml 39 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UseBlocks_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(String[] args) { 6 | if (true) 7 | System.out.println(""); 8 | else 9 | System.out.println(""); 10 | 11 | while (true) 12 | System.out.println(""); 13 | 14 | for (int i = 0; i < 10; i++) 15 | System.out.println(""); 16 | 17 | if (true) { 18 | System.out.println(""); 19 | } else { 20 | System.out.println(""); 21 | } 22 | 23 | while (true) { 24 | System.out.println(""); 25 | } 26 | 27 | for (int i = 0; i < 10; i++) { 28 | System.out.println(""); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/core/action/BatchActionConstants.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.action; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import com.intellij.analysis.AnalysisScope; 6 | import com.intellij.openapi.command.WriteCommandAction; 7 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 8 | 9 | 10 | public interface BatchActionConstants 11 | { 12 | Consumer SAVE_ACTION_BATCH_MANAGER = fixture -> 13 | WriteCommandAction.writeCommandAction(fixture.getProject()).run(() -> runFixure(fixture)); 14 | 15 | static void runFixure(final CodeInsightTestFixture fixture) 16 | { 17 | // set modification timestamp ++ 18 | fixture.getFile().clearCaches(); 19 | 20 | // call plugin on document 21 | new BatchAction().analyze(fixture.getProject(), new AnalysisScope(fixture.getProject())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/StorageFactory.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model; 2 | 3 | import java.util.function.Function; 4 | 5 | import com.intellij.openapi.project.Project; 6 | 7 | import software.xdev.saveactions.model.java.EpfStorage; 8 | 9 | 10 | public enum StorageFactory 11 | { 12 | DEFAULT(project -> project.getService(Storage.class)), 13 | 14 | JAVA(project -> { 15 | Storage defaultStorage = DEFAULT.getStorage(project); 16 | return EpfStorage.INSTANCE.getStorageOrDefault(defaultStorage.getConfigurationPath(), defaultStorage); 17 | }); 18 | 19 | private final Function provider; 20 | 21 | StorageFactory(final Function provider) 22 | { 23 | this.provider = provider; 24 | } 25 | 26 | public Storage getStorage(final Project project) 27 | { 28 | return this.provider.apply(project); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/UseBlocks_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(String[] args) { 6 | if (true) { 7 | System.out.println(""); 8 | } else { 9 | System.out.println(""); 10 | } 11 | 12 | while (true) { 13 | System.out.println(""); 14 | } 15 | 16 | for (int i = 0; i < 10; i++) { 17 | System.out.println(""); 18 | } 19 | 20 | if (true) { 21 | System.out.println(""); 22 | } else { 23 | System.out.println(""); 24 | } 25 | 26 | while (true) { 27 | System.out.println(""); 28 | } 29 | 30 | for (int i = 0; i < 10; i++) { 31 | System.out.println(""); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/model/java/EpfTestConstants.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | 7 | 8 | public final class EpfTestConstants 9 | { 10 | static final Path ROOT = new File(".").toPath().toAbsolutePath().getParent(); 11 | static final Path ROOT_RESOURCES = Paths.get(ROOT.toString(), "src", "test", "resources"); 12 | static final Path ROOT_EPF = Paths.get(ROOT_RESOURCES.toString(), "software", "xdev", "saveactions", "model"); 13 | 14 | public static final Path EXAMPLE_EPF_0 = Paths.get(ROOT_EPF.toString(), "example0.epf"); 15 | public static final Path EXAMPLE_EPF_1 = Paths.get(ROOT_EPF.toString(), "example1.epf"); 16 | public static final Path EXAMPLE_EPF_2 = Paths.get(ROOT_EPF.toString(), "example2.epf"); 17 | 18 | private EpfTestConstants() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/SingleStatementInBlock_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(String[] args) { 6 | if (true) { 7 | System.out.println(""); 8 | } else { 9 | System.out.println(""); 10 | } 11 | 12 | while (true) { 13 | System.out.println(""); 14 | } 15 | 16 | for (int i = 0; i < 10; i++) { 17 | System.out.println(""); 18 | } 19 | 20 | if (true) { 21 | System.out.println(""); 22 | } else { 23 | System.out.println(""); 24 | } 25 | 26 | while (true) { 27 | System.out.println(""); 28 | } 29 | 30 | for (int i = 0; i < 10; i++) { 31 | System.out.println(""); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/model/java/EpfActionTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import software.xdev.saveactions.model.Action; 11 | 12 | 13 | class EpfActionTest 14 | { 15 | @Test 16 | void should_epf_action_have_no_duplicate_action() 17 | { 18 | final List actions = EpfAction.stream().map(EpfAction::getAction).collect(toList()); 19 | assertThat(actions).doesNotHaveDuplicates(); 20 | } 21 | 22 | @Test 23 | void should_java_processor_have_valid_type_and_inspection() 24 | { 25 | EpfAction.stream().forEach(epfAction -> assertThat(epfAction.getAction().getType()).isNotNull()); 26 | EpfAction.stream().forEach(epfAction -> assertThat(epfAction.getEpfKeys()).isNotEmpty()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/ExecutionMode.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core; 2 | 3 | import com.intellij.openapi.editor.Document; 4 | import com.intellij.openapi.fileEditor.FileDocumentManager; 5 | 6 | 7 | public enum ExecutionMode 8 | { 9 | 10 | /** 11 | * When the plugin is called normally (the IDE calls the plugin component on frame deactivation or "save all"). The 12 | * {@link #saveSingle} is also called on every document. 13 | * 14 | * @see FileDocumentManager#saveAllDocuments() 15 | */ 16 | saveAll, 17 | 18 | /** 19 | * When the plugin is called only with a single save (some other plugins like ideavim do that). 20 | * 21 | * @see FileDocumentManager#saveDocument(Document) 22 | */ 23 | saveSingle, 24 | 25 | /** 26 | * When the plugin is called in batch mode (the IDE calls the plugin after a file selection popup). 27 | */ 28 | batch, 29 | 30 | /** 31 | * When the plugin is called from a user input shortcut. 32 | */ 33 | shortcut, 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/service/SaveActionsService.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.service; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import com.intellij.openapi.actionSystem.ex.QuickList; 7 | import com.intellij.openapi.components.Service; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.psi.PsiFile; 10 | 11 | import software.xdev.saveactions.core.ExecutionMode; 12 | import software.xdev.saveactions.model.Action; 13 | 14 | 15 | /** 16 | * This interface is implemented by all SaveAction ApplicationServices. It is used to be able to override a concrete 17 | * implementation. Also, it has to be annotated with {@link Service}. 18 | */ 19 | public interface SaveActionsService 20 | { 21 | void guardedProcessPsiFiles(Project project, Set psiFiles, Action activation, ExecutionMode mode); 22 | 23 | boolean isJavaAvailable(); 24 | 25 | boolean isCompilingAvailable(); 26 | 27 | List getQuickLists(Project project); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/Processor.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import java.util.Comparator; 4 | import java.util.Set; 5 | 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.PsiFile; 8 | 9 | import software.xdev.saveactions.core.ExecutionMode; 10 | import software.xdev.saveactions.model.Action; 11 | 12 | 13 | /** 14 | * Processor interface contains the provider method, the action that enables the processors, and the modes for which the 15 | * processor is available. 16 | */ 17 | public interface Processor 18 | { 19 | Action getAction(); 20 | 21 | Set getModes(); 22 | 23 | int getOrder(); 24 | 25 | SaveCommand getSaveCommand(Project project, Set psiFiles); 26 | 27 | class OrderComparator implements Comparator 28 | { 29 | 30 | @Override 31 | public int compare(final Processor o1, final Processor o2) 32 | { 33 | return Integer.compare(o1.getOrder(), o2.getOrder()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/LocalCanBeFinal_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(String[] args) { 6 | String canBeFinal = ""; 7 | String cannotBeFinal = ""; 8 | cannotBeFinal = ""; 9 | } 10 | 11 | public void method(String param1, String param2) { 12 | String canBeFinal = ""; 13 | String cannotBeFinal = ""; 14 | cannotBeFinal = ""; 15 | } 16 | 17 | public String lombok() { 18 | val example = new ArrayList(); 19 | example.add("Hello, World!"); 20 | val foo = example.get(0); 21 | return foo.toLowerCase(); 22 | } 23 | 24 | public void lombok2() { 25 | val map = new HashMap(); 26 | map.put(0, "zero"); 27 | map.put(5, "five"); 28 | for (val entry : map.entrySet()) { 29 | System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/LocalCanBeFinal_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | public static void main(final String[] args) { 6 | final String canBeFinal = ""; 7 | String cannotBeFinal = ""; 8 | cannotBeFinal = ""; 9 | } 10 | 11 | public void method(final String param1, final String param2) { 12 | final String canBeFinal = ""; 13 | String cannotBeFinal = ""; 14 | cannotBeFinal = ""; 15 | } 16 | 17 | public String lombok() { 18 | val example = new ArrayList(); 19 | example.add("Hello, World!"); 20 | val foo = example.get(0); 21 | return foo.toLowerCase(); 22 | } 23 | 24 | public void lombok2() { 25 | val map = new HashMap(); 26 | map.put(0, "zero"); 27 | map.put(5, "five"); 28 | for (val entry : map.entrySet()) { 29 | System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/LocalCanBeFinalExceptImplicit_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private void method() { 6 | String canBeFinal = ""; 7 | String cannotBeFinal = ""; 8 | cannotBeFinal = ""; 9 | try (Resource r = new Resource()) { 10 | 11 | } 12 | } 13 | 14 | class Resource implements AutoCloseable { 15 | 16 | @Override 17 | public void close() { 18 | 19 | } 20 | 21 | } 22 | 23 | public String lombok() { 24 | val example = new ArrayList(); 25 | example.add("Hello, World!"); 26 | val foo = example.get(0); 27 | return foo.toLowerCase(); 28 | } 29 | 30 | public void lombok2() { 31 | val map = new HashMap(); 32 | map.put(0, "zero"); 33 | map.put(5, "five"); 34 | for (val entry : map.entrySet()) { 35 | System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/LocalCanBeFinalExceptImplicit_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public class Class { 4 | 5 | private void method() { 6 | final String canBeFinal = ""; 7 | String cannotBeFinal = ""; 8 | cannotBeFinal = ""; 9 | try (Resource r = new Resource()) { 10 | 11 | } 12 | } 13 | 14 | class Resource implements AutoCloseable { 15 | 16 | @Override 17 | public void close() { 18 | 19 | } 20 | 21 | } 22 | 23 | public String lombok() { 24 | val example = new ArrayList(); 25 | example.add("Hello, World!"); 26 | val foo = example.get(0); 27 | return foo.toLowerCase(); 28 | } 29 | 30 | public void lombok2() { 31 | val map = new HashMap(); 32 | map.put(0, "zero"); 33 | map.put(5, "five"); 34 | for (val entry : map.entrySet()) { 35 | System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /.run/Run Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.0.0 5 | JavaOnlyWithTests 6 | true 7 | true 8 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # Default 2 | ## Required for template 3 | - name: bug 4 | description: "Something isn't working" 5 | color: 'd73a4a' 6 | - name: enhancement 7 | description: New feature or request 8 | color: '#a2eeef' 9 | - name: question 10 | description: Information is requested 11 | color: '#d876e3' 12 | ## Others 13 | - name: duplicate 14 | description: This already exists 15 | color: '#cfd3d7' 16 | - name: good first issue 17 | description: Good for newcomers 18 | color: '#7057ff' 19 | - name: help wanted 20 | description: Extra attention is needed 21 | color: '#008672' 22 | - name: invalid 23 | description: "This doesn't seem right" 24 | color: '#e4e669' 25 | # Custom 26 | - name: automated 27 | description: Created by an automation 28 | color: '#000000' 29 | - name: "can't reproduce" 30 | color: '#e95f2c' 31 | - name: customer-requested 32 | description: Was requested by a customer of us 33 | color: '#068374' 34 | - name: stale 35 | color: '#ededed' 36 | - name: waiting-for-response 37 | description: If no response is received after a certain time the issue will be closed 38 | color: '#202020' 39 | -------------------------------------------------------------------------------- /.run/Run Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/SaveReadCommand.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import java.util.Set; 4 | import java.util.function.BiFunction; 5 | 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.PsiFile; 8 | 9 | import software.xdev.saveactions.core.ExecutionMode; 10 | import software.xdev.saveactions.model.Action; 11 | 12 | 13 | /** 14 | * Implements a read action that returns a {@link Result}. 15 | */ 16 | public class SaveReadCommand extends SaveCommand 17 | { 18 | public SaveReadCommand( 19 | final Project project, final Set psiFiles, final Set modes, final Action action, 20 | final BiFunction command) 21 | { 22 | 23 | super(project, psiFiles, modes, action, command); 24 | } 25 | 26 | @Override 27 | public synchronized Result execute() 28 | { 29 | try 30 | { 31 | this.getCommand().apply(this.getProject(), this.getPsiFilesAsArray()).run(); 32 | return new Result<>(ResultCode.OK); 33 | } 34 | catch(final Exception e) 35 | { 36 | return new Result<>(ResultCode.FAILED); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.idea/saveactions_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /.run/Run Verifications.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | pluginGroup=software.xdev.saveactions 3 | pluginName=Save Actions X 4 | # SemVer format -> https://semver.org 5 | pluginVersion=1.5.1-SNAPSHOT 6 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 7 | platformType=IC 8 | platformVersion=2025.2.2 9 | platformSinceBuild=252 10 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 11 | # Example: platformBundledPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 12 | platformBundledPlugins=com.intellij.java 13 | platformPlugins= 14 | # Gradle Releases -> https://github.com/gradle/gradle/releases 15 | gradleVersion=8.13 16 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 17 | org.gradle.configuration-cache=true 18 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 19 | org.gradle.caching=true 20 | # Increase default gradle heap size 21 | org.gradle.jvmargs=-Xmx2g 22 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/service/impl/SaveActionsDefaultService.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.service.impl; 2 | 3 | import static software.xdev.saveactions.model.StorageFactory.DEFAULT; 4 | 5 | import software.xdev.saveactions.processors.BuildProcessor; 6 | import software.xdev.saveactions.processors.GlobalProcessor; 7 | 8 | 9 | /** 10 | * This ApplicationService implementation is used for all IDE flavors that are not handling JAVA. 11 | *

12 | * It is assigned as ExtensionPoint from inside plugin.xml. In terms of IDEs using Java this service is overridden by 13 | * the extended JAVA based version {@link SaveActionsJavaService}. Hence, it will not be loaded for Intellij IDEA, 14 | * Android Studio a.s.o. 15 | *

16 | * Services must be final classes as per definition. That is the reason to use an abstract class here. 17 | *

18 | * 19 | * @see AbstractSaveActionsService 20 | */ 21 | public final class SaveActionsDefaultService extends AbstractSaveActionsService 22 | { 23 | public SaveActionsDefaultService() 24 | { 25 | super(DEFAULT); 26 | this.addProcessors(BuildProcessor.stream()); 27 | this.addProcessors(GlobalProcessor.stream()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/junit/JUnit5Utils.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.junit; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | import org.opentest4j.AssertionFailedError; 6 | 7 | import com.intellij.platform.testFramework.core.FileComparisonFailedError; 8 | 9 | 10 | public final class JUnit5Utils 11 | { 12 | private JUnit5Utils() 13 | { 14 | } 15 | 16 | public static void rethrowAsJunit5Error(final AssertionError error) 17 | { 18 | if(error.getCause() instanceof final InvocationTargetException intellijInternal 19 | && intellijInternal.getCause() instanceof final FileComparisonFailedError fileComparisonFailure) 20 | { 21 | final String expected = fileComparisonFailure.getExpected().getStringRepresentation(); 22 | final String actual = fileComparisonFailure.getActual().getStringRepresentation(); 23 | throw new AssertionFailedError("Expected file do not match actual file", expected, actual); 24 | } 25 | 26 | throw error; 27 | } 28 | 29 | public static void rethrowAsJunit5Error(final Runnable runnable) 30 | { 31 | try 32 | { 33 | runnable.run(); 34 | } 35 | catch(final AssertionError error) 36 | { 37 | rethrowAsJunit5Error(error); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/processors/BuildProcessorTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static software.xdev.saveactions.model.ActionType.build; 6 | 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import software.xdev.saveactions.model.Action; 12 | 13 | 14 | class BuildProcessorTest 15 | { 16 | @Test 17 | void should_processor_have_no_duplicate_action() 18 | { 19 | final List actions = BuildProcessor.stream().map(Processor::getAction).collect(toList()); 20 | assertThat(actions).doesNotHaveDuplicates(); 21 | } 22 | 23 | @Test 24 | void should_processor_have_valid_type_and_inspection() 25 | { 26 | BuildProcessor.stream() 27 | .forEach(processor -> assertThat(processor.getAction().getType()).isEqualTo(build)); 28 | BuildProcessor.stream() 29 | .forEach(processor -> assertThat(((BuildProcessor)processor).getCommand()).isNotNull()); 30 | } 31 | 32 | @Test 33 | void should_action_have_java_processor() 34 | { 35 | Action.stream(build).forEach(action -> assertThat(BuildProcessor.getProcessorForAction(action)).isNotEmpty()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: ❓ Question 2 | description: Ask a question 3 | labels: [question] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this form! 9 | 10 | - type: checkboxes 11 | id: checklist 12 | attributes: 13 | label: "Checklist" 14 | options: 15 | - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/intellij-plugin-save-actions/issues) or [closed](https://github.com/xdev-software/intellij-plugin-save-actions/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." 16 | required: true 17 | - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." 18 | required: true 19 | 20 | - type: textarea 21 | id: what-is-the-question 22 | attributes: 23 | label: What is/are your question(s)? 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | id: additional-information 29 | attributes: 30 | label: Additional information 31 | description: "Any other information you'd like to include - for instance logs, screenshots, etc." 32 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/processors/GlobalProcessorTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static software.xdev.saveactions.model.ActionType.global; 6 | 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import software.xdev.saveactions.model.Action; 12 | 13 | 14 | class GlobalProcessorTest 15 | { 16 | @Test 17 | void should_processor_have_no_duplicate_action() 18 | { 19 | final List actions = GlobalProcessor.stream().map(Processor::getAction).collect(toList()); 20 | assertThat(actions).doesNotHaveDuplicates(); 21 | } 22 | 23 | @Test 24 | void should_processor_have_valid_type_and_inspection() 25 | { 26 | GlobalProcessor.stream() 27 | .forEach(processor -> assertThat(processor.getAction().getType()).isEqualTo(global)); 28 | GlobalProcessor.stream() 29 | .forEach(processor -> assertThat(((GlobalProcessor)processor).getCommand()).isNotNull()); 30 | } 31 | 32 | @Test 33 | void should_action_have_java_processor() 34 | { 35 | Action.stream(global).forEach(action -> assertThat(GlobalProcessor.getProcessorForAction(action)).isNotEmpty()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature/Enhancement 2 | description: Suggest a new feature or enhancement 3 | labels: [enhancement] 4 | type: feature 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for suggesting a new feature/enhancement. 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: "Checklist" 15 | options: 16 | - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/intellij-plugin-save-actions/issues) or [closed](https://github.com/xdev-software/intellij-plugin-save-actions/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." 17 | required: true 18 | - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." 19 | required: true 20 | - label: "This issue contains only one feature request/enhancement." 21 | required: true 22 | 23 | - type: textarea 24 | id: description 25 | attributes: 26 | label: Description 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: additional-information 32 | attributes: 33 | label: Additional information 34 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/processors/java/JavaProcessorTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static software.xdev.saveactions.model.ActionType.java; 6 | 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import software.xdev.saveactions.model.Action; 12 | import software.xdev.saveactions.processors.Processor; 13 | 14 | 15 | class JavaProcessorTest 16 | { 17 | @Test 18 | void should_java_processor_have_no_duplicate_action() 19 | { 20 | final List actions = JavaProcessor.stream().map(Processor::getAction).collect(toList()); 21 | assertThat(actions).doesNotHaveDuplicates(); 22 | } 23 | 24 | @Test 25 | void should_java_processor_have_valid_type_and_inspection() 26 | { 27 | JavaProcessor.stream() 28 | .forEach(processor -> assertThat(processor.getAction().getType()).isEqualTo(java)); 29 | JavaProcessor.stream() 30 | .forEach(processor -> assertThat(processor.getInspection()).isNotNull()); 31 | } 32 | 33 | @Test 34 | void should_java_action_have_java_processor() 35 | { 36 | Action.stream(java).forEach(action -> assertThat(JavaProcessor.getProcessorForAction(action)).isNotEmpty()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/service/SaveActionsServiceManager.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.service; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | 5 | import software.xdev.saveactions.core.service.impl.SaveActionsDefaultService; 6 | import software.xdev.saveactions.core.service.impl.SaveActionsJavaService; 7 | 8 | 9 | /** 10 | * SaveActionsServiceManager is providing the concrete service implementation. All actions are handled by the 11 | * {@link SaveActionsService} implementation. 12 | * 13 | * @see SaveActionsDefaultService 14 | * @see SaveActionsJavaService 15 | */ 16 | public final class SaveActionsServiceManager 17 | { 18 | static SaveActionsService instance; 19 | 20 | public static SaveActionsService getService() 21 | { 22 | if(instance == null) 23 | { 24 | initService(); 25 | } 26 | return instance; 27 | } 28 | 29 | private static synchronized void initService() 30 | { 31 | if(instance != null) 32 | { 33 | return; 34 | } 35 | 36 | instance = ApplicationManager.getApplication().getService(SaveActionsJavaService.class); 37 | if(instance == null) 38 | { 39 | instance = ApplicationManager.getApplication().getService(SaveActionsDefaultService.class); 40 | } 41 | } 42 | 43 | private SaveActionsServiceManager() 44 | { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/service/impl/SaveActionsJavaService.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.service.impl; 2 | 3 | import static software.xdev.saveactions.model.StorageFactory.JAVA; 4 | 5 | import software.xdev.saveactions.processors.BuildProcessor; 6 | import software.xdev.saveactions.processors.GlobalProcessor; 7 | import software.xdev.saveactions.processors.java.JavaProcessor; 8 | 9 | 10 | /** 11 | * This ApplicationService implementation is used for all JAVA based IDE flavors. 12 | *

13 | * It is assigned as ExtensionPoint from inside plugin-java.xml and overrides the default implementation 14 | * {@link SaveActionsDefaultService} which is not being loaded for Intellij IDEA, Android Studio a.s.o. Instead this 15 | * implementation will be assigned. Thus, all processors have to be configured by this class as well. 16 | *

17 | * Services must be final classes as per definition. That is the reason to use an abstract class here. 18 | *

19 | * 20 | * @see AbstractSaveActionsService 21 | */ 22 | public final class SaveActionsJavaService extends AbstractSaveActionsService 23 | { 24 | public SaveActionsJavaService() 25 | { 26 | super(JAVA); 27 | this.addProcessors(BuildProcessor.stream()); 28 | this.addProcessors(GlobalProcessor.stream()); 29 | this.addProcessors(JavaProcessor.stream()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/core/component/SaveActionManagerConstants.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.component; 2 | 3 | import static java.util.Collections.singleton; 4 | import static software.xdev.saveactions.core.ExecutionMode.saveAll; 5 | import static software.xdev.saveactions.model.Action.activate; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.function.BiConsumer; 10 | 11 | import com.intellij.openapi.command.WriteCommandAction; 12 | import com.intellij.psi.PsiFile; 13 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 14 | 15 | import software.xdev.saveactions.core.service.SaveActionsService; 16 | 17 | 18 | public interface SaveActionManagerConstants 19 | { 20 | BiConsumer SAVE_ACTION_MANAGER = (fixture, saveActionService) -> 21 | WriteCommandAction.writeCommandAction(fixture.getProject()).run(() -> runFixture(fixture, saveActionService)); 22 | 23 | static void runFixture(final CodeInsightTestFixture fixture, final SaveActionsService saveActionService) 24 | { 25 | // set modification timestamp ++ 26 | fixture.getFile().clearCaches(); 27 | 28 | // call plugin on document 29 | final Set psiFiles = new HashSet<>(singleton(fixture.getFile())); 30 | saveActionService.guardedProcessPsiFiles(fixture.getProject(), psiFiles, activate, saveAll); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/SaveWriteCommand.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import java.util.Set; 4 | import java.util.function.BiFunction; 5 | 6 | import com.intellij.openapi.command.WriteCommandAction; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.psi.PsiFile; 9 | 10 | import software.xdev.saveactions.core.ExecutionMode; 11 | import software.xdev.saveactions.model.Action; 12 | 13 | 14 | /** 15 | * Implements a write action that encapsulates {@link com.intellij.openapi.command.WriteCommandAction} that returns a 16 | * {@link Result}. 17 | */ 18 | public class SaveWriteCommand extends SaveCommand 19 | { 20 | public SaveWriteCommand( 21 | final Project project, final Set psiFiles, final Set modes, final Action action, 22 | final BiFunction command) 23 | { 24 | super(project, psiFiles, modes, action, command); 25 | } 26 | 27 | @Override 28 | public synchronized Result execute() 29 | { 30 | try 31 | { 32 | WriteCommandAction.writeCommandAction(this.getProject(), this.getPsiFilesAsArray()) 33 | .run(() -> this.getCommand().apply(this.getProject(), this.getPsiFilesAsArray()).run()); 34 | return new Result<>(ResultCode.OK); 35 | } 36 | catch(final Exception e) 37 | { 38 | return new Result<>(ResultCode.FAILED); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/java/InspectionPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui.java; 2 | 3 | import static software.xdev.saveactions.model.ActionType.java; 4 | 5 | import java.awt.Dimension; 6 | import java.util.Map; 7 | 8 | import javax.swing.Box; 9 | import javax.swing.BoxLayout; 10 | import javax.swing.JCheckBox; 11 | import javax.swing.JPanel; 12 | 13 | import com.intellij.ui.IdeBorderFactory; 14 | 15 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 16 | import software.xdev.saveactions.model.Action; 17 | 18 | 19 | public class InspectionPanel 20 | { 21 | private static final String TEXT_TITLE_INSPECTIONS = "Java Inspection and Quick Fix"; 22 | 23 | private final Map checkboxes; 24 | 25 | public InspectionPanel(final Map checkboxes) 26 | { 27 | this.checkboxes = checkboxes; 28 | } 29 | 30 | public JPanel getPanel() 31 | { 32 | final JPanel panel = new JPanel(); 33 | if(!SaveActionsServiceManager.getService().isJavaAvailable()) 34 | { 35 | return panel; 36 | } 37 | panel.setBorder(IdeBorderFactory.createTitledBorder(TEXT_TITLE_INSPECTIONS)); 38 | panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 39 | Action.stream(java).map(this.checkboxes::get).forEach(panel::add); 40 | panel.add(Box.createHorizontalGlue()); 41 | panel.setMinimumSize(new Dimension(Short.MAX_VALUE, 0)); 42 | return panel; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/action/ToggleAnAction.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.action; 2 | 3 | import static software.xdev.saveactions.model.Action.activate; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 8 | import com.intellij.openapi.actionSystem.AnActionEvent; 9 | import com.intellij.openapi.actionSystem.ToggleAction; 10 | import com.intellij.openapi.project.DumbAware; 11 | import com.intellij.openapi.project.Project; 12 | 13 | import software.xdev.saveactions.model.Storage; 14 | 15 | 16 | /** 17 | * This action toggles on and off the plugin, by modifying the underlying storage. 18 | */ 19 | public class ToggleAnAction extends ToggleAction implements DumbAware 20 | { 21 | @Override 22 | public boolean isSelected(final AnActionEvent event) 23 | { 24 | final Project project = event.getProject(); 25 | if(project != null) 26 | { 27 | final Storage storage = project.getService(Storage.class); 28 | return storage.isEnabled(activate); 29 | } 30 | return false; 31 | } 32 | 33 | @Override 34 | public void setSelected(final AnActionEvent event, final boolean state) 35 | { 36 | final Project project = event.getProject(); 37 | if(project != null) 38 | { 39 | final Storage storage = project.getService(Storage.class); 40 | storage.setEnabled(activate, state); 41 | } 42 | } 43 | 44 | @Override 45 | public @NotNull ActionUpdateThread getActionUpdateThread() 46 | { 47 | return ActionUpdateThread.BGT; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/FormattingPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui; 2 | 3 | import static software.xdev.saveactions.model.Action.organizeImports; 4 | import static software.xdev.saveactions.model.Action.rearrange; 5 | import static software.xdev.saveactions.model.Action.reformat; 6 | import static software.xdev.saveactions.model.Action.reformatChangedCode; 7 | 8 | import java.awt.Dimension; 9 | import java.util.Map; 10 | 11 | import javax.swing.Box; 12 | import javax.swing.BoxLayout; 13 | import javax.swing.JCheckBox; 14 | import javax.swing.JPanel; 15 | 16 | import com.intellij.ui.IdeBorderFactory; 17 | 18 | import software.xdev.saveactions.model.Action; 19 | 20 | 21 | class FormattingPanel 22 | { 23 | private static final String TEXT_TITLE_ACTIONS = "Formatting Actions"; 24 | 25 | private final Map checkboxes; 26 | 27 | FormattingPanel(final Map checkboxes) 28 | { 29 | this.checkboxes = checkboxes; 30 | } 31 | 32 | JPanel getPanel() 33 | { 34 | final JPanel panel = new JPanel(); 35 | panel.setBorder(IdeBorderFactory.createTitledBorder(TEXT_TITLE_ACTIONS)); 36 | panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 37 | panel.add(this.checkboxes.get(organizeImports)); 38 | panel.add(this.checkboxes.get(reformat)); 39 | panel.add(this.checkboxes.get(reformatChangedCode)); 40 | panel.add(this.checkboxes.get(rearrange)); 41 | panel.add(Box.createHorizontalGlue()); 42 | panel.setMinimumSize(new Dimension(Short.MAX_VALUE, 0)); 43 | return panel; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/test-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Test Deployment 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 60 10 | steps: 11 | - uses: actions/checkout@v5 12 | 13 | - name: Set up JDK 14 | uses: actions/setup-java@v5 15 | with: 16 | distribution: 'temurin' 17 | java-version: 21 18 | 19 | - name: Update/Generify version 20 | run: | 21 | originalVersion=$(grep -Po 'pluginVersion=\K.*' gradle.properties) 22 | newVersion="$(echo $originalVersion | cut -d '-' -f1).$( date -u '+%Y%m%d%H%M%S')-SNAPSHOT" 23 | echo "New version: $newVersion" 24 | sed -i "s/pluginVersion=$originalVersion/pluginVersion=$newVersion/" gradle.properties 25 | 26 | echo "Contents of gradle.properties" 27 | cat gradle.properties 28 | 29 | - name: Publish Plugin 30 | env: 31 | PUBLISH_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_PUBLISH_TOKEN }} 32 | CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_MARKETPLACE_CERTIFICATE_CHAIN }} 33 | PRIVATE_KEY: ${{ secrets.JETBRAINS_MARKETPLACE_PRIVATE_KEY }} 34 | PRIVATE_KEY_PASSWORD: ${{ secrets.JETBRAINS_MARKETPLACE_PRIVATE_KEY_PASSWORD }} 35 | run: ./gradlew publishPlugin --info --stacktrace 36 | 37 | - name: Upload plugin files 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: plugin-files-java-${{ matrix.java }} 41 | path: build/distributions/* 42 | if-no-files-found: error 43 | -------------------------------------------------------------------------------- /.github/workflows/broken-links.yml: -------------------------------------------------------------------------------- 1 | name: Broken links 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "23 23 * * 0" 7 | 8 | permissions: 9 | issues: write 10 | 11 | jobs: 12 | link-checker: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 15 15 | steps: 16 | - uses: actions/checkout@v5 17 | 18 | - run: mv .github/.lycheeignore .lycheeignore 19 | 20 | - name: Link Checker 21 | id: lychee 22 | uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 # v2 23 | with: 24 | fail: false # Don't fail on broken links, create an issue instead 25 | 26 | - name: Find already existing issue 27 | id: find-issue 28 | run: | 29 | echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "Link Checker Report"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT 30 | env: 31 | GH_TOKEN: ${{ github.token }} 32 | 33 | - name: Close issue if everything is fine 34 | if: steps.lychee.outputs.exit_code == 0 && steps.find-issue.outputs.number != '' 35 | run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} 36 | env: 37 | GH_TOKEN: ${{ github.token }} 38 | 39 | - name: Create Issue From File 40 | if: steps.lychee.outputs.exit_code != 0 41 | uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 42 | with: 43 | issue-number: ${{ steps.find-issue.outputs.number }} 44 | title: Link Checker Report 45 | content-filepath: ./lychee/out.md 46 | labels: bug, automated 47 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/FileMaskExclusionPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui; 2 | 3 | import java.util.Set; 4 | 5 | 6 | class FileMaskExclusionPanel extends FileMaskPanel 7 | { 8 | private static final String TEXT_TITLE = "File Path Exclusions (exclusions override inclusions)"; 9 | 10 | private static final String TEXT_ADD_OR_EDIT_MESSAGE = "" 11 | + "

When you add exclusion expressions, only the files that do not match will be impacted by the save " 12 | + "actions.

" 13 | + "

(use case sensitive Java regular expression that matches the end of the full file path)

" 14 | + "
    " 15 | + "
  • Ignore\\.java (exclude file 'Ignore.java' in all folders)
  • " 16 | + "
  • .*\\.properties (exclude all '.properties' in all folders)
  • " 17 | + "
  • src/Ignore\\.java (exclude file 'Ignore.java' in 'src' folders)
  • " 18 | + "
  • ignore/.* (exclude folder 'ignore' recursively)
  • " 19 | + "
  • myProject/Ignore.md (exclude file 'Ignore.md' in project 'myProject')
  • " 20 | + "
" 21 | + ""; 22 | 23 | private static final String TEXT_ADD_TITLE = "Add file path exclusion regex"; 24 | 25 | private static final String TEXT_EDIT_TITLE = "Edit file path exclusion regex"; 26 | 27 | private static final String TEXT_EMPTY = "Nothing excluded"; 28 | 29 | FileMaskExclusionPanel(final Set exclusions) 30 | { 31 | super(exclusions, TEXT_EMPTY, TEXT_TITLE, TEXT_ADD_OR_EDIT_MESSAGE, TEXT_ADD_TITLE, TEXT_ADD_OR_EDIT_MESSAGE, 32 | TEXT_EDIT_TITLE); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/GeneralPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui; 2 | 3 | import static software.xdev.saveactions.model.Action.activate; 4 | import static software.xdev.saveactions.model.Action.activateOnBatch; 5 | import static software.xdev.saveactions.model.Action.activateOnShortcut; 6 | import static software.xdev.saveactions.model.Action.noActionIfCompileErrors; 7 | import static software.xdev.saveactions.model.Action.processAsync; 8 | 9 | import java.awt.Dimension; 10 | import java.util.Map; 11 | 12 | import javax.swing.Box; 13 | import javax.swing.BoxLayout; 14 | import javax.swing.JCheckBox; 15 | import javax.swing.JPanel; 16 | 17 | import com.intellij.ui.IdeBorderFactory; 18 | 19 | import software.xdev.saveactions.model.Action; 20 | 21 | 22 | class GeneralPanel 23 | { 24 | private static final String TEXT_TITLE_ACTIONS = "General"; 25 | 26 | private final Map checkboxes; 27 | 28 | GeneralPanel(final Map checkboxes) 29 | { 30 | this.checkboxes = checkboxes; 31 | } 32 | 33 | JPanel getPanel() 34 | { 35 | final JPanel panel = new JPanel(); 36 | panel.setBorder(IdeBorderFactory.createTitledBorder(TEXT_TITLE_ACTIONS)); 37 | panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 38 | panel.add(this.checkboxes.get(activate)); 39 | panel.add(this.checkboxes.get(activateOnShortcut)); 40 | panel.add(this.checkboxes.get(activateOnBatch)); 41 | panel.add(this.checkboxes.get(noActionIfCompileErrors)); 42 | panel.add(this.checkboxes.get(processAsync)); 43 | panel.add(Box.createHorizontalGlue()); 44 | panel.setMinimumSize(new Dimension(Short.MAX_VALUE, 0)); 45 | return panel; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/FileMaskInclusionPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui; 2 | 3 | import java.util.Set; 4 | 5 | 6 | class FileMaskInclusionPanel extends FileMaskPanel 7 | { 8 | private static final String TEXT_TITLE = "File Path Inclusions (if empty all included)"; 9 | 10 | private static final String TEXT_ADD_OR_EDIT_MESSAGE = "" 11 | + "

When this list is empty, all files are included. When you add inclusion expressions, only the files " 12 | + "that match will be impacted by the save actions.

" 13 | + "

(use case sensitive Java regular expression that matches the end of the full file path)

" 14 | + "
    " 15 | + "
  • .*\\.java (include all '.java' in all folders)
  • " 16 | + "
  • Include\\.java (include file 'Include.java' in all folders)
  • " 17 | + "
  • src/Include\\.java (include file 'Include.java' in 'src' folders)
  • " 18 | + "
  • src/.* (include folder 'src' recursively)
  • " 19 | + "
  • myProject/Include.md (include file 'Include.md' in project 'myProject')
  • " 20 | + "
" 21 | + ""; 22 | 23 | private static final String TEXT_ADD_TITLE = "Add file path inclusion regex"; 24 | 25 | private static final String TEXT_EDIT_TITLE = "Edit file path inclusion regex"; 26 | 27 | private static final String TEXT_EMPTY = "Everything included"; 28 | 29 | FileMaskInclusionPanel(final Set inclusions) 30 | { 31 | super(inclusions, TEXT_EMPTY, TEXT_TITLE, TEXT_ADD_OR_EDIT_MESSAGE, TEXT_ADD_TITLE, TEXT_ADD_OR_EDIT_MESSAGE, 32 | TEXT_EDIT_TITLE); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/integration/ActionTestFile.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | public enum ActionTestFile 4 | { 5 | Reformat_KO_Import_KO, 6 | Reformat_KO_Import_OK, 7 | Reformat_OK_Import_KO, 8 | Reformat_OK_Import_OK, 9 | 10 | Reformat_KO_Rearrange_KO, 11 | Reformat_KO_Rearrange_OK, 12 | Reformat_OK_Rearrange_OK, 13 | 14 | FieldCanBeFinal_KO, 15 | FieldCanBeFinal_OK, 16 | 17 | LocalCanBeFinal_KO, 18 | LocalCanBeFinal_OK, 19 | 20 | LocalCanBeFinalExceptImplicit_KO, 21 | LocalCanBeFinalExceptImplicit_OK, 22 | 23 | MethodMayBeStatic_KO, 24 | MethodMayBeStatic_OK, 25 | 26 | UnqualifiedFieldAccess_KO, 27 | UnqualifiedFieldAccess_OK, 28 | 29 | UnqualifiedMethodAccess_KO, 30 | UnqualifiedMethodAccess_OK, 31 | 32 | UnqualifiedStaticMemberAccess_KO, 33 | UnqualifiedStaticMemberAccess_OK, 34 | 35 | CustomUnqualifiedStaticMemberAccess_KO, 36 | CustomUnqualifiedStaticMemberAccess_OK, 37 | 38 | MissingOverrideAnnotation_KO, 39 | MissingOverrideAnnotation_OK, 40 | 41 | UseBlocks_KO, 42 | UseBlocks_OK, 43 | 44 | GenerateSerialVersionUID_KO, 45 | GenerateSerialVersionUID_OK, 46 | 47 | UnnecessaryThis_KO, 48 | UnnecessaryThis_OK, 49 | 50 | FinalPrivateMethod_KO, 51 | FinalPrivateMethod_OK, 52 | 53 | UnnecessaryFinalOnLocalVariableOrParameter_KO, 54 | UnnecessaryFinalOnLocalVariableOrParameter_OK, 55 | 56 | ExplicitTypeCanBeDiamond_KO, 57 | ExplicitTypeCanBeDiamond_OK, 58 | 59 | UnnecessarySemicolon_KO, 60 | UnnecessarySemicolon_OK, 61 | 62 | SingleStatementInBlock_KO, 63 | SingleStatementInBlock_OK, 64 | 65 | AccessCanBeTightened_KO, 66 | AccessCanBeTightened_OK, 67 | 68 | InspectionsAll_KO, 69 | InspectionsAll_OK; 70 | 71 | public String getFilename() 72 | { 73 | return this.name() + ".java"; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/core/action/ShortcutActionConstants.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.action; 2 | 3 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT; 4 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_FILE; 5 | 6 | import java.util.function.Consumer; 7 | 8 | import com.intellij.openapi.actionSystem.ActionManager; 9 | import com.intellij.openapi.actionSystem.ActionUiKind; 10 | import com.intellij.openapi.actionSystem.AnAction; 11 | import com.intellij.openapi.actionSystem.AnActionEvent; 12 | import com.intellij.openapi.actionSystem.DataContext; 13 | import com.intellij.openapi.actionSystem.impl.SimpleDataContext; 14 | import com.intellij.openapi.command.WriteCommandAction; 15 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 16 | 17 | 18 | public interface ShortcutActionConstants 19 | { 20 | Consumer SAVE_ACTION_SHORTCUT_MANAGER = fixture -> 21 | WriteCommandAction.writeCommandAction(fixture.getProject()).run(() -> runFixure(fixture)); 22 | 23 | static void runFixure(final CodeInsightTestFixture fixture) 24 | { 25 | // set modification timestamp ++ 26 | fixture.getFile().clearCaches(); 27 | 28 | final ActionManager actionManager = ActionManager.getInstance(); 29 | final AnAction action = actionManager.getAction(ShortcutAction.class.getName()); 30 | 31 | final DataContext dataContext = SimpleDataContext.builder() 32 | .add(PROJECT, fixture.getProject()) 33 | .add(PSI_FILE, fixture.getFile()) 34 | .setParent(null) 35 | .build(); 36 | 37 | // call plugin on document 38 | final AnActionEvent event = AnActionEvent.createEvent( 39 | dataContext, 40 | action.getTemplatePresentation().clone(), 41 | "save-actions", 42 | ActionUiKind.NONE, 43 | null); 44 | 45 | new ShortcutAction().actionPerformed(event); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/action/ShortcutAction.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.action; 2 | 3 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_FILE; 4 | import static java.util.Collections.singletonList; 5 | import static software.xdev.saveactions.core.ExecutionMode.shortcut; 6 | import static software.xdev.saveactions.model.Action.activateOnShortcut; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import com.intellij.openapi.actionSystem.AnAction; 14 | import com.intellij.openapi.actionSystem.AnActionEvent; 15 | import com.intellij.openapi.diagnostic.Logger; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.psi.PsiFile; 18 | 19 | import software.xdev.saveactions.core.service.SaveActionsService; 20 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 21 | import software.xdev.saveactions.model.Action; 22 | 23 | 24 | /** 25 | * This action runs the plugin on shortcut, only if property {@link Action#activateOnShortcut} is enabled. It delegates 26 | * to {@link SaveActionsService}. 27 | * 28 | * @see SaveActionsServiceManager 29 | */ 30 | public class ShortcutAction extends AnAction 31 | { 32 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 33 | 34 | @Override 35 | public void actionPerformed(@NotNull final AnActionEvent event) 36 | { 37 | LOGGER.info("[+] Start ShortcutAction#actionPerformed with event " + event); 38 | final PsiFile psiFile = event.getData(PSI_FILE); 39 | final Project project = event.getProject(); 40 | final Set psiFiles = new HashSet<>(singletonList(psiFile)); 41 | SaveActionsServiceManager.getService().guardedProcessPsiFiles(project, psiFiles, activateOnShortcut, shortcut); 42 | LOGGER.info("End ShortcutAction#actionPerformed processed " + psiFiles.size() + " files"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/SaveCommand.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import java.util.Set; 4 | import java.util.function.BiFunction; 5 | 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.PsiFile; 8 | 9 | import software.xdev.saveactions.core.ExecutionMode; 10 | import software.xdev.saveactions.model.Action; 11 | 12 | 13 | /** 14 | * Abstracts a save command with a {@link BiFunction} from pair ({@link Project}, {@link PsiFile}[]) to 15 | * {@link Runnable}. The entry point is {@link #execute()}. 16 | */ 17 | public abstract class SaveCommand 18 | { 19 | private final Project project; 20 | private final Set psiFiles; 21 | private final Set modes; 22 | private final Action action; 23 | private final BiFunction command; 24 | 25 | protected SaveCommand( 26 | final Project project, final Set psiFiles, final Set modes, final Action action, 27 | final BiFunction command) 28 | { 29 | this.project = project; 30 | this.psiFiles = psiFiles; 31 | this.modes = modes; 32 | this.action = action; 33 | this.command = command; 34 | } 35 | 36 | public Project getProject() 37 | { 38 | return this.project; 39 | } 40 | 41 | public Set getPsiFiles() 42 | { 43 | return this.psiFiles; 44 | } 45 | 46 | public PsiFile[] getPsiFilesAsArray() 47 | { 48 | return this.psiFiles.toArray(new PsiFile[0]); 49 | } 50 | 51 | public Set getModes() 52 | { 53 | return this.modes; 54 | } 55 | 56 | public Action getAction() 57 | { 58 | return this.action; 59 | } 60 | 61 | public BiFunction getCommand() 62 | { 63 | return this.command; 64 | } 65 | 66 | @Override 67 | public String toString() 68 | { 69 | return this.action.toString(); 70 | } 71 | 72 | public abstract Result execute(); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/inspection/AccessibleVisibilityInspection.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java.inspection; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import com.intellij.codeInspection.visibility.VisibilityInspection; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiJavaCodeReferenceElement; 11 | import com.intellij.psi.PsiMember; 12 | import com.intellij.psi.SyntaxTraverser; 13 | 14 | 15 | /** 16 | * Fork of {@link com.intellij.codeInspection.visibility.VisibilityInspection} but accessible for the plugin. 17 | */ 18 | public final class AccessibleVisibilityInspection 19 | { 20 | private static final Logger LOG = Logger.getInstance(AccessibleVisibilityInspection.class); 21 | 22 | private AccessibleVisibilityInspection() 23 | { 24 | } 25 | 26 | public static boolean containsReferenceTo(final PsiElement source, final PsiElement target) 27 | { 28 | return SyntaxTraverser.psiTraverser(source) 29 | .filter(PsiJavaCodeReferenceElement.class) 30 | .filter(ref -> ref.isReferenceTo(target)) 31 | .isNotEmpty(); 32 | } 33 | 34 | // reflection is needed because VisibilityInspection members are private 35 | @SuppressWarnings({"java:S3011"}) 36 | public static int getMinVisibilityLevel( 37 | final VisibilityInspection myVisibilityInspection, 38 | @NotNull final PsiMember member) 39 | { 40 | try 41 | { 42 | final Method getMinVisibilityLevel = myVisibilityInspection.getClass() 43 | .getDeclaredMethod("getMinVisibilityLevel", PsiMember.class); 44 | 45 | getMinVisibilityLevel.setAccessible(true); 46 | 47 | return (int)getMinVisibilityLevel.invoke(myVisibilityInspection, member); 48 | } 49 | catch(final Exception e) 50 | { 51 | LOG.error("Failed to invoke getMinVisibilityLevel", e); 52 | throw new IllegalStateException(e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/inspection/CustomLocalCanBeFinal.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java.inspection; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import com.intellij.codeInspection.InspectionManager; 10 | import com.intellij.codeInspection.ProblemDescriptor; 11 | import com.intellij.codeInspection.localCanBeFinal.LocalCanBeFinal; 12 | import com.intellij.psi.PsiClass; 13 | import com.intellij.psi.PsiElement; 14 | import com.intellij.psi.PsiMethod; 15 | import com.intellij.psi.PsiTypeElement; 16 | 17 | 18 | @SuppressWarnings("InspectionDescriptionNotFoundInspection") 19 | public class CustomLocalCanBeFinal extends LocalCanBeFinal 20 | { 21 | @Override 22 | public ProblemDescriptor[] checkMethod( 23 | @NotNull final PsiMethod method, 24 | @NotNull final InspectionManager manager, 25 | final boolean isOnTheFly) 26 | { 27 | return this.checkProblemDescriptors(super.checkMethod(method, manager, isOnTheFly)); 28 | } 29 | 30 | @Override 31 | public ProblemDescriptor[] checkClass( 32 | @NotNull final PsiClass aClass, 33 | @NotNull final InspectionManager manager, 34 | final boolean isOnTheFly) 35 | { 36 | return this.checkProblemDescriptors(super.checkClass(aClass, manager, isOnTheFly)); 37 | } 38 | 39 | private ProblemDescriptor[] checkProblemDescriptors(@Nullable final ProblemDescriptor[] descriptors) 40 | { 41 | return Arrays 42 | .stream(Optional.ofNullable(descriptors).orElse(new ProblemDescriptor[0])) 43 | .filter(descriptor -> this.isNotLombokVal(descriptor.getPsiElement())) 44 | .toArray(ProblemDescriptor[]::new); 45 | } 46 | 47 | private boolean isNotLombokVal(final PsiElement element) 48 | { 49 | return Arrays 50 | .stream(element.getParent().getChildren()) 51 | .noneMatch(child -> child instanceof PsiTypeElement && "val".equals(child.getText())); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/inspection/CustomSerializableHasSerialVersionUidFieldInspection.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java.inspection; 2 | 3 | import org.jetbrains.annotations.Nls; 4 | import org.jetbrains.annotations.NonNls; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.jetbrains.uast.UClass; 8 | 9 | import com.intellij.codeInspection.InspectionManager; 10 | import com.intellij.codeInspection.ProblemDescriptor; 11 | import com.intellij.codeInspection.SerializableHasSerialVersionUidFieldInspection; 12 | import com.intellij.codeInspection.USerializableInspectionBase; 13 | 14 | 15 | /** 16 | * Wrapper for the SerializableHasSerialVersionUidFieldInspection class. 17 | *

18 | * We have to set isOnTheFly to true. Otherwise, the inspection will not be applied. 19 | * 20 | * @since IDEA 2021.3 21 | */ 22 | public class CustomSerializableHasSerialVersionUidFieldInspection extends USerializableInspectionBase 23 | { 24 | @SuppressWarnings("unchecked") 25 | public CustomSerializableHasSerialVersionUidFieldInspection() 26 | { 27 | super(UClass.class); 28 | } 29 | 30 | @Override 31 | public @NonNls @NotNull String getID() 32 | { 33 | return "serial"; 34 | } 35 | 36 | @Override 37 | public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getDisplayName() 38 | { 39 | return this.getClass().getSimpleName(); 40 | } 41 | 42 | @Override 43 | public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getGroupDisplayName() 44 | { 45 | return "SaveActionsInternal"; 46 | } 47 | 48 | @Override 49 | public ProblemDescriptor @Nullable [] checkClass( 50 | @NotNull final UClass aClass, 51 | @NotNull final InspectionManager manager, 52 | final boolean isOnTheFly) 53 | { 54 | final SerializableHasSerialVersionUidFieldInspection inspection = 55 | new SerializableHasSerialVersionUidFieldInspection(); 56 | return inspection.checkClass(aClass, manager, true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_KO_Rearrange_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | Storage storage; 29 | private CodeInsightTestFixture fixture; 30 | 31 | @BeforeEach 32 | public void before() throws Exception { 33 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 34 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 35 | } 36 | 37 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 38 | String afterFilename) { 39 | fixture.configureByFile(beforeFilename + ".java"); 40 | saveActionManager.accept(fixture); 41 | fixture.checkResultByFile(afterFilename + ".java"); 42 | } 43 | 44 | private String getTestDataPath() { 45 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 46 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 47 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 48 | return root.toString(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_KO_Import_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | 29 | private CodeInsightTestFixture fixture; 30 | 31 | Storage storage; 32 | 33 | @BeforeEach 34 | public void before() throws Exception { 35 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 36 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 37 | } 38 | 39 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 40 | String afterFilename) { 41 | fixture.configureByFile(beforeFilename + ".java"); 42 | saveActionManager.accept(fixture); 43 | fixture.checkResultByFile(afterFilename + ".java"); 44 | } 45 | 46 | private String getTestDataPath() { 47 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 48 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 49 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 50 | return root.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_KO_Import_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | 29 | private CodeInsightTestFixture fixture; 30 | 31 | Storage storage; 32 | 33 | @BeforeEach 34 | public void before() throws Exception { 35 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 36 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 37 | } 38 | 39 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 40 | String afterFilename) { 41 | fixture.configureByFile(beforeFilename + ".java"); 42 | saveActionManager.accept(fixture); 43 | fixture.checkResultByFile(afterFilename + ".java"); 44 | } 45 | 46 | private String getTestDataPath() { 47 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 48 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 49 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 50 | return root.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_KO_Rearrange_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | 29 | private CodeInsightTestFixture fixture; 30 | 31 | Storage storage; 32 | 33 | @BeforeEach 34 | public void before() throws Exception { 35 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 36 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 37 | } 38 | 39 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 40 | String afterFilename) { 41 | fixture.configureByFile(beforeFilename + ".java"); 42 | saveActionManager.accept(fixture); 43 | fixture.checkResultByFile(afterFilename + ".java"); 44 | } 45 | 46 | private String getTestDataPath() { 47 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 48 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 49 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 50 | return root.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_OK_Rearrange_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | Storage storage; 29 | private CodeInsightTestFixture fixture; 30 | 31 | @BeforeEach 32 | public void before() throws Exception { 33 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 34 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 35 | } 36 | 37 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 38 | String afterFilename) { 39 | fixture.configureByFile(beforeFilename + ".java"); 40 | saveActionManager.accept(fixture); 41 | fixture.checkResultByFile(afterFilename + ".java"); 42 | } 43 | 44 | private String getTestDataPath() { 45 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 46 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 47 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 48 | return root.toString(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_OK_Import_KO.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | 29 | private CodeInsightTestFixture fixture; 30 | 31 | Storage storage; 32 | 33 | @BeforeEach 34 | public void before() throws Exception { 35 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 36 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 37 | } 38 | 39 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 40 | String afterFilename) { 41 | fixture.configureByFile(beforeFilename + ".java"); 42 | saveActionManager.accept(fixture); 43 | fixture.checkResultByFile(afterFilename + ".java"); 44 | } 45 | 46 | private String getTestDataPath() { 47 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 48 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 49 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 50 | return root.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/integration/Reformat_OK_Import_OK.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.psi.impl.source.PsiFileImpl; 5 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 6 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 7 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import software.xdev.saveactions.model.Storage; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Consumer; 14 | 15 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 16 | 17 | public class Class { 18 | 19 | static final String STATIC = "static"; 20 | 21 | static final Consumer SAVE_ACTION_MANAGER = (fixture) -> 22 | new WriteCommandAction.Simple(fixture.getProject()) { 23 | @Override 24 | protected void run() { 25 | ((PsiFileImpl) fixture.getFile()).clearCaches(); 26 | } 27 | }.execute(); 28 | 29 | private CodeInsightTestFixture fixture; 30 | 31 | Storage storage; 32 | 33 | @BeforeEach 34 | public void before() throws Exception { 35 | IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 36 | IdeaProjectTestFixture fixture = factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR).getFixture(); 37 | } 38 | 39 | protected void assertFormat(String beforeFilename, Consumer saveActionManager, 40 | String afterFilename) { 41 | fixture.configureByFile(beforeFilename + ".java"); 42 | saveActionManager.accept(fixture); 43 | fixture.checkResultByFile(afterFilename + ".java"); 44 | } 45 | 46 | private String getTestDataPath() { 47 | Path classes = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); 48 | Path resources = Paths.get(classes.getParent().toString(), "resources"); 49 | Path root = Paths.get(resources.toString(), getClass().getPackage().getName().split("[.]")); 50 | return root.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/action/BatchAction.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.action; 2 | 3 | import static java.util.Collections.synchronizedSet; 4 | import static software.xdev.saveactions.core.ExecutionMode.batch; 5 | import static software.xdev.saveactions.model.Action.activateOnBatch; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import com.intellij.analysis.AnalysisScope; 13 | import com.intellij.analysis.BaseAnalysisAction; 14 | import com.intellij.openapi.diagnostic.Logger; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.psi.PsiElementVisitor; 17 | import com.intellij.psi.PsiFile; 18 | 19 | import software.xdev.saveactions.core.service.SaveActionsService; 20 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 21 | import software.xdev.saveactions.model.Action; 22 | 23 | 24 | /** 25 | * This action runs the save actions on the given scope of files, only if property {@link Action#activateOnShortcut} is 26 | * enabled. The user is asked for the scope using a standard IDEA dialog. It delegates to {@link SaveActionsService}. 27 | * Originally based on {@link com.intellij.codeInspection.inferNullity.InferNullityAnnotationsAction}. 28 | * 29 | * @author markiewb 30 | * @see SaveActionsServiceManager 31 | */ 32 | public class BatchAction extends BaseAnalysisAction 33 | { 34 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 35 | private static final String COMPONENT_NAME = "Save Actions"; 36 | 37 | public BatchAction() 38 | { 39 | super(COMPONENT_NAME, COMPONENT_NAME); 40 | } 41 | 42 | @Override 43 | protected void analyze(@NotNull final Project project, @NotNull final AnalysisScope scope) 44 | { 45 | LOGGER.info("[+] Start BatchAction#analyze with project " + project + " and scope " + scope); 46 | final Set psiFiles = synchronizedSet(new HashSet<>()); 47 | scope.accept(new PsiElementVisitor() 48 | { 49 | @Override 50 | public void visitFile(final PsiFile psiFile) 51 | { 52 | super.visitFile(psiFile); 53 | psiFiles.add(psiFile); 54 | } 55 | }); 56 | SaveActionsServiceManager.getService().guardedProcessPsiFiles(project, psiFiles, activateOnBatch, batch); 57 | LOGGER.info("End BatchAction#analyze processed " + psiFiles.size() + " files"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug 2 | description: Create a bug report for something that is broken 3 | labels: [bug] 4 | type: bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for reporting a bug. 10 | 11 | Please fill in as much information as possible about your bug so that we don't have to play "information ping-pong" and can help you immediately. 12 | 13 | - type: checkboxes 14 | id: checklist 15 | attributes: 16 | label: "Checklist" 17 | options: 18 | - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/intellij-plugin-save-actions/releases/latest)" 19 | required: true 20 | - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/intellij-plugin-save-actions/issues) or [closed](https://github.com/xdev-software/intellij-plugin-save-actions/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." 21 | required: true 22 | - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." 23 | required: true 24 | - label: "This issue contains only one bug." 25 | required: true 26 | 27 | - type: input 28 | id: app-version 29 | attributes: 30 | label: Affected version 31 | description: "In which version did you encounter the bug?" 32 | placeholder: "x.x.x" 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: description 38 | attributes: 39 | label: Description of the problem 40 | description: | 41 | Describe as exactly as possible what is not working. 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: steps-to-reproduce 47 | attributes: 48 | label: Steps to reproduce the bug 49 | description: | 50 | What did you do for the bug to show up? 51 | 52 | If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. 53 | placeholder: | 54 | 1. Use '...' 55 | 2. Do '...' 56 | validations: 57 | required: true 58 | 59 | - type: textarea 60 | id: additional-information 61 | attributes: 62 | label: Additional information 63 | description: | 64 | Any other relevant information you'd like to include 65 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/model/example1.epf: -------------------------------------------------------------------------------- 1 | # @title Save Actions 2 | # @description Save Actions 3 | # @task_type LASTMOD 4 | file_export_version=3.0 5 | /instance/org.eclipse.jdt.ui/editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true 6 | /instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code=true 7 | /instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code_changes_only=false 8 | /instance/org.eclipse.jdt.ui/sp_cleanup.organize_imports=true 9 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces=true 10 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_all=true 11 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_ignore_empty=false 12 | /instance/org.eclipse.jdt.ui/sp_cleanup.make_local_variable_final=true 13 | /instance/org.eclipse.jdt.ui/sp_cleanup.make_private_fields_final=false 14 | /instance/org.eclipse.jdt.ui/sp_cleanup.make_parameters_final=true 15 | /instance/org.eclipse.jdt.ui/sp_cleanup.make_variable_declarations_final=true 16 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_annotations=true 17 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_deprecated_annotations=true 18 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations=true 19 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations_interface_methods=true 20 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_imports=true 21 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unnecessary_casts=true 22 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_serial_version_id=false 23 | /instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks=true 24 | /instance/org.eclipse.jdt.ui/sp_cleanup.always_use_blocks=false 25 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_members=false 26 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_local_variables=false 27 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_methods=false 28 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_fields=false 29 | /instance/org.eclipse.jdt.ui/sp_cleanup.add_generated_serial_version_id=false 30 | /instance/org.eclipse.jdt.ui/sp_cleanup.correct_indentation=false 31 | /instance/org.eclipse.jdt.ui/sp_cleanup.sort_members_all=false 32 | /instance/org.eclipse.jdt.ui/sp_cleanup.on_save_use_additional_actions=true 33 | /instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks_only_for_return_and_throw=false 34 | /instance/org.eclipse.jdt.ui/sp_cleanup.sort_members=true 35 | /instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_field_access=true 36 | /instance/org.eclipse.jdt.ui/sp_cleanup.remove_redundant_type_arguments=true 37 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/core/listener/SaveActionsDocumentManagerListener.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.core.listener; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | import com.intellij.openapi.diagnostic.Logger; 10 | import com.intellij.openapi.editor.Document; 11 | import com.intellij.openapi.fileEditor.FileDocumentManager; 12 | import com.intellij.openapi.fileEditor.FileDocumentManagerListener; 13 | import com.intellij.openapi.project.Project; 14 | import com.intellij.psi.PsiDocumentManager; 15 | import com.intellij.psi.PsiFile; 16 | 17 | import software.xdev.saveactions.core.ExecutionMode; 18 | import software.xdev.saveactions.core.service.SaveActionsService; 19 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 20 | import software.xdev.saveactions.model.Action; 21 | 22 | 23 | /** 24 | * FileDocumentManagerListener to catch save events. This listener is registered as ExtensionPoint. 25 | */ 26 | public final class SaveActionsDocumentManagerListener implements FileDocumentManagerListener 27 | { 28 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 29 | 30 | private final Project project; 31 | private PsiDocumentManager psiDocumentManager; 32 | 33 | public SaveActionsDocumentManagerListener(final Project project) 34 | { 35 | this.project = project; 36 | } 37 | 38 | @Override 39 | public void beforeAllDocumentsSaving() 40 | { 41 | LOGGER.debug( 42 | "[+] Start SaveActionsDocumentManagerListener#beforeAllDocumentsSaving, " + this.project.getName()); 43 | 44 | final List unsavedDocuments = Arrays.asList(FileDocumentManager.getInstance().getUnsavedDocuments()); 45 | if(!unsavedDocuments.isEmpty()) 46 | { 47 | LOGGER.debug(String.format( 48 | "Locating psi files for %d documents: %s", 49 | unsavedDocuments.size(), 50 | unsavedDocuments)); 51 | this.beforeDocumentsSaving(unsavedDocuments); 52 | } 53 | LOGGER.debug("End SaveActionsDocumentManagerListener#beforeAllDocumentsSaving"); 54 | } 55 | 56 | public void beforeDocumentsSaving(final List documents) 57 | { 58 | if(this.project.isDisposed()) 59 | { 60 | return; 61 | } 62 | this.initPsiDocManager(); 63 | final Set psiFiles = documents.stream() 64 | .map(this.psiDocumentManager::getPsiFile) 65 | .filter(Objects::nonNull) 66 | .collect(Collectors.toSet()); 67 | SaveActionsServiceManager.getService() 68 | .guardedProcessPsiFiles(this.project, psiFiles, Action.activate, ExecutionMode.saveAll); 69 | } 70 | 71 | private synchronized void initPsiDocManager() 72 | { 73 | if(this.psiDocumentManager == null) 74 | { 75 | this.psiDocumentManager = PsiDocumentManager.getInstance(this.project); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/java/EpfKey.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.stream.Stream; 7 | 8 | 9 | @SuppressWarnings("java:S115") 10 | public enum EpfKey 11 | { 12 | add_default_serial_version_id, 13 | add_generated_serial_version_id, 14 | add_missing_annotations, 15 | add_missing_deprecated_annotations, 16 | add_missing_methods, 17 | add_missing_nls_tags, 18 | add_missing_override_annotations, 19 | add_missing_override_annotations_interface_methods, 20 | add_serial_version_id, 21 | always_use_blocks, 22 | always_use_parentheses_in_expressions, 23 | always_use_this_for_non_static_field_access, 24 | always_use_this_for_non_static_method_access, 25 | convert_functional_interfaces, 26 | convert_to_enhanced_for_loop, 27 | correct_indentation, 28 | format_source_code, 29 | format_source_code_changes_only, 30 | insert_inferred_type_arguments, 31 | make_local_variable_final, 32 | make_parameters_final, 33 | make_private_fields_final, 34 | make_type_abstract_if_missing_method, 35 | make_variable_declarations_final, 36 | never_use_blocks, 37 | never_use_parentheses_in_expressions, 38 | on_save_use_additional_actions, 39 | organize_imports, 40 | qualify_static_field_accesses_with_declaring_class, 41 | qualify_static_member_accesses_through_instances_with_declaring_class, 42 | qualify_static_member_accesses_through_subtypes_with_declaring_class, 43 | qualify_static_member_accesses_with_declaring_class, 44 | qualify_static_method_accesses_with_declaring_class, 45 | remove_private_constructors, 46 | remove_redundant_type_arguments, 47 | remove_trailing_whitespaces, 48 | remove_trailing_whitespaces_all, 49 | remove_trailing_whitespaces_ignore_empty, 50 | remove_unnecessary_casts, 51 | remove_unnecessary_nls_tags, 52 | remove_unused_imports, 53 | remove_unused_local_variables, 54 | remove_unused_private_fields, 55 | remove_unused_private_members, 56 | remove_unused_private_methods, 57 | remove_unused_private_types, 58 | sort_members, 59 | sort_members_all, 60 | use_anonymous_class_creation, 61 | use_blocks, 62 | use_blocks_only_for_return_and_throw, 63 | use_lambda, 64 | use_parentheses_in_expressions, 65 | use_this_for_non_static_field_access, 66 | use_this_for_non_static_field_access_only_if_necessary, 67 | use_this_for_non_static_method_access, 68 | use_this_for_non_static_method_access_only_if_necessary; 69 | 70 | private static final List PREFIXES = Arrays.asList( 71 | "sp_cleanup", 72 | "/instance/org.eclipse.jdt.ui/sp_cleanup" 73 | ); 74 | 75 | public static List getPrefixes() 76 | { 77 | return Collections.unmodifiableList(PREFIXES); 78 | } 79 | 80 | public static Stream stream() 81 | { 82 | return Arrays.stream(values()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/model/java/EpfKeyTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Properties; 10 | 11 | import org.junit.jupiter.api.Test; 12 | 13 | 14 | class EpfKeyTest 15 | { 16 | @Test 17 | void should_all_example_file_0_keys_be_present_in_epf_key() throws IOException 18 | { 19 | final Properties properties = this.readProperties(EpfTestConstants.EXAMPLE_EPF_0.toString()); 20 | this.assertPropertyPresenceInEpf(properties); 21 | } 22 | 23 | @Test 24 | void should_all_example_file_1_keys_be_present_in_epf_key() throws IOException 25 | { 26 | final Properties properties = this.readProperties(EpfTestConstants.EXAMPLE_EPF_1.toString()); 27 | this.assertPropertyPresenceInEpf(properties); 28 | } 29 | 30 | @Test 31 | void should_all_example_file_2_keys_be_present_in_epf_key() throws IOException 32 | { 33 | final Properties properties = this.readProperties(EpfTestConstants.EXAMPLE_EPF_2.toString()); 34 | this.assertPropertyPresenceInEpf(properties); 35 | } 36 | 37 | @Test 38 | void should_all_epf_key_be_present_in_example_files_2_to_remove_unused_keys() throws IOException 39 | { 40 | final Properties properties = this.readProperties(EpfTestConstants.EXAMPLE_EPF_2.toString()); 41 | final List epfKeyNames = this.getEpfKeyNames(); 42 | final List propertiesKeyNames = this.getPropertiesKeyNames(properties); 43 | epfKeyNames.forEach(epfKeyName -> assertThat(propertiesKeyNames).contains(epfKeyName)); 44 | } 45 | 46 | private void assertPropertyPresenceInEpf(final Properties properties) 47 | { 48 | final List epfKeyNames = this.getEpfKeyNames(); 49 | final List propertiesKeyNames = this.getPropertiesKeyNames(properties); 50 | propertiesKeyNames.forEach(propertiesKeyName -> assertThat(epfKeyNames).contains(propertiesKeyName)); 51 | } 52 | 53 | private List getPropertiesKeyNames(final Properties properties) 54 | { 55 | return properties.keySet().stream() 56 | .map(Object::toString) 57 | .filter(key -> EpfKey.getPrefixes().stream().anyMatch(key::startsWith)) 58 | .map(key -> key.substring(key.lastIndexOf('.') == -1 ? 0 : key.lastIndexOf('.') + 1)) 59 | .collect(toList()); 60 | } 61 | 62 | private List getEpfKeyNames() 63 | { 64 | return EpfKey.stream() 65 | .map(EpfKey::name) 66 | .collect(toList()); 67 | } 68 | 69 | private Properties readProperties(final String configurationPath) throws IOException 70 | { 71 | final Properties properties = new Properties(); 72 | try(final FileInputStream in = new FileInputStream(configurationPath)) 73 | { 74 | properties.load(in); 75 | } 76 | return properties; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/inspection/SerializableHasSerialVersionUIDFieldInspectionWrapper.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java.inspection; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.Arrays; 5 | import java.util.Objects; 6 | 7 | import com.intellij.codeInspection.LocalInspectionTool; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | 10 | import software.xdev.saveactions.core.service.SaveActionsService; 11 | 12 | 13 | /** 14 | * This class changes package between intellij versions. 15 | * 16 | * @see com.intellij.codeInspection.SerializableHasSerialVersionUidFieldInspection 17 | */ 18 | public final class SerializableHasSerialVersionUIDFieldInspectionWrapper 19 | { 20 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 21 | 22 | private SerializableHasSerialVersionUIDFieldInspectionWrapper() 23 | { 24 | } 25 | 26 | public static LocalInspectionTool get() 27 | { 28 | return Arrays.stream(SerializableClass.values()) 29 | .map(SerializableClass::getInspectionInstance) 30 | .filter(Objects::nonNull) 31 | .findFirst() 32 | .orElseThrow(() -> new IllegalStateException( 33 | "Cannot find inspection tool SerializableHasSerialVersionUIDFieldInspection")); 34 | } 35 | 36 | private enum SerializableClass 37 | { 38 | CLASS_NAME_INTELLIJ_2021_3( 39 | "com.intellij.codeInspection.SerializableHasSerialVersionUidFieldInspection", 40 | "software.xdev.saveactions.processors.java.inspection" 41 | + ".CustomSerializableHasSerialVersionUidFieldInspection"); 42 | 43 | /** 44 | * Field className: Inspection class provided by IDE 45 | */ 46 | private final String className; 47 | 48 | /** 49 | * Field targetClass: Inspection class to run. Needed to apply wrapper class for Idea 2021.3 and up. 50 | * 51 | * @see CustomSerializableHasSerialVersionUidFieldInspection 52 | */ 53 | private final String targetClass; 54 | 55 | SerializableClass(final String className, final String targetClass) 56 | { 57 | this.className = className; 58 | this.targetClass = targetClass; 59 | } 60 | 61 | public LocalInspectionTool getInspectionInstance() 62 | { 63 | try 64 | { 65 | Class.forName(this.className).asSubclass(LocalInspectionTool.class); 66 | final Class targetInspectionClass = 67 | Class.forName(this.targetClass).asSubclass(LocalInspectionTool.class); 68 | LOGGER.info(String.format("Found serial version uid class %s", targetInspectionClass.getName())); 69 | return targetInspectionClass.cast(targetInspectionClass.getDeclaredConstructor().newInstance()); 70 | } 71 | catch(final ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException 72 | | InvocationTargetException e) 73 | { 74 | return null; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/java/EpfAction.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import static java.util.Collections.unmodifiableList; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.function.Function; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | import software.xdev.saveactions.model.Action; 14 | 15 | 16 | public enum EpfAction 17 | { 18 | organizeImports( 19 | Action.organizeImports, 20 | EpfKey.organize_imports, EpfKey.remove_unused_imports), 21 | 22 | reformat( 23 | Action.reformat, 24 | EpfKey.format_source_code), 25 | 26 | reformatChangedCode( 27 | Action.reformatChangedCode, 28 | EpfKey.format_source_code_changes_only), 29 | 30 | rearrange( 31 | Action.rearrange, 32 | EpfKey.sort_members, EpfKey.sort_members_all), 33 | 34 | fieldCanBeFinal( 35 | Action.fieldCanBeFinal, 36 | EpfKey.make_private_fields_final), 37 | 38 | localCanBeFinal( 39 | Action.localCanBeFinal, 40 | EpfKey.make_local_variable_final), 41 | 42 | unqualifiedFieldAccess( 43 | Action.unqualifiedFieldAccess, 44 | EpfKey.use_this_for_non_static_field_access), 45 | 46 | unqualifiedMethodAccess( 47 | Action.unqualifiedMethodAccess, 48 | EpfKey.always_use_this_for_non_static_method_access), 49 | 50 | unqualifiedStaticMemberAccess( 51 | Action.unqualifiedStaticMemberAccess, 52 | EpfKey.qualify_static_member_accesses_with_declaring_class), 53 | 54 | missingOverrideAnnotation( 55 | Action.missingOverrideAnnotation, 56 | EpfKey.add_missing_override_annotations, EpfKey.add_missing_override_annotations_interface_methods), 57 | 58 | useBlocks( 59 | Action.useBlocks, 60 | EpfKey.use_blocks, EpfKey.always_use_blocks), 61 | 62 | generateSerialVersionUID( 63 | Action.generateSerialVersionUID, 64 | EpfKey.add_serial_version_id, EpfKey.add_default_serial_version_id, EpfKey.add_generated_serial_version_id), 65 | 66 | explicitTypeCanBeDiamond( 67 | Action.explicitTypeCanBeDiamond, 68 | EpfKey.remove_redundant_type_arguments); 69 | 70 | private static final Map ACTION_VALUES = stream() 71 | .collect(Collectors.toMap(EpfAction::getAction, Function.identity())); 72 | 73 | private final Action action; 74 | private final List epfKeys; 75 | 76 | EpfAction(final Action action, final EpfKey... epfKeys) 77 | { 78 | this.action = action; 79 | this.epfKeys = Arrays.asList(epfKeys); 80 | } 81 | 82 | public Action getAction() 83 | { 84 | return this.action; 85 | } 86 | 87 | public List getEpfKeys() 88 | { 89 | return unmodifiableList(this.epfKeys); 90 | } 91 | 92 | public static Optional getEpfActionForAction(final Action action) 93 | { 94 | return Optional.ofNullable(ACTION_VALUES.get(action)); 95 | } 96 | 97 | public static Stream stream() 98 | { 99 | return Arrays.stream(values()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/test/resources/software/xdev/saveactions/model/example2.epf: -------------------------------------------------------------------------------- 1 | content_assist_proposals_background=255,255,255 2 | content_assist_proposals_foreground=0,0,0 3 | eclipse.preferences.version=1 4 | editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true 5 | org.eclipse.jdt.ui.formatterprofiles.version=13 6 | sp_cleanup.add_default_serial_version_id=true 7 | sp_cleanup.add_generated_serial_version_id=false 8 | sp_cleanup.add_missing_annotations=true 9 | sp_cleanup.add_missing_deprecated_annotations=true 10 | sp_cleanup.add_missing_methods=false 11 | sp_cleanup.add_missing_nls_tags=false 12 | sp_cleanup.add_missing_override_annotations=true 13 | sp_cleanup.add_missing_override_annotations_interface_methods=true 14 | sp_cleanup.add_serial_version_id=false 15 | sp_cleanup.always_use_blocks=true 16 | sp_cleanup.always_use_parentheses_in_expressions=false 17 | sp_cleanup.always_use_this_for_non_static_field_access=false 18 | sp_cleanup.always_use_this_for_non_static_method_access=false 19 | sp_cleanup.convert_functional_interfaces=true 20 | sp_cleanup.convert_to_enhanced_for_loop=true 21 | sp_cleanup.correct_indentation=true 22 | sp_cleanup.format_source_code=false 23 | sp_cleanup.format_source_code_changes_only=false 24 | sp_cleanup.insert_inferred_type_arguments=false 25 | sp_cleanup.make_local_variable_final=true 26 | sp_cleanup.make_parameters_final=true 27 | sp_cleanup.make_private_fields_final=true 28 | sp_cleanup.make_type_abstract_if_missing_method=false 29 | sp_cleanup.make_variable_declarations_final=true 30 | sp_cleanup.never_use_blocks=false 31 | sp_cleanup.never_use_parentheses_in_expressions=true 32 | sp_cleanup.on_save_use_additional_actions=true 33 | sp_cleanup.organize_imports=true 34 | sp_cleanup.qualify_static_field_accesses_with_declaring_class=true 35 | sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true 36 | sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true 37 | sp_cleanup.qualify_static_member_accesses_with_declaring_class=true 38 | sp_cleanup.qualify_static_method_accesses_with_declaring_class=true 39 | sp_cleanup.remove_private_constructors=true 40 | sp_cleanup.remove_redundant_type_arguments=true 41 | sp_cleanup.remove_trailing_whitespaces=true 42 | sp_cleanup.remove_trailing_whitespaces_all=true 43 | sp_cleanup.remove_trailing_whitespaces_ignore_empty=false 44 | sp_cleanup.remove_unnecessary_casts=true 45 | sp_cleanup.remove_unnecessary_nls_tags=true 46 | sp_cleanup.remove_unused_imports=true 47 | sp_cleanup.remove_unused_local_variables=true 48 | sp_cleanup.remove_unused_private_fields=true 49 | sp_cleanup.remove_unused_private_members=true 50 | sp_cleanup.remove_unused_private_methods=true 51 | sp_cleanup.remove_unused_private_types=true 52 | sp_cleanup.sort_members=true 53 | sp_cleanup.sort_members_all=false 54 | sp_cleanup.use_anonymous_class_creation=false 55 | sp_cleanup.use_blocks=true 56 | sp_cleanup.use_blocks_only_for_return_and_throw=false 57 | sp_cleanup.use_lambda=true 58 | sp_cleanup.use_parentheses_in_expressions=true 59 | sp_cleanup.use_this_for_non_static_field_access=true 60 | sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true 61 | sp_cleanup.use_this_for_non_static_method_access=true 62 | sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true 63 | spelling_locale_initialized=true 64 | useAnnotationsPrefPage=true 65 | useQuickDiffPrefPage=true 66 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/java/EpfStorage.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model.java; 2 | 3 | import static java.util.Collections.emptyList; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.Properties; 10 | 11 | import com.intellij.openapi.diagnostic.Logger; 12 | 13 | import software.xdev.saveactions.core.service.SaveActionsService; 14 | import software.xdev.saveactions.model.Action; 15 | import software.xdev.saveactions.model.Storage; 16 | 17 | 18 | /** 19 | * Storage implementation for the Workspace-Mechanic-Format. Only the Java language-specific actions are supported. 20 | *

21 | * The main method {@link #getStorageOrDefault(String, Storage)} return a configuration based on EPF if the path to EPF 22 | * configuration file is set and valid, or else the default configuration is returned. 23 | *

24 | * The default storage is used to copy the actions, the inclusions and the exclusions, then the actions are taken from 25 | * the epf file and overrides the actions precedently set. 26 | * 27 | * @author markiewb 28 | */ 29 | public enum EpfStorage 30 | { 31 | INSTANCE; 32 | 33 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 34 | 35 | public Storage getStorageOrDefault(final String configurationPath, final Storage defaultStorage) 36 | { 37 | try 38 | { 39 | return this.getStorageOrDefault0(configurationPath, defaultStorage); 40 | } 41 | catch(final IOException e) 42 | { 43 | LOGGER.info("Error in configuration file " + defaultStorage.getConfigurationPath(), e); 44 | return defaultStorage; 45 | } 46 | } 47 | 48 | private Storage getStorageOrDefault0(final String configurationPath, final Storage defaultStorage) 49 | throws IOException 50 | { 51 | if("".equals(configurationPath) || configurationPath == null) 52 | { 53 | return defaultStorage; 54 | } 55 | final Storage storage = new Storage(defaultStorage); 56 | final Properties properties = this.readProperties(configurationPath); 57 | Action.stream().forEach(action -> storage.setEnabled(action, this.isEnabledInEpf(properties, action) 58 | .orElse(defaultStorage.isEnabled(action)))); 59 | return storage; 60 | } 61 | 62 | private Optional isEnabledInEpf(final Properties properties, final Action action) 63 | { 64 | final List epfKeys = EpfAction.getEpfActionForAction(action) 65 | .map(EpfAction::getEpfKeys) 66 | .orElse(emptyList()); 67 | for(final EpfKey epfKey : epfKeys) 68 | { 69 | if(this.isEnabledInEpf(properties, epfKey, true)) 70 | { 71 | return Optional.of(true); 72 | } 73 | if(this.isEnabledInEpf(properties, epfKey, false)) 74 | { 75 | return Optional.of(false); 76 | } 77 | } 78 | return Optional.empty(); 79 | } 80 | 81 | private boolean isEnabledInEpf(final Properties properties, final EpfKey key, final boolean value) 82 | { 83 | return EpfKey.getPrefixes().stream() 84 | .anyMatch(prefix -> String.valueOf(value).equals(properties.getProperty(prefix + "." + key))); 85 | } 86 | 87 | private Properties readProperties(final String configurationPath) throws IOException 88 | { 89 | final Properties properties = new Properties(); 90 | try(final FileInputStream in = new FileInputStream(configurationPath)) 91 | { 92 | properties.load(in); 93 | } 94 | return properties; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/integration/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import static com.intellij.testFramework.LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR; 4 | 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import org.junit.jupiter.api.AfterEach; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.TestInfo; 11 | 12 | import com.intellij.testFramework.fixtures.CodeInsightTestFixture; 13 | import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; 14 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; 15 | import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl; 16 | 17 | import software.xdev.saveactions.core.action.BatchActionConstants; 18 | import software.xdev.saveactions.core.action.ShortcutActionConstants; 19 | import software.xdev.saveactions.core.component.SaveActionManagerConstants; 20 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 21 | import software.xdev.saveactions.junit.JUnit5Utils; 22 | import software.xdev.saveactions.model.Storage; 23 | 24 | 25 | public abstract class IntegrationTest 26 | { 27 | private CodeInsightTestFixture fixture; 28 | 29 | Storage storage; 30 | 31 | @BeforeEach 32 | public void before(final TestInfo testInfo) throws Exception 33 | { 34 | final IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory(); 35 | final IdeaProjectTestFixture testFixture = 36 | factory.createLightFixtureBuilder(EMPTY_PROJECT_DESCRIPTOR, testInfo.getDisplayName()).getFixture(); 37 | this.fixture = factory.createCodeInsightFixture(testFixture, new LightTempDirTestFixtureImpl(true)); 38 | this.fixture.setUp(); 39 | this.fixture.setTestDataPath(this.getTestDataPath()); 40 | this.storage = testFixture.getProject().getService(Storage.class); 41 | } 42 | 43 | @AfterEach 44 | public void after() throws Exception 45 | { 46 | this.fixture.tearDown(); 47 | this.storage.clear(); 48 | } 49 | 50 | void assertSaveAction(final ActionTestFile before, final ActionTestFile after) 51 | { 52 | this.fixture.configureByFile(before.getFilename()); 53 | SaveActionManagerConstants.SAVE_ACTION_MANAGER.accept(this.fixture, SaveActionsServiceManager.getService()); 54 | JUnit5Utils.rethrowAsJunit5Error(() -> this.fixture.checkResultByFile(after.getFilename())); 55 | } 56 | 57 | void assertSaveActionShortcut(final ActionTestFile before, final ActionTestFile after) 58 | { 59 | this.fixture.configureByFile(before.getFilename()); 60 | ShortcutActionConstants.SAVE_ACTION_SHORTCUT_MANAGER.accept(this.fixture); 61 | JUnit5Utils.rethrowAsJunit5Error(() -> this.fixture.checkResultByFile(after.getFilename())); 62 | } 63 | 64 | void assertSaveActionBatch(final ActionTestFile before, final ActionTestFile after) 65 | { 66 | this.fixture.configureByFile(before.getFilename()); 67 | BatchActionConstants.SAVE_ACTION_BATCH_MANAGER.accept(this.fixture); 68 | JUnit5Utils.rethrowAsJunit5Error(() -> this.fixture.checkResultByFile(after.getFilename())); 69 | } 70 | 71 | private String getTestDataPath() 72 | { 73 | /* See gradle config. Previous implementation not compatible with intellij gradle plugin >= 1.6.0 */ 74 | final Path resources = Paths.get("./build/classes/java/resources"); 75 | final Path root = Paths.get(resources.toString(), this.getClass().getPackage().getName().split("[.]")); 76 | return root.toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.0 2 | * Dropped support for IntelliJ versions < 2025.2 3 | * Resolves "``ActionUtil.performActionDumbAwareWithCallbacks`` deprecated" #250 4 | 5 | ## 1.4.2 6 | * Fix storage deserialization crash on unknown actions value #273 7 | 8 | ## 1.4.1 9 | * Fix ``Add class qualifier to static member access outside declaring class`` not working correctly for ``switch`` statements #263 10 | 11 | ## 1.4.0 12 | * Dropped support for IntelliJ versions < 2024.3 13 | * This is required to fix a few deprecations and remove some workarounds #171 14 | 15 | ## 1.3.1 16 | * Fix IDE hang when projects with different "Process files asynchronously" are open #160 17 | 18 | ## 1.3.0 19 | * Make it possible to run processors asynchronously #130 20 | * This way the UI should be more responsive when processing a lot of files 21 | * May break processors that interact with the UI e.g. when showing dialogs 22 | * Don't process files during project load #145 23 | * This should cause less race conditions due to partial project initialization 24 | * Only active on IntelliJ < 2024.3 as [the underlying problem was fixed in IntelliJ 2024.3](https://github.com/JetBrains/intellij-community/commit/765caa71175d0a67a54836cf840fae829da590d9) 25 | 26 | ## 1.2.4 27 | * Dropped support for IntelliJ versions < 2024.2 28 | * Removed deprecated code that was only required for older IDE versions 29 | 30 | ## 1.2.3 31 | * Fix "run on multiple files" not working when the file is not a text file #129 32 | 33 | ## 1.2.2 34 | * Workaround scaling problem on "New UI" [#26](https://github.com/xdev-software/intellij-plugin-template/issues/26) 35 | 36 | ## 1.2.1 37 | * Fixed ``ToggleAnAction must override getActionUpdateThread`` warning inside IntelliJ 2024+ 38 | * Dropped support for IntelliJ versions < 2023.2 39 | 40 | ## 1.2.0 41 | * Run GlobalProcessors (e.g. Reformat) last so that code is formatted correctly #90 42 | * Dropped support for IntelliJ versions < 2023 43 | 44 | ## 1.1.1 45 | * Shortened plugin name - new name: "Save Actions X" 46 | * Updated assets 47 | 48 | ## 1.1.0 49 | * Removed "Remove unused suppress warning annotation" 50 | * This option never worked #64 51 | * Allows usage of the plugin with IntelliJ IDEA 2024+ #63 52 | * If you used this option you should remove the line ``

8 |
9 |

Supports configurable, Eclipse like, save actions, including "optimize imports", "reformat code", "rearrange code", "compile file" and some quick fixes for Java like "add / remove 'this' qualifier", etc. The plugin executes the configured actions when the file is synchronised (or saved) on disk.

10 |
11 |

More information is available on GitHub.

12 | ]]> 13 | 14 | https://github.com/xdev-software/intellij-plugin-save-actions/releases 16 | ]]> 17 | 18 | 19 | 20 | com.intellij.modules.java 21 | 22 | 23 | 25 | 26 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 41 | 44 | 45 | 49 | 50 | 54 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /.github/workflows/check-ide-compatibility.yml: -------------------------------------------------------------------------------- 1 | name: Check IDE Compatibility 2 | 3 | on: 4 | schedule: 5 | - cron: '55 8 * * 1' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check-ide-compatibility: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 45 12 | steps: 13 | - name: Free up disk space 14 | run: | 15 | sudo df -h 16 | declare -a paths_to_wipe=( 17 | "/usr/share/dotnet" 18 | "/usr/local/.ghcup" 19 | "/usr/local/swift" 20 | "/usr/share/swift" 21 | "/usr/lib/jvm" 22 | "/usr/local/lib/android" 23 | "/usr/lib/google-cloud-sdk" 24 | "/usr/local/share/boost" 25 | "/usr/local/share/powershell" 26 | "/usr/local/share/chromium" 27 | "/usr/local/lib/node_modules" 28 | "/usr/lib/mono" 29 | "/usr/lib/heroku" 30 | "/usr/lib/firefox" 31 | "/usr/share/miniconda" 32 | "/opt/microsoft" 33 | "/opt/chrome" 34 | "/opt/pipx" 35 | "$AGENT_TOOLSDIRECTORY" 36 | ) 37 | for p in "${paths_to_wipe[@]}" 38 | do 39 | echo "Clearing $p" 40 | sudo rm -rf $p || true 41 | done 42 | sudo df -h 43 | 44 | - uses: actions/checkout@v5 45 | 46 | - name: Set up JDK 47 | uses: actions/setup-java@v5 48 | with: 49 | distribution: 'temurin' 50 | java-version: 21 51 | 52 | - name: Cache Gradle 53 | uses: actions/cache@v4 54 | with: 55 | path: | 56 | ~/.gradle/caches 57 | ~/.gradle/wrapper 58 | key: ${{ runner.os }}-gradle-ide-compatibility-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 59 | restore-keys: | 60 | ${{ runner.os }}-gradle-ide-compatibility- 61 | 62 | - name: Check compatibility 63 | id: check-compatibility 64 | run: ./gradlew verifyPlugin --info --stacktrace 65 | 66 | - name: Upload report 67 | uses: actions/upload-artifact@v4 68 | if: ${{ always() }} 69 | with: 70 | name: plugin-verifier-reports 71 | path: build/reports/pluginVerifier/** 72 | if-no-files-found: warn 73 | 74 | - name: Find already existing issue 75 | id: find-issue 76 | if: ${{ always() }} 77 | run: | 78 | echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "IDE Compatibility Problem"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT 79 | env: 80 | GH_TOKEN: ${{ github.token }} 81 | 82 | - name: Close issue if everything is fine 83 | if: ${{ success() && steps.find-issue.outputs.number != '' }} 84 | run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} 85 | env: 86 | GH_TOKEN: ${{ github.token }} 87 | 88 | - name: Create issue report 89 | if: ${{ failure() && steps.check-compatibility.conclusion == 'failure' }} 90 | run: | 91 | echo 'Encountered problems during plugin verification' > reported.md 92 | echo 'Please check the build logs for details' >> reported.md 93 | 94 | - name: Create Issue From File 95 | if: ${{ failure() && steps.check-compatibility.conclusion == 'failure' }} 96 | uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 97 | with: 98 | issue-number: ${{ steps.find-issue.outputs.number }} 99 | title: IDE Compatibility Problem 100 | content-filepath: ./reported.md 101 | labels: bug, automated 102 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/java/IdeSupportPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui.java; 2 | 3 | import static com.intellij.openapi.ui.TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT; 4 | import static java.awt.BorderLayout.CENTER; 5 | import static java.awt.BorderLayout.EAST; 6 | import static java.awt.BorderLayout.WEST; 7 | 8 | import java.awt.BorderLayout; 9 | import java.awt.Dimension; 10 | 11 | import javax.swing.JButton; 12 | import javax.swing.JPanel; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 17 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 18 | import com.intellij.openapi.fileChooser.FileChooserFactory; 19 | import com.intellij.openapi.fileChooser.FileTextField; 20 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 21 | import com.intellij.ui.IdeBorderFactory; 22 | import com.intellij.ui.components.JBLabel; 23 | 24 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 25 | import software.xdev.saveactions.ui.Configuration; 26 | 27 | 28 | /** 29 | * @author markiewb 30 | */ 31 | public class IdeSupportPanel 32 | { 33 | private static final String BUTTON = "Reset"; 34 | private static final String EXTENSION = "epf"; 35 | private static final String LABEL = "Use external Eclipse configuration file (.epf)"; 36 | private static final String TITLE = "Eclipse Support"; 37 | 38 | private TextFieldWithBrowseButton path; 39 | 40 | public JPanel getPanel(final String configurationPath) 41 | { 42 | final JPanel panel = new JPanel(); 43 | if(!SaveActionsServiceManager.getService().isJavaAvailable()) 44 | { 45 | return panel; 46 | } 47 | 48 | panel.setBorder(IdeBorderFactory.createTitledBorder(TITLE)); 49 | panel.setLayout(new BorderLayout()); 50 | 51 | final JBLabel label = this.getLabel(); 52 | this.path = this.getPath(configurationPath); 53 | final JButton reset = this.getResetButton(this.path); 54 | 55 | panel.add(label, WEST); 56 | panel.add(this.path, CENTER); 57 | panel.add(reset, EAST); 58 | 59 | panel.setMaximumSize(new Dimension(Configuration.BOX_LAYOUT_MAX_WIDTH, Configuration.BOX_LAYOUT_MAX_HEIGHT)); 60 | 61 | return panel; 62 | } 63 | 64 | @NotNull 65 | private JBLabel getLabel() 66 | { 67 | final JBLabel label = new JBLabel(); 68 | label.setText(LABEL); 69 | label.setLabelFor(this.path); 70 | return label; 71 | } 72 | 73 | @NotNull 74 | private TextFieldWithBrowseButton getPath(final String configurationPath) 75 | { 76 | final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor(EXTENSION); 77 | final FileTextField field = FileChooserFactory.getInstance().createFileTextField(descriptor, null); 78 | field.getField().setEnabled(false); 79 | field.getField().setText(configurationPath); 80 | final TextFieldWithBrowseButton resultPath = new TextFieldWithBrowseButton(field.getField()); 81 | resultPath.addBrowseFolderListener(null, descriptor, TEXT_FIELD_WHOLE_TEXT); 82 | return resultPath; 83 | } 84 | 85 | @NotNull 86 | private JButton getResetButton(final TextFieldWithBrowseButton path) 87 | { 88 | final JButton reset = new JButton(BUTTON); 89 | reset.addActionListener(e -> path.setText("")); 90 | return reset; 91 | } 92 | 93 | public String getPath() 94 | { 95 | return this.path == null ? null : this.path.getText(); 96 | } 97 | 98 | public void setPath(final String configurationPath) 99 | { 100 | if(this.path != null) 101 | { 102 | this.path.setText(configurationPath); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/InspectionRunnable.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import java.util.Set; 8 | import java.util.stream.Collectors; 9 | 10 | import com.intellij.codeInspection.GlobalInspectionContext; 11 | import com.intellij.codeInspection.InspectionEngine; 12 | import com.intellij.codeInspection.InspectionManager; 13 | import com.intellij.codeInspection.LocalInspectionEP; 14 | import com.intellij.codeInspection.LocalInspectionTool; 15 | import com.intellij.codeInspection.ProblemDescriptor; 16 | import com.intellij.codeInspection.QuickFix; 17 | import com.intellij.codeInspection.ex.InspectionToolWrapper; 18 | import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; 19 | import com.intellij.openapi.diagnostic.Logger; 20 | import com.intellij.openapi.project.IndexNotReadyException; 21 | import com.intellij.openapi.project.Project; 22 | import com.intellij.psi.PsiFile; 23 | 24 | import software.xdev.saveactions.core.service.SaveActionsService; 25 | 26 | 27 | /** 28 | * Implements a runnable for inspections commands. 29 | */ 30 | class InspectionRunnable implements Runnable 31 | { 32 | private static final Logger LOGGER = Logger.getInstance(SaveActionsService.class); 33 | 34 | private final Project project; 35 | private final Set psiFiles; 36 | private final InspectionToolWrapper toolWrapper; 37 | 38 | InspectionRunnable(final Project project, final Set psiFiles, final LocalInspectionTool inspectionTool) 39 | { 40 | this.project = project; 41 | this.psiFiles = psiFiles; 42 | this.toolWrapper = new LocalInspectionToolWrapper(inspectionTool); 43 | LOGGER.info(String.format("Running inspection for %s - %s", inspectionTool.getShortName(), project.getName())); 44 | } 45 | 46 | @Override 47 | public void run() 48 | { 49 | final InspectionManager inspectionManager = InspectionManager.getInstance(this.project); 50 | final GlobalInspectionContext context = inspectionManager.createNewGlobalContext(); 51 | this.psiFiles.forEach(pf -> this.getProblemDescriptors(context, pf).forEach(this::writeQuickFixes)); 52 | } 53 | 54 | private List getProblemDescriptors( 55 | final GlobalInspectionContext context, 56 | final PsiFile psiFile) 57 | { 58 | try 59 | { 60 | return InspectionEngine.runInspectionOnFile(psiFile, this.toolWrapper, context); 61 | } 62 | catch(final IndexNotReadyException exception) 63 | { 64 | LOGGER.info(String.format( 65 | "Cannot inspect file %s: index not ready (%s)", 66 | psiFile.getName(), 67 | exception.getMessage())); 68 | return Collections.emptyList(); 69 | } 70 | } 71 | 72 | @SuppressWarnings({"unchecked", "squid:S1905", "squid:S3740"}) 73 | private void writeQuickFixes(final ProblemDescriptor problemDescriptor) 74 | { 75 | final QuickFix[] fixes = problemDescriptor.getFixes(); 76 | if(fixes == null) 77 | { 78 | return; 79 | } 80 | 81 | final Set> quickFixes = Arrays.stream(fixes) 82 | .filter(Objects::nonNull) 83 | .map(qf -> (QuickFix)qf) 84 | .collect(Collectors.toSet()); 85 | 86 | for(final QuickFix typedFix : quickFixes) 87 | { 88 | try 89 | { 90 | LOGGER.info(String.format("Applying fix \"%s\"", typedFix.getName())); 91 | typedFix.applyFix(this.project, problemDescriptor); 92 | } 93 | catch(final Exception e) 94 | { 95 | LOGGER.error(e.getMessage(), e); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/GlobalProcessor.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import java.util.Arrays; 4 | import java.util.EnumSet; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.function.BiFunction; 9 | import java.util.function.Function; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import com.intellij.codeInsight.CodeInsightBundle; 16 | import com.intellij.codeInsight.actions.OptimizeImportsProcessor; 17 | import com.intellij.codeInsight.actions.RearrangeCodeProcessor; 18 | import com.intellij.codeInsight.actions.ReformatCodeProcessor; 19 | import com.intellij.openapi.project.Project; 20 | import com.intellij.psi.PsiFile; 21 | 22 | import software.xdev.saveactions.core.ExecutionMode; 23 | import software.xdev.saveactions.model.Action; 24 | 25 | 26 | /** 27 | * Available processors for global. 28 | */ 29 | @SuppressWarnings("java:S115") 30 | public enum GlobalProcessor implements Processor 31 | { 32 | organizeImports(Action.organizeImports, GlobalProcessor::optimizeImports), 33 | 34 | reformat( 35 | Action.reformat, 36 | (project, psiFiles) -> reformatCode(project, psiFiles, false)), 37 | 38 | reformatChangedCode( 39 | Action.reformatChangedCode, 40 | (project, psiFiles) -> reformatCode(project, psiFiles, true)), 41 | 42 | rearrange(Action.rearrange, GlobalProcessor::rearrangeCode); 43 | 44 | private static final Map ACTION_VALUES = stream() 45 | .collect(Collectors.toMap(Processor::getAction, Function.identity())); 46 | 47 | @NotNull 48 | private static Runnable rearrangeCode(final Project project, final PsiFile[] psiFiles) 49 | { 50 | return new RearrangeCodeProcessor( 51 | project, 52 | psiFiles, 53 | CodeInsightBundle.message("command.rearrange.code"), 54 | null)::run; 55 | } 56 | 57 | @NotNull 58 | private static Runnable optimizeImports(final Project project, final PsiFile[] psiFiles) 59 | { 60 | return new OptimizeImportsProcessor(project, psiFiles, null)::run; 61 | } 62 | 63 | @NotNull 64 | private static Runnable reformatCode( 65 | final Project project, 66 | final PsiFile[] psiFiles, 67 | final boolean processChangedTextOnly) 68 | { 69 | return new ReformatCodeProcessor(project, psiFiles, null, processChangedTextOnly)::run; 70 | } 71 | 72 | private final Action action; 73 | private final BiFunction command; 74 | 75 | GlobalProcessor(final Action action, final BiFunction command) 76 | { 77 | this.action = action; 78 | this.command = command; 79 | } 80 | 81 | @Override 82 | public Action getAction() 83 | { 84 | return this.action; 85 | } 86 | 87 | @Override 88 | public Set getModes() 89 | { 90 | return EnumSet.allOf(ExecutionMode.class); 91 | } 92 | 93 | @Override 94 | public int getOrder() 95 | { 96 | return 3; 97 | } 98 | 99 | @Override 100 | public SaveWriteCommand getSaveCommand(final Project project, final Set psiFiles) 101 | { 102 | return new SaveWriteCommand(project, psiFiles, this.getModes(), this.getAction(), this.getCommand()); 103 | } 104 | 105 | public BiFunction getCommand() 106 | { 107 | return this.command; 108 | } 109 | 110 | public static Optional getProcessorForAction(final Action action) 111 | { 112 | return Optional.ofNullable(ACTION_VALUES.get(action)); 113 | } 114 | 115 | public static Stream stream() 116 | { 117 | return Arrays.stream(values()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest version](https://img.shields.io/jetbrains/plugin/v/22113?logo=jetbrains)](https://plugins.jetbrains.com/plugin/22113) 2 | [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/intellij-plugin-save-actions/check-build.yml?branch=develop)](https://github.com/xdev-software/intellij-plugin-save-actions/actions/workflows/check-build.yml?query=branch%3Adevelop) 3 | [![Feel free to leave a rating](https://img.shields.io/jetbrains/plugin/r/rating/22113?style=social&logo=jetbrains&label=Feel%20free%20to%20leave%20a%20rating)](https://plugins.jetbrains.com/plugin/22113/reviews) 4 | 5 | # Save Actions Plugin icon light Save Actions X 6 | 7 | > [!NOTE] 8 | > This plugin is a fork of [dubreuia/intellij-plugin-save-actions](https://github.com/dubreuia/intellij-plugin-save-actions) and [fishermans/intellij-plugin-save-actions](https://github.com/fishermans/intellij-plugin-save-actions) and is kept in maintenance mode: 9 | > * Keep the plugin up-to-date with the latest IDEA versions 10 | > * Distribute the plugin on the IDEA marketplace 11 | > * Fix serious bugs 12 | > * Keep the repo in sync with XDEV's standards 13 | > * Hardly used features may be removed to speed up development 14 | > 15 | > There is no guarantee that work outside of this scope will be done. 16 | 17 | Supports configurable, Eclipse like, save actions, including "optimize imports", "reformat code", "rearrange code", "compile file" and some quick fixes like "add / remove 'this' qualifier", etc. The plugin executes the configured actions when the file is synchronized (or saved) on disk. 18 | 19 | Using the save actions plugin makes your code cleaner and more uniform across your code base by enforcing your code style and code rules every time you save. The settings file (see [files location](./USAGE.md#files-location)) can be shared in your development team so that every developer has the same configuration. 20 | 21 | The code style applied by the save actions plugin is the one configured your settings at "File > Settings > Editor > Code Style". For some languages, custom formatter (Dartfmt, Prettier, etc.) may also be triggered by the save actions plugin. See the [Editor Actions](./USAGE.md#editor-actions) configuration for more information. 22 | 23 | ## Features 24 | 25 | ### All JetBrains products 26 | 27 | - Optimize imports 28 | - Run on file save, shortcut, batch (or a combination) 29 | - Run on multiple files by choosing a scope 30 | - Reformat code (whole file or only changed text) 31 | - Rearrange code (reorder methods, fields, etc.) 32 | - Include / exclude files with regex support 33 | - Works on any file type (Java, Python, XML, etc.) 34 | - Launch any editor action using "quick lists" 35 | - Uses a settings file per project you can commit (see [Files location](./USAGE.md#files-location)) 36 | - Available keymaps and actions for activation (see [Keymap and actions](./USAGE.md#keymap-and-actions)) 37 | 38 | Save actions plugin settings page 39 | 40 | ### Java IDE products 41 | 42 | Works in JetBrains IDE with Java support, like Intellij IDEA and AndroidStudio. 43 | 44 | - Compile project after save (if compiling is available) 45 | - Reload debugger after save (if compiling is available) 46 | - Eclipse configuration file `.epf` support (see [Eclipse support](./USAGE.md#eclipse-support)) 47 | - Automatically fix Java inspections (see [Java quick fixes](./USAGE.md#java-fixes)) 48 | 49 | Save actions plugin settings page for Java 50 | 51 | ## Installation 52 | [Installation guide for the latest release](https://github.com/xdev-software/intellij-plugin-save-actions/releases/latest#Installation) 53 | 54 | > [!TIP] 55 | > [Development versions](https://plugins.jetbrains.com/plugin/22113/versions/snapshot) can be installed by [adding the ``snapshot`` release channel as a plugin repository](https://www.jetbrains.com/help/idea/managing-plugins.html#repos):
56 | > ``https://plugins.jetbrains.com/plugins/snapshot/list`` 57 | 58 | ## Usage 59 | 60 | Read the [full usage guide here](./USAGE.md). 61 | 62 | ## Contributing 63 | See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. 64 | -------------------------------------------------------------------------------- /src/test/java/software/xdev/saveactions/integration/GlobalIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.integration; 2 | 3 | import static software.xdev.saveactions.model.Action.activate; 4 | import static software.xdev.saveactions.model.Action.activateOnBatch; 5 | import static software.xdev.saveactions.model.Action.activateOnShortcut; 6 | import static software.xdev.saveactions.model.Action.organizeImports; 7 | import static software.xdev.saveactions.model.Action.rearrange; 8 | import static software.xdev.saveactions.model.Action.reformat; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | 13 | class GlobalIntegrationTest extends IntegrationTest 14 | { 15 | @Test 16 | void should_reformat_without_activation_produces_same_file() 17 | { 18 | this.storage.setEnabled(reformat, true); 19 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_KO); 20 | } 21 | 22 | @Test 23 | void should_reformat_with_activation_produces_indented_file() 24 | { 25 | this.storage.setEnabled(activate, true); 26 | this.storage.setEnabled(reformat, true); 27 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_OK_Import_KO); 28 | } 29 | 30 | @Test 31 | void should_reformat_with_shortcut_produces_same_file() 32 | { 33 | this.storage.setEnabled(activateOnShortcut, true); 34 | this.storage.setEnabled(reformat, true); 35 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_KO); 36 | } 37 | 38 | @Test 39 | void should_reformat_with_shortcut_produces_indented_file_on_shortcut() 40 | { 41 | this.storage.setEnabled(activateOnShortcut, true); 42 | this.storage.setEnabled(reformat, true); 43 | this.assertSaveActionShortcut(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_OK_Import_KO); 44 | } 45 | 46 | @Test 47 | void should_reformat_as_batch_produces_indented_file() 48 | { 49 | this.storage.setEnabled(activateOnBatch, true); 50 | this.storage.setEnabled(reformat, true); 51 | this.assertSaveActionBatch(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_OK_Import_KO); 52 | } 53 | 54 | @Test 55 | void should_reformat_as_batch_on_shortcut_produces_same_file() 56 | { 57 | this.storage.setEnabled(activateOnShortcut, true); 58 | this.storage.setEnabled(reformat, true); 59 | this.assertSaveActionBatch(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_KO); 60 | } 61 | 62 | @Test 63 | void should_import_without_activation_produces_same_file() 64 | { 65 | this.storage.setEnabled(organizeImports, true); 66 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_KO); 67 | } 68 | 69 | @Test 70 | void should_import_with_activation_produces_cleaned_import_file() 71 | { 72 | this.storage.setEnabled(activate, true); 73 | this.storage.setEnabled(organizeImports, true); 74 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_OK); 75 | } 76 | 77 | @Test 78 | void should_import_and_format_with_activation_produces_cleaned_import_and_formated_file() 79 | { 80 | this.storage.setEnabled(activate, true); 81 | this.storage.setEnabled(organizeImports, true); 82 | this.storage.setEnabled(reformat, true); 83 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_OK_Import_OK); 84 | } 85 | 86 | @Test 87 | void should_rearrange_without_activation_produces_same_file() 88 | { 89 | this.storage.setEnabled(rearrange, true); 90 | this.assertSaveAction(ActionTestFile.Reformat_KO_Import_KO, ActionTestFile.Reformat_KO_Import_KO); 91 | } 92 | 93 | @Test 94 | void should_rearrange_with_activation_produces_ordered_file() 95 | { 96 | this.storage.setEnabled(activate, true); 97 | this.storage.setEnabled(rearrange, true); 98 | this.assertSaveAction(ActionTestFile.Reformat_KO_Rearrange_KO, ActionTestFile.Reformat_KO_Rearrange_OK); 99 | } 100 | 101 | @Test 102 | void should_rearrange_and_format_with_activation_produces_ordered_file_and_formated_file() 103 | { 104 | this.storage.setEnabled(activate, true); 105 | this.storage.setEnabled(reformat, true); 106 | this.storage.setEnabled(rearrange, true); 107 | this.assertSaveAction(ActionTestFile.Reformat_KO_Rearrange_KO, ActionTestFile.Reformat_OK_Rearrange_OK); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | We would absolutely love to get the community involved, and we welcome any form of contributions – comments and questions on different communication channels, issues and pull request and anything that you build and share using our components. 4 | 5 | ### Communication channels 6 | * Communication is primarily done using issues. 7 | * If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). 8 | * As a last resort measure or on otherwise important matter you may also [contact us directly](https://xdev.software/en/about-us/contact). 9 | 10 | ### Ways to help 11 | * **Report bugs**
Create an issue or send a pull request 12 | * **Send pull requests**
If you want to contribute code, check out the development instructions below. 13 | * However when contributing new features, please first discuss the change you wish to make via issue with the owners of this repository before making a change. Otherwise your work might be rejected and your effort was pointless. 14 | 15 | We also encourage you to read the [contribution instructions by GitHub](https://docs.github.com/en/get-started/quickstart/contributing-to-projects). 16 | 17 | ## Developing 18 | 19 | ### Software Requirements 20 | You should have the following things installed: 21 | * Git 22 | * Java 21 - should be as unmodified as possible (Recommended: [Eclipse Adoptium](https://adoptium.net/temurin/releases/)) 23 | * Gradle (shipped inside the repo as Gradle Wrapper - also available inside IntelliJ) 24 | 25 | ### Recommended setup 26 | * Install ``IntelliJ`` (Community Edition is sufficient) 27 | * Install the following plugins: 28 | * [Save Actions](https://plugins.jetbrains.com/plugin/22113) - Provides save actions, like running the formatter or adding ``final`` to fields 29 | * [SonarLint](https://plugins.jetbrains.com/plugin/7973-sonarlint) - CodeStyle/CodeAnalysis 30 | * [Checkstyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) - CodeStyle/CodeAnalysis 31 | * [Plugin DevKit](https://plugins.jetbrains.com/plugin/22851) - IntelliJ Plugin development 32 | * Import the project 33 | * Ensure that everything is encoded in ``UTF-8`` 34 | * Ensure that the JDK/Java-Version is correct 35 | 36 | ## Development environment 37 | 38 | See also [JetBrains Docs for developing IntelliJ Plugins](https://plugins.jetbrains.com/docs/intellij/developing-plugins.html) 39 | 40 | The plugin is built with gradle, but you don't need to install it if you build with the IntelliJ gradle plugin (check out the [prerequisites](https://plugins.jetbrains.com/docs/intellij/plugin-required-experience.html)). If you don't intend to use the IntelliJ gradle plugin, you can use native gradle (replace `./gradlew` by `gradle`). 41 | 42 | Start idea and import the `build.gradle` file with "File > Open". Then in the "Import Project from Gradle" window, make sure you check "Use gradle 'wrapper' task configuration" before clicking "Finish". You now have a gradle wrapper installed (`gradlew`) that you can use on the command line to generate idea folders: 43 | 44 | ```bash 45 | # Initialize idea folders 46 | ./gradlew cleanIdea idea 47 | ``` 48 | 49 | IntelliJ should refresh and the project is now configured as a gradle project. You can find IntelliJ gradle tasks in "Gradle > Gradle projects > intellij-plugin-save-actions > Tasks > intellij". To run the plugin, use the `runIde` task: 50 | 51 | ```bash 52 | # Run the plugin (starts new idea) 53 | ./gradlew runIde 54 | ``` 55 | 56 | Based on the [original documentation](https://github.com/dubreuia/intellij-plugin-save-actions/blob/main/CONTRIBUTING.md) 57 | 58 | ## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/intellij-plugin-save-actions/release.yml?branch=master)](https://github.com/xdev-software/intellij-plugin-save-actions/actions/workflows/release.yml) 59 | 60 | Before releasing: 61 | * Consider doing a [test-deployment](https://github.com/xdev-software/intellij-plugin-save-actions/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. 62 | * Check the [changelog](CHANGELOG.md) 63 | 64 | If the ``develop`` is ready for release, create a pull request to the ``master``-Branch and merge the changes 65 | 66 | When the release is finished do the following: 67 | * Merge the auto-generated PR (with the incremented version number) back into the ``develop`` 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/Storage.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import java.util.Set; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import com.intellij.openapi.components.PersistentStateComponent; 12 | import com.intellij.openapi.components.Service; 13 | import com.intellij.openapi.components.State; 14 | import com.intellij.serviceContainer.NonInjectable; 15 | import com.intellij.util.xmlb.XmlSerializerUtil; 16 | 17 | 18 | @State(name = "SaveActionSettings", storages = {@com.intellij.openapi.components.Storage("saveactions_settings.xml")}) 19 | @Service(Service.Level.PROJECT) 20 | @SuppressWarnings("PMD.UseEnumCollections") // Set must be nullable! 21 | public final class Storage implements PersistentStateComponent 22 | { 23 | private boolean firstLaunch; 24 | // Must use a set that supports nullable values! 25 | // For example EnumSet will crash when encountering an unknown/null value 26 | private Set actions; 27 | private Set exclusions; 28 | private Set inclusions; 29 | private String configurationPath; 30 | private List quickLists; 31 | 32 | @NonInjectable 33 | public Storage() 34 | { 35 | this.firstLaunch = true; 36 | this.actions = new HashSet<>(); 37 | this.exclusions = new HashSet<>(); 38 | this.inclusions = new HashSet<>(); 39 | this.configurationPath = null; 40 | this.quickLists = new ArrayList<>(); 41 | } 42 | 43 | @NonInjectable 44 | public Storage(final Storage storage) 45 | { 46 | this.firstLaunch = storage.firstLaunch; 47 | this.actions = new HashSet<>(storage.actions); 48 | this.exclusions = new HashSet<>(storage.exclusions); 49 | this.inclusions = new HashSet<>(storage.inclusions); 50 | this.configurationPath = storage.configurationPath; 51 | this.quickLists = new ArrayList<>(storage.quickLists); 52 | } 53 | 54 | @Override 55 | public Storage getState() 56 | { 57 | return this; 58 | } 59 | 60 | @Override 61 | public void loadState(@NotNull final Storage state) 62 | { 63 | this.firstLaunch = false; 64 | XmlSerializerUtil.copyBean(state, this); 65 | 66 | // Remove null values that might have been caused by non-parsable values 67 | this.actions.removeIf(Objects::isNull); 68 | this.exclusions.removeIf(Objects::isNull); 69 | this.inclusions.removeIf(Objects::isNull); 70 | this.quickLists.removeIf(Objects::isNull); 71 | } 72 | 73 | public Set getActions() 74 | { 75 | return this.actions; 76 | } 77 | 78 | public void setActions(final Set actions) 79 | { 80 | this.actions = actions; 81 | } 82 | 83 | public Set getExclusions() 84 | { 85 | return this.exclusions; 86 | } 87 | 88 | public void setExclusions(final Set exclusions) 89 | { 90 | this.exclusions = exclusions; 91 | } 92 | 93 | public boolean isEnabled(final Action action) 94 | { 95 | return this.actions.contains(action); 96 | } 97 | 98 | public void setEnabled(final Action action, final boolean enable) 99 | { 100 | if(enable) 101 | { 102 | this.actions.add(action); 103 | } 104 | else 105 | { 106 | this.actions.remove(action); 107 | } 108 | } 109 | 110 | public Set getInclusions() 111 | { 112 | return this.inclusions; 113 | } 114 | 115 | public void setInclusions(final Set inclusions) 116 | { 117 | this.inclusions = inclusions; 118 | } 119 | 120 | public boolean isFirstLaunch() 121 | { 122 | return this.firstLaunch; 123 | } 124 | 125 | public void stopFirstLaunch() 126 | { 127 | this.firstLaunch = false; 128 | } 129 | 130 | public String getConfigurationPath() 131 | { 132 | return this.configurationPath; 133 | } 134 | 135 | public void setConfigurationPath(final String configurationPath) 136 | { 137 | this.configurationPath = configurationPath; 138 | } 139 | 140 | public List getQuickLists() 141 | { 142 | return this.quickLists; 143 | } 144 | 145 | public void setQuickLists(final List quickLists) 146 | { 147 | this.quickLists = quickLists; 148 | } 149 | 150 | public void clear() 151 | { 152 | this.firstLaunch = true; 153 | this.actions.clear(); 154 | this.exclusions.clear(); 155 | this.inclusions.clear(); 156 | this.configurationPath = null; 157 | this.quickLists.clear(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /.github/workflows/check-build.yml: -------------------------------------------------------------------------------- 1 | name: Check Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ develop ] 7 | paths-ignore: 8 | - '**.md' 9 | - '.config/**' 10 | - '.github/**' 11 | - '.idea/**' 12 | - 'assets/**' 13 | pull_request: 14 | branches: [ develop ] 15 | paths-ignore: 16 | - '**.md' 17 | - '.config/**' 18 | - '.github/**' 19 | - '.idea/**' 20 | - 'assets/**' 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | timeout-minutes: 30 26 | strategy: 27 | matrix: 28 | java: [21] 29 | distribution: [temurin] 30 | steps: 31 | - uses: actions/checkout@v5 32 | 33 | - name: Set up JDK 34 | uses: actions/setup-java@v5 35 | with: 36 | distribution: ${{ matrix.distribution }} 37 | java-version: ${{ matrix.java }} 38 | 39 | - name: Cache Gradle 40 | uses: actions/cache@v4 41 | with: 42 | path: | 43 | ~/.gradle/caches 44 | ~/.gradle/wrapper 45 | key: ${{ runner.os }}-gradle-build-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 46 | restore-keys: | 47 | ${{ runner.os }}-gradle-build- 48 | 49 | - name: Build 50 | run: ./gradlew build buildPlugin --info --stacktrace 51 | 52 | - name: Try upload test reports when failure occurs 53 | uses: actions/upload-artifact@v4 54 | if: failure() 55 | with: 56 | name: test-reports-${{ matrix.java }} 57 | path: build/reports/tests/test/** 58 | 59 | - name: Check for uncommited changes 60 | run: | 61 | if [[ "$(git status --porcelain)" != "" ]]; then 62 | echo ---------------------------------------- 63 | echo git status 64 | echo ---------------------------------------- 65 | git status 66 | echo ---------------------------------------- 67 | echo git diff 68 | echo ---------------------------------------- 69 | git diff 70 | echo ---------------------------------------- 71 | echo Troubleshooting 72 | echo ---------------------------------------- 73 | echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && mvn -B clean package" 74 | exit 1 75 | fi 76 | 77 | - name: Upload plugin files 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: plugin-files-java-${{ matrix.java }} 81 | path: build/libs/intellij-plugin-save-actions-*.jar 82 | if-no-files-found: error 83 | 84 | checkstyle: 85 | runs-on: ubuntu-latest 86 | if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} 87 | timeout-minutes: 15 88 | strategy: 89 | matrix: 90 | java: [21] 91 | distribution: [temurin] 92 | steps: 93 | - uses: actions/checkout@v5 94 | 95 | - name: Set up JDK 96 | uses: actions/setup-java@v5 97 | with: 98 | distribution: ${{ matrix.distribution }} 99 | java-version: ${{ matrix.java }} 100 | 101 | - name: Cache Gradle 102 | uses: actions/cache@v4 103 | with: 104 | path: | 105 | ~/.gradle/caches 106 | ~/.gradle/wrapper 107 | key: ${{ runner.os }}-gradle-checkstyle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 108 | restore-keys: | 109 | ${{ runner.os }}-gradle-checkstyle- 110 | 111 | - name: Run Checkstyle 112 | run: ./gradlew checkstyleMain checkstyleTest -PcheckstyleEnabled --stacktrace 113 | 114 | pmd: 115 | runs-on: ubuntu-latest 116 | if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} 117 | timeout-minutes: 15 118 | strategy: 119 | matrix: 120 | java: [21] 121 | distribution: [temurin] 122 | steps: 123 | - uses: actions/checkout@v5 124 | 125 | - name: Set up JDK 126 | uses: actions/setup-java@v5 127 | with: 128 | distribution: ${{ matrix.distribution }} 129 | java-version: ${{ matrix.java }} 130 | 131 | - name: Run PMD 132 | run: ./gradlew pmdMain pmdTest -PpmdEnabled --stacktrace -x test 133 | 134 | - name: Cache Gradle 135 | uses: actions/cache@v4 136 | with: 137 | path: | 138 | ~/.gradle/caches 139 | ~/.gradle/wrapper 140 | key: ${{ runner.os }}-gradle-pmd-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 141 | restore-keys: | 142 | ${{ runner.os }}-gradle-pmd- 143 | 144 | - name: Upload report 145 | if: always() 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: pmd-report 149 | if-no-files-found: ignore 150 | path: | 151 | build/reports/pmd/*.html 152 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/model/Action.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.model; 2 | 3 | import static java.util.stream.Collectors.toSet; 4 | import static software.xdev.saveactions.model.ActionType.activation; 5 | import static software.xdev.saveactions.model.ActionType.build; 6 | import static software.xdev.saveactions.model.ActionType.global; 7 | import static software.xdev.saveactions.model.ActionType.java; 8 | 9 | import java.util.Arrays; 10 | import java.util.Set; 11 | import java.util.stream.Stream; 12 | 13 | 14 | @SuppressWarnings("java:S115") 15 | public enum Action 16 | { 17 | // Activation 18 | activate("Activate save actions on save (before saving each file, performs the configured actions below)", 19 | activation, true), 20 | 21 | activateOnShortcut("Activate save actions on shortcut (default \"CTRL + SHIFT + S\")", 22 | activation, false), 23 | 24 | activateOnBatch("Activate save actions on batch (\"Code > Save Actions > Execute on multiple files\")", 25 | activation, false), 26 | 27 | noActionIfCompileErrors("No action if compile errors (applied per file)", 28 | activation, false), 29 | 30 | processAsync("Process files asynchronously " 31 | + "(will result in less UI hangs but may break if a processor needs the UI)", 32 | activation, false), 33 | 34 | // Global 35 | organizeImports("Optimize imports", 36 | global, true), 37 | 38 | reformat("Reformat file", 39 | global, true), 40 | 41 | reformatChangedCode("Reformat only changed code (only if VCS configured)", 42 | global, false), 43 | 44 | rearrange("Rearrange fields and methods " 45 | + "(configured in \"File > Settings > Editor > Code Style > (...) > Arrangement\")", 46 | global, false), 47 | 48 | // Build 49 | compile("[experimental] Compile files (using \"Build > Build Project\")", 50 | build, false), 51 | 52 | reload("[experimental] Reload files in running debugger (using \"Run > Reload Changed Classes\")", 53 | build, false), 54 | 55 | executeAction("[experimental] Execute an action (using quick lists at " 56 | + "\"File > Settings > Appearance & Behavior > Quick Lists\")", 57 | build, false), 58 | 59 | // Java fixes 60 | fieldCanBeFinal("Add final modifier to field", 61 | java, false), 62 | 63 | localCanBeFinal("Add final modifier to local variable or parameter", 64 | java, false), 65 | 66 | localCanBeFinalExceptImplicit("Add final modifier to local variable or parameter except if it is implicit", 67 | java, false), 68 | 69 | methodMayBeStatic("Add static modifier to methods", 70 | java, false), 71 | 72 | unqualifiedFieldAccess("Add this to field access", 73 | java, false), 74 | 75 | unqualifiedMethodAccess("Add this to method access", 76 | java, false), 77 | 78 | unqualifiedStaticMemberAccess("Add class qualifier to static member access", 79 | java, false), 80 | 81 | customUnqualifiedStaticMemberAccess("Add class qualifier to static member access outside declaring class", 82 | java, false), 83 | 84 | missingOverrideAnnotation("Add missing @Override annotations", 85 | java, false), 86 | 87 | useBlocks("Add blocks to if/while/for statements", 88 | java, false), 89 | 90 | generateSerialVersionUID("Add a serialVersionUID field for Serializable classes", 91 | java, false), 92 | 93 | unnecessaryThis("Remove unnecessary this to field and method", 94 | java, false), 95 | 96 | finalPrivateMethod("Remove final from private method", 97 | java, false), 98 | 99 | unnecessaryFinalOnLocalVariableOrParameter("Remove unnecessary final for local variable or parameter", 100 | java, false), 101 | 102 | explicitTypeCanBeDiamond("Remove explicit generic type for diamond", 103 | java, false), 104 | 105 | unnecessarySemicolon("Remove unnecessary semicolon", 106 | java, false), 107 | 108 | singleStatementInBlock("Remove blocks from if/while/for statements", 109 | java, false), 110 | 111 | accessCanBeTightened("Change visibility of field or method to lower access", 112 | java, false); 113 | 114 | private final String text; 115 | private final ActionType type; 116 | private final boolean defaultValue; 117 | 118 | Action(final String text, final ActionType type, final boolean defaultValue) 119 | { 120 | this.text = text; 121 | this.type = type; 122 | this.defaultValue = defaultValue; 123 | } 124 | 125 | public String getText() 126 | { 127 | return this.text; 128 | } 129 | 130 | public ActionType getType() 131 | { 132 | return this.type; 133 | } 134 | 135 | public boolean isDefaultValue() 136 | { 137 | return this.defaultValue; 138 | } 139 | 140 | public static Set getDefaults() 141 | { 142 | return Arrays.stream(Action.values()) 143 | .filter(Action::isDefaultValue) 144 | .collect(toSet()); 145 | } 146 | 147 | public static Stream stream() 148 | { 149 | return Arrays.stream(values()); 150 | } 151 | 152 | public static Stream stream(final ActionType type) 153 | { 154 | return Arrays.stream(values()).filter(action -> action.type.equals(type)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/ui/FileMaskPanel.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.ui; 2 | 3 | import java.util.Collection; 4 | import java.util.Enumeration; 5 | import java.util.Set; 6 | import java.util.regex.Pattern; 7 | import java.util.regex.PatternSyntaxException; 8 | 9 | import javax.swing.DefaultListModel; 10 | import javax.swing.JPanel; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import com.intellij.openapi.ui.InputValidator; 15 | import com.intellij.openapi.ui.Messages; 16 | import com.intellij.ui.AnActionButtonRunnable; 17 | import com.intellij.ui.IdeBorderFactory; 18 | import com.intellij.ui.ToolbarDecorator; 19 | import com.intellij.ui.components.JBList; 20 | 21 | 22 | abstract class FileMaskPanel extends JPanel 23 | { 24 | private final SortedListModel patternModels = new SortedListModel(); 25 | 26 | private final JBList patternList; 27 | 28 | private final JPanel patternPanel; 29 | 30 | private final String textAddMessage; 31 | 32 | private final String textAddTitle; 33 | 34 | private final String textEditMessage; 35 | 36 | private final String textEditTitle; 37 | 38 | FileMaskPanel( 39 | final Set patterns, final String textEmpty, final String textTitle, final String textAddMessage, 40 | final String textAddTitle, final String textEditMessage, final String textEditTitle) 41 | { 42 | this.textAddMessage = textAddMessage; 43 | this.textAddTitle = textAddTitle; 44 | this.textEditMessage = textEditMessage; 45 | this.textEditTitle = textEditTitle; 46 | this.patternList = new JBList<>(this.patternModels); 47 | this.patternList.setEmptyText(textEmpty); 48 | this.patternPanel = ToolbarDecorator.createDecorator(this.patternList) 49 | .setAddAction(this.getAddActionButtonRunnable(patterns)) 50 | .setRemoveAction(this.getRemoveActionButtonRunnable(patterns)) 51 | .setEditAction(this.getEditActionButtonRunnable(patterns)) 52 | .disableUpDownActions() 53 | .createPanel(); 54 | this.patternPanel.setBorder(IdeBorderFactory.createTitledBorder(textTitle)); 55 | } 56 | 57 | private AnActionButtonRunnable getEditActionButtonRunnable(final Set patterns) 58 | { 59 | return actionButton -> { 60 | final String oldValue = this.patternList.getSelectedValue(); 61 | final String pattern = Messages.showInputDialog( 62 | this.textEditMessage, this.textEditTitle, null, oldValue, this.getRegexInputValidator()); 63 | if(pattern != null && !pattern.equals(oldValue)) 64 | { 65 | patterns.remove(oldValue); 66 | this.patternModels.removeElement(oldValue); 67 | if(patterns.add(pattern)) 68 | { 69 | this.patternModels.addElementSorted(pattern); 70 | } 71 | } 72 | }; 73 | } 74 | 75 | JPanel getPanel() 76 | { 77 | return this.patternPanel; 78 | } 79 | 80 | void update(final Set patterns) 81 | { 82 | this.patternModels.clear(); 83 | this.patternModels.addAllSorted(patterns); 84 | } 85 | 86 | @NotNull 87 | private AnActionButtonRunnable getRemoveActionButtonRunnable(final Set patterns) 88 | { 89 | return actionButton -> { 90 | for(final String selectedValue : this.patternList.getSelectedValuesList()) 91 | { 92 | patterns.remove(selectedValue); 93 | this.patternModels.removeElement(selectedValue); 94 | } 95 | }; 96 | } 97 | 98 | @NotNull 99 | private AnActionButtonRunnable getAddActionButtonRunnable(final Set patterns) 100 | { 101 | return actionButton -> { 102 | final String pattern = Messages.showInputDialog( 103 | this.textAddMessage, this.textAddTitle, null, null, this.getRegexInputValidator()); 104 | if(pattern != null && (patterns.add(pattern))) 105 | { 106 | this.patternModels.addElementSorted(pattern); 107 | } 108 | }; 109 | } 110 | 111 | @NotNull 112 | private InputValidator getRegexInputValidator() 113 | { 114 | return new InputValidator() 115 | { 116 | @Override 117 | public boolean checkInput(final String string) 118 | { 119 | if(string == null || string.isBlank()) 120 | { 121 | // do not allow null or blank entries 122 | return false; 123 | } 124 | try 125 | { 126 | Pattern.compile(string); 127 | return true; 128 | } 129 | catch(final PatternSyntaxException e) 130 | { 131 | return false; 132 | } 133 | } 134 | 135 | @Override 136 | public boolean canClose(final String s) 137 | { 138 | return true; 139 | } 140 | }; 141 | } 142 | 143 | private static final class SortedListModel extends DefaultListModel 144 | { 145 | private void addElementSorted(final String element) 146 | { 147 | final Enumeration modelElements = this.elements(); 148 | int index = 0; 149 | while(modelElements.hasMoreElements()) 150 | { 151 | final String modelElement = (String)modelElements.nextElement(); 152 | if(0 < modelElement.compareTo(element)) 153 | { 154 | this.add(index, element); 155 | return; 156 | } 157 | index++; 158 | } 159 | this.addElement(element); 160 | } 161 | 162 | private void addAllSorted(final Collection elements) 163 | { 164 | for(final String element : elements) 165 | { 166 | this.addElementSorted(element); 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/BuildProcessor.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors; 2 | 3 | import static com.intellij.openapi.actionSystem.ActionPlaces.UNKNOWN; 4 | import static com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR; 5 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT; 6 | import static software.xdev.saveactions.utils.Helper.toVirtualFiles; 7 | 8 | import java.util.Arrays; 9 | import java.util.EnumSet; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | import java.util.function.BiFunction; 14 | import java.util.stream.Stream; 15 | 16 | import com.intellij.debugger.DebuggerManagerEx; 17 | import com.intellij.debugger.impl.DebuggerSession; 18 | import com.intellij.debugger.ui.HotSwapUI; 19 | import com.intellij.openapi.actionSystem.ActionManager; 20 | import com.intellij.openapi.actionSystem.ActionUiKind; 21 | import com.intellij.openapi.actionSystem.AnAction; 22 | import com.intellij.openapi.actionSystem.AnActionEvent; 23 | import com.intellij.openapi.actionSystem.DataContext; 24 | import com.intellij.openapi.actionSystem.ex.ActionUtil; 25 | import com.intellij.openapi.actionSystem.impl.SimpleDataContext; 26 | import com.intellij.openapi.application.ApplicationManager; 27 | import com.intellij.openapi.compiler.CompilerManager; 28 | import com.intellij.openapi.fileEditor.FileEditorManager; 29 | import com.intellij.openapi.project.Project; 30 | import com.intellij.psi.PsiFile; 31 | 32 | import software.xdev.saveactions.core.ExecutionMode; 33 | import software.xdev.saveactions.core.service.SaveActionsServiceManager; 34 | import software.xdev.saveactions.model.Action; 35 | 36 | 37 | /** 38 | * Available processors for build. 39 | */ 40 | @SuppressWarnings("java:S115") 41 | public enum BuildProcessor implements Processor 42 | { 43 | compile( 44 | Action.compile, 45 | (project, psiFiles) -> () -> { 46 | if(!SaveActionsServiceManager.getService().isCompilingAvailable()) 47 | { 48 | return; 49 | } 50 | CompilerManager.getInstance(project).compile(toVirtualFiles(psiFiles), null); 51 | }), 52 | 53 | reload( 54 | Action.reload, 55 | (project, psiFiles) -> () -> { 56 | if(!SaveActionsServiceManager.getService().isCompilingAvailable()) 57 | { 58 | return; 59 | } 60 | DebuggerManagerEx debuggerManager = DebuggerManagerEx.getInstanceEx(project); 61 | DebuggerSession session = debuggerManager.getContext().getDebuggerSession(); 62 | if(session != null && session.isAttached()) 63 | { 64 | HotSwapUI.getInstance(project).reloadChangedClasses(session, true); 65 | } 66 | }), 67 | 68 | executeAction( 69 | Action.executeAction, 70 | (project, psiFiles) -> () -> { 71 | ActionManager actionManager = ActionManager.getInstance(); 72 | 73 | List actionIds = SaveActionsServiceManager.getService().getQuickLists(project).stream() 74 | .flatMap(quickList -> Arrays.stream(quickList.getActionIds())) 75 | .toList(); 76 | 77 | for(String actionId : actionIds) 78 | { 79 | AnAction anAction = actionManager.getAction(actionId); 80 | if(anAction == null) 81 | { 82 | continue; 83 | } 84 | DataContext dataContext = SimpleDataContext.builder() 85 | .add(PROJECT, project) 86 | .add(EDITOR, FileEditorManager.getInstance(project).getSelectedTextEditor()) 87 | .setParent(null) 88 | .build(); 89 | AnActionEvent event = AnActionEvent.createEvent( 90 | dataContext, 91 | anAction.getTemplatePresentation().clone(), 92 | UNKNOWN, 93 | ActionUiKind.NONE, 94 | null); 95 | 96 | // Run Action on EDT thread 97 | ApplicationManager.getApplication().invokeLater(() -> 98 | ActionUtil.performAction(anAction, event)); 99 | } 100 | }) 101 | { 102 | @Override 103 | public SaveCommand getSaveCommand(final Project project, final Set psiFiles) 104 | { 105 | return new SaveReadCommand(project, psiFiles, this.getModes(), this.getAction(), this.getCommand()); 106 | } 107 | }; 108 | 109 | private final Action action; 110 | private final BiFunction command; 111 | 112 | BuildProcessor(final Action action, final BiFunction command) 113 | { 114 | this.action = action; 115 | this.command = command; 116 | } 117 | 118 | @Override 119 | public Action getAction() 120 | { 121 | return this.action; 122 | } 123 | 124 | @Override 125 | public Set getModes() 126 | { 127 | return EnumSet.allOf(ExecutionMode.class); 128 | } 129 | 130 | @Override 131 | public int getOrder() 132 | { 133 | return 2; 134 | } 135 | 136 | @Override 137 | public SaveCommand getSaveCommand(final Project project, final Set psiFiles) 138 | { 139 | return new SaveWriteCommand(project, psiFiles, this.getModes(), this.getAction(), this.getCommand()); 140 | } 141 | 142 | public BiFunction getCommand() 143 | { 144 | return this.command; 145 | } 146 | 147 | public static Optional getProcessorForAction(final Action action) 148 | { 149 | return stream().filter(processor -> processor.getAction().equals(action)).findFirst(); 150 | } 151 | 152 | public static Stream stream() 153 | { 154 | return Arrays.stream(values()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 99 | -------------------------------------------------------------------------------- /src/main/java/software/xdev/saveactions/processors/java/inspection/style/CustomUnqualifiedStaticUsageInspection.java: -------------------------------------------------------------------------------- 1 | package software.xdev.saveactions.processors.java.inspection.style; 2 | 3 | import org.jetbrains.annotations.Nls; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import com.intellij.psi.JavaResolveResult; 7 | import com.intellij.psi.PsiCaseLabelElementList; 8 | import com.intellij.psi.PsiClass; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiEnumConstant; 11 | import com.intellij.psi.PsiExpression; 12 | import com.intellij.psi.PsiField; 13 | import com.intellij.psi.PsiImportStaticStatement; 14 | import com.intellij.psi.PsiMember; 15 | import com.intellij.psi.PsiMethod; 16 | import com.intellij.psi.PsiMethodCallExpression; 17 | import com.intellij.psi.PsiModifier; 18 | import com.intellij.psi.PsiReferenceExpression; 19 | import com.intellij.psi.PsiSwitchLabelStatementBase; 20 | import com.intellij.psi.util.PsiTreeUtil; 21 | import com.intellij.psi.util.PsiUtil; 22 | import com.siyeh.ig.BaseInspectionVisitor; 23 | import com.siyeh.ig.style.UnqualifiedStaticUsageInspection; 24 | 25 | 26 | /** 27 | * Copy pasting because: cannot extend. Do not reformat (useful for diffs) 28 | * 29 | * @implNote Class needs to be inside a special package otherwise name resolution fails as seen in 30 | * {@link com.siyeh.ig.GroupDisplayNameUtil} 31 | * @see com.siyeh.ig.style.UnqualifiedStaticUsageInspection.UnqualifiedStaticCallVisitor 32 | */ 33 | public class CustomUnqualifiedStaticUsageInspection extends UnqualifiedStaticUsageInspection 34 | { 35 | @Override 36 | public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getDisplayName() 37 | { 38 | return this.getClass().getSimpleName(); 39 | } 40 | 41 | @Override 42 | public BaseInspectionVisitor buildVisitor() 43 | { 44 | return new CustomUnqualifiedStaticCallVisitor(); 45 | } 46 | 47 | private final class CustomUnqualifiedStaticCallVisitor extends BaseInspectionVisitor 48 | { 49 | @Override 50 | public void visitMethodCallExpression(@NotNull final PsiMethodCallExpression expression) 51 | { 52 | super.visitMethodCallExpression(expression); 53 | if(CustomUnqualifiedStaticUsageInspection.this.m_ignoreStaticMethodCalls) 54 | { 55 | return; 56 | } 57 | final PsiReferenceExpression methodExpression = expression.getMethodExpression(); 58 | if(!this.isUnqualifiedStaticAccess(methodExpression)) 59 | { 60 | return; 61 | } 62 | this.registerError(methodExpression, expression); 63 | } 64 | 65 | @Override 66 | public void visitReferenceExpression(@NotNull final PsiReferenceExpression expression) 67 | { 68 | super.visitReferenceExpression(expression); 69 | if(CustomUnqualifiedStaticUsageInspection.this.m_ignoreStaticFieldAccesses) 70 | { 71 | return; 72 | } 73 | final PsiElement element = expression.resolve(); 74 | if(!(element instanceof final PsiField field)) 75 | { 76 | return; 77 | } 78 | if(field.hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isOnAssignmentLeftHand(expression)) 79 | { 80 | return; 81 | } 82 | if(!this.isUnqualifiedStaticAccess(expression)) 83 | { 84 | return; 85 | } 86 | this.registerError(expression, expression); 87 | } 88 | 89 | @SuppressWarnings("PMD.NPathComplexity") 90 | private boolean isUnqualifiedStaticAccess(final PsiReferenceExpression expression) 91 | { 92 | if(CustomUnqualifiedStaticUsageInspection.this.m_ignoreStaticAccessFromStaticContext) 93 | { 94 | final PsiMember member = PsiTreeUtil.getParentOfType(expression, PsiMember.class); 95 | if(member != null && member.hasModifierProperty(PsiModifier.STATIC)) 96 | { 97 | return false; 98 | } 99 | } 100 | final PsiExpression qualifierExpression = expression.getQualifierExpression(); 101 | if(qualifierExpression != null) 102 | { 103 | return false; 104 | } 105 | final JavaResolveResult resolveResult = expression.advancedResolve(false); 106 | final PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope(); 107 | if(currentFileResolveScope instanceof PsiImportStaticStatement) 108 | { 109 | return false; 110 | } 111 | final PsiElement element = resolveResult.getElement(); 112 | if(!(element instanceof PsiField) && !(element instanceof PsiMethod)) 113 | { 114 | return false; 115 | } 116 | final PsiMember member = (PsiMember)element; 117 | if(this.isEnumInSwitch(member, expression)) 118 | { 119 | return false; 120 | } 121 | final PsiClass expressionClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class); 122 | final PsiClass memberClass = member.getContainingClass(); 123 | if(memberClass != null && memberClass.equals(expressionClass)) 124 | { 125 | return false; 126 | } 127 | return member.hasModifierProperty(PsiModifier.STATIC); 128 | } 129 | 130 | private boolean isEnumInSwitch(final PsiMember member, final PsiReferenceExpression expression) 131 | { 132 | if(!(member instanceof PsiEnumConstant)) 133 | { 134 | return false; 135 | } 136 | 137 | final PsiElement parent = expression.getParent(); 138 | return parent instanceof PsiCaseLabelElementList 139 | || parent != null && parent.getParent() instanceof PsiSwitchLabelStatementBase 140 | // This was the original code and might be needed for older java versions 141 | || parent instanceof PsiSwitchLabelStatementBase; 142 | } 143 | } 144 | } 145 | --------------------------------------------------------------------------------