/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* 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.
*/
package com.liferay.source.formatter.checkstyle.checks;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.source.formatter.checkstyle.util.DetailASTUtil;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Hugo Huijser
*/
public class PlusStatementCheck extends AbstractCheck {
public static final String MSG_COMBINE_LITERAL_STRINGS =
"literal.string.combine";
public static final String MSG_INCORRECT_TABBING = "tabbing.incorrect";
public static final String MSG_INVALID_END_CHARACTER =
"end.character.invalid";
public static final String MSG_INVALID_START_CHARACTER =
"start.character.invalid";
public static final String MSG_MOVE_LITERAL_STRING = "literal.string.move";
public static final String MSG_STATEMENT_TOO_LONG =
"plus.statement.too.long";
@Override
public int[] getDefaultTokens() {
return new int[] {TokenTypes.PLUS};
}
public void setMaxLineLength(int maxLineLength) {
_maxLineLength = maxLineLength;
}
@Override
public void visitToken(DetailAST detailAST) {
_checkMultiLinesPlusStatement(detailAST);
_checkTabbing(detailAST);
if (detailAST.getChildCount() != 2) {
return;
}
DetailAST firstChild = detailAST.getFirstChild();
String literalString1 = _getLiteralString(firstChild);
if (literalString1 == null) {
return;
}
DetailAST lastChild = detailAST.getLastChild();
String literalString2 = _getLiteralString(lastChild);
if (literalString2 == null) {
return;
}
if (firstChild.getLineNo() == lastChild.getLineNo()) {
log(
firstChild.getLineNo(), MSG_COMBINE_LITERAL_STRINGS,
literalString1, literalString2);
return;
}
if (_isRegexPattern(detailAST)) {
return;
}
if (literalString1.endsWith(StringPool.SLASH)) {
log(
detailAST.getLineNo(), MSG_INVALID_END_CHARACTER,
literalString1.charAt(literalString1.length() - 1));
}
if (literalString2.startsWith(StringPool.SPACE) ||
(!literalString1.endsWith(StringPool.SPACE) &&
literalString2.matches("^[-:;.].*"))) {
log(
lastChild.getLineNo(), MSG_INVALID_START_CHARACTER,
literalString2.charAt(0));
return;
}
String line1 = getLine(lastChild.getLineNo() - 2);
String line2 = getLine(lastChild.getLineNo() - 1);
if (_getLeadingTabCount(line1) == _getLeadingTabCount(line2)) {
return;
}
int lineLength1 = CommonUtils.lengthExpandedTabs(
line1, line1.length(), getTabWidth());
String trimmedLine2 = StringUtil.trim(line2);
if ((lineLength1 + trimmedLine2.length() - 4) <= _maxLineLength) {
log(
lastChild.getLineNo(), MSG_COMBINE_LITERAL_STRINGS,
literalString1, literalString2);
return;
}
DetailAST parentAST = detailAST.getParent();
if ((parentAST.getType() == TokenTypes.PLUS) &&
((lineLength1 + literalString2.length()) <= _maxLineLength)) {
log(
detailAST.getLineNo(), MSG_COMBINE_LITERAL_STRINGS,
literalString1, literalString2);
return;
}
int pos = _getStringBreakPos(
literalString1, literalString2, _maxLineLength - lineLength1);
if (pos != -1) {
log(
lastChild.getLineNo(), MSG_MOVE_LITERAL_STRING,
literalString2.substring(0, pos + 1));
}
}
private void _checkMultiLinesPlusStatement(DetailAST detailAST) {
DetailAST firstChildAST = detailAST.getFirstChild();
if (firstChildAST.getType() == TokenTypes.PLUS) {
return;
}
if (DetailASTUtil.hasParentWithTokenType(
detailAST, TokenTypes.ANNOTATION) ||
!DetailASTUtil.hasParentWithTokenType(
detailAST, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)) {
return;
}
Set<Integer> lineNumbers = new HashSet<>();
lineNumbers.add(detailAST.getLineNo());
DetailAST parentAST = detailAST;
while (true) {
if (parentAST.getType() != TokenTypes.PLUS) {
break;
}
DetailAST lastChildAST = parentAST.getLastChild();
lineNumbers.add(lastChildAST.getLineNo());
parentAST = parentAST.getParent();
}
if (lineNumbers.size() > 3) {
log(detailAST.getLineNo(), MSG_STATEMENT_TOO_LONG);
}
}
private void _checkTabbing(DetailAST detailAST) {
DetailAST afterPlusAST = detailAST.getLastChild();
if (afterPlusAST.getType() == TokenTypes.RPAREN) {
while (afterPlusAST.getType() != TokenTypes.LPAREN) {
afterPlusAST = afterPlusAST.getPreviousSibling();
}
}
int afterPlusLineNo = DetailASTUtil.getStartLine(afterPlusAST);
if (afterPlusLineNo == detailAST.getLineNo()) {
return;
}
String line1 = getLine(detailAST.getLineNo() - 1);
String line2 = getLine(afterPlusLineNo - 1);
int tabCount = _getLeadingTabCount(line1);
if ((tabCount + 1) != _getLeadingTabCount(line2)) {
log(afterPlusLineNo, MSG_INCORRECT_TABBING, tabCount + 1);
}
}
private int _getLeadingTabCount(String line) {
int leadingTabCount = 0;
while (line.startsWith(StringPool.TAB)) {
line = line.substring(1);
leadingTabCount++;
}
return leadingTabCount;
}
private String _getLiteralString(DetailAST detailAST) {
String literalString = null;
if (detailAST.getType() == TokenTypes.STRING_LITERAL) {
literalString = detailAST.getText();
}
else if ((detailAST.getType() == TokenTypes.PLUS) &&
(detailAST.getChildCount() == 2)) {
DetailAST lastChild = detailAST.getLastChild();
if (lastChild.getType() == TokenTypes.STRING_LITERAL) {
literalString = lastChild.getText();
}
}
if (literalString != null) {
return literalString.substring(1, literalString.length() - 1);
}
return null;
}
private int _getStringBreakPos(String s1, String s2, int i) {
if (s2.startsWith(StringPool.SLASH)) {
int pos = s2.lastIndexOf(StringPool.SLASH, i);
if (pos > 0) {
return pos - 1;
}
return -1;
}
if (s1.endsWith(StringPool.DASH)) {
return Math.max(
s2.lastIndexOf(StringPool.DASH, i - 1),
s2.lastIndexOf(StringPool.SPACE, i - 1));
}
if (s1.endsWith(StringPool.PERIOD)) {
return Math.max(
s2.lastIndexOf(StringPool.PERIOD, i - 1),
s2.lastIndexOf(StringPool.SPACE, i - 1));
}
if (s1.endsWith(StringPool.SPACE)) {
return s2.lastIndexOf(StringPool.SPACE, i - 1);
}
return -1;
}
private boolean _isRegexPattern(DetailAST detailAST) {
DetailAST parentAST = detailAST.getParent();
while (parentAST != null) {
if (parentAST.getType() != TokenTypes.METHOD_CALL) {
parentAST = parentAST.getParent();
continue;
}
DetailAST firstChild = parentAST.getFirstChild();
if (firstChild.getType() != TokenTypes.DOT) {
return false;
}
List<DetailAST> nameASTList = DetailASTUtil.getAllChildTokens(
firstChild, false, TokenTypes.IDENT);
if (nameASTList.size() != 2) {
return false;
}
DetailAST classNameAST = nameASTList.get(0);
DetailAST methodNameAST = nameASTList.get(1);
String methodCallClassName = classNameAST.getText();
String methodCallMethodName = methodNameAST.getText();
if (methodCallClassName.equals("Pattern") &&
methodCallMethodName.equals("compile")) {
return true;
}
return false;
}
return false;
}
private int _maxLineLength = 80;
}