This is an automated email from the ASF dual-hosted git repository.

dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 0665eb1  Delete unused private/package private elements hint fix 
added. (#3841)
0665eb1 is described below

commit 0665eb12aaa64cbe3b7bae657f46594a579a41cc
Author: Dusan Balek <[email protected]>
AuthorDate: Thu Mar 24 15:51:35 2022 +0100

    Delete unused private/package private elements hint fix added. (#3841)
---
 .../java/editor/base/semantic/UnusedDetector.java  | 127 +++++++++++++++--
 .../base/semantic/DetectorTest/testColorings1.pass |   2 +-
 .../testImportDisambiguation203874.pass            |   4 +-
 .../DetectorTest/testMultiFields116520a.pass       |   4 +-
 .../DetectorTest/testMultiFields116520b.pass       |   4 +-
 .../DetectorTest/testSemanticInnerClasses.pass     |   2 +-
 .../DetectorTest/testStaticImport189226.pass       |   4 +-
 .../testTwoPackagePrivateConstructors.pass         |   4 +-
 .../semantic/DetectorTest/testUnusedImports.pass   |   2 +-
 .../DetectorTest/testUsedImport129988.pass         |   2 +-
 .../java/editor/base/semantic/DetectorTest.java    |  14 +-
 .../netbeans/modules/java/hints/bugs/Unused.java   |  22 ++-
 .../java/lsp/server/protocol/ServerTest.java       |   8 +-
 java/java.source.base/apichanges.xml               |  12 ++
 java/java.source.base/nbproject/project.properties |   2 +-
 .../netbeans/api/java/source/TreeUtilities.java    |  14 +-
 java/spi.java.hints/nbproject/project.properties   |   2 +-
 .../netbeans/spi/java/hints/JavaFixUtilities.java  | 151 ++++++++++++++++++++-
 18 files changed, 334 insertions(+), 46 deletions(-)

diff --git 
a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
 
b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
index 2f1a47a..35cfe12 100644
--- 
a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
+++ 
b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
@@ -25,7 +25,6 @@ import com.sun.source.tree.EnhancedForLoopTree;
 import com.sun.source.tree.IdentifierTree;
 import com.sun.source.tree.MemberReferenceTree;
 import com.sun.source.tree.MemberSelectTree;
-import com.sun.source.tree.MethodInvocationTree;
 import com.sun.source.tree.MethodTree;
 import com.sun.source.tree.NewClassTree;
 import com.sun.source.tree.Tree;
@@ -33,6 +32,7 @@ import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -44,6 +44,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -51,9 +52,13 @@ import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
+import org.netbeans.api.java.source.ClassIndex;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Exceptions;
 
 /**
  *
@@ -101,25 +106,44 @@ public class UnusedDetector {
             TreePath declaration = e.getValue();
             Set<UseTypes> uses = uv.useTypes.getOrDefault(el, 
Collections.emptySet());
             boolean isPrivate = el.getModifiers().contains(Modifier.PRIVATE); 
//TODO: effectivelly private!
-            if (isLocalVariableClosure(el) || (el.getKind().isField() && 
isPrivate)) {
+            boolean isPkgPrivate = !isPrivate && 
!el.getModifiers().contains(Modifier.PUBLIC) && 
!el.getModifiers().contains(Modifier.PROTECTED);
+            if (isLocalVariableClosure(el)) {
+                boolean isWritten = uses.contains(UseTypes.WRITTEN);
+                boolean isRead = uses.contains(UseTypes.READ);
+                if (!isWritten && !isRead) {
+                    result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_WRITTEN_READ));
+                } else if (!isWritten) {
+                    result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_WRITTEN));
+                } else if (!isRead) {
+                    result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_READ));
+                }
+            } else if (el.getKind().isField() && (isPrivate || isPkgPrivate)) {
                 if (!isSerialSpecField(info, el)) {
                     boolean isWritten = uses.contains(UseTypes.WRITTEN);
                     boolean isRead = uses.contains(UseTypes.READ);
                     if (!isWritten && !isRead) {
-                        result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_WRITTEN_READ));
+                        if (isPrivate || isUnusedInPkg(info, el)) {
+                            result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_WRITTEN_READ));
+                        }
                     } else if (!isWritten) {
                         result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_WRITTEN));
                     } else if (!isRead) {
-                        result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_READ));
+                        if (isPrivate || isUnusedInPkg(info, el)) {
+                            result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_READ));
+                        }
                     }
                 }
-            } else if ((el.getKind() == ElementKind.CONSTRUCTOR || 
el.getKind() == ElementKind.METHOD) && isPrivate) {
+            } else if ((el.getKind() == ElementKind.CONSTRUCTOR || 
el.getKind() == ElementKind.METHOD) && (isPrivate || isPkgPrivate)) {
                 if (!isSerializationMethod(info, (ExecutableElement)el) && 
!uses.contains(UseTypes.USED)) {
-                    result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_USED));
+                    if (isPrivate || isUnusedInPkg(info, el)) {
+                        result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_USED));
+                    }
                 }
-            } else if ((el.getKind().isClass() || el.getKind().isInterface()) 
&& isPrivate) {
+            } else if ((el.getKind().isClass() || el.getKind().isInterface()) 
&& (isPrivate || isPkgPrivate)) {
                 if (!uses.contains(UseTypes.USED)) {
-                    result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_USED));
+                    if (isPrivate || isUnusedInPkg(info, el)) {
+                        result.add(new UnusedDescription(el, declaration, 
UnusedReason.NOT_USED));
+                    }
                 }
             }
         }
@@ -248,6 +272,88 @@ public class UnusedDetector {
                LOCAL_VARIABLES.contains(el.getKind());
     }
 
+    private static boolean isUnusedInPkg(CompilationInfo info, Element el) {
+        TypeElement typeElement;
+        Set<? extends String> packageSet = 
Collections.singleton(info.getElements().getPackageOf(el).getQualifiedName().toString());
+        Set<ClassIndex.SearchKind> searchKinds;
+        Set<ClassIndex.SearchScopeType> scope = Collections.singleton(new 
ClassIndex.SearchScopeType() {
+            @Override
+            public Set<? extends String> getPackages() {
+                return packageSet;
+            }
+
+            @Override
+            public boolean isSources() {
+                return true;
+            }
+
+            @Override
+            public boolean isDependencies() {
+                return false;
+            }
+        });
+        switch (el.getKind()) {
+            case FIELD:
+                typeElement = 
info.getElementUtilities().enclosingTypeElement(el);
+                searchKinds = 
EnumSet.of(ClassIndex.SearchKind.FIELD_REFERENCES);
+                break;
+            case METHOD:
+            case CONSTRUCTOR:
+                typeElement = 
info.getElementUtilities().enclosingTypeElement(el);
+                searchKinds = 
EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES);
+                break;
+            case ANNOTATION_TYPE:
+            case CLASS:
+            case ENUM:
+            case INTERFACE:
+                List<? extends TypeElement> topLevelElements = 
info.getTopLevelElements();
+                if (topLevelElements.size() == 1 && topLevelElements.get(0) == 
el) {
+                    return false;
+                }
+                typeElement = (TypeElement) el;
+                searchKinds = 
EnumSet.of(ClassIndex.SearchKind.TYPE_REFERENCES);
+                break;
+
+            default:
+                return true;
+        }
+        ElementHandle eh = ElementHandle.create(el);
+        Set<FileObject> res = 
info.getClasspathInfo().getClassIndex().getResources(ElementHandle.create(typeElement),
 searchKinds, scope);
+        for (FileObject fo : res) {
+            if (fo != info.getFileObject()) {
+                JavaSource js = JavaSource.forFileObject(fo);
+                if (js == null) {
+                    return false;
+                }
+                AtomicBoolean found = new AtomicBoolean();
+                try {
+                    js.runUserActionTask(cc -> {
+                        cc.toPhase(JavaSource.Phase.RESOLVED);
+                        new TreePathScanner<Void, Element>() {
+                            @Override
+                            public Void scan(Tree tree, Element p) {
+                                if (!found.get() && tree != null) {
+                                    Element element = 
cc.getTrees().getElement(new TreePath(getCurrentPath(), tree));
+                                    if (element != null && 
eh.signatureEquals(element)) {
+                                        found.set(true);
+                                    }
+                                    super.scan(tree, p);
+                                }
+                                return null;
+                            }
+                        }.scan(new TreePath(cc.getCompilationUnit()), el);
+                    }, true);
+                } catch (IOException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+                if (found.get()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private enum UseTypes {
         READ, WRITTEN, USED;
     }
@@ -295,8 +401,9 @@ public class UnusedDetector {
             }
 
             boolean isPrivate = el.getModifiers().contains(Modifier.PRIVATE); 
//TODO: effectivelly private!
+            boolean isPkgPrivate = !isPrivate && 
!el.getModifiers().contains(Modifier.PUBLIC) && 
!el.getModifiers().contains(Modifier.PROTECTED);
 
-            if (isLocalVariableClosure(el) || (el.getKind().isField() && 
isPrivate)) {
+            if (isLocalVariableClosure(el) || (el.getKind().isField() && 
(isPrivate | isPkgPrivate))) {
                 TreePath effectiveUse = getCurrentPath();
                 boolean isWrite = false;
                 boolean isRead = false;
@@ -343,7 +450,7 @@ public class UnusedDetector {
                 if (isRead) {
                     addUse(el, UseTypes.READ);
                 }
-            } else if (isPrivate) {
+            } else if (isPrivate | isPkgPrivate) {
                 if (el.getKind() != ElementKind.METHOD || recursionDetector != 
el)
                 addUse(el, UseTypes.USED);
             }
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testColorings1.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testColorings1.pass
index d86a957..b7fda67 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testColorings1.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testColorings1.pass
@@ -18,7 +18,7 @@
 [PUBLIC, INTERFACE], 17:11-17:15
 [PUBLIC, DEPRECATED, FIELD, DECLARATION], 17:16-17:21
 [PUBLIC, INTERFACE], 19:11-19:15
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 19:16-19:21
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 19:16-19:21
 [PUBLIC, INTERFACE], 21:14-21:18
 [PROTECTED, FIELD, DECLARATION], 21:19-21:24
 [STATIC, PUBLIC, METHOD, DECLARATION], 23:23-23:27
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testImportDisambiguation203874.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testImportDisambiguation203874.pass
index c340c4d..fb0784f 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testImportDisambiguation203874.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testImportDisambiguation203874.pass
@@ -1,7 +1,7 @@
 [PUBLIC, INTERFACE], 2:17-2:21
 [PUBLIC, CLASS, DECLARATION], 6:13-6:33
 [PUBLIC, INTERFACE], 8:4-8:8
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 8:9-8:10
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 8:9-8:10
 [PUBLIC, CONSTRUCTOR], 8:17-8:26
 [ABSTRACT, PUBLIC, CLASS], 9:4-9:13
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 9:14-9:15
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 9:14-9:15
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520a.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520a.pass
index 428e5c4..6521042 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520a.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520a.pass
@@ -1,3 +1,3 @@
 [PUBLIC, CLASS, DECLARATION], 2:13-2:24
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 4:9-4:11
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 4:13-4:15
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 4:9-4:11
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 4:13-4:15
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520b.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520b.pass
index 428e5c4..6521042 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520b.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testMultiFields116520b.pass
@@ -1,3 +1,3 @@
 [PUBLIC, CLASS, DECLARATION], 2:13-2:24
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 4:9-4:11
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 4:13-4:15
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 4:9-4:11
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 4:13-4:15
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testSemanticInnerClasses.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testSemanticInnerClasses.pass
index 9135616..8f65897 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testSemanticInnerClasses.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testSemanticInnerClasses.pass
@@ -15,4 +15,4 @@
 [PUBLIC, CONSTRUCTOR, DECLARATION], 34:15-34:21
 [PRIVATE, CLASS, DECLARATION], 39:18-39:24
 [PUBLIC, CONSTRUCTOR, DECLARATION], 40:15-40:21
-[PACKAGE_PRIVATE, CLASS, DECLARATION], 45:10-45:16
+[PACKAGE_PRIVATE, CLASS, UNUSED, DECLARATION], 45:10-45:16
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testStaticImport189226.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testStaticImport189226.pass
index fe329d2..cb284c1 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testStaticImport189226.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testStaticImport189226.pass
@@ -3,6 +3,6 @@
 [PUBLIC, CLASS], 3:26-3:37
 [PACKAGE_PRIVATE, CLASS, DECLARATION], 5:6-5:24
 [ABSTRACT, PUBLIC, CLASS], 7:4-7:9
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 7:10-7:11
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 8:8-8:9
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 7:10-7:11
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 8:8-8:9
 [STATIC, PUBLIC, FIELD], 8:12-8:26
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testTwoPackagePrivateConstructors.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testTwoPackagePrivateConstructors.pass
index 950b968..8c3a5b7 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testTwoPackagePrivateConstructors.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testTwoPackagePrivateConstructors.pass
@@ -1,4 +1,4 @@
 [PUBLIC, CLASS, DECLARATION], 2:13-2:42
-[PACKAGE_PRIVATE, CONSTRUCTOR, DECLARATION], 3:4-3:33
-[PACKAGE_PRIVATE, CONSTRUCTOR, DECLARATION], 4:4-4:33
+[PACKAGE_PRIVATE, CONSTRUCTOR, UNUSED, DECLARATION], 3:4-3:33
+[PACKAGE_PRIVATE, CONSTRUCTOR, UNUSED, DECLARATION], 4:4-4:33
 [PARAMETER, DECLARATION], 4:38-4:39
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUnusedImports.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUnusedImports.pass
index d946661..919d214 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUnusedImports.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUnusedImports.pass
@@ -32,7 +32,7 @@
 [PUBLIC, CLASS], 19:41-19:47
 [PUBLIC, INTERFACE], 19:59-19:71
 [STATIC, PUBLIC, INTERFACE], 21:4-21:9
-[PACKAGE_PRIVATE, FIELD, DECLARATION], 21:10-21:11
+[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 21:10-21:11
 [PUBLIC, INTERFACE], 23:12-23:16
 [PRIVATE, FIELD, UNUSED, DECLARATION], 23:17-23:21
 [PUBLIC, INTERFACE], 25:25-25:29
diff --git 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUsedImport129988.pass
 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUsedImport129988.pass
index 1a4d812..0ef982c 100644
--- 
a/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUsedImport129988.pass
+++ 
b/java/java.editor.base/test/unit/data/goldenfiles/org/netbeans/modules/java/editor/base/semantic/DetectorTest/testUsedImport129988.pass
@@ -1,6 +1,6 @@
 [PUBLIC, INTERFACE], 2:17-2:21
 [PUBLIC, CLASS, DECLARATION], 4:13-4:29
-[PACKAGE_PRIVATE, METHOD, DECLARATION], 5:9-5:11
+[PACKAGE_PRIVATE, METHOD, UNUSED, DECLARATION], 5:9-5:11
 [PUBLIC, CONSTRUCTOR], 6:12-6:28
 [PUBLIC, INTERFACE], 6:29-6:33
 [PUBLIC, CLASS], 6:34-6:40
diff --git 
a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
 
b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
index 4a00265..5440841 100644
--- 
a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
+++ 
b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
@@ -527,7 +527,7 @@ public class DetectorTest extends TestBase {
                     "[PUBLIC, RECORD, DECLARATION], 0:14-0:18",
                     "[PUBLIC, CLASS], 0:19-0:25",
                     "[PUBLIC, RECORD_COMPONENT, DECLARATION], 0:26-0:27",
-                    "[PACKAGE_PRIVATE, CLASS, DECLARATION], 1:6-1:7",
+                    "[PACKAGE_PRIVATE, CLASS, UNUSED, DECLARATION], 1:6-1:7",
                     "[PUBLIC, CLASS], 2:11-2:17",
                     "[PUBLIC, METHOD, DECLARATION], 2:18-2:19",
                     "[PUBLIC, RECORD], 2:20-2:24",
@@ -591,7 +591,7 @@ public class DetectorTest extends TestBase {
                 "[PACKAGE_PRIVATE, CLASS, DECLARATION], 0:13-0:17",
                 "[KEYWORD], 1:0-1:3",
                 "[KEYWORD], 1:4-1:10",
-                "[PACKAGE_PRIVATE, CLASS, DECLARATION], 1:17-1:22",
+                "[PACKAGE_PRIVATE, CLASS, UNUSED, DECLARATION], 1:17-1:22",
                 "[PACKAGE_PRIVATE, CLASS], 1:31-1:35");
     }
 
@@ -638,7 +638,7 @@ public class DetectorTest extends TestBase {
                 "[PUBLIC, CLASS, DECLARATION], 0:13-0:30\n"
                 + "[PUBLIC, CLASS], 1:4-1:10\n"
                 + "[PACKAGE_PRIVATE, FIELD, DECLARATION], 1:11-1:19\n"
-                + "[PACKAGE_PRIVATE, METHOD, DECLARATION], 2:9-2:11\n"
+                + "[PACKAGE_PRIVATE, METHOD, UNUSED, DECLARATION], 2:9-2:11\n"
                 + "[PUBLIC, CLASS], 3:8-3:14\n"
                 + "[LOCAL_VARIABLE, DECLARATION], 3:15-3:18\n"
                 + "[LOCAL_VARIABLE], 4:16-4:19\n"
@@ -691,11 +691,11 @@ public class DetectorTest extends TestBase {
                     "}\n",
                     "[PUBLIC, CLASS, DECLARATION], 0:13-0:29",
                     "[PUBLIC, CLASS], 1:4-1:10",
-                    "[PACKAGE_PRIVATE, FIELD, DECLARATION], 1:11-1:13",
+                    "[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 1:11-1:13",
                     "[UNINDENTED_TEXT_BLOCK], 2:13-2:27",
                     "[UNINDENTED_TEXT_BLOCK], 3:13-3:29",
                     "[PUBLIC, CLASS], 5:4-5:10",
-                    "[PACKAGE_PRIVATE, FIELD, DECLARATION], 5:11-5:13",
+                    "[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 5:11-5:13",
                     "[UNINDENTED_TEXT_BLOCK], 6:16-6:27",
                     "[UNINDENTED_TEXT_BLOCK], 7:16-7:29");
     }
@@ -739,7 +739,7 @@ public class DetectorTest extends TestBase {
                     "[PUBLIC, CLASS], 1:36-1:42",
                     "[PARAMETER, UNUSED, DECLARATION], 1:43-1:47",
                     "[PUBLIC, CLASS], 1:54-1:60",
-                    "[PACKAGE_PRIVATE, FIELD, DECLARATION], 1:61-1:65",
+                    "[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 1:61-1:65",
                     "[PACKAGE_PRIVATE, CONSTRUCTOR], 2:19-2:25");
     }
 
@@ -814,7 +814,7 @@ public class DetectorTest extends TestBase {
                     "}\n",
                     "[PUBLIC, CLASS, DECLARATION], 0:13-0:29",
                     "[PUBLIC, CLASS], 1:4-1:10",
-                    "[PACKAGE_PRIVATE, FIELD, DECLARATION], 1:11-1:13",
+                    "[PACKAGE_PRIVATE, FIELD, UNUSED, DECLARATION], 1:11-1:13",
                     "[UNINDENTED_TEXT_BLOCK], 2:13-2:27",
                     "[UNINDENTED_TEXT_BLOCK], 6:13-6:29");
     }
diff --git 
a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java 
b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
index cc87f67..28b96bd 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
@@ -25,9 +25,11 @@ import javax.lang.model.element.ElementKind;
 import org.netbeans.modules.java.editor.base.semantic.UnusedDetector;
 import 
org.netbeans.modules.java.editor.base.semantic.UnusedDetector.UnusedDescription;
 import org.netbeans.spi.editor.hints.ErrorDescription;
+import org.netbeans.spi.editor.hints.Fix;
 import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
 import org.netbeans.spi.java.hints.Hint;
 import org.netbeans.spi.java.hints.HintContext;
+import org.netbeans.spi.java.hints.JavaFixUtilities;
 import org.netbeans.spi.java.hints.TriggerTreeKind;
 import org.openide.util.NbBundle.Messages;
 
@@ -61,25 +63,37 @@ public class Unused {
         "# {0} - element name",
         "ERR_NotUsed={0} is never used",
         "ERR_NotUsedConstructor=Constructor is never used",
+        "# {0} - element name",
+        "FIX_RemoveUsedElement=Remove unused \"{0}\"",
+        "FIX_RemoveUsedConstructor=Remove unused constructor",
     })
     private static ErrorDescription convertUnused(HintContext ctx, 
UnusedDescription ud) {
         //TODO: switch expression candidate!
         String name = ud.unusedElement.getSimpleName().toString();
         String message;
+        Fix fix = null;
         switch (ud.reason) {
-            case NOT_WRITTEN_READ: message = 
Bundle.ERR_NeitherReadOrWritten(name); break;
-            case NOT_WRITTEN: message = Bundle.ERR_NotWritten(name); break;
-            case NOT_READ: message = Bundle.ERR_NotRead(name); break;
+            case NOT_WRITTEN_READ: message = 
Bundle.ERR_NeitherReadOrWritten(name);
+                fix = JavaFixUtilities.removeFromParent(ctx, 
Bundle.FIX_RemoveUsedElement(name), ud.unusedElementPath);
+                break;
+            case NOT_WRITTEN: message = Bundle.ERR_NotWritten(name);
+                break;
+            case NOT_READ: message = Bundle.ERR_NotRead(name);
+                fix = JavaFixUtilities.safelyRemoveFromParent(ctx, 
Bundle.FIX_RemoveUsedElement(name), ud.unusedElementPath);
+                break;
             case NOT_USED:
                 if (ud.unusedElement.getKind() == ElementKind.CONSTRUCTOR) {
                     message = Bundle.ERR_NotUsedConstructor();
+                    fix = JavaFixUtilities.removeFromParent(ctx, 
Bundle.FIX_RemoveUsedConstructor(), ud.unusedElementPath);
                 } else {
                     message = Bundle.ERR_NotUsed(name);
+                    fix = JavaFixUtilities.removeFromParent(ctx, 
Bundle.FIX_RemoveUsedElement(name), ud.unusedElementPath);
                 }
                 break;
             default:
                 throw new IllegalStateException("Unknown unused type: " + 
ud.reason);
         }
-        return ErrorDescriptionFactory.forName(ctx, ud.unusedElementPath, 
message);
+        return fix != null ? ErrorDescriptionFactory.forName(ctx, 
ud.unusedElementPath, message, fix)
+                           : ErrorDescriptionFactory.forName(ctx, 
ud.unusedElementPath, message);
     }
 }
diff --git 
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 5a5acb1..0525b4a 100644
--- 
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -312,7 +312,7 @@ public class ServerTest extends NbTestCase {
     public void testMain() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();
-        String code = "public class Test { int i = \"\".hashCode(); public 
void run() { this.test(); } /**Test.*/public void test() {} }";
+        String code = "public class Test { public int i = \"\".hashCode(); 
public void run() { this.test(); } /**Test.*/public void test() {} }";
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
@@ -331,8 +331,8 @@ public class ServerTest extends NbTestCase {
         VersionedTextDocumentIdentifier id = new 
VersionedTextDocumentIdentifier(1);
         id.setUri(toURI(src));
         server.getTextDocumentService().didChange(new 
DidChangeTextDocumentParams(id, Arrays.asList(new 
TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new 
Position(0, hashCodeStart + "hashCode".length())), "hashCode".length(), 
"equ"))));
-        assertDiags(diags, "Error:0:31-0:34");//errors
-        assertDiags(diags, "Error:0:31-0:34");//hints
+        assertDiags(diags, "Error:0:38-0:41");//errors
+        assertDiags(diags, "Error:0:38-0:41");//hints
         completion = server.getTextDocumentService().completion(new 
CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, 
hashCodeStart + 2))).get();
         actualItems = completion.getRight().getItems().stream().map(ci -> 
ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
         if (jdk9Plus()) {
@@ -378,7 +378,7 @@ public class ServerTest extends NbTestCase {
         assertEquals("(String) ", edit.getNewText());
         server.getTextDocumentService().didChange(new 
DidChangeTextDocumentParams(id, Arrays.asList(new 
TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new 
Position(0, closingBrace)), 0, "public  void assignToSelf(Object o) { o = o; 
}"))));
         assertDiags(diags, "Error:1:0-1:9");//errors
-        assertDiags(diags, "Error:1:0-1:9", "Warning:0:148-0:153", 
"Warning:0:152-0:153");//hints
+        assertDiags(diags, "Error:1:0-1:9", "Warning:0:155-0:160", 
"Warning:0:159-0:160");//hints
     }
     
     /**
diff --git a/java/java.source.base/apichanges.xml 
b/java/java.source.base/apichanges.xml
index 306d262..bf7cc2d 100644
--- a/java/java.source.base/apichanges.xml
+++ b/java/java.source.base/apichanges.xml
@@ -25,6 +25,18 @@
     <apidef name="javasource_base">Java Source API</apidef>
 </apidefs>
 <changes>
+    <change id="TreeUtilities.isClassFile">
+        <api name="javasource_base" />
+        <summary>Adding 
TreeUtilities.isExpressionStatement(ExpressionTree)</summary>
+        <version major="1" minor="2.56"/>
+        <date day="23" month="3" year="2021"/>
+        <author login="dbalek"/>
+        <compatibility addition="yes" binary="compatible" source="compatible"/>
+        <description>
+                Adding TreeUtilities.isExpressionStatement(ExpressionTree).
+        </description>
+        <class name="TreeUtilities" package="org.netbeans.api.java.source"/>
+    </change>
     <change id="SourceUtils.isClassFile">
         <api name="javasource_base" />
         <summary>Adding SourceUtils.isClassFile(FileObject)</summary>
diff --git a/java/java.source.base/nbproject/project.properties 
b/java/java.source.base/nbproject/project.properties
index e5b4d68..b62d7cf 100644
--- a/java/java.source.base/nbproject/project.properties
+++ b/java/java.source.base/nbproject/project.properties
@@ -23,7 +23,7 @@ javadoc.name=Java Source Base
 javadoc.title=Java Source Base
 javadoc.arch=${basedir}/arch.xml
 javadoc.apichanges=${basedir}/apichanges.xml
-spec.version.base=2.55.0
+spec.version.base=2.56.0
 
test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/nb-javac-api.jar
 test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\
     ${o.n.core.dir}/lib/boot.jar:\
diff --git 
a/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java 
b/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java
index 8aed857..73a7886 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java
@@ -146,7 +146,7 @@ public final class TreeUtilities {
     }
 
     /**
-     * Checks wheteher given variable tree represents an enum constant.
+     * Checks whether given variable tree represents an enum constant.
      */
     public boolean isEnumConstant(VariableTree tree) {
         return (((JCTree.JCModifiers) tree.getModifiers()).flags & Flags.ENUM) 
!= 0;
@@ -161,7 +161,7 @@ public final class TreeUtilities {
     }
     
     /**
-     * Checks wheteher given compilation unit represents a package-info.
+     * Checks whether given compilation unit represents a package-info.
      * @since 2.23
     */
     public boolean isPackageInfo(CompilationUnitTree tree) {
@@ -169,13 +169,21 @@ public final class TreeUtilities {
     }
     
     /**
-     * Checks wheteher given compilation unit represents a module-info.
+     * Checks whether given compilation unit represents a module-info.
      * @since 2.23
      */
     public boolean isModuleInfo(CompilationUnitTree tree) {
         return TreeInfo.isModuleInfo((JCTree.JCCompilationUnit)tree);
     }
     
+    /**
+     * Checks whether given expression represents an expression statement.
+     * @since 2.56
+     */
+    public boolean isExpressionStatement(ExpressionTree tree) {
+        return TreeInfo.isExpressionStatement((JCTree.JCExpression)tree);
+    }
+    
     /**Returns whether or not the given tree is synthetic - generated by the 
parser.
      * Please note that this method does not check trees transitively - a 
child of a syntetic tree
      * may be considered non-syntetic.
diff --git a/java/spi.java.hints/nbproject/project.properties 
b/java/spi.java.hints/nbproject/project.properties
index 84c91b9..5a5fb84 100644
--- a/java/spi.java.hints/nbproject/project.properties
+++ b/java/spi.java.hints/nbproject/project.properties
@@ -17,7 +17,7 @@
 is.autoload=true
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
-spec.version.base=1.47.0
+spec.version.base=1.48.0
 requires.nb.javac=true
 javadoc.arch=${basedir}/arch.xml
 javadoc.apichanges=${basedir}/apichanges.xml
diff --git 
a/java/spi.java.hints/src/org/netbeans/spi/java/hints/JavaFixUtilities.java 
b/java/spi.java.hints/src/org/netbeans/spi/java/hints/JavaFixUtilities.java
index c632a57..748159d 100644
--- a/java/spi.java.hints/src/org/netbeans/spi/java/hints/JavaFixUtilities.java
+++ b/java/spi.java.hints/src/org/netbeans/spi/java/hints/JavaFixUtilities.java
@@ -58,6 +58,7 @@ import com.sun.source.tree.VariableTree;
 import com.sun.source.tree.WhileLoopTree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
 import com.sun.source.util.TreeScanner;
 import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
 import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
@@ -78,6 +79,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -224,7 +226,21 @@ public class JavaFixUtilities {
      * @return an editor fix that removes the give tree from the source code
      */
     public static Fix removeFromParent(HintContext ctx, String displayName, 
TreePath what) {
-        return new RemoveFromParent(displayName, ctx.getInfo(), 
what).toEditorFix();
+        return new RemoveFromParent(displayName, ctx.getInfo(), what, 
false).toEditorFix();
+    }
+
+    /**Creates a fix that removes the given code corresponding to the given 
tree
+     * node together with all its usages from the source code
+     * 
+     * @param ctx basic context for which the fix should be created
+     * @param displayName the display name of the fix
+     * @param what the tree node that should be removed
+     * @return an editor fix that removes the give tree from the source code
+     * 
+     * @since 1.48
+     */
+    public static Fix safelyRemoveFromParent(HintContext ctx, String 
displayName, TreePath what) {
+        return RemoveFromParent.canSafelyRemove(ctx.getInfo(), what) ? new 
RemoveFromParent(displayName, ctx.getInfo(), what, true).toEditorFix() : null;
     }
 
     private static String defaultFixDisplayName(CompilationInfo info, 
Map<String, TreePath> variables, String replaceTarget) {
@@ -1612,10 +1628,12 @@ public class JavaFixUtilities {
     private static final class RemoveFromParent extends JavaFix {
 
         private final String displayName;
+        private final boolean safely;
 
-        public RemoveFromParent(String displayName, CompilationInfo info, 
TreePath toRemove) {
+        public RemoveFromParent(String displayName, CompilationInfo info, 
TreePath toRemove, boolean safely) {
             super(info, toRemove);
             this.displayName = displayName;
+            this.safely = safely;
         }
 
         @Override
@@ -1629,6 +1647,24 @@ public class JavaFixUtilities {
             TreePath tp = ctx.getPath();
             
             doRemoveFromParent(wc, tp);
+            if (safely) {
+                Element el = wc.getTrees().getElement(tp);
+                if (el != null) {
+                    new TreePathScanner<Void, Void>() {
+                        @Override
+                        public Void scan(Tree tree, Void p) {
+                            if (tree != null && tree != tp.getLeaf()) {
+                                TreePath treePath = new 
TreePath(getCurrentPath(), tree);
+                                Element e = wc.getTrees().getElement(treePath);
+                                if (el == e) {
+                                    doRemoveFromParent(wc, treePath);
+                                }
+                            }
+                            return super.scan(tree, p);
+                        }
+                    }.scan(new TreePath(wc.getCompilationUnit()), null);
+                }
+            }
         }
         
         private void doRemoveFromParent(WorkingCopy wc, TreePath what) {
@@ -1768,12 +1804,123 @@ public class JavaFixUtilities {
                 case EXPRESSION_STATEMENT:
                     doRemoveFromParent(wc, what.getParentPath());
                     break;
+                case ASSIGNMENT:
+                    AssignmentTree assignmentTree = (AssignmentTree) 
parentLeaf;
+                    if (leaf == assignmentTree.getVariable()) {
+                        if 
(wc.getTreeUtilities().isExpressionStatement(assignmentTree.getExpression())) {
+                            wc.rewrite(parentLeaf, 
assignmentTree.getExpression());
+                        } else {
+                            doRemoveFromParent(wc, what.getParentPath());
+                        }
+                    } else {
+                        throw new UnsupportedOperationException();
+                    }
+                    break;
+                case AND_ASSIGNMENT:
+                case DIVIDE_ASSIGNMENT:
+                case LEFT_SHIFT_ASSIGNMENT:
+                case MINUS_ASSIGNMENT:
+                case MULTIPLY_ASSIGNMENT:
+                case OR_ASSIGNMENT:
+                case PLUS_ASSIGNMENT:
+                case REMAINDER_ASSIGNMENT:
+                case RIGHT_SHIFT_ASSIGNMENT:
+                case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+                case XOR_ASSIGNMENT:
+                    CompoundAssignmentTree compoundAssignmentTree = 
(CompoundAssignmentTree) parentLeaf;
+                    if (leaf == compoundAssignmentTree.getVariable()) {
+                        if 
(wc.getTreeUtilities().isExpressionStatement(compoundAssignmentTree.getExpression()))
 {
+                            wc.rewrite(parentLeaf, 
compoundAssignmentTree.getExpression());
+                        } else {
+                            doRemoveFromParent(wc, what.getParentPath());
+                        }
+                    } else {
+                        throw new UnsupportedOperationException();
+                    }
+                    break;
                 default:
                     wc.rewrite(what.getLeaf(), 
make.Block(Collections.<StatementTree>emptyList(), false));
                     break;
             }
         }
 
+        private static boolean canSafelyRemove(CompilationInfo info, TreePath 
tp) {
+            AtomicBoolean ret = new AtomicBoolean(true);
+            Element el = info.getTrees().getElement(tp);
+            if (el != null) {
+                new TreePathScanner<Void, Void>() {
+                    @Override
+                    public Void scan(Tree tree, Void p) {
+                        if (tree != null && tree != tp.getLeaf()) {
+                            TreePath treePath = new TreePath(getCurrentPath(), 
tree);
+                            Element e = info.getTrees().getElement(treePath);
+                            if (el == e) {
+                                Tree parentLeaf = 
treePath.getParentPath().getLeaf();
+                                switch (parentLeaf.getKind()) {
+                                    case ASSIGNMENT:
+                                        AssignmentTree assignmentTree = 
(AssignmentTree) parentLeaf;
+                                        if (tree == 
assignmentTree.getVariable()) {
+                                            if 
(!info.getTreeUtilities().isExpressionStatement(assignmentTree.getExpression()) 
&& canHaveSideEffects(assignmentTree.getExpression())) {
+                                                ret.set(false);
+                                            }
+                                        } else {
+                                            ret.set(false);
+                                        }
+                                        break;
+                                    case AND_ASSIGNMENT:
+                                    case DIVIDE_ASSIGNMENT:
+                                    case LEFT_SHIFT_ASSIGNMENT:
+                                    case MINUS_ASSIGNMENT:
+                                    case MULTIPLY_ASSIGNMENT:
+                                    case OR_ASSIGNMENT:
+                                    case PLUS_ASSIGNMENT:
+                                    case REMAINDER_ASSIGNMENT:
+                                    case RIGHT_SHIFT_ASSIGNMENT:
+                                    case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+                                    case XOR_ASSIGNMENT:
+                                        CompoundAssignmentTree 
compoundAssignmentTree = (CompoundAssignmentTree) parentLeaf;
+                                        if (tree == 
compoundAssignmentTree.getVariable()) {
+                                            if 
(!info.getTreeUtilities().isExpressionStatement(compoundAssignmentTree.getExpression())
 && canHaveSideEffects(compoundAssignmentTree.getExpression())) {
+                                                ret.set(false);
+                                            }
+                                        } else {
+                                            ret.set(false);
+                                        }
+                                        break;
+                                    default:
+                                        ret.set(false);
+                                }
+                            }
+                        }
+                        return super.scan(tree, p);
+                    }
+                }.scan(new TreePath(info.getCompilationUnit()), null);
+            }
+            return ret.get();
+        }
+        
+        private static boolean canHaveSideEffects(Tree tree) {
+            AtomicBoolean ret = new AtomicBoolean();
+            new TreeScanner<Void, Void>() {
+                @Override
+                public Void scan(Tree tree, Void p) {
+                    if (tree != null) {
+                        switch (tree.getKind()) {
+                            case METHOD_INVOCATION:
+                            case NEW_CLASS:
+                            case POSTFIX_DECREMENT:
+                            case POSTFIX_INCREMENT:
+                            case PREFIX_DECREMENT:
+                            case PREFIX_INCREMENT:
+                                ret.set(true);
+                                break;
+                        }
+                    }
+                    return super.scan(tree, p);
+                }
+            }.scan(tree, null);
+            return ret.get();
+        }
     }
 
     //TODO: from FileMovePlugin

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to