diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S9360.json b/its/ruling/src/test/resources/commons-beanutils/java-S9360.json new file mode 100644 index 00000000000..02372bac91b --- /dev/null +++ b/its/ruling/src/test/resources/commons-beanutils/java-S9360.json @@ -0,0 +1,7 @@ +{ +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [ +251, +574, +935 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json new file mode 100644 index 00000000000..20dc4c8ec75 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json @@ -0,0 +1,23 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java": [ +61 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java": [ +54 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ +37, +48, +77 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java": [ +322 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java": [ +192 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ +759, +768 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json new file mode 100644 index 00000000000..40c3e4c3556 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json @@ -0,0 +1,33 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java": [ +61 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java": [ +54 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ +37, +48, +77 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java": [ +322 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java": [ +192 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ +759, +768 +], +"org.eclipse.jetty:jetty-project:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java": [ +71, +79, +157, +170, +183 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/compression/CompressionPool.java": [ +43 +] +} diff --git a/its/ruling/src/test/resources/guava/java-S9360.json b/its/ruling/src/test/resources/guava/java-S9360.json new file mode 100644 index 00000000000..80b6ef94754 --- /dev/null +++ b/its/ruling/src/test/resources/guava/java-S9360.json @@ -0,0 +1,663 @@ +{ +"com.google.guava:guava:src/com/google/common/annotations/GwtCompatible.java": [ +63 +], +"com.google.guava:guava:src/com/google/common/base/CharMatcher.java": [ +155, +165, +175, +185, +268, +274, +280, +286, +565, +605 +], +"com.google.guava:guava:src/com/google/common/base/Converter.java": [ +92, +135 +], +"com.google.guava:guava:src/com/google/common/base/Equivalence.java": [ +245, +325 +], +"com.google.guava:guava:src/com/google/common/base/FinalizableReferenceQueue.java": [ +190 +], +"com.google.guava:guava:src/com/google/common/base/FinalizableSoftReference.java": [ +29 +], +"com.google.guava:guava:src/com/google/common/base/FinalizableWeakReference.java": [ +29 +], +"com.google.guava:guava:src/com/google/common/base/Functions.java": [ +110, +258 +], +"com.google.guava:guava:src/com/google/common/base/Joiner.java": [ +331, +389, +401 +], +"com.google.guava:guava:src/com/google/common/base/MoreObjects.java": [ +112, +125 +], +"com.google.guava:guava:src/com/google/common/base/Objects.java": [ +389 +], +"com.google.guava:guava:src/com/google/common/base/Optional.java": [ +148, +184 +], +"com.google.guava:guava:src/com/google/common/base/Preconditions.java": [ +95 +], +"com.google.guava:guava:src/com/google/common/base/Splitter.java": [ +101, +371 +], +"com.google.guava:guava:src/com/google/common/base/Stopwatch.java": [ +75 +], +"com.google.guava:guava:src/com/google/common/base/Strings.java": [ +76 +], +"com.google.guava:guava:src/com/google/common/base/Throwables.java": [ +87, +109, +126, +157 +], +"com.google.guava:guava:src/com/google/common/cache/AbstractCache.java": [ +142, +189 +], +"com.google.guava:guava:src/com/google/common/cache/CacheBuilder.java": [ +150, +513, +568, +730 +], +"com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [ +2807 +], +"com.google.guava:guava:src/com/google/common/cache/LongAdder.java": [ +48, +138 +], +"com.google.guava:guava:src/com/google/common/collect/AbstractIterator.java": [ +60, +113 +], +"com.google.guava:guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ +90, +353 +], +"com.google.guava:guava:src/com/google/common/collect/AbstractMultiset.java": [ +227 +], +"com.google.guava:guava:src/com/google/common/collect/ArrayListMultimap.java": [ +65 +], +"com.google.guava:guava:src/com/google/common/collect/ArrayTable.java": [ +85, +448 +], +"com.google.guava:guava:src/com/google/common/collect/ClassToInstanceMap.java": [ +47 +], +"com.google.guava:guava:src/com/google/common/collect/ComparisonChain.java": [ +63, +193, +221, +228 +], +"com.google.guava:guava:src/com/google/common/collect/DiscreteDomain.java": [ +214, +225, +242 +], +"com.google.guava:guava:src/com/google/common/collect/EnumMultiset.java": [ +53 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingCollection.java": [ +46, +128, +139, +162, +181, +193, +205, +228, +239, +251 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingDeque.java": [ +36 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingList.java": [ +53, +144, +155, +203, +239 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingMap.java": [ +57, +145, +161, +197, +213, +228, +258 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingMapEntry.java": [ +53, +96, +126 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingMultiset.java": [ +152, +165, +179, +192, +205, +219, +248, +300, +311 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingObject.java": [ +51, +64 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingQueue.java": [ +43 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingSet.java": [ +47 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingSortedMap.java": [ +54 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingSortedMultiset.java": [ +67 +], +"com.google.guava:guava:src/com/google/common/collect/ForwardingSortedSet.java": [ +55 +], +"com.google.guava:guava:src/com/google/common/collect/GenericMapMaker.java": [ +46, +106 +], +"com.google.guava:guava:src/com/google/common/collect/GwtTransient.java": [ +32 +], +"com.google.guava:guava:src/com/google/common/collect/HashBasedTable.java": [ +54 +], +"com.google.guava:guava:src/com/google/common/collect/HashMultimap.java": [ +48 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableCollection.java": [ +150 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableEnumMap.java": [ +33 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableEnumSet.java": [ +30 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableMultimap.java": [ +67 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSet.java": [ +58, +231, +261 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedMap.java": [ +55, +363, +375 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java": [ +49, +63, +78, +93, +108 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedMultiset.java": [ +47 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java": [ +98, +113, +128 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedSet.java": [ +55, +199, +347, +416 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java": [ +58, +72, +86, +101, +116, +131, +145 +], +"com.google.guava:guava:src/com/google/common/collect/ImmutableTable.java": [ +149, +191 +], +"com.google.guava:guava:src/com/google/common/collect/Interners.java": [ +61 +], +"com.google.guava:guava:src/com/google/common/collect/Iterables.java": [ +536, +564, +699 +], +"com.google.guava:guava:src/com/google/common/collect/Iterators.java": [ +115, +126, +730, +1043, +1085, +1113, +1135 +], +"com.google.guava:guava:src/com/google/common/collect/LinkedHashMultimap.java": [ +81 +], +"com.google.guava:guava:src/com/google/common/collect/LinkedListMultimap.java": [ +101, +269, +712, +761 +], +"com.google.guava:guava:src/com/google/common/collect/Lists.java": [ +86, +108, +140, +182, +230, +255, +311, +445, +507 +], +"com.google.guava:guava:src/com/google/common/collect/MapConstraint.java": [ +52 +], +"com.google.guava:guava:src/com/google/common/collect/MapConstraints.java": [ +49, +189, +215, +249, +267, +290 +], +"com.google.guava:guava:src/com/google/common/collect/MapDifference.java": [ +69 +], +"com.google.guava:guava:src/com/google/common/collect/MapMaker.java": [ +103, +143, +260, +328, +721, +732 +], +"com.google.guava:guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +2473 +], +"com.google.guava:guava:src/com/google/common/collect/Maps.java": [ +163, +218, +232, +266, +299, +315, +1167, +1201, +1252, +1266, +1281, +1692, +1753, +1812, +2284, +2320, +2362, +2399, +2434, +2469, +2514, +2561, +2599 +], +"com.google.guava:guava:src/com/google/common/collect/MinMaxPriorityQueue.java": [ +102, +167, +902 +], +"com.google.guava:guava:src/com/google/common/collect/Multimap.java": [ +162, +282, +351 +], +"com.google.guava:guava:src/com/google/common/collect/Multimaps.java": [ +113, +846, +1171, +1379 +], +"com.google.guava:guava:src/com/google/common/collect/Multiset.java": [ +87, +128, +233 +], +"com.google.guava:guava:src/com/google/common/collect/Multisets.java": [ +55, +196 +], +"com.google.guava:guava:src/com/google/common/collect/MutableClassToInstanceMap.java": [ +43 +], +"com.google.guava:guava:src/com/google/common/collect/Ordering.java": [ +262, +854, +950 +], +"com.google.guava:guava:src/com/google/common/collect/PeekingIterator.java": [ +53, +66 +], +"com.google.guava:guava:src/com/google/common/collect/Range.java": [ +112, +373, +384, +401, +412, +529 +], +"com.google.guava:guava:src/com/google/common/collect/RangeMap.java": [ +34 +], +"com.google.guava:guava:src/com/google/common/collect/RowSortedTable.java": [ +36 +], +"com.google.guava:guava:src/com/google/common/collect/Serialization.java": [ +45 +], +"com.google.guava:guava:src/com/google/common/collect/SetMultimap.java": [ +53 +], +"com.google.guava:guava:src/com/google/common/collect/Sets.java": [ +220, +238, +290, +345, +364, +543, +1089, +1149 +], +"com.google.guava:guava:src/com/google/common/collect/StandardRowSortedTable.java": [ +48 +], +"com.google.guava:guava:src/com/google/common/collect/StandardTable.java": [ +67 +], +"com.google.guava:guava:src/com/google/common/collect/Table.java": [ +56, +292 +], +"com.google.guava:guava:src/com/google/common/collect/Tables.java": [ +138 +], +"com.google.guava:guava:src/com/google/common/collect/TransformedIterator.java": [ +30 +], +"com.google.guava:guava:src/com/google/common/collect/TreeBasedTable.java": [ +76 +], +"com.google.guava:guava:src/com/google/common/collect/TreeMultimap.java": [ +77 +], +"com.google.guava:guava:src/com/google/common/collect/package-info.java": [ +224 +], +"com.google.guava:guava:src/com/google/common/escape/CharEscaper.java": [ +44 +], +"com.google.guava:guava:src/com/google/common/escape/Escaper.java": [ +58 +], +"com.google.guava:guava:src/com/google/common/escape/UnicodeEscaper.java": [ +56, +149 +], +"com.google.guava:guava:src/com/google/common/escape/package-info.java": [ +28 +], +"com.google.guava:guava:src/com/google/common/eventbus/package-info.java": [ +253 +], +"com.google.guava:guava:src/com/google/common/hash/AbstractNonStreamingHashFunction.java": [ +31 +], +"com.google.guava:guava:src/com/google/common/io/CharStreams.java": [ +250 +], +"com.google.guava:guava:src/com/google/common/io/FileBackedOutputStream.java": [ +89 +], +"com.google.guava:guava:src/com/google/common/io/Files.java": [ +81, +98, +272, +327, +342, +358, +373, +520, +540, +571, +645, +676 +], +"com.google.guava:guava:src/com/google/common/io/LineBuffer.java": [ +32 +], +"com.google.guava:guava:src/com/google/common/io/Resources.java": [ +111, +126, +146 +], +"com.google.guava:guava:src/com/google/common/net/InternetDomainName.java": [ +74 +], +"com.google.guava:guava:src/com/google/common/net/PercentEscaper.java": [ +56 +], +"com.google.guava:guava:src/com/google/common/primitives/Booleans.java": [ +96, +119, +280, +317, +346 +], +"com.google.guava:guava:src/com/google/common/primitives/Bytes.java": [ +50, +260 +], +"com.google.guava:guava:src/com/google/common/primitives/Chars.java": [ +82, +397, +460 +], +"com.google.guava:guava:src/com/google/common/primitives/Doubles.java": [ +97, +384, +451 +], +"com.google.guava:guava:src/com/google/common/primitives/Floats.java": [ +93, +380, +447 +], +"com.google.guava:guava:src/com/google/common/primitives/Ints.java": [ +89, +445, +509 +], +"com.google.guava:guava:src/com/google/common/primitives/Longs.java": [ +81, +481, +545 +], +"com.google.guava:guava:src/com/google/common/primitives/Shorts.java": [ +88, +444, +508 +], +"com.google.guava:guava:src/com/google/common/primitives/SignedBytes.java": [ +62, +101, +185 +], +"com.google.guava:guava:src/com/google/common/primitives/UnsignedBytes.java": [ +284 +], +"com.google.guava:guava:src/com/google/common/reflect/AbstractInvocationHandler.java": [ +62 +], +"com.google.guava:guava:src/com/google/common/reflect/ImmutableTypeToInstanceMap.java": [ +31 +], +"com.google.guava:guava:src/com/google/common/reflect/Invokable.java": [ +58 +], +"com.google.guava:guava:src/com/google/common/reflect/TypeResolver.java": [ +89 +], +"com.google.guava:guava:src/com/google/common/reflect/TypeToken.java": [ +273 +], +"com.google.guava:guava:src/com/google/common/reflect/Types.java": [ +353 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractExecutionThreadService.java": [ +115 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ +62, +501, +775 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractScheduledService.java": [ +95, +567 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractService.java": [ +295, +329, +354 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AtomicDouble.java": [ +54 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AtomicDoubleArray.java": [ +41 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ExecutionError.java": [ +34 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ExecutionList.java": [ +45, +53, +140 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/FutureCallback.java": [ +49 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Futures.java": [ +495, +871, +1055, +1269, +1482, +1745, +1821, +1879, +1934, +1988 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/JdkFutureAdapters.java": [ +38, +57, +89 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ListenableFuture.java": [ +88, +138 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Monitor.java": [ +200 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/MoreExecutors.java": [ +65, +279, +426, +447, +947 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/RateLimiter.java": [ +92 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ServiceManager.java": [ +144, +154, +160, +167, +188, +268, +296, +311, +333, +356, +487 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/SimpleTimeLimiter.java": [ +65, +78 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/Striped.java": [ +86 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/ThreadFactoryBuilder.java": [ +45, +114 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java": [ +32 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/UncheckedExecutionException.java": [ +39 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/package-info.java": [ +31 +], +"com.google.guava:guava:src/com/google/thirdparty/publicsuffix/PublicSuffixPatterns.java": [ +37 +] +} diff --git a/its/ruling/src/test/resources/jboss-ejb3-tutorial/java-S9360.json b/its/ruling/src/test/resources/jboss-ejb3-tutorial/java-S9360.json new file mode 100644 index 00000000000..4eefb0764d5 --- /dev/null +++ b/its/ruling/src/test/resources/jboss-ejb3-tutorial/java-S9360.json @@ -0,0 +1,9 @@ +{ +"jboss-ejb3-tutorial:partial_deployment_descriptor/src/org/jboss/tutorial/partial_deployment_descriptor/bean/CompleteXMLDDBean.java": [ +53, +64, +74, +84, +97 +] +} diff --git a/its/ruling/src/test/resources/sonar-server/java-S9360.json b/its/ruling/src/test/resources/sonar-server/java-S9360.json new file mode 100644 index 00000000000..305898623dd --- /dev/null +++ b/its/ruling/src/test/resources/sonar-server/java-S9360.json @@ -0,0 +1,32 @@ +{ +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/ce/queue/CeTaskResult.java": [ +29 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/event/EventRepository.java": [ +30 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/MutableMovedFilesRepository.java": [ +32 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/formula/Counter.java": [ +38 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java": [ +40 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/source/DuplicationLineReader.java": [ +82 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/source/SourceLinesRepository.java": [ +39 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/IndexingResult.java": [ +67 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/setting/ThreadLocalSettings.java": [ +51 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistScannerContextStepTest.java": [ +85 +] +} diff --git a/java-checks-test-sources/default/src/main/java/checks/JavadocReferencesExistingSymbolsCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/JavadocReferencesExistingSymbolsCheckSample.java new file mode 100644 index 00000000000..e5fcb6510b5 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/JavadocReferencesExistingSymbolsCheckSample.java @@ -0,0 +1,125 @@ +package checks; + +import java.util.List; +import java.util.Map; + +/** @see java.util.List + */ // Compliant - existing type +class JavadocReferencesExistingSymbolsCheckSample { + + /** @see java.util.NonExistentClass + */ // Noncompliant {{Make sure this reference is valid.}} + void nonExistentClass() { + } + + /** @see java.util.NonExistentClass + */ // Noncompliant {{Make sure this reference is valid.}} + void nonExistentClass2() { + } + + /** @see http://example.com + */ + void externalUrl() { + } + + /** @see https://example.com + */ + void externalUrlHttps() { + } + + /** @see #existingMethod + */ + void methodReference() { + } + + /** @see java.util.List#size() + */ + void validMethodReference() { + } + + /** {@link java.util.List} + */ + void validLinkTag() { + } + + /** {@link java.util.NonExistentClass} + */ // Noncompliant {{Make sure this reference is valid.}} + void invalidLinkTag() { + } + + /** {@linkplain java.util.NonExistentClass} + */ // Noncompliant {{Make sure this reference is valid.}} + void invalidLinkplainTag() { + } + + /** {@link java.util.Map#get(Object)} + */ + void validLinkWithMethod() { + } + + /** @see NonExistentSimpleClass + */ // Noncompliant {{Make sure this reference is valid.}} + void simpleNameNonExistent() { + } + + /** @see java.util.List#size() + * {@link java.util.NonExistentClass} + */ // Noncompliant {{Make sure this reference is valid.}} + void mixedValidSeeInvalidLink() { + } + + /** @see String + */ // Compliant - java.lang.String is implicitly available + void simpleNameJavaLang() { + } + + /** @see List + */ // Compliant - List is imported + void simpleNameImported() { + } + + /** {@link Map} + */ // Compliant - Map is imported + void simpleNameImportedLink() { + } + + /** @see JavadocReferencesExistingSymbolsCheckSample + */ // Compliant - same-package class + void simpleNameSamePackage() { + } + + /** @see Example + */ // Compliant - HTML anchor @see tag + void htmlAnchorSeeTag() { + } + + /** @see "The Java Programming Language" + */ // Compliant - quoted string @see tag + void quotedStringSeeTag() { + } + + /** @see InnerClass + */ // Compliant - inner type of the current class + void innerTypeReference() { + } + + /** {@link InnerEnum} + */ // Compliant - inner enum of the current class + void innerEnumReference() { + } + + /** @see Map.Entry + */ // Compliant - inner type via imported outer class + void innerTypeViaImport() { + } + + void existingMethod() { + } + + static class InnerClass { + } + + enum InnerEnum { + VALUE + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheck.java new file mode 100644 index 00000000000..293c4e57d7b --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheck.java @@ -0,0 +1,258 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.sonar.check.Rule; +import org.sonar.java.checks.helpers.ExpressionsHelper; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.semantic.Sema; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.CompilationUnitTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.ImportTree; +import org.sonar.plugins.java.api.tree.PackageDeclarationTree; +import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S9360") +public class JavadocReferencesExistingSymbolsCheck extends IssuableSubscriptionVisitor { + + private static final String MESSAGE = "Make sure this reference is valid."; + private static final Pattern SEE_TAG_PATTERN = Pattern.compile("@see\\s++(\\S++)"); + private static final Pattern LINK_TAG_PATTERN = Pattern.compile("\\{@link(?:plain)?\\s++([^\\s}]+)"); + private static final String JAVA_LANG_PREFIX = "java.lang."; + private static final Tree.Kind[] CLASS_KINDS = Tree.Kind.CLASS_KINDS.toArray(new Tree.Kind[0]); + + private String currentPackage = ""; + private final Map importedSimpleNames = new HashMap<>(); + private final Set declaredTypeSimpleNames = new HashSet<>(); + + @Override + public void setContext(JavaFileScannerContext context) { + super.setContext(context); + currentPackage = ""; + importedSimpleNames.clear(); + declaredTypeSimpleNames.clear(); + CompilationUnitTree cut = context.getTree(); + PackageDeclarationTree pkg = cut.packageDeclaration(); + if (pkg != null) { + currentPackage = ExpressionsHelper.concatenate(pkg.packageName()); + } + collectImports(cut); + collectDeclaredTypes(cut); + } + + private void collectImports(CompilationUnitTree cut) { + cut.imports().stream() + .filter(importClause -> importClause.is(Tree.Kind.IMPORT)) + .map(ImportTree.class::cast) + .filter(importTree -> !importTree.isStatic()) + .forEach(importTree -> { + String fqn = ExpressionsHelper.concatenate((ExpressionTree) importTree.qualifiedIdentifier()); + if (!fqn.endsWith(".*")) { + int lastDot = fqn.lastIndexOf('.'); + String simpleName = lastDot >= 0 ? fqn.substring(lastDot + 1) : fqn; + importedSimpleNames.put(simpleName, fqn); + } + }); + } + + private void collectDeclaredTypes(CompilationUnitTree cut) { + for (Tree typeTree : cut.types()) { + collectDeclaredTypesFromTree(typeTree); + } + } + + private void collectDeclaredTypesFromTree(Tree tree) { + if (!tree.is(CLASS_KINDS)) { + return; + } + ClassTree classTree = (ClassTree) tree; + if (classTree.simpleName() != null) { + declaredTypeSimpleNames.add(classTree.simpleName().name()); + } + for (Tree member : classTree.members()) { + collectDeclaredTypesFromTree(member); + } + } + + @Override + public List nodesToVisit() { + return Collections.singletonList(Tree.Kind.TRIVIA); + } + + @Override + public void visitNode(Tree tree) { + // intentionally empty - we only care about trivia + } + + @Override + public void visitTrivia(SyntaxTrivia syntaxTrivia) { + if (!syntaxTrivia.isComment(SyntaxTrivia.CommentKind.JAVADOC)) { + return; + } + + Object semanticModel = context.getSemanticModel(); + if (semanticModel == null) { + return; + } + + Sema sema = (Sema) semanticModel; + + if (hasInvalidReference(sema, syntaxTrivia.comment())) { + addIssue(syntaxTrivia.range().end().line(), MESSAGE); + } + } + + private boolean hasInvalidReference(Sema sema, String javadocText) { + for (String reference : extractSeeReferences(javadocText)) { + String typeName = stripMemberReference(reference); + if (typeName != null && isUnresolvableReference(sema, typeName)) { + return true; + } + } + return false; + } + + /** + * Strips method/member references from a Javadoc reference and returns the type name part, + * or null if the reference is a method-only reference (e.g., "#myMethod"). + */ + static String stripMemberReference(String reference) { + // Skip method references (starting with #) + if (reference.startsWith("#")) { + return null; + } + + String result = reference; + + // Remove method signature if present (e.g., "MyClass#myMethod()") + int parenIdx = result.indexOf('('); + if (parenIdx >= 0) { + result = result.substring(0, parenIdx); + } + + // Remove member reference if present (e.g., "MyClass#myField") + int hashIdx = result.indexOf('#'); + if (hashIdx > 0) { + result = result.substring(0, hashIdx); + } + + return result; + } + + private boolean isUnresolvableReference(Sema sema, String typeName) { + // If already fully qualified (contains dots), check directly and with inner-class $ notation + if (typeName.contains(".")) { + if (!isUnknownType(sema, typeName)) { + return false; + } + // Try interpreting dots as inner class separators (e.g., Outer.Inner -> pkg.Outer$Inner) + return isUnresolvableInnerClassReference(sema, typeName); + } + + // Simple name: try imports, then java.lang, then current package, then locally declared types + String importedFqn = importedSimpleNames.get(typeName); + if (importedFqn != null) { + return isUnknownType(sema, importedFqn); + } + + if (!isUnknownType(sema, JAVA_LANG_PREFIX + typeName)) { + return false; + } + + if (!currentPackage.isEmpty() && !isUnknownType(sema, currentPackage + "." + typeName)) { + return false; + } + + // Check if it matches a type declared in the current file (handles inner types) + return !declaredTypeSimpleNames.contains(typeName); + } + + private boolean isUnresolvableInnerClassReference(Sema sema, String typeName) { + // For dotted references like "Outer.Inner" or "pkg.Outer.Inner.Deep", + // try interpreting trailing dots as inner class separators (replacing with $). + // We split on dots and try each possible split point between package/outer and inner classes. + int firstDot = typeName.indexOf('.'); + while (firstDot > 0 && firstDot < typeName.length() - 1) { + String prefix = typeName.substring(0, firstDot); + String suffix = typeName.substring(firstDot + 1).replace('.', '$'); + String withDollar = prefix + "$" + suffix; + if (!isUnknownType(sema, withDollar)) { + return false; + } + if (!currentPackage.isEmpty() && !isUnknownType(sema, currentPackage + "." + withDollar)) { + return false; + } + // Try imported prefix + int prefixLastDot = prefix.lastIndexOf('.'); + String outerSimple = prefixLastDot >= 0 ? prefix.substring(prefixLastDot + 1) : prefix; + String importedOuter = importedSimpleNames.get(outerSimple); + if (importedOuter != null) { + String resolvedInner = importedOuter + "$" + suffix; + if (!isUnknownType(sema, resolvedInner)) { + return false; + } + } + firstDot = typeName.indexOf('.', firstDot + 1); + } + return true; + } + + private static boolean isUnknownType(Sema sema, String fullyQualifiedName) { + Type type = sema.getClassType(fullyQualifiedName); + return type == null || type.isUnknown(); + } + + @Override + public void leaveNode(Tree tree) { + // intentionally empty - no cleanup needed + } + + static List extractSeeReferences(String javadocText) { + List references = new ArrayList<>(); + + Matcher matcher = SEE_TAG_PATTERN.matcher(javadocText); + while (matcher.find()) { + String ref = matcher.group(1); + if (!ref.startsWith("http://") && !ref.startsWith("https://") && !ref.startsWith("<") && !ref.startsWith("\"")) { + references.add(ref); + } + } + + Matcher anchorMatcher = LINK_TAG_PATTERN.matcher(javadocText); + while (anchorMatcher.find()) { + String ref = anchorMatcher.group(1); + if (!ref.startsWith("http://") && !ref.startsWith("https://")) { + references.add(ref); + } + } + + return references; + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheckTest.java new file mode 100644 index 00000000000..860718eec5a --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/JavadocReferencesExistingSymbolsCheckTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class JavadocReferencesExistingSymbolsCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/JavadocReferencesExistingSymbolsCheckSample.java")) + .withCheck(new JavadocReferencesExistingSymbolsCheck()) + .verifyIssues(); + } + + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/JavadocReferencesExistingSymbolsCheckSample.java")) + .withCheck(new JavadocReferencesExistingSymbolsCheck()) + .withoutSemantic() + .verifyNoIssues(); + } + + @Test + void extractSeeReferences_extracts_see_tags() { + List refs = JavadocReferencesExistingSymbolsCheck.extractSeeReferences( + "/** @see java.util.List */"); + assertThat(refs).containsExactly("java.util.List"); + } + + static Stream nonReferenceInputs() { + return Stream.of( + Arguments.of("URLs", "/** @see http://example.com @see https://example.com */"), + Arguments.of("HTML anchors", "/** @see Example */"), + Arguments.of("quoted strings", "/** @see \"The Java Programming Language\" */") + ); + } + + @ParameterizedTest(name = "extractSeeReferences skips {0}") + @MethodSource("nonReferenceInputs") + void extractSeeReferences_skips_non_references(String description, String javadoc) { + List refs = JavadocReferencesExistingSymbolsCheck.extractSeeReferences(javadoc); + assertThat(refs).isEmpty(); + } + + @Test + void extractSeeReferences_extracts_link_tags() { + List refs = JavadocReferencesExistingSymbolsCheck.extractSeeReferences( + "/** {@link java.util.List} */"); + assertThat(refs).containsExactly("java.util.List"); + } + + @Test + void extractSeeReferences_extracts_linkplain_tags() { + List refs = JavadocReferencesExistingSymbolsCheck.extractSeeReferences( + "/** {@linkplain java.util.Map} */"); + assertThat(refs).containsExactly("java.util.Map"); + } + + @Test + void extractSeeReferences_handles_mixed_tags() { + List refs = JavadocReferencesExistingSymbolsCheck.extractSeeReferences( + "/** @see java.util.List {@link java.util.Map} */"); + assertThat(refs).containsExactly("java.util.List", "java.util.Map"); + } + + @Test + void stripMemberReference_returns_null_for_method_references() { + assertThat(JavadocReferencesExistingSymbolsCheck.stripMemberReference("#myMethod")).isNull(); + } + + @Test + void stripMemberReference_returns_fully_qualified_name_as_is() { + assertThat(JavadocReferencesExistingSymbolsCheck.stripMemberReference("java.util.List")).isEqualTo("java.util.List"); + } + + @Test + void stripMemberReference_strips_method_signature() { + assertThat(JavadocReferencesExistingSymbolsCheck.stripMemberReference("java.util.List#size()")).isEqualTo("java.util.List"); + } + + @Test + void stripMemberReference_strips_member_reference() { + assertThat(JavadocReferencesExistingSymbolsCheck.stripMemberReference("java.util.List#EMPTY_LIST")).isEqualTo("java.util.List"); + } + + @Test + void stripMemberReference_returns_simple_name() { + assertThat(JavadocReferencesExistingSymbolsCheck.stripMemberReference("MyClass")).isEqualTo("MyClass"); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html new file mode 100644 index 00000000000..049d754191b --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html @@ -0,0 +1,50 @@ +

This rule detects @see references in Javadoc comments that point to non-existent types or members.

+

Why is this an issue?

+

Javadoc @see tags are used to create cross-references between documentation. When these references point to non-existent types or members, they result in broken links in the generated documentation, reducing its usefulness.

+

The following patterns are detected:

+
    +
  • References to classes that cannot be resolved (e.g., @see com.example.NonExistentClass)
  • +
  • References to methods or fields that don't exist in the referenced class
  • +
  • Relative references to classes that don't exist in the current package
  • +
+

This rule does not flag references to external URLs (starting with http:// or https://), as these are handled separately.

+

How to fix it

+

Verify that each @see reference points to an existing type or member. If the reference is intentional (e.g., documenting a planned API), consider removing it or adding a note explaining why it's referenced.

+

Code examples

+

Noncompliant code example

+
+import java.util.List;
+
+/**
+ * @see java.util.NonExistentClass
+ */
+public class MyClass { }
+
+

Compliant solution

+
+import java.util.List;
+
+/**
+ * @see java.util.List
+ */
+public class MyClass { }
+
+

Noncompliant code example

+
+/**
+ * @see com.example.NonExistentClass
+ */
+class MyClass { }
+
+

Compliant solution

+
+/**
+ * @see com.example.ExistentClass
+ */
+class MyClass { }
+
+

Resources

+

Documentation

+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json new file mode 100644 index 00000000000..8c789ca15e6 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json @@ -0,0 +1,23 @@ +{ + "title": "Javadoc references should point to existing types and members", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "5min" + }, + "tags": [ + "documentation" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-9360", + "sqKey": "S9360", + "scope": "All", + "quickfix": "covered", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CLEAR" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9360 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9360 new file mode 100644 index 00000000000..e69de29bb2d