From a3cca5da7417946fa8690d4915d8976f8c0c16b9 Mon Sep 17 00:00:00 2001 From: Michael Bien Date: Thu, 27 Aug 2026 08:05:39 +0200 Subject: [PATCH] Update SuppressWarnings hint with full set of diag keys unfortunately there doesn't seem to be a good way to get the diagnostics keys from javac directly and map them to category keys. junit test will check for updates and suggest a code snippet on failure --- java/java.hints/nbproject/project.properties | 2 +- .../hints/errors/SuppressWarningsFixer.java | 197 ++++++++++++++---- .../errors/SuppressWarningsFixerTest.java | 83 +++++++- 3 files changed, 238 insertions(+), 44 deletions(-) diff --git a/java/java.hints/nbproject/project.properties b/java/java.hints/nbproject/project.properties index 1b95ef282f5b..576fd1b40d31 100644 --- a/java/java.hints/nbproject/project.properties +++ b/java/java.hints/nbproject/project.properties @@ -17,7 +17,7 @@ spec.version.base=1.117.0 -javac.release=17 +javac.release=21 nbroot=../.. jbrowse.external=${nbroot}/retouche diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixer.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixer.java index 8c78d2acf32b..87afc15d4361 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixer.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixer.java @@ -19,8 +19,6 @@ package org.netbeans.modules.java.hints.errors; import com.sun.source.util.TreePath; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,65 +29,180 @@ import org.netbeans.spi.editor.hints.Fix; import org.openide.util.NbBundle; +import static java.util.Map.entry; + /** - * + * SupressWarnings hint for standard javac warnings. + * * @author Jan Lahoda */ public class SuppressWarningsFixer implements ErrorRule { - /** Creates a new instance of SuppressWarningsFixer */ - public SuppressWarningsFixer() { - } - - private static final Map KEY2SUPRESS_KEY; + static final Map DIAG2LINT; static { - Map map = new HashMap(); - - String uncheckedKey = "unchecked"; - - map.put("compiler.warn.prob.found.req", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.cast.to.type", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.assign", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.assign.to.var", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.call.mbr.of.raw.type", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.meth.invocation.applied", uncheckedKey); // NOI18N - map.put("compiler.warn.unchecked.generic.array.creation", uncheckedKey); // NOI18N - - String fallThroughKey = "fallthrough"; // NOI18N - - map.put("compiler.warn.possible.fall-through.into.case", fallThroughKey); // NOI18N - - String deprecationKey = "deprecation"; // NOI18N - - map.put("compiler.warn.has.been.deprecated", deprecationKey); // NOI18N - - KEY2SUPRESS_KEY = Collections.unmodifiableMap(map); + + // checked and generated via unit test + + DIAG2LINT = Map.ofEntries( + entry("compiler.warn.auxiliary.class.accessed.from.outside.of.its.source.file", "auxiliaryclass"), // NOI18N + entry("compiler.warn.redundant.cast", "cast"), // NOI18N + entry("compiler.warn.annotation.method.not.found", "classfile"), // NOI18N + entry("compiler.warn.annotation.method.not.found.reason", "classfile"), // NOI18N + entry("compiler.warn.future.attr", "classfile"), // NOI18N + entry("compiler.warn.inconsistent.inner.classes", "classfile"), // NOI18N + entry("compiler.warn.runtime.invisible.parameter.annotations", "classfile"), // NOI18N + entry("compiler.warn.runtime.visible.invisible.param.annotations.mismatch", "classfile"), // NOI18N + entry("compiler.warn.unknown.enum.constant", "classfile"), // NOI18N + entry("compiler.warn.unknown.enum.constant.reason", "classfile"), // NOI18N + entry("compiler.warn.dangling.doc.comment", "dangling-doc-comments"), // NOI18N + entry("compiler.warn.missing.deprecated.annotation", "dep-ann"), // NOI18N + entry("compiler.warn.deprecated.annotation.has.no.effect", "deprecation"), // NOI18N + entry("compiler.warn.has.been.deprecated", "deprecation"), // NOI18N + entry("compiler.warn.has.been.deprecated.module", "deprecation"), // NOI18N + entry("compiler.warn.div.zero", "divzero"), // NOI18N + entry("compiler.warn.empty.if", "empty"), // NOI18N + entry("compiler.warn.leaks.not.accessible", "exports"), // NOI18N + entry("compiler.warn.leaks.not.accessible.not.required.transitive", "exports"), // NOI18N + entry("compiler.warn.leaks.not.accessible.unexported", "exports"), // NOI18N + entry("compiler.warn.leaks.not.accessible.unexported.qualified", "exports"), // NOI18N + entry("compiler.warn.possible.fall-through.into.case", "fallthrough"), // NOI18N + entry("compiler.warn.finally.cannot.complete", "finally"), // NOI18N + entry("compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", "identity"), // NOI18N + entry("compiler.warn.attempt.to.use.value.based.where.identity.expected", "identity"), // NOI18N + entry("compiler.warn.incubating.modules", "incubating"), // NOI18N + entry("compiler.warn.bit.shift.out.of.range", "lossy-conversions"), // NOI18N + entry("compiler.warn.possible.loss.of.precision", "lossy-conversions"), // NOI18N + entry("compiler.warn.missing-explicit-ctor", "missing-explicit-ctor"), // NOI18N + entry("compiler.warn.module.not.found", "module"), // NOI18N + entry("compiler.warn.poor.choice.for.module.name", "module"), // NOI18N + entry("compiler.warn.package.empty.or.not.found", "opens"), // NOI18N + entry("compiler.warn.addopens.ignored", "options"), // NOI18N + entry("compiler.warn.module.for.option.not.found", "options"), // NOI18N + entry("compiler.warn.option.obsolete.source", "options"), // NOI18N + entry("compiler.warn.option.obsolete.suppression", "options"), // NOI18N + entry("compiler.warn.option.obsolete.target", "options"), // NOI18N + entry("compiler.warn.source.no.bootclasspath", "options"), // NOI18N + entry("compiler.warn.source.no.system.modules.path", "options"), // NOI18N + entry("compiler.warn.output.file.clash", "output-file-clash"), // NOI18N + entry("compiler.warn.potentially.ambiguous.overload", "overloads"), // NOI18N + entry("compiler.warn.override.equals.but.not.hashcode", "overrides"), // NOI18N + entry("compiler.warn.override.varargs.extra", "overrides"), // NOI18N + entry("compiler.warn.override.varargs.missing", "overrides"), // NOI18N + entry("compiler.warn.dir.path.element.not.directory", "path"), // NOI18N + entry("compiler.warn.dir.path.element.not.found", "path"), // NOI18N + entry("compiler.warn.invalid.archive.file", "path"), // NOI18N + entry("compiler.warn.invalid.path", "path"), // NOI18N + entry("compiler.warn.locn.unknown.file.on.module.path", "path"), // NOI18N + entry("compiler.warn.outdir.is.in.exploded.module", "path"), // NOI18N + entry("compiler.warn.path.element.not.found", "path"), // NOI18N + entry("compiler.warn.unexpected.archive.file", "path"), // NOI18N + entry("compiler.warn.declared.using.preview", "preview"), // NOI18N + entry("compiler.warn.is.preview", "preview"), // NOI18N + entry("compiler.warn.is.preview.reflective", "preview"), // NOI18N + entry("compiler.warn.preview.feature.use", "preview"), // NOI18N + entry("compiler.warn.preview.feature.use.classfile", "preview"), // NOI18N + entry("compiler.warn.preview.feature.use.plural", "preview"), // NOI18N + entry("compiler.warn.proc.annotations.without.processors", "processing"), // NOI18N + entry("compiler.warn.proc.duplicate.option.name", "processing"), // NOI18N + entry("compiler.warn.proc.duplicate.supported.annotation", "processing"), // NOI18N + entry("compiler.warn.proc.file.reopening", "processing"), // NOI18N + entry("compiler.warn.proc.illegal.file.name", "processing"), // NOI18N + entry("compiler.warn.proc.malformed.supported.string", "processing"), // NOI18N + entry("compiler.warn.proc.redundant.types.with.wildcard", "processing"), // NOI18N + entry("compiler.warn.proc.suspicious.class.name", "processing"), // NOI18N + entry("compiler.warn.proc.type.already.exists", "processing"), // NOI18N + entry("compiler.warn.proc.type.recreate", "processing"), // NOI18N + entry("compiler.warn.raw.class.use", "rawtypes"), // NOI18N + entry("compiler.warn.has.been.deprecated.for.removal", "removal"), // NOI18N + entry("compiler.warn.has.been.deprecated.for.removal.module", "removal"), // NOI18N + entry("compiler.warn.requires.automatic", "requires-automatic"), // NOI18N + entry("compiler.warn.requires.transitive.automatic", "requires-transitive-automatic"), // NOI18N + entry("compiler.warn.restricted.method", "restricted"), // NOI18N + entry("compiler.warn.OSF.array.SPF", "serial"), // NOI18N + entry("compiler.warn.SPF.null.init", "serial"), // NOI18N + entry("compiler.warn.access.to.member.from.serializable.element", "serial"), // NOI18N + entry("compiler.warn.access.to.member.from.serializable.lambda", "serial"), // NOI18N + entry("compiler.warn.constant.SVUID", "serial"), // NOI18N + entry("compiler.warn.default.ineffective", "serial"), // NOI18N + entry("compiler.warn.externalizable.missing.public.no.arg.ctor", "serial"), // NOI18N + entry("compiler.warn.improper.SPF", "serial"), // NOI18N + entry("compiler.warn.improper.SVUID", "serial"), // NOI18N + entry("compiler.warn.ineffectual.extern.method.enum", "serial"), // NOI18N + entry("compiler.warn.ineffectual.externalizable.method.record", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.field.enum", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.field.externalizable", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.field.interface", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.field.record", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.method.enum", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.method.externalizable", "serial"), // NOI18N + entry("compiler.warn.ineffectual.serial.method.record", "serial"), // NOI18N + entry("compiler.warn.long.SVUID", "serial"), // NOI18N + entry("compiler.warn.missing.SVUID", "serial"), // NOI18N + entry("compiler.warn.non.private.method.weaker.access", "serial"), // NOI18N + entry("compiler.warn.non.serializable.instance.field", "serial"), // NOI18N + entry("compiler.warn.non.serializable.instance.field.array", "serial"), // NOI18N + entry("compiler.warn.serial.concrete.instance.method", "serial"), // NOI18N + entry("compiler.warn.serial.method.no.args", "serial"), // NOI18N + entry("compiler.warn.serial.method.not.private", "serial"), // NOI18N + entry("compiler.warn.serial.method.one.arg", "serial"), // NOI18N + entry("compiler.warn.serial.method.parameter.type", "serial"), // NOI18N + entry("compiler.warn.serial.method.static", "serial"), // NOI18N + entry("compiler.warn.serial.method.unexpected.exception", "serial"), // NOI18N + entry("compiler.warn.serial.method.unexpected.return.type", "serial"), // NOI18N + entry("compiler.warn.serializable.missing.access.no.arg.ctor", "serial"), // NOI18N + entry("compiler.warn.static.not.qualified.by.type", "static"), // NOI18N + entry("compiler.warn.static.not.qualified.by.type2", "static"), // NOI18N + entry("compiler.warn.strictfp", "strictfp"), // NOI18N + entry("compiler.warn.inconsistent.white.space.indentation", "text-blocks"), // NOI18N + entry("compiler.warn.trailing.white.space.will.be.removed", "text-blocks"), // NOI18N + entry("compiler.warn.possible.this.escape", "this-escape"), // NOI18N + entry("compiler.warn.possible.this.escape.location", "this-escape"), // NOI18N + entry("compiler.warn.try.explicit.close.call", "try"), // NOI18N + entry("compiler.warn.try.resource.can.throw.interrupted.exc", "try"), // NOI18N + entry("compiler.warn.try.resource.not.referenced", "try"), // NOI18N + entry("compiler.warn.try.resource.throws.interrupted.exc", "try"), // NOI18N + entry("compiler.warn.override.unchecked.ret", "unchecked"), // NOI18N + entry("compiler.warn.override.unchecked.thrown", "unchecked"), // NOI18N + entry("compiler.warn.prob.found.req", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.assign", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.assign.to.var", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.call.mbr.of.raw.type", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.cast.to.type", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.generic.array.creation", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.meth.invocation.applied", "unchecked"), // NOI18N + entry("compiler.warn.unchecked.varargs.non.reifiable.type", "unchecked"), // NOI18N + entry("compiler.warn.varargs.redundant.trustme.anno", "varargs"), // NOI18N + entry("compiler.warn.varargs.unsafe.use.varargs.param", "varargs") // NOI18N + ); } - + + public SuppressWarningsFixer() { + } + + @Override public Set getCodes() { - return KEY2SUPRESS_KEY.keySet(); + return DIAG2LINT.keySet(); } - public List run(CompilationInfo compilationInfo, String diagnosticKey, - int offset, TreePath treePath, - Data data) { - String suppressKey = KEY2SUPRESS_KEY.get(diagnosticKey); - - if (suppressKey != null) { - return FixFactory.createSuppressWarnings(compilationInfo, treePath, suppressKey); - } - - return Collections.emptyList(); + @Override + public List run(CompilationInfo compilationInfo, String diagnosticKey, int offset, TreePath treePath, Data data) { + String suppressKey = DIAG2LINT.get(diagnosticKey); + return suppressKey != null + ? FixFactory.createSuppressWarnings(compilationInfo, treePath, suppressKey) + : List.of(); } + @Override public void cancel() { } + @Override public String getId() { - return "SuppressWarningsFixer"; // NOI18N + return getClass().getSimpleName(); } + @Override public String getDisplayName() { return NbBundle.getMessage(SuppressWarningsFixer.class, "LBL_Suppress_Waning"); // NOI18N } diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixerTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixerTest.java index 920bfeecf769..0e0cb8324cf0 100644 --- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixerTest.java +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/SuppressWarningsFixerTest.java @@ -18,6 +18,16 @@ */ package org.netbeans.modules.java.hints.errors; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Collectors; import org.netbeans.modules.java.hints.infrastructure.HintsTestBase; import org.netbeans.modules.java.hints.spiimpl.TestCompilerSettings; @@ -92,7 +102,78 @@ public void testSuppressWarnings11() throws Exception { } public void testSuppressWarnings106794() throws Exception { - performTestDoNotPerform("Test3", 3, 10); + performTestDoNotPerform("Test3", 3, 10); + } + + public void testDiag2LintMap() throws Exception { + // We could get the suppression keys via Lint.LintCategory but not the diag keys. + // The diag keys are in a properties file and the suppression key in comments, so we have to get them the hacky way to create a mapping. + // https://github.com/openjdk/jdk/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(SuppressWarningsFixer.class.getResourceAsStream("/com/sun/tools/javac/resources/compiler.properties")))) { + + List lines = readAllLines(reader); + + Map> mapping = new TreeMap<>(); + // Gatherer with windowSliding(3) + for (int i = 0; i < lines.size(); i++) { +// # 0: symbol +// # lint: removal +// # flags: aggregate, mandatory, default-enabled +// compiler.warn.has.been.deprecated.for.removal.module=\ +// module {0} has been deprecated and marked for removal + String comment = lines.get(i); + if (comment.startsWith("# lint: ")) { + String lintCategory = comment.substring(8).strip(); + String propertyLine = lines.get(i + 1).startsWith("#") // skip line if still a comment + ? lines.get(i + 2) + : lines.get(i + 1); + String diagKey = propertyLine.substring(0, propertyLine.indexOf("=")).strip(); + mapping.computeIfAbsent(lintCategory, k -> new TreeSet<>()) + .add(diagKey); + } + } + + Set diagKeys = mapping.values().stream().flatMap(Set::stream).collect(Collectors.toSet()); + for (String key : diagKeys) { + if (!SuppressWarningsFixer.DIAG2LINT.containsKey(key)) { + dumpCodeSnippet(mapping); + fail(key + " not found"); + } + } + if (diagKeys.size() != SuppressWarningsFixer.DIAG2LINT.size()) { + dumpCodeSnippet(mapping); + fail("map size does not match"); + } + } + } + + private void dumpCodeSnippet(Map> diag2lint) { + StringBuilder snippet = new StringBuilder(); + snippet.append("Please update the keys in ").append(SuppressWarningsFixer.class.getName()).append("\n"); + snippet.append("- - -\n"); + for (Map.Entry> entry : diag2lint.entrySet()) { + for (String diag : entry.getValue()) { + snippet.append( + "entry(\"%s\", \"%s\"), // NOI18N\n" + .formatted(diag, entry.getKey()) + .indent(12) + ); + } + } + snippet.append("- - -\n"); + System.out.println(snippet); + } + + // TODO remove after JDK 25 + private static List readAllLines(BufferedReader reader) throws IOException { + List lines = new ArrayList<>(); + String line = reader.readLine(); + while (line != null) { + lines.add(line); + line = reader.readLine(); + } + return lines; } }