/* * 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 consulo.testFramework.util.TestPathUtil; 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 consulo.annotations.RequiredDispatchThread; 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; } } @RequiredDispatchThread @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 = "<<</" + resultNumber + ">>>"; 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 TestPathUtil.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<File> failedFiles = new ArrayList<File>(); doSanityTestForDirectory(directory, failedFiles, formatWithPsi); if (!failedFiles.isEmpty()) { fail("Failed for files: " + composeMessage(failedFiles)); } } private void doSanityTestForDirectory(final File directory, final List<File> 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<File> failedFiles = new ArrayList<File>(); 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<File> 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<File> failedFiles) { final StringBuffer result = new StringBuffer(); for (File file : failedFiles) { result.append(file.getPath()); result.append("\n"); } return result.toString(); } }