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 extends LocalInspectionTool> 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 `` `` inside ``saveactions_settings.xml``
53 | * Allow compilation with Java 21
54 |
55 | ## 1.0.5
56 | * Fixed ``Add class qualifier to static member access outside declaring class`` not working in combination with Qodana plugin #25
57 |
58 | ## 1.0.4
59 | * Fixed pluginIcon being not displayed #35
60 | * Improved support of Android Studio (until a 2023 version is released) #27
61 |
62 | ## 1.0.3
63 | * Fixed problem in combination with Qodana plugin #25
64 | * Improved compatibility and cleaned up code #27
65 |
66 | ## 1.0.2
67 | * Fixed missing display name which causes an error when multiple configurable plugins are installed #20
68 |
69 | ## 1.0.1
70 | * Fixed ``Change visibility of field or method to lower access`` not working #14
71 |
72 | ## 1.0.0
73 | Initial release
74 | * 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)
75 | * ⚠️ This plugin is not compatible with the old/deprecated/forked one. Please ensure that the old plugin is uninstalled.
76 | * Rebrand
77 | * Updated copy pasted classes from IDEA
78 |
79 |
--------------------------------------------------------------------------------
/src/test/java/software/xdev/saveactions/core/component/PsiFileTest.java:
--------------------------------------------------------------------------------
1 | package software.xdev.saveactions.core.component;
2 |
3 | import static java.util.Collections.emptySet;
4 | import static java.util.Collections.singleton;
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | import java.util.Set;
8 | import java.util.stream.Stream;
9 |
10 | import org.junit.jupiter.params.ParameterizedTest;
11 | import org.junit.jupiter.params.provider.Arguments;
12 | import org.junit.jupiter.params.provider.MethodSource;
13 |
14 | import software.xdev.saveactions.model.Storage;
15 |
16 |
17 | class PsiFileTest
18 | {
19 | @ParameterizedTest(name = "[{index}] included={0}, psiFile={1}, inclusion={2}, exclusion={3}")
20 | @MethodSource("parameters")
21 | void test(final boolean included, final String psiFile, final String inclusion, final String exclusion)
22 | {
23 | final Storage storage = new Storage();
24 | storage.setInclusions(this.toSet(inclusion));
25 | storage.setExclusions(this.toSet(exclusion));
26 |
27 | final Engine engine = new Engine(storage, null, null, null, null, null);
28 |
29 | assertThat(engine.isIncludedAndNotExcluded(psiFile))
30 | .isEqualTo(included);
31 | }
32 |
33 | static Stream parameters()
34 | {
35 | return Stream.of(
36 | // Only excludes - taken from PsiFilesIsUrlIncludedTest
37 | // Default cases and invalid regex
38 | Arguments.of(true, null, "", "*"),
39 | Arguments.of(true, "file", "", ""),
40 | Arguments.of(true, "/home/alex/projects/project1/ignore.java", "", "*"),
41 |
42 | // Base cases
43 | Arguments.of(false, "/project/Ignore.java", "", "Ignore.java"),
44 | Arguments.of(true, "/project/Ignore.java", "", "ignore.java"),
45 | Arguments.of(false, "/project/Ignore.java", "", ".*\\.java"),
46 |
47 | // With different project strings
48 | Arguments.of(true, "/home/alex/projects/project1/ignore.java", "", ".*\\.properties"),
49 | Arguments.of(false, "/home/alex/projects/project1/ignore.properties", "", ".*\\.properties"),
50 | Arguments.of(false, "c://projects/project/ignore.properties", "", ".*\\.properties"),
51 |
52 | // With specific paths
53 | Arguments.of(true, "/home/alex/projects/project1/ignore.properties", "", "src/.*\\.properties"),
54 | Arguments.of(false, "/home/alex/projects/project1/src/ignore.properties", "", "src/.*\\.properties"),
55 |
56 | // With specific folders recursive
57 | Arguments.of(false, "/project1/src/ignore/Ignore.java", "", "ignore/.*"),
58 | Arguments.of(false, "/project1/src/ignore/sub/Ignore.java", "", "ignore/.*"),
59 | Arguments.of(false, "/project1/src/ignore/sub/Ignore.java", "", "ignore/.*\\.java"),
60 | Arguments.of(false, "/project1/src/ignore/sub/Ignore.java", "", "ignore/.*/.*\\.java"),
61 |
62 | // Only includes
63 | Arguments.of(true, "/project/Include.java", "Include.java", ""),
64 | Arguments.of(false, "/project/Include.java", "include.java", ""),
65 | Arguments.of(true, "/project/Include.java", ".*\\.java", ""),
66 |
67 | // Includes and excludes
68 | Arguments.of(false, "/project/Include.java", ".*\\.java", ".*\\.java"),
69 | Arguments.of(true, "/project/Include.java", ".*\\.java", ".*\\.xml"),
70 | Arguments.of(false, "/project/Include.xml", ".*\\.java", ".*\\.xml")
71 | );
72 | }
73 |
74 | private Set toSet(final String inclusion)
75 | {
76 | if(null == inclusion || inclusion.isEmpty())
77 | {
78 | return emptySet();
79 | }
80 | return singleton(inclusion);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | xdev-software
5 |
6 | Fork of the popular but now deprecated Save Actions plugin by XDEV Software .
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 | [](https://plugins.jetbrains.com/plugin/22113)
2 | [](https://github.com/xdev-software/intellij-plugin-save-actions/actions/workflows/check-build.yml?query=branch%3Adevelop)
3 | [](https://plugins.jetbrains.com/plugin/22113/reviews)
4 |
5 | # 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 |
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 |
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 [](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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
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 |
--------------------------------------------------------------------------------