001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.Arrays; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailNode; 028import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 030 031/** 032 * <div> 033 * Checks that Javadoc comments avoid unnecessary {@code {@link}} and {@code {@linkplain}} tags 034 * for APIs that are considered well-known. Linking well-known APIs can make comments harder to 035 * read without adding much value for the reader. 036 * </div> 037 * 038 * <p> 039 * This check reports {@code {@link}} references to configured well-known APIs. 040 * Two properties are supported: 041 * {@code wellKnownQualifiedPackages} and {@code wellKnownSimpleNames}. 042 * </p> 043 * 044 * <p> 045 * Both properties are needed because Checkstyle does not resolve Javadoc link targets. 046 * For example, {@code java.lang.String} contains the package name, so it can be 047 * matched through {@code wellKnownQualifiedPackages}. However, {@code String} 048 * only contains the simple name {@code String}, so it needs to be matched through 049 * {@code wellKnownSimpleNames}. Resolution of imports is not a solution since 050 * {@code java.lang} is implicitly imported. 051 * </p> 052 * 053 * <p> 054 * For {@code wellKnownQualifiedPackages}, only references to classes that are 055 * direct members of a well-known package are reported. References to a member 056 * (for example, {@code String#length()}), a nested class (for example, 057 * {@code System.Logger}), a subpackage (for example, {@code java.lang.ref.WeakReference}), 058 * and a package itself (for example, {@code java.lang.ref}) are not reported. 059 * </p> 060 * 061 * @since 14.1.0 062 */ 063@StatelessCheck 064public class JavadocLinkWellKnownApiCheck extends AbstractJavadocCheck { 065 066 /** 067 * A key is pointing to the warning message text in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_WELL_KNOWN_API = "javadoc.wellKnownApi"; 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" 074 * file. 075 */ 076 public static final String MSG_WELL_KNOWN_PACKAGE = "javadoc.wellKnownPackage"; 077 078 /** 079 * Dot. 080 */ 081 private static final char DOT = '.'; 082 083 /** 084 * Package names whose fully qualified API references should not be linked. 085 */ 086 private Set<String> wellKnownQualifiedPackages = Set.of("java.lang"); 087 088 /** 089 * Simple API names that should not be linked. 090 */ 091 private Set<String> wellKnownSimpleNames = Set.of("String"); 092 093 /** 094 * Creates a new {@code JavadocLinkWellKnownApiCheck} instance. 095 */ 096 public JavadocLinkWellKnownApiCheck() { 097 // no code by default 098 } 099 100 @Override 101 public int[] getDefaultJavadocTokens() { 102 return getRequiredJavadocTokens(); 103 } 104 105 @Override 106 public int[] getRequiredJavadocTokens() { 107 return new int[] { 108 JavadocCommentsTokenTypes.LINK_INLINE_TAG, 109 JavadocCommentsTokenTypes.LINKPLAIN_INLINE_TAG, 110 }; 111 } 112 113 /** 114 * Setter to specify package names whose fully qualified API references should not be 115 * linked. 116 * 117 * @param values user's values. 118 * @since 14.1.0 119 */ 120 public final void setWellKnownQualifiedPackages(String... values) { 121 wellKnownQualifiedPackages = Arrays.stream(values).collect(Collectors.toUnmodifiableSet()); 122 } 123 124 /** 125 * Setter to specify simple API names that should not be linked. 126 * 127 * @param values user's values. 128 * @since 14.1.0 129 */ 130 public final void setWellKnownSimpleNames(String... values) { 131 wellKnownSimpleNames = Arrays.stream(values).collect(Collectors.toUnmodifiableSet()); 132 } 133 134 @Override 135 public void visitJavadocToken(DetailNode ast) { 136 final DetailNode referenceNode = JavadocUtil.findFirstToken(ast, 137 JavadocCommentsTokenTypes.REFERENCE); 138 if (JavadocUtil.findFirstToken(referenceNode, 139 JavadocCommentsTokenTypes.MEMBER_REFERENCE) == null) { 140 final String apiName = referenceNode.getFirstChild().getText(); 141 if (isWellKnownQualified(apiName)) { 142 log(ast, MSG_WELL_KNOWN_PACKAGE, apiName); 143 } 144 else if (wellKnownSimpleNames.contains(apiName)) { 145 log(ast, MSG_WELL_KNOWN_API, apiName); 146 } 147 } 148 } 149 150 /** 151 * Checks whether the given API name belongs to a well-known qualified package. 152 * 153 * @param apiName the API name to check 154 * @return true if the API name belongs to a well-known qualified package 155 */ 156 private boolean isWellKnownQualified(String apiName) { 157 boolean result = false; 158 for (String packageName : wellKnownQualifiedPackages) { 159 final String prefix = packageName + DOT; 160 final int prefixLength = prefix.length(); 161 if (apiName.startsWith(prefix) 162 && apiName.length() > prefixLength 163 && Character.isUpperCase(apiName.charAt(prefixLength)) 164 && apiName.indexOf(DOT, prefixLength) == -1) { 165 result = true; 166 break; 167 } 168 } 169 return result; 170 } 171 172}