| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * 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 org.jetbrains.android.inspections.lint; |
| |
| import com.android.annotations.Nullable; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiJavaFile; |
| import com.intellij.psi.PsiManager; |
| import lombok.ast.CompilationUnit; |
| import lombok.ast.Node; |
| import lombok.ast.ecj.EcjTreeConverter; |
| import lombok.ast.printer.SourcePrinter; |
| import lombok.ast.printer.StructureFormatter; |
| import lombok.ast.printer.TextFormatter; |
| import org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.compiler.parser.Parser; |
| import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; |
| import org.intellij.lang.annotations.Language; |
| import org.jetbrains.android.AndroidTestCase; |
| |
| import java.util.List; |
| |
| public class LombokPsiConverterTest extends AndroidTestCase { |
| /** |
| * Check that AST positions are okay? This works by comparing the |
| * offsets of each AST node with the positions in the corresponding |
| * offsets in an AST generated with the Parboiled Java parser. There are |
| * a lot of individual differences in these two files; it's not clear |
| * whether a method should include its javadoc range etc -- so these |
| * tests aren't expected to pass, but the diff is useful to inspect |
| * the position ranges of AST nodes, and fill out the missing ones etc. |
| * (There are quite a few missing ones right now; the focus has been |
| * on adding the ones that Lint will actually look up and use.) |
| */ |
| private static final boolean CHECK_POSITIONS = false; |
| |
| // TODO: |
| // Test interface body, test enum body, test annotation body! |
| |
| /** Include varargs in the test (currently fails, but isn't used by lint) */ |
| private static final boolean INCLUDE_VARARGS = false; |
| |
| public void testPsiToLombokConversion1() { |
| VirtualFile file = myFixture.copyFileToProject("intentions/R.java", "src/p1/p2/R.java"); |
| check(file); |
| } |
| |
| public void testPsiToLombokConversion2() { |
| VirtualFile file = myFixture.copyFileToProject("intentions/R.java", "src/p1/p2/R.java"); |
| check(file); |
| } |
| |
| public void testPsiToLombokConversion3() { |
| // This code is formatted a bit strangely; this is done such that it matches how the |
| // code is printed *back* by Lombok's AST pretty printer, so we can diff the two. |
| String testClass = |
| "package p1.p2;\n" + |
| "\n" + |
| // Imports |
| "import java.util.List;\n" + |
| "import java.util.regexp.*;\n" + |
| "import static java.util.Arrays.asList;\n" + |
| "\n" + |
| "public final class R2<K,V> {\n" + |
| " int myField1;\n" + |
| " \n" + |
| " final int myField2 = 42;\n" + |
| " \n" + |
| " // Comments and extra whitespace gets stripped\n" + |
| " \n" + |
| " private static final int CONSTANT = 42;\n" + |
| " private int[] foo1 = new int[5];\n" + |
| " private int[] foo2 = new int[] {1};\n" + |
| " private static int myRed = android.R.color.red;\n" + |
| " \n" + |
| // Constructors |
| " public R2(int x,List list) {\n" + |
| // Method invocations |
| " System.out.println(list.size());\n" + |
| " System.out.println(R2.drawable.s2);\n" + |
| " }\n" + |
| " \n" + |
| // Methods |
| " @Override\n" + |
| " @SuppressWarnings({\"foo1\", \"foo2\"})\n" + |
| //" @android.annotation.SuppressLint({\"foo1\",\"foo2\"})\n" + |
| //" @android.annotation.TargetApi(value=5})\n" + |
| " public void myMethod1(List list) {\n" + |
| " }\n" + |
| " \n" + |
| " public int myMethod2() {\n" + |
| " return 42;\n" + |
| " }\n" + |
| " \n" + |
| // Misc |
| " private void myvarargs(int" + (INCLUDE_VARARGS ? "..." : "[]") + " x) {\n" + |
| " Collections.<Map<String,String>>emptyMap();\n" + |
| " }\n" + |
| " \n" + |
| " private void myvarargs2(java.lang.String" + (INCLUDE_VARARGS ? "..." : "[]") + " x) {\n" + |
| " }\n" + |
| " \n" + |
| " private void myarraytest(String[][] args, int index) {\n" + |
| " int y = args[5][index + 1];\n" + |
| " }\n" + |
| " \n" + |
| " private void controlStructs(\n" + |
| " @SuppressWarnings(\"all\")\n" + |
| " int myAnnotatedArg) {\n" + |
| " boolean x = false;\n" + |
| " int y = 4, z = 5, w;\n" + |
| " if (x) {\n" + |
| " System.out.println(\"Ok\");\n" + |
| " }\n" + |
| " if (x) {\n" + |
| " System.out.println(\"Ok\");\n" + |
| " } else {\n" + |
| " System.out.println(\"Not OK\");\n" + |
| " }\n" + |
| " String[] args = new String[] {\"test1\", \"test2\"};\n" + |
| " for (String arg : args) {\n" + |
| " System.out.println(arg);\n" + |
| " }\n" + |
| " for (int i = 0, n = args.length; i < n; i++, i--, i++) {\n" + |
| " y++;\n" + |
| " --z;\n" + |
| " w = y;\n" + |
| " x = !x;\n" + |
| " if (w == 2) {\n" + |
| " continue;\n" + |
| " }\n" + |
| " }\n" + |
| " switch (y) {\n" + |
| " case 1:\n" + |
| " {\n" + |
| " x = false;\n" + |
| " break;\n" + |
| " }\n" + |
| " case 2:\n" + |
| " {\n" + |
| " }\n" + |
| " }\n" + |
| " synchronized (this) {\n" + |
| " x = false;\n" + |
| " }\n" + |
| " w = y + z;\n" + |
| " w = y - z;\n" + |
| " w = y * z;\n" + |
| " w = y / z;\n" + |
| " w = y % z;\n" + |
| " w = y ^ z;\n" + |
| " w = y & z;\n" + |
| " w = y | z;\n" + |
| " w = y << z;\n" + |
| " w = y >> z;\n" + |
| " w = y >>> z;\n" + |
| " f = y < z;\n" + |
| " f = y <= z;\n" + |
| " f = y > z;\n" + |
| " f = y >= z;\n" + |
| " y++;\n" + |
| " y--;\n" + |
| " ++y;\n" + |
| " --y;\n" + |
| " y += 1;\n" + |
| " y -= 1;\n" + |
| " y *= 1;\n" + |
| " y /= 1;\n" + |
| " y %= 1;\n" + |
| " y <<= 1;\n" + |
| " y >>= 1;\n" + |
| " y >>>= 1;\n" + |
| |
| " f = f && x;\n" + |
| " f = f || x;\n" + |
| " f = !f;\n" + |
| " f = y != z;\n" + |
| " y = -y;\n" + |
| " y = +y;\n" + |
| // We're stripping parentheses from the Lombok AST: |
| //" y = (y + z) * w;\n" + |
| " y = x ? y : w;\n" + |
| "\n" + |
| // Anonymous inner class |
| " Runnable r = new Runnable() {\n" + |
| " @Override\n" + |
| " public void run() {\n" + |
| " System.out.println(\"Test\");\n" + |
| " }\n" + |
| " };\n" + |
| "\n" + |
| " }\n" + |
| " \n" + |
| // Innerclass |
| " public static final class drawable {\n" + |
| // Check literals |
| " public static String s = \"This is a test\";\n" + |
| " \n" + |
| " public static int s2 = 42;\n" + |
| " \n" + |
| " public static int s2octal = 042;\n" + |
| " \n" + |
| " public static int s2hex = 0x42;\n" + |
| " \n" + |
| " public static long s3 = 42L;\n" + |
| " \n" + |
| " public static double s4 = 3.3;\n" + |
| " \n" + |
| " public static float s5 = 3.2e5f;\n" + |
| " \n" + |
| " public static char s6 = 'x';\n" + |
| " \n" + |
| " public static int icon = 0x7f020000;\n" + |
| " \n" + |
| " public static double s7 = -3.3;\n" + |
| " \n" + |
| " public static int s8 = -1;\n" + |
| " \n" + |
| " public static char s9 = 'a';\n" + |
| " }\n" + |
| "}"; |
| check(testClass, "src/p1/p2/R2.java"); |
| } |
| |
| public void testPsiToLombokConversion4() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import android.annotation.SuppressLint;\n" + |
| "import android.annotation.TargetApi;\n" + |
| "import android.os.Build;\n" + |
| "import org.w3c.dom.DOMError;\n" + |
| "import org.w3c.dom.DOMErrorHandler;\n" + |
| "import org.w3c.dom.DOMLocator;\n" + |
| "\n" + |
| "import android.view.ViewGroup.LayoutParams;\n" + |
| "import android.app.Activity;\n" + |
| "import android.app.ApplicationErrorReport;\n" + |
| "import android.app.ApplicationErrorReport.BatteryInfo;\n" + |
| "import android.graphics.PorterDuff;\n" + |
| "import android.graphics.PorterDuff.Mode;\n" + |
| "import android.widget.Chronometer;\n" + |
| "import android.widget.GridLayout;\n" + |
| "import dalvik.bytecode.OpcodeInfo;\n" + |
| "\n" + |
| "public class ApiCallTest extends Activity {\n" + |
| " @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)\n" + |
| " public void method(Chronometer chronometer, DOMLocator locator) {\n" + |
| " String s = \"/sdcard/fyfaen\";\n" + |
| " // Virtual call\n" + |
| " getActionBar(); // API 11\n" + |
| "\n" + |
| " // Class references (no call or field access)\n" + |
| " DOMError error = null; // API 8\n" + |
| " Class<?> clz = DOMErrorHandler.class; // API 8\n" + |
| "\n" + |
| " // Method call\n" + |
| " chronometer.getOnChronometerTickListener(); // API 3\n" + |
| "\n" + |
| " // Inherited method call (from TextView\n" + |
| " chronometer.setTextIsSelectable(true); // API 11\n" + |
| "\n" + |
| " // Field access\n" + |
| " int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" + |
| " int fillParent = LayoutParams.FILL_PARENT; // API 1\n" + |
| " // This is a final int, which means it gets inlined\n" + |
| " int matchParent = LayoutParams.MATCH_PARENT; // API 8\n" + |
| " // Field access: non final\n" + |
| " BatteryInfo batteryInfo = getReport().batteryInfo;\n" + |
| "\n" + |
| " // Enum access\n" + |
| " Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" + |
| " }\n" + |
| "\n" + |
| " // Return type\n" + |
| " GridLayout getGridLayout() { // API 14\n" + |
| " return null;\n" + |
| " }\n" + |
| "\n" + |
| " private ApplicationErrorReport getReport() {\n" + |
| " return null;\n" + |
| " }\n" + |
| "}\n"; |
| |
| // Parse the above file as a PSI datastructure |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/ApiCallTest.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiToLombokConversion5() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */\n" + |
| "public final class Manifest {\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/Manifest.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiToLombokConversion6() { |
| String testClass = |
| "package test.pkg;\n" + |
| "import java.util.HashMap;\n" + |
| "\n" + |
| "/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */\n" + |
| "public final class Wildcards {\n" + |
| " HashMap<Integer, Integer> s4 = new HashMap<Integer, Integer>();\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/Wildcards.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiToLombokConversion7() { |
| String testClass = |
| "package test.pkg;\n" + |
| "import java.util.HashMap;\n" + |
| "\n" + |
| "/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */\n" + |
| "public final class R3<K, V> {\n" + |
| " HashMap<Integer, Map<String, Integer>> s1 = new HashMap<Integer, Map<String, Integer>>();\n" + |
| " Map<Map<String[], List<Integer[]>>,List<String[]>>[] s2;\n" + |
| " Map<Integer, Map<String, List<Integer>>> s3;\n" + |
| " int[][] s4;\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R3.java", testClass); |
| check(file, testClass); |
| } |
| |
| // This test currently fails; need to tweak handling of whitespace around type parameters |
| //public void testPsiToLombokConversion8() { |
| // String testClass = |
| // "package test.pkg;\n" + |
| // "import java.util.HashMap;\n" + |
| // "\n" + |
| // "/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */\n" + |
| // "public final class R4 {\n" + |
| // " Object o = Collections.<Map<String,String>>emptyMap();\n" + |
| // " Object o2 = Collections.< Map < String , String>>>emptyMap();\n" + |
| // "}"; |
| // PsiFile file = myFixture.addFileToProject("src/test/pkg/R4.java", testClass); |
| // check(file, testClass); |
| //} |
| |
| public void testPsiToLombokConversion9() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public final class R5 {\n" + |
| " public void foo() {\n" + |
| " setTitleColor(android.R.color.black);\n" + |
| " setTitleColor(R.color.black);\n" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R5.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiToLombokConversion10() { |
| // Checks that annotations on variable declarations are preserved |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import android.annotation.SuppressLint;\n" + |
| "import android.annotation.TargetApi;\n" + |
| "import android.os.Build;\n" + |
| "\n" + |
| "public class SuppressTest {\n" + |
| " @SuppressLint(\"ResourceAsColor\")\n" + |
| " @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n" + |
| " private void test() {\n" + |
| " @SuppressLint(\"SdCardPath\") String s = \"/sdcard/fyfaen\";\n" + |
| " setTitleColor(android.R.color.black);\n" + |
| " }\n" + |
| "\n" + |
| " private void setTitleColor(int color) {\n" + |
| " //To change body of created methods use File | Settings | File Templates.\n" + |
| " }\n" + |
| "}\n"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/SuppressTest.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void test() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import android.annotation.SuppressLint;\n" + |
| "import android.annotation.TargetApi;\n" + |
| "import android.os.Build;\n" + |
| "\n" + |
| "public class EmptyStatementTest {\n" + |
| " public final View test(View start, Predicate<View> predicate) {\n" + |
| " View childToSkip = null;\n" + |
| " for (;;) {\n" + |
| " View view = start.findViewByPredicateTraversal(predicate, childToSkip);\n" + |
| " }\n" + |
| " }\n" + |
| "}\n"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/SuppressTest.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiToLombokConversion11() { |
| // Test Polyadic Expression |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public final class R5 {\n" + |
| " public static final int test = 5;\n" + |
| " public String getString(int id) { return \"\"; }\n" + |
| " public void foo() {\n" + |
| " String trackName = \"test\";\n" + |
| " String x = trackName + \" - \" + getString(R5.test);\n" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R5.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPsiNullValue() { |
| // From the IO scheduling app: |
| // String SESSIONS_SNIPPET = "snippet(" + Tables.SESSIONS_SEARCH + ",'{','}','\u2026')"; |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public final class R6 {\n" + |
| " public void foo() {\n" + |
| " String s = \",'{','}','\\u2026')\";\n" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R6.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testEmptyR() { |
| String testClass = |
| "/*___Generated_by_IDEA___*/\n" + |
| "\n" + |
| "package com.g.android.u;\n" + |
| "\n" + |
| "/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */\n" + |
| "public final class R {\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/com/g/android/u/R.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void test57783() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public final class R7 {\n" + |
| " public void foo() {\n" + |
| " int i = 0;\n" + |
| " int j = 0;\n" + |
| " for (i = 0; i < 10; i++)\n" + |
| " i++;" + |
| " for (i = 0, j = 0; i < 10; i++)\n" + |
| " i++;" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R7.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testSuper() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public final class R8 {\n" + |
| " public String toString() {\n" + |
| " return super.toString();\n" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R8.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPrivateEnum2() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import com.android.demo.myapplication.R;\n" + |
| "\n" + |
| "public class UnusedReference2 {\n" + |
| " public enum DocumentBulkAction {\n" + |
| " UPDATE_ALL() {\n" + |
| " @Override\n" + |
| " public int getLabelId() {\n" + |
| " return R.string.update_all;\n" + |
| " }\n" + |
| " };\n" + |
| "\n" + |
| " public abstract int getLabelId();\n" + |
| " }\n" + |
| "}\n"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/UnusedReference2.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testPrivateEnum() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import android.os.Bundle;\n" + |
| "import android.app.Activity;\n" + |
| "\n" + |
| "public class R9 extends Activity {\n" + |
| "\n" + |
| " @Override\n" + |
| " protected void onCreate(Bundle savedInstanceState) {\n" + |
| " super.onCreate(savedInstanceState);\n" + |
| " setContentView(R.layout.main);\n" + |
| " }\n" + |
| "\n" + |
| " private enum IconGridSize {\n" + |
| " NORMAL(R.layout.other);\n" + |
| "\n" + |
| " IconGridSize(int foo) {\n" + |
| " }\n" + |
| " }\n" + |
| "}\n"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R9.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testInterface() { |
| String testClass = |
| "package test.pkg;\n" + |
| "public interface R11 {\n" + |
| " int call();\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R10.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testAnnotation() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import java.lang.annotation.Retention;\n" + |
| "import java.lang.annotation.RetentionPolicy;\n" + |
| "import java.lang.annotation.Target;\n" + |
| "\n" + |
| "import static java.lang.annotation.ElementType.ANNOTATION_TYPE;\n" + |
| "import static java.lang.annotation.ElementType.FIELD;\n" + |
| "import static java.lang.annotation.ElementType.METHOD;\n" + |
| "import static java.lang.annotation.ElementType.PARAMETER;\n" + |
| "import static java.lang.annotation.RetentionPolicy.CLASS;\n" + |
| "import static java.lang.annotation.RetentionPolicy.SOURCE;\n" + |
| "@Retention(CLASS)\n" + |
| "@Target({ANNOTATION_TYPE})\n" + |
| "public @interface R11 {\n" + |
| " long[] value();\n" + |
| " boolean flag();\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R11.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testClassLiterals() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import java.lang.reflect.Field;\n" + |
| "\n" + |
| "public class R13 {\n" + |
| " private static void dumpFlags(Field field) {\n" + |
| " if (field.getType().equals(int.class)) {\n" + |
| " } else if (field.getType().equals(int[].class)) {\n" + |
| " } else if (field.getType().equals(int[][].class)) {\n" + |
| " }\n" + |
| " }\n" + |
| "}"; |
| PsiFile file = myFixture.addFileToProject("src/test/pkg/R12.java", testClass); |
| check(file, testClass); |
| } |
| |
| public void testClassDeclarationInBlockStatement() { |
| // Regression test for issue 161534 |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "public class R15 {\n" + |
| " private static void sendMessage(String message) {\n" + |
| " int x, y = 5;\n" + |
| " class MessageThread extends Thread {\n" + |
| " @Override public void run() {\n" + |
| " }\n" + |
| " }\n" + |
| " new MessageThread().start();\n" + |
| " }\n" + |
| "}\n"; |
| |
| check(testClass, "src/test/pkg/R15.java"); |
| } |
| |
| public void testJava7() { |
| String testClass = |
| "package test.pkg;\n" + |
| "\n" + |
| "import java.io.BufferedReader;\n" + |
| "import java.io.FileReader;\n" + |
| "import java.io.IOException;\n" + |
| "import java.lang.reflect.InvocationTargetException;\n" + |
| "import java.util.List;\n" + |
| "import java.util.Map;\n" + |
| "import java.util.TreeMap;\n" + |
| "\n" + |
| "public class Java7LanguageFeatureTest {\n" + |
| " public void testDiamondOperator() {\n" + |
| " Map<String, List<Integer>> map = new TreeMap<>();\n" + |
| " }\n" + |
| "\n" + |
| " public int testStringSwitches(String value) {\n" + |
| " final String first = \"first\";\n" + |
| " final String second = \"second\";\n" + |
| "\n" + |
| " switch (value) {\n" + |
| " case first:\n" + |
| " return 41;\n" + |
| " case second:\n" + |
| " return 42;\n" + |
| " default:\n" + |
| " return 0;\n" + |
| " }\n" + |
| " }\n" + |
| "\n" + |
| " public String testTryWithResources(String path) throws IOException {\n" + |
| " try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n" + |
| " return br.readLine();\n" + |
| " }\n" + |
| " }\n" + |
| "\n" + |
| " public void testNumericLiterals() {\n" + |
| " int thousand = 1_000;\n" + |
| " int million = 1_000_000;\n" + |
| " int binary = 0B01010101;\n" + |
| " }\n" + |
| "\n" + |
| " public void testMultiCatch() {\n" + |
| "\n" + |
| " try {\n" + |
| " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" + |
| " } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n" + |
| " e.printStackTrace();\n" + |
| " } catch (ClassNotFoundException e) {\n" + |
| " // TODO: Logging here\n" + |
| " }\n" + |
| " }\n" + |
| "}\n"; |
| PsiFile psiFile = myFixture.addFileToProject("src/test/pkg/R9.java", testClass); |
| |
| // Can't call check(psiFile, testClass) here; the source code won't parse |
| // with Lombok's parser since it doesn't support Java 7, so we just manually |
| // check that the LombokPsiConverter doesn't abort, and format it's view of |
| // the Lombok AST and check that it looks like what we expect; an AST containing |
| // fragments usable by lint, but not providing support for syntactic constructs |
| // such as try with resources or containing type variables where the diamond operator |
| // should have been etc. |
| assertTrue(psiFile.getClass().getName(), psiFile instanceof PsiJavaFile); |
| PsiJavaFile psiJavaFile = (PsiJavaFile)psiFile; |
| CompilationUnit node = LombokPsiConverter.convert(psiJavaFile); |
| assertNotNull(node); |
| TextFormatter formatter = new TextFormatter(); |
| node.accept(new SourcePrinter(formatter)); |
| String actual = formatter.finish(); |
| |
| assertEquals("package test.pkg;\n" + |
| "\n" + |
| "import java.io.BufferedReader;\n" + |
| "import java.io.FileReader;\n" + |
| "import java.io.IOException;\n" + |
| "import java.lang.reflect.InvocationTargetException;\n" + |
| "import java.util.List;\n" + |
| "import java.util.Map;\n" + |
| "import java.util.TreeMap;\n" + |
| "\n" + |
| "public class Java7LanguageFeatureTest {\n" + |
| " public void testDiamondOperator() {\n" + |
| " Map<String, List<Integer>> map = new TreeMap();\n" + |
| " }\n" + |
| " \n" + |
| " public int testStringSwitches(String value) {\n" + |
| " final String first = \"first\";\n" + |
| " final String second = \"second\";\n" + |
| " switch (value) {\n" + |
| " case first:\n" + |
| " return 41;\n" + |
| " case second:\n" + |
| " return 42;\n" + |
| " case :\n" + |
| " return 0;\n" + |
| " }\n" + |
| " }\n" + |
| " \n" + |
| " public String testTryWithResources(String path) throws IOException {\n" + |
| " try {\n" + |
| " return br.readLine();\n" + |
| " }\n" + |
| " }\n" + |
| " \n" + |
| " public void testNumericLiterals() {\n" + |
| " int thousand = 1_000;\n" + |
| " int million = 1_000_000;\n" + |
| " int binary = 0B01010101;\n" + |
| " }\n" + |
| " \n" + |
| " public void testMultiCatch() {\n" + |
| " try {\n" + |
| " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" + |
| " } catch (?!?INVALID_IDENTIFIER: IllegalAccessException | InvocationTargetException | NoSuchMethodException?!? e) {\n" + |
| " e.printStackTrace();\n" + |
| " } catch (ClassNotFoundException e) {\n" + |
| " }\n" + |
| " }\n" + |
| "}", |
| actual); |
| } |
| |
| private void check(VirtualFile file) { |
| assertNotNull(file); |
| assertTrue(file.exists()); |
| Project project = getProject(); |
| assertNotNull(project); |
| PsiFile psiFile = PsiManager.getInstance(project).findFile(file); |
| assertNotNull(psiFile); |
| check(psiFile, psiFile.getText()); |
| } |
| |
| private void check(@Language("JAVA") String source, String relativePath) { |
| PsiFile file = myFixture.addFileToProject(relativePath, source); |
| check(file, source); |
| } |
| |
| private static void check(PsiFile psiFile, @Language("JAVA") String source) { |
| assertTrue(psiFile.getClass().getName(), psiFile instanceof PsiJavaFile); |
| PsiJavaFile psiJavaFile = (PsiJavaFile)psiFile; |
| CompilationUnit node = LombokPsiConverter.convert(psiJavaFile); |
| assertNotNull(node); |
| |
| String actualStructure; |
| if (CHECK_POSITIONS) { |
| StructureFormatter structureFormatter = StructureFormatter.formatterWithPositions(); |
| node.accept(new SourcePrinter(structureFormatter)); |
| actualStructure = structureFormatter.finish(); |
| } |
| |
| TextFormatter formatter = new TextFormatter(); |
| node.accept(new SourcePrinter(formatter)); |
| String actual = formatter.finish(); |
| |
| Node expectedNode = parse(source); |
| assertNotNull(expectedNode); |
| |
| if (CHECK_POSITIONS) { |
| StructureFormatter structureFormatter = StructureFormatter.formatterWithPositions(); |
| expectedNode.accept(new SourcePrinter(structureFormatter)); |
| String masterStructure = structureFormatter.finish(); |
| assertEquals(masterStructure, actualStructure); |
| } |
| |
| formatter = new TextFormatter(); |
| expectedNode.accept(new SourcePrinter(formatter)); |
| String master = formatter.finish(); |
| assertEquals(master, actual); |
| } |
| |
| @Nullable |
| private static Node parse(String code) { |
| CompilerOptions options = new CompilerOptions(); |
| options.complianceLevel = options.sourceLevel = options.targetJDK = ClassFileConstants.JDK1_7; |
| options.parseLiteralExpressionsAsConstants = true; |
| ProblemReporter problemReporter = new ProblemReporter( |
| DefaultErrorHandlingPolicies.exitOnFirstError(), options, new DefaultProblemFactory()); |
| Parser parser = new Parser(problemReporter, options.parseLiteralExpressionsAsConstants); |
| parser.javadocParser.checkDocComment = false; |
| EcjTreeConverter converter = new EcjTreeConverter(); |
| org.eclipse.jdt.internal.compiler.batch.CompilationUnit sourceUnit = |
| new org.eclipse.jdt.internal.compiler.batch.CompilationUnit(code.toCharArray(), "unitTest", "UTF-8"); |
| CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0); |
| CompilationUnitDeclaration unit = parser.parse(sourceUnit, compilationResult); |
| if (unit == null) { |
| return null; |
| } |
| converter.visit(code, unit); |
| List<? extends Node> nodes = converter.getAll(); |
| for (lombok.ast.Node node : nodes) { |
| if (node instanceof lombok.ast.CompilationUnit) { |
| return node; |
| } |
| } |
| return null; |
| } |
| } |