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