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