blob: 0d132473cbe84baa6a2a00421f07884fb7a9a192 [file] [log] [blame]
/*
* 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());
}
}