001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.HashSet; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import com.google.common.base.CharMatcher; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 032 033/** 034 * <p> 035 * Checks that <a href= 036 * "http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#firstsentence"> 037 * Javadoc summary sentence</a> does not contain phrases that are not recommended to use. 038 * By default Check validate that first sentence is not empty:</p><br> 039 * <pre> 040 * <module name="SummaryJavadocCheck"/> 041 * </pre> 042 * 043 * <p>To ensure that summary do not contain phrase like "This method returns", 044 * use following config: 045 * 046 * <pre> 047 * <module name="SummaryJavadocCheck"> 048 * <property name="forbiddenSummaryFragments" 049 * value="^This method returns.*"/> 050 * </module> 051 * </pre> 052 * <p> 053 * To specify period symbol at the end of first javadoc sentence - use following config: 054 * </p> 055 * <pre> 056 * <module name="SummaryJavadocCheck"> 057 * <property name="period" 058 * value="period"/> 059 * </module> 060 * </pre> 061 * 062 * 063 * @author max 064 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 065 */ 066public class SummaryJavadocCheck extends AbstractJavadocCheck { 067 068 /** 069 * A key is pointing to the warning message text in "messages.properties" 070 * file. 071 */ 072 public static final String MSG_SUMMARY_FIRST_SENTENCE = "summary.first.sentence"; 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_SUMMARY_JAVADOC = "summary.javaDoc"; 079 /** 080 * This regexp is used to convert multiline javadoc to single line without stars. 081 */ 082 private static final Pattern JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN = 083 Pattern.compile("\n[ ]+(\\*)|^[ ]+(\\*)"); 084 085 /** Period literal. */ 086 private static final String PERIOD = "."; 087 088 /** 089 * Stores allowed values in document for inherit doc literal. 090 */ 091 private static final Set<Integer> SKIP_TOKENS = new HashSet<>( 092 Arrays.asList(JavadocTokenTypes.NEWLINE, 093 JavadocTokenTypes.LEADING_ASTERISK, 094 JavadocTokenTypes.EOF) 095 ); 096 /** 097 * Regular expression for forbidden summary fragments. 098 */ 099 private Pattern forbiddenSummaryFragments = CommonUtils.createPattern("^$"); 100 101 /** 102 * Period symbol at the end of first javadoc sentence. 103 */ 104 private String period = PERIOD; 105 106 /** 107 * Sets custom value of regular expression for forbidden summary fragments. 108 * @param pattern a pattern. 109 */ 110 public void setForbiddenSummaryFragments(Pattern pattern) { 111 forbiddenSummaryFragments = pattern; 112 } 113 114 /** 115 * Sets value of period symbol at the end of first javadoc sentence. 116 * @param period period's value. 117 */ 118 public void setPeriod(String period) { 119 this.period = period; 120 } 121 122 @Override 123 public int[] getDefaultJavadocTokens() { 124 return new int[] { 125 JavadocTokenTypes.JAVADOC, 126 }; 127 } 128 129 @Override 130 public int[] getRequiredJavadocTokens() { 131 return getAcceptableJavadocTokens(); 132 } 133 134 @Override 135 public int[] getAcceptableTokens() { 136 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN }; 137 } 138 139 @Override 140 public int[] getRequiredTokens() { 141 return getAcceptableTokens(); 142 } 143 144 @Override 145 public void visitJavadocToken(DetailNode ast) { 146 String firstSentence = getFirstSentence(ast); 147 final int endOfSentence = firstSentence.lastIndexOf(period); 148 if (endOfSentence == -1) { 149 if (!isOnlyInheritDoc(ast)) { 150 log(ast.getLineNumber(), MSG_SUMMARY_FIRST_SENTENCE); 151 } 152 } 153 else { 154 firstSentence = firstSentence.substring(0, endOfSentence); 155 if (containsForbiddenFragment(firstSentence)) { 156 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC); 157 } 158 } 159 } 160 161 /** 162 * Finds if inheritDoc is placed properly in java doc. 163 * @param ast Javadoc root node. 164 * @return true if inheritDoc is valid or false. 165 */ 166 private static boolean isOnlyInheritDoc(DetailNode ast) { 167 boolean extraTextFound = false; 168 boolean containsInheritDoc = false; 169 for (DetailNode child : ast.getChildren()) { 170 if (child.getType() == JavadocTokenTypes.TEXT) { 171 if (!child.getText().trim().isEmpty()) { 172 extraTextFound = true; 173 } 174 } 175 else if (child.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 176 if (child.getChildren()[1].getType() == JavadocTokenTypes.INHERIT_DOC_LITERAL) { 177 containsInheritDoc = true; 178 } 179 else { 180 extraTextFound = true; 181 } 182 } 183 else if (!SKIP_TOKENS.contains(child.getType())) { 184 extraTextFound = true; 185 } 186 if (extraTextFound) { 187 break; 188 } 189 } 190 return containsInheritDoc && !extraTextFound; 191 } 192 193 /** 194 * Finds and returns first sentence. 195 * @param ast Javadoc root node. 196 * @return first sentence. 197 */ 198 private static String getFirstSentence(DetailNode ast) { 199 final StringBuilder result = new StringBuilder(); 200 final String periodSuffix = PERIOD + ' '; 201 for (DetailNode child : ast.getChildren()) { 202 final String text = child.getText(); 203 204 if (child.getType() != JavadocTokenTypes.JAVADOC_INLINE_TAG 205 && text.contains(periodSuffix)) { 206 result.append(text.substring(0, text.indexOf(periodSuffix) + 1)); 207 break; 208 } 209 else { 210 result.append(text); 211 } 212 } 213 return result.toString(); 214 } 215 216 /** 217 * Tests if first sentence contains forbidden summary fragment. 218 * @param firstSentence String with first sentence. 219 * @return true, if first sentence contains forbidden summary fragment. 220 */ 221 private boolean containsForbiddenFragment(String firstSentence) { 222 String javadocText = JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN 223 .matcher(firstSentence).replaceAll(" "); 224 javadocText = CharMatcher.WHITESPACE.trimAndCollapseFrom(javadocText, ' '); 225 return forbiddenSummaryFragments.matcher(javadocText).find(); 226 } 227}