Skip to content

Commit

Permalink
Issue checkstyle#2840: Extract implementations of the block and inlin…
Browse files Browse the repository at this point in the history
…e parsers and fix the inline tag parser to properly parse inline tags that span multiple lines. Put tests on both
  • Loading branch information
nanaze committed May 8, 2017
1 parent 2a14515 commit c1c4371
Show file tree
Hide file tree
Showing 7 changed files with 670 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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 GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.puppycrawl.tools.checkstyle.api.LineColumn;

/**
* Tools for parsing block tags from a Javadoc comment.
*
* @author Nathan Naze
*/
public final class BlockTagUtils {

/** Block tag pattern for a first line. */
private static final Pattern BLOCK_TAG_PATTERN_FIRST_LINE = Pattern.compile(
"/\\*{2,}\\s*@(\\p{Alpha}+)\\s");

/** Block tag pattern. */
private static final Pattern BLOCK_TAG_PATTERN = Pattern.compile(
"^\\s*\\**\\s*@(\\p{Alpha}+)\\s");

/** Closing tag. */
private static final String JAVADOC_CLOSING_TAG = "*/";

/** Not instantiable. */
private BlockTagUtils() { }

/**
* Extract the block tags from a Javadoc comment.
* @param lines The text of the comment, as separate lines.
* @return The tags extracted from the block.
*/
public static List<TagInfo> extractBlockTags(String... lines) {
final List<TagInfo> tags = new ArrayList<>();

for (int i = 0; i < lines.length; i++) {
// Starting lines of a comment have a different first line pattern.
final boolean isFirstLine = i == 0;
final Pattern pattern;
if (isFirstLine) {
pattern = BLOCK_TAG_PATTERN_FIRST_LINE;
}
else {
pattern = BLOCK_TAG_PATTERN;
}

final String line = lines[i];
final Matcher tagMatcher = pattern.matcher(line);

if (tagMatcher.find()) {
final String tagName = tagMatcher.group(1);

// offset of one for the @ character
final int colNum = tagMatcher.start(1) - 1;
final int lineNum = i + 1;

final String remainder = line.substring(tagMatcher.end(1));
String tagValue = remainder.trim();

// Handle the case where we're on the last line of a Javadoc comment.
if (tagValue.endsWith(JAVADOC_CLOSING_TAG)) {
final int endIndex = tagValue.length() - JAVADOC_CLOSING_TAG.length();
tagValue = tagValue.substring(0, endIndex).trim();
}

final LineColumn position = new LineColumn(lineNum, colNum);
tags.add(new TagInfo(tagName, tagValue, position));
}
}

return tags;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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 GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.puppycrawl.tools.checkstyle.api.LineColumn;

/**
* Tools for extracting inline tags from Javadoc comments.
*
* @author Nathan Naze
*/
final class InlineTagUtils {

/**
* Inline tag pattern.
*/
private static final Pattern INLINE_TAG_PATTERN = Pattern.compile(
".*?\\{@(\\p{Alpha}+)\\b(.*?)}", Pattern.DOTALL);

/** Pattern to recognize leading "*" characters in Javadoc. */
private static final Pattern JAVADOC_PREFIX_PATTERN = Pattern.compile(
"^\\s*\\*", Pattern.MULTILINE);

/** Pattern matching whitespace, used by {@link InlineTagUtils#collapseWhitespace(String)}. */
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");

/** Pattern matching a newline. */
private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\n");

/** Line feed character. */
private static final String LINE_FEED = "\n";

/** Carriage return character. */
private static final String CARRIAGE_RETURN = "\r";

/** Non instantiable. */
private InlineTagUtils() { }

/**
* Extract inline Javadoc tags from the given comment.
* @param lines The Javadoc comment (as lines).
* @return The extracted inline Javadoc tags.
*/
public static List<TagInfo> extractInlineTags(String... lines) {
for (String line : lines) {
if (line.contains(LINE_FEED) || line.contains(CARRIAGE_RETURN)) {
throw new AssertionError("comment lines cannot contain newlines");
}
}

final String commentText = convertLinesToString(lines);
final Matcher inlineTagMatcher = INLINE_TAG_PATTERN.matcher(commentText);

final List<TagInfo> tags = new ArrayList<>();

while (inlineTagMatcher.find()) {
final String tagName = inlineTagMatcher.group(1);

// Remove the leading asterisks (in case the tag spans a line) and collapse
// the whitespace.
String matchedTagValue = inlineTagMatcher.group(2);
matchedTagValue = removeLeadingJavaDoc(matchedTagValue);
matchedTagValue = collapseWhitespace(matchedTagValue);

final String tagValue = matchedTagValue;

final int startIndex = inlineTagMatcher.start(1);
final LineColumn position = getLineColumnOfIndex(commentText,
// correct start index offset
startIndex - 1);

tags.add(new TagInfo(tagName, tagValue, position));
}

return tags;
}

/**
* @param lines A number of lines, in order.
* @return The lines, joined together with newlines, as a single string.
*/
public static String convertLinesToString(String... lines) {
final StringBuilder builder = new StringBuilder();
for (String line : lines) {
builder.append(line);
builder.append(LINE_FEED);
}
return builder.toString();
}

/**
* @param source Source string.
* @param index An index into the string.
* @return A position in the source representing what line and column that index appears on.
*/
public static LineColumn getLineColumnOfIndex(String source, int index) {
if (index < 0) {
throw new AssertionError("index must be positive");
}

if (index >= source.length()) {
throw new AssertionError("index must be less than length of source");
}

final String precedingText = source.subSequence(0, index).toString();
final String[] precedingLines = NEWLINE_PATTERN.split(precedingText);
final String lastLine = precedingLines[precedingLines.length - 1];
return new LineColumn(precedingLines.length, lastLine.length());
}

/**
* @param str Source string.
* @return The given string with all whitespace collapsed.
*/
public static String collapseWhitespace(String str) {
final Matcher matcher = WHITESPACE_PATTERN.matcher(str);
return matcher.replaceAll(" ").trim();
}

/**
* @param source A string to remove leading Javadoc from.
* @return The given string with leading Javadoc "*" characters from each line removed.
*/
public static String removeLeadingJavaDoc(String source) {
final Matcher matcher = JAVADOC_PREFIX_PATTERN.matcher(source);
return matcher.replaceAll("");
}
}
Loading

0 comments on commit c1c4371

Please sign in to comment.