diff --git a/platform/testFramework/testSrc/com/intellij/psi/formatter/FormatterTestCase.java b/platform/testFramework/testSrc/com/intellij/psi/formatter/FormatterTestCase.java new file mode 100644 index 000000000000..2f6b52687955 --- /dev/null +++ b/platform/testFramework/testSrc/com/intellij/psi/formatter/FormatterTestCase.java @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2004 JetBrains s.r.o. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduct the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * Neither the name of JetBrains or IntelliJ IDEA + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. JETBRAINS AND ITS LICENSORS SHALL NOT + * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT + * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL JETBRAINS OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN + * IF JETBRAINS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + */ +package com.intellij.psi.formatter; + +import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeInsight.daemon.impl.CodeFoldingPassFactory; +import com.intellij.lang.Language; +import com.intellij.mock.MockProgressIndicator; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.impl.UndoManagerImpl; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.LocalTimeCounter; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings({"HardCodedStringLiteral"}) +public abstract class FormatterTestCase extends LightPlatformTestCase { + protected boolean doReformatRangeTest; + protected TextRange myTextRange; + protected EditorImpl myEditor; + protected PsiFile myFile; + + enum CheckPolicy { + + PSI(true, false),DOCUMENT(false, true),BOTH(true, true); + + private final boolean myCheckPsi; + private final boolean myCheckDocument; + + private CheckPolicy(boolean checkPsi, boolean checkDocument) { + myCheckDocument = checkDocument; + myCheckPsi = checkPsi; + } + + public boolean isCheckPsi() { + return myCheckPsi; + } + + public boolean isCheckDocument() { + return myCheckDocument; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + assertFalse(CodeStyleSettingsManager.getInstance(getProject()).USE_PER_PROJECT_SETTINGS); + assertNull(CodeStyleSettingsManager.getInstance(getProject()).PER_PROJECT_SETTINGS); + } + + protected void doTest(String resultNumber) throws Exception { + doTestForResult(getTestName(true), resultNumber); + } + + protected void doTest() throws Exception { + doTest(null); + } + + private void doTestForResult(String testName, String resultNumber) throws Exception { + doTest(testName + "." + getFileExtension(), testName + "_after." + getFileExtension(), resultNumber); + } + + protected void doTest(String fileNameBefore, String fileNameAfter, String resultNumber) throws Exception { + doTextTest(loadFile(fileNameBefore, null), loadFile(fileNameAfter, resultNumber)); + } + + protected final void doTest(@NonNls String fileNameBefore, @NonNls String fileNameAfter) throws Exception { + doTextTest(loadFile(fileNameBefore + "." + getFileExtension(), null), loadFile(fileNameAfter + "." + getFileExtension(), null)); + } + + protected void doTextTest(@NonNls final String text, @NonNls final String textAfter) throws IncorrectOperationException { + doTextTest(text, textAfter, CheckPolicy.BOTH); + } + + protected void doTextTest(final String text, final String textAfter, @NotNull CheckPolicy checkPolicy) + throws IncorrectOperationException { + final String fileName = "before." + getFileExtension(); + final PsiFile file = createFileFromText(text, fileName, PsiFileFactory.getInstance(getProject())); + + if (checkPolicy.isCheckDocument()) { + checkDocument(file, text, textAfter); + } + + if (checkPolicy.isCheckPsi()) { + /* + restoreFileContent(file, text); + + checkPsi(file, textAfter); + */ + } + + + } + + protected PsiFile createFileFromText(String text, String fileName, final PsiFileFactory fileFactory) { + return fileFactory.createFileFromText(fileName, getFileType(fileName), text, LocalTimeCounter.currentTime(), true, false); + } + + protected FileType getFileType(String fileName) { + return FileTypeManager.getInstance().getFileTypeByFileName(fileName); + } + + + @Override + protected void tearDown() throws Exception { + if (myFile != null) { + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(myFile.getVirtualFile()); + FileEditorManager.getInstance(getProject()).closeFile(myFile.getVirtualFile()); + } + myEditor = null; + myFile = null; + super.tearDown(); + } + + @SuppressWarnings({"UNUSED_SYMBOL"}) + private void restoreFileContent(final PsiFile file, final String text) { + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file); + document.replaceString(0, document.getTextLength(), text); + PsiDocumentManager.getInstance(getProject()).commitDocument(document); + } + }); + + } + }, "test", null); + } + + protected boolean doCheckDocumentUpdate() { + return false; + } + + private void checkDocument(final PsiFile file, final String text, String textAfter) { + final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file); + final EditorImpl editor; + + if (doCheckDocumentUpdate()) { + editor =(EditorImpl)FileEditorManager.getInstance(getProject()).openTextEditor(new OpenFileDescriptor(getProject(), file.getVirtualFile(), 0), false); + editor.putUserData(EditorImpl.DO_DOCUMENT_UPDATE_TEST, Boolean.TRUE); + if (myFile != null) { + FileEditorManager.getInstance(getProject()).closeFile(myFile.getVirtualFile()); + } + myEditor = editor; + myFile = file; + } + else { + editor = null; + } + + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(0, document.getTextLength(), text); + PsiDocumentManager.getInstance(getProject()).commitDocument(document); + assertEquals(file.getText(), document.getText()); + + if (false && doCheckDocumentUpdate()) { + makeFolding(file, editor); + } + try { + if (doReformatRangeTest) { + CodeStyleManager.getInstance(getProject()).reformatRange(file, file.getTextRange().getStartOffset(), file.getTextRange().getEndOffset()); + } else if (myTextRange != null) { + CodeStyleManager.getInstance(getProject()).reformatText(file, myTextRange.getStartOffset(), myTextRange.getEndOffset()); + } + else { + CodeStyleManager.getInstance(getProject()) + .reformatText(file, file.getTextRange().getStartOffset(), file.getTextRange().getEndOffset()); + } + } + catch (IncorrectOperationException e) { + fail(); + } + + } + }); + } + }, "", ""); + + assertEquals(textAfter, document.getText()); + PsiDocumentManager.getInstance(getProject()).commitDocument(document); + assertEquals(textAfter, file.getText()); + } + + protected static void makeFolding(final PsiFile file, final EditorImpl editor) { + final CodeFoldingPassFactory factory = getProject().getComponent(CodeFoldingPassFactory.class); + final TextEditorHighlightingPass highlightingPass = factory.createHighlightingPass(file, editor); + highlightingPass.collectInformation(new MockProgressIndicator()); + highlightingPass.doApplyInformationToEditor(); + } + + @SuppressWarnings({"UNUSED_SYMBOL"}) + private void checkPsi(final PsiFile file, String textAfter) { + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + performFormatting(file); + } + }); + } + }, "", ""); + + + String fileText = file.getText(); + assertEquals(textAfter, fileText); + } + + protected void performFormatting(final PsiFile file) { + try { + if (myTextRange == null) { + CodeStyleManager.getInstance(getProject()).reformat(file); + } + else { + CodeStyleManager.getInstance(getProject()).reformatRange(file, myTextRange.getStartOffset(), myTextRange.getEndOffset()); + } + } + catch (IncorrectOperationException e) { + fail(); + } + } + + protected void performFormattingWithDocument(final PsiFile file) { + try { + if (myTextRange == null) { + CodeStyleManager.getInstance(getProject()).reformatText(file, 0, file.getTextLength()); + } + else { + CodeStyleManager.getInstance(getProject()).reformatText(file, myTextRange.getStartOffset(), myTextRange.getEndOffset()); + } + } + catch (IncorrectOperationException e) { + fail(); + } + } + + protected String loadFile(String name, String resultNumber) throws Exception { + String fullName = getTestDataPath() + File.separatorChar + getBasePath() + File.separatorChar + name; + String text = FileUtil.loadFile(new File(fullName)); + text = StringUtil.convertLineSeparators(text); + if (resultNumber == null) { + return prepareText(text); + } + else { + String beginLine = "<<<" + resultNumber + ">>>"; + String endLine = "<<>>"; + int beginPos = text.indexOf(beginLine); + assertTrue(beginPos >= 0); + int endPos = text.indexOf(endLine); + assertTrue(endPos >= 0); + + return prepareText(text.substring(beginPos + beginLine.length(), endPos).trim()); + + } + } + + protected String getTestDataPath() { + return PathManagerEx.getTestDataPath(); + } + + protected String prepareText(final String text) { + return text; + } + + protected abstract String getBasePath(); + + protected abstract String getFileExtension(); + + protected void defaultSettings() { + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + + settings.ALIGN_MULTILINE_PARAMETERS = true; + settings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false; + settings.ALIGN_MULTILINE_FOR = true; + + settings.ALIGN_MULTILINE_BINARY_OPERATION = false; + settings.ALIGN_MULTILINE_TERNARY_OPERATION = false; + settings.ALIGN_MULTILINE_THROWS_LIST = false; + settings.ALIGN_MULTILINE_EXTENDS_LIST = false; + settings.ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION = false; + settings.DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS = false; + + getSettings().SPACE_BEFORE_ANOTATION_PARAMETER_LIST = false; + getSettings().SPACE_AROUND_ASSIGNMENT_OPERATORS = true; + getSettings().SPACE_WITHIN_ANNOTATION_PARENTHESES = false; + getSettings().SPACE_AROUND_ASSIGNMENT_OPERATORS = true; + } + + /** + * Returns common (spacing, blank lines etc.) settings for the given language. + * @param language The language to search settings for. + * @return Language common settings or root settings if the language doesn't have any common + * settings of its own. + */ + protected static CommonCodeStyleSettings getSettings(Language language) { + return CodeStyleSettingsManager.getSettings(getProject()).getCommonSettings(language); + } + + protected CodeStyleSettings getSettings() { + return CodeStyleSettingsManager.getSettings(getProject()); + } + + protected void doSanityTestForDirectory(File directory, final boolean formatWithPsi) throws IOException, IncorrectOperationException { + final List failedFiles = new ArrayList(); + doSanityTestForDirectory(directory, failedFiles, formatWithPsi); + if (!failedFiles.isEmpty()) { + fail("Failed for files: " + composeMessage(failedFiles)); + } + } + + private void doSanityTestForDirectory(final File directory, final List failedFiles, final boolean formatWithPsi) + throws IOException, IncorrectOperationException { + final File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + doSanityTestForFile(file, failedFiles, formatWithPsi); + doSanityTestForDirectory(file, failedFiles, formatWithPsi); + } + } + } + + protected void doSanityTest(final boolean formatWithPsi) throws IOException, IncorrectOperationException { + final File sanityDirectory = new File(getTestDataPath() + File.separatorChar + getBasePath(), "sanity"); + final File[] subFiles = sanityDirectory.listFiles(); + final List failedFiles = new ArrayList(); + + if (subFiles != null) { + for (final File subFile : subFiles) { + doSanityTestForFile(subFile, failedFiles, formatWithPsi); + } + + if (!failedFiles.isEmpty()) { + fail("Failed for files: " + composeMessage(failedFiles)); + } + } + + } + + private void doSanityTestForFile(final File subFile, final List failedFiles, final boolean formatWithPsi) + throws IOException, IncorrectOperationException { + if (subFile.isFile() && subFile.getName().endsWith(getFileExtension())) { + final byte[] bytes = FileUtil.loadFileBytes(subFile); + final String text = new String(bytes); + final String fileName = "before." + getFileExtension(); + final PsiFile file = PsiFileFactory.getInstance(getProject()).createFileFromText(fileName, getFileType(fileName), StringUtil.convertLineSeparators(text), LocalTimeCounter.currentTime(), true); + + try { + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + if (formatWithPsi) { + performFormatting(file); + } + else { + performFormattingWithDocument(file); + } + } + catch (Throwable e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + failedFiles.add(subFile); + } + //noinspection UseOfSystemOutOrSystemErr + System.out.println(subFile.getPath() + ": finished"); + } + }); + } + }, "", null); + } + finally { + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null) { + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(virtualFile); + ((UndoManagerImpl)UndoManager.getGlobalInstance()).clearUndoRedoQueueInTests(virtualFile); + } + } + } + } + + private String composeMessage(final List failedFiles) { + final StringBuffer result = new StringBuffer(); + for (File file : failedFiles) { + result.append(file.getPath()); + result.append("\n"); + } + return result.toString(); + } +}