| //////////////////////////////////////////////////////////////////////////////// |
| // 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.api; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotSame; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.OutputStreamWriter; |
| import java.lang.reflect.Method; |
| import java.nio.charset.StandardCharsets; |
| import java.text.MessageFormat; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.function.Consumer; |
| |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.powermock.reflect.Whitebox; |
| |
| import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport; |
| import com.puppycrawl.tools.checkstyle.DefaultConfiguration; |
| import com.puppycrawl.tools.checkstyle.TreeWalker; |
| import com.puppycrawl.tools.checkstyle.checks.TodoCommentCheck; |
| import com.puppycrawl.tools.checkstyle.utils.CommonUtils; |
| |
| /** |
| * TestCase to check DetailAST. |
| * @author Oliver Burn |
| */ |
| public class DetailASTTest extends AbstractModuleTestSupport { |
| @Rule |
| public final TemporaryFolder temporaryFolder = new TemporaryFolder(); |
| |
| @Override |
| protected String getPackageLocation() { |
| return "com/puppycrawl/tools/checkstyle/api/detailast"; |
| } |
| |
| private static Method getSetParentMethod() throws Exception { |
| final Class<DetailAST> detailAstClass = DetailAST.class; |
| final Method setParentMethod = |
| detailAstClass.getDeclaredMethod("setParent", DetailAST.class); |
| setParentMethod.setAccessible(true); |
| return setParentMethod; |
| } |
| |
| @Test |
| public void testGetChildCount() throws Exception { |
| final DetailAST root = new DetailAST(); |
| final DetailAST firstLevelA = new DetailAST(); |
| final DetailAST firstLevelB = new DetailAST(); |
| final DetailAST secondLevelA = new DetailAST(); |
| |
| root.setFirstChild(firstLevelA); |
| |
| final Method setParentMethod = getSetParentMethod(); |
| setParentMethod.invoke(firstLevelA, root); |
| firstLevelA.setFirstChild(secondLevelA); |
| firstLevelA.setNextSibling(firstLevelB); |
| |
| setParentMethod.invoke(firstLevelB, root); |
| |
| setParentMethod.invoke(secondLevelA, root); |
| |
| assertEquals("Invalid child count", 0, secondLevelA.getChildCount()); |
| assertEquals("Invalid child count", 0, firstLevelB.getChildCount()); |
| assertEquals("Invalid child count", 1, firstLevelA.getChildCount()); |
| assertEquals("Invalid child count", 2, root.getChildCount()); |
| assertEquals("Invalid child count", 2, root.getChildCount()); |
| |
| assertNull("Previous sibling should be null", root.getPreviousSibling()); |
| assertNull("Previous sibling should be null", firstLevelA.getPreviousSibling()); |
| assertNull("Previous sibling should be null", secondLevelA.getPreviousSibling()); |
| assertEquals("Invalid previous sibling", firstLevelA, firstLevelB.getPreviousSibling()); |
| } |
| |
| @Test |
| public void testSetSiblingNull() throws Exception { |
| final DetailAST root = new DetailAST(); |
| final DetailAST firstLevelA = new DetailAST(); |
| |
| root.setFirstChild(firstLevelA); |
| |
| assertEquals("Invalid child count", 1, root.getChildCount()); |
| |
| getSetParentMethod().invoke(firstLevelA, root); |
| firstLevelA.addPreviousSibling(null); |
| firstLevelA.addNextSibling(null); |
| |
| assertEquals("Invalid child count", 1, root.getChildCount()); |
| } |
| |
| @Test |
| public void testInsertSiblingBetween() throws Exception { |
| final DetailAST root = new DetailAST(); |
| final DetailAST firstLevelA = new DetailAST(); |
| final DetailAST firstLevelB = new DetailAST(); |
| final DetailAST firstLevelC = new DetailAST(); |
| |
| assertEquals("Invalid child count", 0, root.getChildCount()); |
| |
| root.setFirstChild(firstLevelA); |
| final Method setParentMethod = getSetParentMethod(); |
| setParentMethod.invoke(firstLevelA, root); |
| |
| assertEquals("Invalid child count", 1, root.getChildCount()); |
| |
| firstLevelA.addNextSibling(firstLevelB); |
| setParentMethod.invoke(firstLevelB, root); |
| |
| assertEquals("Invalid next sibling", firstLevelB, firstLevelA.getNextSibling()); |
| |
| firstLevelA.addNextSibling(firstLevelC); |
| setParentMethod.invoke(firstLevelC, root); |
| |
| assertEquals("Invalid next sibling", firstLevelC, firstLevelA.getNextSibling()); |
| } |
| |
| @Test |
| public void testBranchContains() { |
| final DetailAST root = createToken(null, TokenTypes.CLASS_DEF); |
| final DetailAST modifiers = createToken(root, TokenTypes.MODIFIERS); |
| createToken(modifiers, TokenTypes.LITERAL_PUBLIC); |
| |
| assertTrue("invalid result", root.branchContains(TokenTypes.LITERAL_PUBLIC)); |
| assertFalse("invalid result", root.branchContains(TokenTypes.OBJBLOCK)); |
| } |
| |
| private static DetailAST createToken(DetailAST root, int type) { |
| final DetailAST result = new DetailAST(); |
| result.setType(type); |
| if (root != null) { |
| root.addChild(result); |
| } |
| return result; |
| } |
| |
| @Test |
| public void testClearBranchTokenTypes() throws Exception { |
| final DetailAST parent = new DetailAST(); |
| final DetailAST child = new DetailAST(); |
| parent.setFirstChild(child); |
| |
| final List<Consumer<DetailAST>> clearBranchTokenTypesMethods = Arrays.asList( |
| child::setFirstChild, |
| child::setNextSibling, |
| child::addPreviousSibling, |
| child::addNextSibling, |
| child::addChild, |
| ast -> { |
| try { |
| Whitebox.invokeMethod(child, "setParent", ast); |
| } |
| // -@cs[IllegalCatch] Cannot avoid catching it. |
| catch (Exception exception) { |
| throw new IllegalStateException(exception); |
| } |
| } |
| ); |
| |
| for (Consumer<DetailAST> method : clearBranchTokenTypesMethods) { |
| final BitSet branchTokenTypes = Whitebox.invokeMethod(parent, "getBranchTokenTypes"); |
| method.accept(null); |
| final BitSet branchTokenTypes2 = Whitebox.invokeMethod(parent, "getBranchTokenTypes"); |
| assertEquals("Branch token types are not equal", branchTokenTypes, branchTokenTypes2); |
| assertNotSame("Branch token types should not be the same", |
| branchTokenTypes, branchTokenTypes2); |
| } |
| } |
| |
| @Test |
| public void testClearChildCountCache() { |
| final DetailAST parent = new DetailAST(); |
| final DetailAST child = new DetailAST(); |
| parent.setFirstChild(child); |
| |
| final List<Consumer<DetailAST>> clearChildCountCacheMethods = Arrays.asList( |
| child::setNextSibling, |
| child::addPreviousSibling, |
| child::addNextSibling |
| ); |
| |
| for (Consumer<DetailAST> method : clearChildCountCacheMethods) { |
| final int startCount = parent.getChildCount(); |
| method.accept(null); |
| final int intermediateCount = Whitebox.getInternalState(parent, "childCount"); |
| final int finishCount = parent.getChildCount(); |
| assertEquals("Child count has changed", startCount, finishCount); |
| assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount); |
| } |
| |
| final int startCount = child.getChildCount(); |
| child.addChild(null); |
| final int intermediateCount = Whitebox.getInternalState(child, "childCount"); |
| final int finishCount = child.getChildCount(); |
| assertEquals("Child count has changed", startCount, finishCount); |
| assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount); |
| } |
| |
| @Test |
| public void testAddNextSibling() { |
| final DetailAST parent = new DetailAST(); |
| final DetailAST child = new DetailAST(); |
| final DetailAST sibling = new DetailAST(); |
| final DetailAST newSibling = new DetailAST(); |
| parent.setFirstChild(child); |
| child.setNextSibling(sibling); |
| child.addNextSibling(newSibling); |
| |
| assertEquals("Invalid parent", parent, newSibling.getParent()); |
| assertEquals("Invalid next sibling", sibling, newSibling.getNextSibling()); |
| assertEquals("Invalid child", newSibling, child.getNextSibling()); |
| } |
| |
| @Test |
| public void testManyComments() throws Exception { |
| final File file = temporaryFolder.newFile("InputDetailASTManyComments.java"); |
| |
| try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( |
| new FileOutputStream(file), StandardCharsets.UTF_8))) { |
| bw.write("class C {\n"); |
| for (int i = 0; i <= 30000; i++) { |
| bw.write("// " + i + "\n"); |
| } |
| bw.write("}\n"); |
| } |
| |
| final DefaultConfiguration checkConfig = createModuleConfig(TodoCommentCheck.class); |
| |
| final String[] expected = CommonUtils.EMPTY_STRING_ARRAY; |
| verify(checkConfig, file.getAbsolutePath(), expected); |
| } |
| |
| /** |
| * There are asserts in checkNode, but idea does not see them |
| * @noinspection JUnitTestMethodWithNoAssertions |
| */ |
| @Test |
| public void testTreeStructure() throws Exception { |
| checkDir(new File("src/test/resources/com/puppycrawl/tools/checkstyle")); |
| } |
| |
| @Test |
| public void testToString() { |
| final DetailAST ast = new DetailAST(); |
| ast.setText("text"); |
| ast.setColumnNo(0); |
| ast.setLineNo(0); |
| assertEquals("Invalid text", "text[0x0]", ast.toString()); |
| } |
| |
| private static void checkDir(File dir) throws Exception { |
| final File[] files = dir.listFiles(file -> { |
| return (file.getName().endsWith(".java") |
| || file.isDirectory()) |
| && !file.getName().endsWith("InputGrammar.java"); |
| }); |
| for (File file : files) { |
| if (file.isFile()) { |
| checkFile(file.getCanonicalPath()); |
| } |
| else if (file.isDirectory()) { |
| checkDir(file); |
| } |
| } |
| } |
| |
| private static void checkFile(String filename) throws Exception { |
| final FileText text = new FileText(new File(filename), |
| System.getProperty("file.encoding", StandardCharsets.UTF_8.name())); |
| final FileContents contents = new FileContents(text); |
| final DetailAST rootAST = TreeWalker.parse(contents); |
| if (rootAST != null) { |
| checkTree(filename, rootAST); |
| } |
| } |
| |
| private static void checkTree(final String filename, final DetailAST root) { |
| DetailAST curNode = root; |
| DetailAST parent = null; |
| DetailAST prev = null; |
| while (curNode != null) { |
| checkNode(curNode, parent, prev, filename, root); |
| DetailAST toVisit = curNode.getFirstChild(); |
| if (toVisit == null) { |
| while (curNode != null && toVisit == null) { |
| toVisit = curNode.getNextSibling(); |
| if (toVisit == null) { |
| curNode = curNode.getParent(); |
| if (curNode != null) { |
| parent = curNode.getParent(); |
| } |
| } |
| else { |
| prev = curNode; |
| curNode = toVisit; |
| } |
| } |
| } |
| else { |
| parent = curNode; |
| curNode = toVisit; |
| prev = null; |
| } |
| } |
| } |
| |
| private static void checkNode(final DetailAST node, |
| final DetailAST parent, |
| final DetailAST prev, |
| final String filename, |
| final DetailAST root) { |
| final Object[] params = { |
| node, parent, prev, filename, root, |
| }; |
| final MessageFormat badParentFormatter = new MessageFormat( |
| "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT); |
| final String badParentMsg = badParentFormatter.format(params); |
| assertEquals(badParentMsg, parent, node.getParent()); |
| final MessageFormat badPrevFormatter = new MessageFormat( |
| "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT); |
| final String badPrevMsg = badPrevFormatter.format(params); |
| assertEquals(badPrevMsg, prev, node.getPreviousSibling()); |
| } |
| } |