| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.impl.source.codeStyle; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.impl.LineSet; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.TokenType; |
| import com.intellij.psi.tree.IElementType; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jmock.Expectations; |
| import org.jmock.Mockery; |
| import org.jmock.api.Invocation; |
| import org.jmock.integration.junit4.JMock; |
| import org.jmock.integration.junit4.JUnit4Mockery; |
| import org.jmock.lib.action.CustomAction; |
| import org.jmock.lib.legacy.ClassImposteriser; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| /** |
| * @author Denis Zhdanov |
| * @since 08/01/2012 |
| */ |
| @RunWith(JMock.class) |
| public class TabPostFormatProcessorTest { |
| |
| private static final String START_RANGE_MARKER = "<range>"; |
| private static final String END_RANGE_MARKER = "</range>"; |
| |
| private Mockery myMockery; |
| private Document myDocument; |
| |
| @Before |
| public void setUp() { |
| myMockery = new JUnit4Mockery() {{ |
| setImposteriser(ClassImposteriser.INSTANCE); |
| }}; |
| myDocument = myMockery.mock(Document.class); |
| } |
| |
| @After |
| public void checkExpectations() { |
| myMockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void spacesAndWholeLineInsideRange() { |
| doTestSpaces( |
| "line 1<range>\n" + |
| " \t \tline2\n" + |
| "line</range> 3", |
| 4, |
| "line 1\n" + |
| " line2\n" + |
| "line 3" |
| ); |
| } |
| @Test |
| public void spacesAndExactRange() { |
| doTestSpaces( |
| "line 1\n" + |
| "<range> \t \tline2</range>\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| " line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void spacesAndHeadIntersection() { |
| doTestSpaces( |
| "line 1<range>\n" + |
| " \t </range>\tline2\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| " \tline2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void spacesAndTailIntersection() { |
| doTestSpaces( |
| "line 1\n" + |
| " \t <range>\tline2</range>\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| " \t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void spacesAndPartialIndentInsideRange() { |
| doTestSpaces( |
| "line 1\n" + |
| " \t <range>\t</range> \t line2\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| " \t \t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void tabsAndWholeLineInsideRange() { |
| doTestTabs( |
| "line 1<range>\n" + |
| " \t line2\n" + |
| "line</range> 3", |
| 4, |
| "line 1\n" + |
| "\t \t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void tabsAndHeadIntersection() { |
| doTestTabs( |
| "line 1<range>\n" + |
| " \t </range> line2\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| "\t\t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void tabsAndTailIntersection() { |
| doTestTabs( |
| "line 1\n" + |
| " <range> line2\n" + |
| "</range>line 3", |
| 4, |
| "line 1\n" + |
| " \t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void tabsAndPartialIndentInsideRange() { |
| doTestTabs( |
| "line 1\n" + |
| " <range> </range> line2\n" + |
| "line 3", |
| 4, |
| "line 1\n" + |
| " \t line2\n" + |
| "line 3" |
| ); |
| } |
| |
| @Test |
| public void smartTabsForTheFirstLine() { |
| doTestSmartTabs( |
| " line 1\n" + |
| "\t line 2", |
| 4, |
| "\t\t line 1\n" + |
| "\t line 2" |
| ); |
| } |
| |
| @Test |
| public void smartTabsFromUpperLine() { |
| doTestSmartTabs( |
| "\t\t line 1<range>\n" + |
| " 2</range>", |
| 4, |
| "\t\t line 1\n" + |
| "\t\t 2" |
| ); |
| } |
| |
| @Test |
| public void smartTabsExactReplacement() { |
| doTestSmartTabs( |
| "\tline 1<range>\n" + |
| " line 2</range>", |
| 4, |
| "\tline 1\n" + |
| "\tline 2" |
| ); |
| } |
| |
| @Test |
| public void smartTabsMismatchedIndent() { |
| doTestSmartTabs( |
| " \tline 1<range>\n" + |
| "\t line 2</range>", |
| 4, |
| " \tline 1\n" + |
| "\t line 2" |
| ); |
| } |
| |
| @Test |
| public void smartTabsPartialMatchedIndent() { |
| doTestSmartTabs( |
| "\t\tline 1\n" + |
| " <range> line 2</range>", |
| 4, |
| "\t\tline 1\n" + |
| " \tline 2" |
| ); |
| } |
| |
| @Test |
| public void smartTabsPartialMisMatchedIndent() { |
| doTestSmartTabs( |
| "\t\tline 1\n" + |
| " <range> line 2</range>", |
| 4, |
| "\t\tline 1\n" + |
| " line 2" |
| ); |
| } |
| |
| private void doTestSpaces(@NotNull String initial, final int tabWidth, @NotNull String expected) { |
| doTest(initial, expected, false, false, tabWidth); |
| } |
| |
| private void doTestTabs(@NotNull String initial, final int tabWidth, @NotNull String expected) { |
| doTest(initial, expected, true, false, tabWidth); |
| } |
| |
| private void doTestSmartTabs(@NotNull String initial, final int tabWidth, @NotNull String expected) { |
| doTest(initial, expected, true, true, tabWidth); |
| } |
| |
| private void doTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { |
| doDocumentTest(initial, expected, useTabs, smartTabs, tabWidth); |
| doPsiTest(initial, expected, useTabs, smartTabs, tabWidth); |
| } |
| |
| private void doDocumentTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { |
| Pair<String, TextRange> pair = parse(initial); |
| final StringBuilder text = new StringBuilder(pair.first); |
| final TextRange range = pair.second; |
| |
| myMockery.checking(new Expectations() {{ |
| allowing(myDocument).getCharsSequence(); will(returnValue(text.toString())); |
| allowing(myDocument).getTextLength(); will(returnValue(text.length())); |
| }}); |
| |
| final LineSet lines = new LineSet(); |
| lines.documentCreated(myDocument); |
| myMockery.checking(new Expectations() {{ |
| allowing(myDocument).getLineNumber(with(any(int.class))); will(new CustomAction("getLineNumber()") { |
| @Override |
| public Object invoke(Invocation invocation) throws Throwable { |
| return lines.findLineIndex((Integer)invocation.getParameter(0)); |
| } |
| }); |
| allowing(myDocument).getLineStartOffset(with(any(int.class))); will(new CustomAction("getLineStartOffset()") { |
| @Override |
| public Object invoke(Invocation invocation) throws Throwable { |
| return lines.getLineStart((Integer)invocation.getParameter(0)); |
| } |
| }); |
| allowing(myDocument).getLineEndOffset(with(any(int.class))); will(new CustomAction("getLineEndOffset()") { |
| @Override |
| public Object invoke(Invocation invocation) throws Throwable { |
| return lines.getLineEnd((Integer)invocation.getParameter(0)); |
| } |
| }); |
| allowing(myDocument).replaceString(with(any(int.class)), with(any(int.class)), with(any(String.class))); |
| will(new CustomAction("replaceString") { |
| @Nullable |
| @Override |
| public Object invoke(Invocation invocation) throws Throwable { |
| int start = (Integer)invocation.getParameter(0); |
| int end = (Integer)invocation.getParameter(1); |
| String newText = (String)invocation.getParameter(2); |
| text.replace(start, end, newText); |
| return null; |
| } |
| }); |
| }}); |
| |
| TabPostFormatProcessor.processViaDocument(myDocument, range, useTabs, smartTabs, tabWidth); |
| assertEquals(expected, text.toString()); |
| } |
| |
| private static Pair<String, TextRange> parse(@NotNull String text) { |
| int rangeMarkerStart = text.indexOf(START_RANGE_MARKER); |
| int rangeMarkerEnd = text.indexOf(END_RANGE_MARKER); |
| final StringBuilder buffer = new StringBuilder(); |
| final TextRange range; |
| if (rangeMarkerStart >= 0 && rangeMarkerEnd >= 0) { |
| range = TextRange.create(rangeMarkerStart, rangeMarkerEnd - START_RANGE_MARKER.length()); |
| buffer.append(text.substring(0, rangeMarkerStart)) |
| .append(text.substring(rangeMarkerStart + START_RANGE_MARKER.length(), rangeMarkerEnd)) |
| .append(text.substring(rangeMarkerEnd + END_RANGE_MARKER.length())); |
| } |
| else { |
| range = TextRange.create(0, text.length()); |
| buffer.append(text); |
| } |
| return Pair.create(buffer.toString(), range); |
| } |
| |
| private void doPsiTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { |
| final List<ASTNode> children = new ArrayList<ASTNode>(); |
| final List<StringBuilder> childrenText = new ArrayList<StringBuilder>(); |
| Pair<String, TextRange> pair = parse(initial); |
| final String text = pair.first; |
| int start = 0; |
| boolean inWhiteSpace = initial.charAt(0) == ' ' || initial.charAt(0) == '\t'; |
| for (int i = 1; i <= text.length(); i++) { |
| if (i == text.length() || ((StringUtil.isWhiteSpace(text.charAt(i))) ^ inWhiteSpace)) { |
| final int childIndex = children.size(); |
| final int startOffset = start; |
| childrenText.add(new StringBuilder(text.substring(start, i))); |
| final ASTNode child = myMockery.mock(ASTNode.class, "child" + childIndex); |
| children.add(child); |
| final IElementType type = inWhiteSpace ? TokenType.WHITE_SPACE : TokenType.CODE_FRAGMENT; |
| myMockery.checking(new Expectations() {{ |
| allowing(child).getElementType(); will(returnValue(type)); |
| allowing(child).getChars(); will(returnValue(childrenText.get(childIndex))); |
| allowing(child).getTextLength(); will(returnValue(childrenText.get(childIndex).length())); |
| allowing(child).getStartOffset(); will(returnValue(startOffset)); |
| }}); |
| inWhiteSpace = !inWhiteSpace; |
| start = i; |
| } |
| } |
| |
| final ASTNode root = myMockery.mock(ASTNode.class); |
| myMockery.checking(new Expectations() {{ |
| allowing(root).getFirstChildNode(); will(returnValue(children.get(0))); |
| allowing(root).getTextLength(); will(returnValue(text.length())); |
| allowing(root).getStartOffset(); will(returnValue(0)); |
| }}); |
| |
| TabPostFormatProcessor.TreeHelper helper = new TabPostFormatProcessor.TreeHelper() { |
| |
| @Override |
| public ASTNode prevLeaf(@NotNull ASTNode current) { |
| int i = children.indexOf(current); |
| return i > 0 ? children.get(i - 1) : null; |
| } |
| |
| @Override |
| public ASTNode nextLeaf(@NotNull ASTNode current) { |
| int i = children.indexOf(current); |
| return i < children.size() - 1 ? children.get(i + 1) : null; |
| } |
| |
| @Override |
| public ASTNode firstLeaf(@NotNull ASTNode startNode) { |
| return root == startNode ? children.get(0) : null; |
| } |
| |
| @Override |
| public void replace(@NotNull String newText, @NotNull TextRange range, @NotNull ASTNode leaf) { |
| int i = children.indexOf(leaf); |
| childrenText.get(i).replace(range.getStartOffset() - leaf.getStartOffset(), range.getEndOffset() - leaf.getStartOffset(), newText); |
| } |
| }; |
| |
| TabPostFormatProcessor.processViaPsi(root, pair.second, helper, useTabs, smartTabs, tabWidth); |
| StringBuilder actual = new StringBuilder(); |
| for (ASTNode child : children) { |
| actual.append(child.getChars()); |
| } |
| assertEquals(expected, actual.toString()); |
| } |
| } |