From 34567094caa5f1b81beb0886c683a8a0c26bdfad Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Fri, 27 Mar 2015 19:20:52 +0300 Subject: [PATCH] Added simple way of detecting if line starts with continuation indent: we check whether before current line there was opened and not closed left parenthesis --- .../continuationIndents_DoNotCount.java | 537 ++++++++++++++++++ ...Indents_InMethodParameters_DoNotCount.java | 301 ++++++++++ .../autodetect/JavaAutoDetectIndentTest.java | 9 + .../ContinuationIndentDetector.java | 101 ++++ .../codeStyle/autodetect/LineIndentInfo.java | 4 +- .../autodetect/LineIndentInfoBuilder.java | 9 +- .../ContinuationIndentDetectorTest.java | 138 +++++ 7 files changed, 1096 insertions(+), 3 deletions(-) create mode 100644 java/java-tests/testData/psi/autodetect/continuationIndents_DoNotCount.java create mode 100644 java/java-tests/testData/psi/autodetect/continuationIndents_InMethodParameters_DoNotCount.java create mode 100644 platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetector.java create mode 100644 platform/lang-impl/testSources/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetectorTest.java diff --git a/java/java-tests/testData/psi/autodetect/continuationIndents_DoNotCount.java b/java/java-tests/testData/psi/autodetect/continuationIndents_DoNotCount.java new file mode 100644 index 000000000000..a2714c6652c6 --- /dev/null +++ b/java/java-tests/testData/psi/autodetect/continuationIndents_DoNotCount.java @@ -0,0 +1,537 @@ +/* + * Copyright 2000-2013 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; + +import com.intellij.lang.FileASTNode; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.source.JavaLightStubBuilder; +import com.intellij.psi.stubs.StubElement; +import com.intellij.testFramework.LightIdeaTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.util.ThrowableRunnable; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.security.SecureRandom; + +public class Test { + private static final StubBuilder NEW_BUILDER = new JavaLightStubBuilder(); + + @Override + public void setUp() throws Exception { + super.setUp(); + doTest("@interface A { int i() default 42; }\n class C { void m(int p) throws E { } }", null); // warm up + LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.HIGHEST); + } + + public void testEmpty() { + doTest("/**/", + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n"); + } + + public void testFileHeader() { + doTest("package p;\n" + + "import a/*comment to skip*/.b;\n" + + "import static c.d.*;\n" + + "import static java.util.Arrays.sort;", + + "PsiJavaFileStub [p]\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " IMPORT_STATEMENT:PsiImportStatementStub[a.b]\n" + + " IMPORT_STATIC_STATEMENT:PsiImportStatementStub[static c.d.*]\n" + + " IMPORT_STATIC_STATEMENT:PsiImportStatementStub[static java.util.Arrays.sort]\n"); + } + + public void testClassDeclaration() { + doTest("package p;" + + "class A implements I, J { }\n" + + "class B extends a/*skip*/.A { class I { } }\n" + + "@java.lang.Deprecated interface I { }\n" + + "/** @deprecated just don't use */ @interface N { }", + + "PsiJavaFileStub [p]\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=A fqn=p.A]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:I, J]\n" + + " CLASS:PsiClassStub[name=B fqn=p.B]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " TYPE_PARAMETER:PsiTypeParameter[K]\n" + + " EXTENDS_BOUND_LIST:PsiRefListStub[EXTENDS_BOUNDS_LIST:]\n" + + " TYPE_PARAMETER:PsiTypeParameter[V]\n" + + " EXTENDS_BOUND_LIST:PsiRefListStub[EXTENDS_BOUNDS_LIST:X]\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:a/*skip*/.A]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " CLASS:PsiClassStub[name=I fqn=p.B.I]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " CLASS:PsiClassStub[interface deprecatedA name=I fqn=p.I]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=5120]\n" + + " ANNOTATION:PsiAnnotationStub[@java.lang.Deprecated]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " CLASS:PsiClassStub[interface annotation deprecated name=N fqn=p.N]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=5120]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n"); + } + + public void testMethods() { + doTest("public @interface Anno {\n" + + " int i() default 42;\n" + + " public static String s();\n" + + "}\n" + + "private class C {\n" + + " public C() throws Exception { }\n" + + " public abstract void m(final int i, int[] a1, int a2[], int[] a3[]);\n" + + " private static int v2a(int... v) [] { return v; }\n" + + "}\n" + + "interface I {\n" + + " void m1();\n" + + " default void m2() { }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[interface annotation name=Anno fqn=Anno]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1025]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " ANNOTATION_METHOD:PsiMethodStub[annotation i:int default=42]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1025]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " ANNOTATION_METHOD:PsiMethodStub[annotation s:String]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1033]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=2]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[cons C:null]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:Exception]\n" + + " METHOD:PsiMethodStub[m:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1025]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " PARAMETER:PsiParameterStub[i:int]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4112]\n" + + " PARAMETER:PsiParameterStub[a1:int[]]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " PARAMETER:PsiParameterStub[a2:int[]]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " PARAMETER:PsiParameterStub[a3:int[][]]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " METHOD:PsiMethodStub[varargs v2a:int[]]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=10]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " PARAMETER:PsiParameterStub[v:int...]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " CLASS:PsiClassStub[interface name=I fqn=I]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=5120]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[m1:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1025]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " METHOD:PsiMethodStub[m2:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=513]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + public void testFields() { + doTest("static class C {\n" + + " strictfp float f;\n" + + " int j[] = {0}, k;\n" + + " static String s = \"-\";\n" + + "}\n" + + "public class D {\n" + + " private volatile boolean b;\n" + + " public final double x;\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4104]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " FIELD:PsiFieldStub[f:float]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=6144]\n" + + " FIELD:PsiFieldStub[j:int[]={0}]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " FIELD:PsiFieldStub[k:int]\n" + + " FIELD:PsiFieldStub[s:String=\"-\"]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4104]\n" + + " CLASS:PsiClassStub[name=D fqn=D]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " FIELD:PsiFieldStub[b:boolean]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=66]\n" + + " FIELD:PsiFieldStub[x:double]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=17]\n"); + } + + public void testAnonymousClasses() { + doTest("class C { {\n" + + " new O.P() { };\n" + + " X.new Y() { };\n" + + " f(p -> new R() { });\n" + + "} }", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " CLASS_INITIALIZER:PsiClassInitializerStub\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=O.P]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=Y inqualifnew]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=R]\n"); + } + + public void testEnums() { + doTest("enum E {\n" + + " E1() { };\n" + + " abstract void m();\n" + + "}\n" + + "public enum U { U1, U2 }", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[enum name=E fqn=E]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=5120]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " ENUM_CONSTANT:PsiFieldStub[enumconst E1:E]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=25]\n" + + " ENUM_CONSTANT_INITIALIZER:PsiClassStub[anonymous enumInit name=null fqn=null baseref=E]\n" + + " METHOD:PsiMethodStub[m:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=5120]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " CLASS:PsiClassStub[enum name=U fqn=U]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=17]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " ENUM_CONSTANT:PsiFieldStub[enumconst U1:U]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=25]\n" + + " ENUM_CONSTANT:PsiFieldStub[enumconst U2:U]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=25]\n"); + } + + public void testLocalVariables() { + doTest("class C {\n" + + " void m() {\n" + + " int local = 0;\n" + + " Object r = new Runnable() {\n" + + " public void run() { }\n" + + " };\n" + + " for (int loop = 0; loop < 10; loop++) ;\n" + + " try (Resource r = new Resource()) { }\n" + + " try (Resource r = new Resource() {\n" + + " @Override public void close() { }\n" + + " }) { }\n" + + " }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " METHOD:PsiMethodStub[m:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=Runnable]\n" + + " METHOD:PsiMethodStub[run:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=Resource]\n" + + " METHOD:PsiMethodStub[close:void]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1]\n" + + " ANNOTATION:PsiAnnotationStub[@Override]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + public void testNonListParameters() { + doTest("class C {\n" + + " {\n" + + " for (int i : arr) ;\n" + + " for (String s : new Iterable() {\n" + + " @Override public Iterator iterator() { return null; }\n" + + " }) ;\n" + + " try { }\n" + + " catch (Throwable t) { }\n" + + " catch (E1|E2 e) { }\n" + + " }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " CLASS_INITIALIZER:PsiClassInitializerStub\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANONYMOUS_CLASS:PsiClassStub[anonymous name=null fqn=null baseref=Iterable]\n" + + " METHOD:PsiMethodStub[iterator:Iterator]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=1]\n" + + " ANNOTATION:PsiAnnotationStub[@Override]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + public void testAnnotations() { + doTest("@Deprecated\n" + + "@SuppressWarnings(\"UnusedDeclaration\")\n" + + "class Foo { }", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[deprecatedA name=Foo fqn=Foo]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANNOTATION:PsiAnnotationStub[@Deprecated]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@SuppressWarnings(\"UnusedDeclaration\")]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " NAME_VALUE_PAIR:PsiNameValuePairStubImpl\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n"); + } + + public void testTypeAnnotations() { + doTest("import j.u.@A C;\n" + + "import @A j.u.D;\n" + + "\n" + + "class C<@A T extends @A C> implements @A I<@A T> {\n" + + " @TA T<@A T1, @A ? extends @A T2> f;\n" + + " @TA T m(/*@A C this,*/ @TA int p) throws @A E {\n" + + " o.<@A1 C>m();\n" + + " new @A2 C();\n" + + " C.@A3 B v = (@A4 C)v.new @A5 C();\n" + + " m(@A6 C::m);\n" + + " @A7 T @A8[] @A9[] a = new @A7 T @A8[0] @A9[0];\n" + + " }\n" + + " int @A [] v() @A [] { }\n" + + "}", + + "PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " IMPORT_STATEMENT:PsiImportStatementStub[j.u.C]\n" + + " IMPORT_STATEMENT:PsiImportStatementStub[j.u.D]\n" + + " CLASS:PsiClassStub[name=C fqn=C]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " TYPE_PARAMETER:PsiTypeParameter[T]\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " EXTENDS_BOUND_LIST:PsiRefListStub[EXTENDS_BOUNDS_LIST:@A C]\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:@A I<@A T>]\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " FIELD:PsiFieldStub[f:T<@A T1, @A ? extends @A T2>]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANNOTATION:PsiAnnotationStub[@TA]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " METHOD:PsiMethodStub[m:T]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANNOTATION:PsiAnnotationStub[@TA]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " PARAMETER:PsiParameterStub[p:int]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " ANNOTATION:PsiAnnotationStub[@TA]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:@A E]\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A1]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A2]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A3]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A4]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A5]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A6]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A7]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A8]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A9]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A7]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A8]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " ANNOTATION:PsiAnnotationStub[@A9]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " METHOD:PsiMethodStub[v:int[][]]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " PARAMETER_LIST:PsiParameterListStub\n" + + " ANNOTATION:PsiAnnotationStub[@A]\n" + + " ANNOTATION_PARAMETER_LIST:PsiAnnotationParameterListStubImpl\n" + + " THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n"); + } + + public void testSOEProof() { + final StringBuilder sb = new StringBuilder(); + final SecureRandom random = new SecureRandom(); + sb.append("class SOE_test {\n BigInteger BIG = new BigInteger(\n"); + int i; + for (i = 0; i < 100000; i++) { + sb.append(" \"").append(Math.abs(random.nextInt())).append("\" +\n"); + } + sb.append(" \"\");\n}"); + + final PsiJavaFile file = (PsiJavaFile)createLightFile("SOE_test.java", sb.toString()); + long t = System.currentTimeMillis(); + final StubElement tree = NEW_BUILDER.buildStubTree(file); + t = System.currentTimeMillis() - t; + assertEquals("PsiJavaFileStub []\n" + + " IMPORT_LIST:PsiImportListStub\n" + + " CLASS:PsiClassStub[name=SOE_test fqn=SOE_test]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" + + " TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" + + " EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" + + " IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" + + " FIELD:PsiFieldStub[BIG:BigInteger=;INITIALIZER_NOT_STORED;]\n" + + " MODIFIER_LIST:PsiModifierListStub[mask=4096]\n", + DebugUtil.stubTreeToString(tree)); + System.out.println("SOE depth=" + i + ", time=" + t + "ms"); + } + + public void testPerformance() throws Exception { + final String path = PathManagerEx.getTestDataPath() + "/psi/stub/StubPerformanceTest.java"; + String text = FileUtil.loadFile(new File(path)); + final PsiJavaFile file = (PsiJavaFile)createLightFile("test.java", text); + + PlatformTestUtil.startPerformanceTest("Source file size: " + text.length(), 2000, new ThrowableRunnable() { + @Override + public void run() throws Exception { + NEW_BUILDER.buildStubTree(file); + } + }).cpuBound().assertTiming(); + } + + private static void doTest(@NonNls final String source, @NonNls @Nullable final String tree) { + final PsiJavaFile file = (PsiJavaFile)createLightFile("test.java", source); + final FileASTNode fileNode = file.getNode(); + assertNotNull(fileNode); + assertFalse(fileNode.isParsed()); + + long t1 = System.nanoTime(); + final StubElement lighterTree = NEW_BUILDER.buildStubTree(file); + t1 = Math.max((System.nanoTime() - t1)/1000, 1); + assertFalse(fileNode.isParsed()); + + file.getNode().getChildren(null); // force switch to AST + + long t2 = System.nanoTime(); + final StubElement lighterTree2 = NEW_BUILDER.buildStubTree(file); // build over AST + t2 = Math.max((System.nanoTime() - t2)/1000, 1); + + file.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + assert !(element instanceof PsiErrorElement) : element; + super.visitElement(element); + } + }); + + final String lightStr = DebugUtil.stubTreeToString(lighterTree); + final String lightStr2 = DebugUtil.stubTreeToString(lighterTree2); + if (tree != null) { + System.out.println("light=" + t1 + "mks, heavy=" + t2 + "mks"); + if (!tree.isEmpty()) { + assertEquals("light tree differs", tree, lightStr); + assertEquals("light tree (2nd) differs", tree, lightStr2); + } + } + } +} diff --git a/java/java-tests/testData/psi/autodetect/continuationIndents_InMethodParameters_DoNotCount.java b/java/java-tests/testData/psi/autodetect/continuationIndents_InMethodParameters_DoNotCount.java new file mode 100644 index 000000000000..1a42f6d92b00 --- /dev/null +++ b/java/java-tests/testData/psi/autodetect/continuationIndents_InMethodParameters_DoNotCount.java @@ -0,0 +1,301 @@ +import java.util.Arrays; +import java.util.List; + +public final class TimeContentItemHelper { + + public static List TIME_PERIOD_1_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD1_1, + TimePeriods.TIME_PERIOD1_2, + TimePeriods.TIME_PERIOD1_3, + TimePeriods.TIME_PERIOD1_4, + TimePeriods.TIME_PERIOD1_5, + TimePeriods.TIME_PERIOD1_6 + ); + + public static List TIME_PERIOD_2_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD2_1, + TimePeriods.TIME_PERIOD2_2, + TimePeriods.TIME_PERIOD2_3, + TimePeriods.TIME_PERIOD2_4, + TimePeriods.TIME_PERIOD2_5, + TimePeriods.TIME_PERIOD2_6 + ); + + public static List TIME_PERIOD_3_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD3_1, + TimePeriods.TIME_PERIOD3_2, + TimePeriods.TIME_PERIOD3_3, + TimePeriods.TIME_PERIOD3_4, + TimePeriods.TIME_PERIOD3_5, + TimePeriods.TIME_PERIOD3_6 + ); + + public static List TIME_PERIOD_4_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD4_1, + TimePeriods.TIME_PERIOD4_2, + TimePeriods.TIME_PERIOD4_3, + TimePeriods.TIME_PERIOD4_4, + TimePeriods.TIME_PERIOD4_5, + TimePeriods.TIME_PERIOD4_6 + ); + + public static List TIME_PERIOD_5_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD5_1, + TimePeriods.TIME_PERIOD5_2, + TimePeriods.TIME_PERIOD5_3, + TimePeriods.TIME_PERIOD5_4, + TimePeriods.TIME_PERIOD5_5, + TimePeriods.TIME_PERIOD5_6 + ); + + public static List TIME_PERIOD_6_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_PERIOD6_1, + TimePeriods.TIME_PERIOD6_2, + TimePeriods.TIME_PERIOD6_3, + TimePeriods.TIME_PERIOD6_4, + TimePeriods.TIME_PERIOD6_5, + TimePeriods.TIME_PERIOD6_6 + ); + + public static List> TIME_PERIOD_CONTENT_ITEM_KEY_MATRIX = Arrays.asList( + TIME_PERIOD_1_CONTENT_ITEM_KEYS, + TIME_PERIOD_2_CONTENT_ITEM_KEYS, + TIME_PERIOD_3_CONTENT_ITEM_KEYS, + TIME_PERIOD_4_CONTENT_ITEM_KEYS, + TIME_PERIOD_5_CONTENT_ITEM_KEYS, + TIME_PERIOD_6_CONTENT_ITEM_KEYS + ); + + public static List TIME_AVERAGE_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE1, + TimePeriods.TIME_AVERAGE2, + TimePeriods.TIME_AVERAGE3, + TimePeriods.TIME_AVERAGE4, + TimePeriods.TIME_AVERAGE5, + TimePeriods.TIME_AVERAGE6 + ); + + public static List TIME_ROOM_DESCRIPTION_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_DESCRIPTION1, + TimePeriods.TIME_AVERAGE_DESCRIPTION2, + TimePeriods.TIME_AVERAGE_DESCRIPTION3, + TimePeriods.TIME_AVERAGE_DESCRIPTION4, + TimePeriods.TIME_AVERAGE_DESCRIPTION5, + TimePeriods.TIME_AVERAGE_DESCRIPTION6 + ); + + public static List TIME_1_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE1_1, + TimePeriods.TIME_AVERAGE_PRICE1_2, + TimePeriods.TIME_AVERAGE_PRICE1_3, + TimePeriods.TIME_AVERAGE_PRICE1_4, + TimePeriods.TIME_AVERAGE_PRICE1_5, + TimePeriods.TIME_AVERAGE_PRICE1_6 + ); + + public static List TIME_2_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE2_1, + TimePeriods.TIME_AVERAGE_PRICE2_2, + TimePeriods.TIME_AVERAGE_PRICE2_3, + TimePeriods.TIME_AVERAGE_PRICE2_4, + TimePeriods.TIME_AVERAGE_PRICE2_5, + TimePeriods.TIME_AVERAGE_PRICE2_6 + ); + + public static List TIME_3_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE3_1, + TimePeriods.TIME_AVERAGE_PRICE3_2, + TimePeriods.TIME_AVERAGE_PRICE3_3, + TimePeriods.TIME_AVERAGE_PRICE3_4, + TimePeriods.TIME_AVERAGE_PRICE3_5, + TimePeriods.TIME_AVERAGE_PRICE3_6 + ); + + public static List TIME_4_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE4_1, + TimePeriods.TIME_AVERAGE_PRICE4_2, + TimePeriods.TIME_AVERAGE_PRICE4_3, + TimePeriods.TIME_AVERAGE_PRICE4_4, + TimePeriods.TIME_AVERAGE_PRICE4_5, + TimePeriods.TIME_AVERAGE_PRICE4_6 + ); + + public static List TIME_5_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE5_1, + TimePeriods.TIME_AVERAGE_PRICE5_2, + TimePeriods.TIME_AVERAGE_PRICE5_3, + TimePeriods.TIME_AVERAGE_PRICE5_4, + TimePeriods.TIME_AVERAGE_PRICE5_5, + TimePeriods.TIME_AVERAGE_PRICE5_6 + ); + + public static List TIME_6_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_AVERAGE_PRICE6_1, + TimePeriods.TIME_AVERAGE_PRICE6_2, + TimePeriods.TIME_AVERAGE_PRICE6_3, + TimePeriods.TIME_AVERAGE_PRICE6_4, + TimePeriods.TIME_AVERAGE_PRICE6_5, + TimePeriods.TIME_AVERAGE_PRICE6_6 + ); + + public static List> TIME_CONTENT_ITEM_KEY_MATRIX = Arrays.asList( + TIME_1_CONTENT_ITEM_KEYS, + TIME_2_CONTENT_ITEM_KEYS, + TIME_3_CONTENT_ITEM_KEYS, + TIME_4_CONTENT_ITEM_KEYS, + TIME_5_CONTENT_ITEM_KEYS, + TIME_6_CONTENT_ITEM_KEYS + ); + + public static List TIME_NOTE_LABELS_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_NOTE_LABEL_1, + TimePeriods.TIME_NOTE_LABEL_2, + TimePeriods.TIME_NOTE_LABEL_3, + TimePeriods.TIME_NOTE_LABEL_4, + TimePeriods.TIME_NOTE_LABEL_5, + TimePeriods.TIME_NOTE_LABEL_6, + TimePeriods.TIME_NOTE_LABEL_7, + TimePeriods.TIME_NOTE_LABEL_8, + TimePeriods.TIME_NOTE_LABEL_9, + TimePeriods.TIME_NOTE_LABEL_10 + ); + + public static List TIME_NOTES_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIME_NOTE_1, + TimePeriods.TIME_NOTE_2, + TimePeriods.TIME_NOTE_3, + TimePeriods.TIME_NOTE_4, + TimePeriods.TIME_NOTE_5, + TimePeriods.TIME_NOTE_6, + TimePeriods.TIME_NOTE_7, + TimePeriods.TIME_NOTE_8, + TimePeriods.TIME_NOTE_9, + TimePeriods.TIME_NOTE_10 + ); + + public static List TIME_CONTENT_ITEM_KEYS = Arrays.asList( + TimePeriods.TIMES_ENABLED, + TimePeriods.TIMES_COUNT_OF_TIMES, + TimePeriods.TIMES_COUNT_OF_TIME_PERIODS, + TimePeriods.TIMES_COUNT_OF_AVERAGES, + TimePeriods.TIME_NAME1, + TimePeriods.TIME_NAME2, + TimePeriods.TIME_NAME3, + TimePeriods.TIME_NAME4, + TimePeriods.TIME_NAME5, + TimePeriods.TIME_NAME6, + TimePeriods.TIME_PERIOD1_1, + TimePeriods.TIME_PERIOD1_2, + TimePeriods.TIME_PERIOD1_3, + TimePeriods.TIME_PERIOD1_4, + TimePeriods.TIME_PERIOD1_5, + TimePeriods.TIME_PERIOD1_6, + TimePeriods.TIME_PERIOD2_1, + TimePeriods.TIME_PERIOD2_2, + TimePeriods.TIME_PERIOD2_3, + TimePeriods.TIME_PERIOD2_4, + TimePeriods.TIME_PERIOD2_5, + TimePeriods.TIME_PERIOD2_6, + TimePeriods.TIME_PERIOD3_1, + TimePeriods.TIME_PERIOD3_2, + TimePeriods.TIME_PERIOD3_3, + TimePeriods.TIME_PERIOD3_4, + TimePeriods.TIME_PERIOD3_5, + TimePeriods.TIME_PERIOD3_6, + TimePeriods.TIME_PERIOD4_1, + TimePeriods.TIME_PERIOD4_2, + TimePeriods.TIME_PERIOD4_3, + TimePeriods.TIME_PERIOD4_4, + TimePeriods.TIME_PERIOD4_5, + TimePeriods.TIME_PERIOD4_6, + TimePeriods.TIME_PERIOD5_1, + TimePeriods.TIME_PERIOD5_2, + TimePeriods.TIME_PERIOD5_3, + TimePeriods.TIME_PERIOD5_4, + TimePeriods.TIME_PERIOD5_5, + TimePeriods.TIME_PERIOD5_6, + TimePeriods.TIME_PERIOD6_1, + TimePeriods.TIME_PERIOD6_2, + TimePeriods.TIME_PERIOD6_3, + TimePeriods.TIME_PERIOD6_4, + TimePeriods.TIME_PERIOD6_5, + TimePeriods.TIME_PERIOD6_6, + TimePeriods.TIME_AVERAGE1, + TimePeriods.TIME_AVERAGE2, + TimePeriods.TIME_AVERAGE3, + TimePeriods.TIME_AVERAGE4, + TimePeriods.TIME_AVERAGE5, + TimePeriods.TIME_AVERAGE6, + TimePeriods.TIME_AVERAGE_PRICE1_1, + TimePeriods.TIME_AVERAGE_PRICE1_2, + TimePeriods.TIME_AVERAGE_PRICE1_3, + TimePeriods.TIME_AVERAGE_PRICE1_4, + TimePeriods.TIME_AVERAGE_PRICE1_5, + TimePeriods.TIME_AVERAGE_PRICE1_6, + TimePeriods.TIME_AVERAGE_PRICE2_1, + TimePeriods.TIME_AVERAGE_PRICE2_2, + TimePeriods.TIME_AVERAGE_PRICE2_3, + TimePeriods.TIME_AVERAGE_PRICE2_4, + TimePeriods.TIME_AVERAGE_PRICE2_5, + TimePeriods.TIME_AVERAGE_PRICE2_6, + TimePeriods.TIME_AVERAGE_PRICE3_1, + TimePeriods.TIME_AVERAGE_PRICE3_2, + TimePeriods.TIME_AVERAGE_PRICE3_3, + TimePeriods.TIME_AVERAGE_PRICE3_4, + TimePeriods.TIME_AVERAGE_PRICE3_5, + TimePeriods.TIME_AVERAGE_PRICE3_6, + TimePeriods.TIME_AVERAGE_PRICE4_1, + TimePeriods.TIME_AVERAGE_PRICE4_2, + TimePeriods.TIME_AVERAGE_PRICE4_3, + TimePeriods.TIME_AVERAGE_PRICE4_4, + TimePeriods.TIME_AVERAGE_PRICE4_5, + TimePeriods.TIME_AVERAGE_PRICE4_6, + TimePeriods.TIME_AVERAGE_PRICE5_1, + TimePeriods.TIME_AVERAGE_PRICE5_2, + TimePeriods.TIME_AVERAGE_PRICE5_3, + TimePeriods.TIME_AVERAGE_PRICE5_4, + TimePeriods.TIME_AVERAGE_PRICE5_5, + TimePeriods.TIME_AVERAGE_PRICE5_6, + TimePeriods.TIME_AVERAGE_PRICE6_1, + TimePeriods.TIME_AVERAGE_PRICE6_2, + TimePeriods.TIME_AVERAGE_PRICE6_3, + TimePeriods.TIME_AVERAGE_PRICE6_4, + TimePeriods.TIME_AVERAGE_PRICE6_5, + TimePeriods.TIME_AVERAGE_PRICE6_6, + TimePeriods.TIME_AVERAGE_DESCRIPTION1, + TimePeriods.TIME_AVERAGE_DESCRIPTION2, + TimePeriods.TIME_AVERAGE_DESCRIPTION3, + TimePeriods.TIME_AVERAGE_DESCRIPTION4, + TimePeriods.TIME_AVERAGE_DESCRIPTION5, + TimePeriods.TIME_AVERAGE_DESCRIPTION6, + TimePeriods.TIME_NOTE_LABEL_1, + TimePeriods.TIME_NOTE_LABEL_2, + TimePeriods.TIME_NOTE_LABEL_3, + TimePeriods.TIME_NOTE_LABEL_4, + TimePeriods.TIME_NOTE_LABEL_5, + TimePeriods.TIME_NOTE_LABEL_6, + TimePeriods.TIME_NOTE_LABEL_7, + TimePeriods.TIME_NOTE_LABEL_8, + TimePeriods.TIME_NOTE_LABEL_9, + TimePeriods.TIME_NOTE_LABEL_10, + TimePeriods.TIME_NOTE_1, + TimePeriods.TIME_NOTE_2, + TimePeriods.TIME_NOTE_3, + TimePeriods.TIME_NOTE_4, + TimePeriods.TIME_NOTE_5, + TimePeriods.TIME_NOTE_6, + TimePeriods.TIME_NOTE_7, + TimePeriods.TIME_NOTE_8, + TimePeriods.TIME_NOTE_9, + TimePeriods.TIME_NOTE_10 + ); + + public static boolean check(TimePeriods key) { + int keyI = key.getIntValue(); + if (keyI < 0) { + return false; + } else { + return true; + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/autodetect/JavaAutoDetectIndentTest.java b/java/java-tests/testSrc/com/intellij/psi/autodetect/JavaAutoDetectIndentTest.java index e3600a290bea..5f75745352e5 100644 --- a/java/java-tests/testSrc/com/intellij/psi/autodetect/JavaAutoDetectIndentTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/autodetect/JavaAutoDetectIndentTest.java @@ -36,4 +36,13 @@ public class JavaAutoDetectIndentTest extends AbstractIndentAutoDetectionTest { public void testNotIndentedComment() { doTestIndentSize(3); } + + public void testContinuationIndents_DoNotCount() { + doTestIndentSize(2); + } + + public void testContinuationIndents_InMethodParameters_DoNotCount() { + doTestIndentSize(4); + } + } diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetector.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetector.java new file mode 100644 index 000000000000..efc82583cb42 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetector.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2015 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.codeStyle.autodetect; + +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +class ContinuationIndentDetector { + private List myBrackets = ContainerUtil.newArrayList(); + + private final CharSequence myText; + private final int myLength; + private boolean myStackCorrupted = false; + + public ContinuationIndentDetector(@NotNull CharSequence text) { + myText = text; + myLength = text.length(); + } + + public void feedLineStartingAt(int startOffset) { + if (myStackCorrupted) return; + + int lineEndOffset = getLineEndOffset(startOffset); + + for (int i = startOffset; i < lineEndOffset; i++) { + char c = myText.charAt(i); + Bracket bracket = Bracket.forChar(c); + if (bracket == null) continue; + + if (bracket.isOpening()) { + myBrackets.add(bracket); + } + else { + if (myBrackets.isEmpty()) { + myStackCorrupted = true; + } + myBrackets.remove(myBrackets.size() - 1); + } + } + } + + public boolean isContinuationIndent(int lineStartOffset) { + if (myStackCorrupted || myBrackets.isEmpty()) { + return false; + } + int textStartOffset = CharArrayUtil.shiftForward(myText, lineStartOffset, " \t"); + + for (int i = myBrackets.size() - 1; i >= 0; i--) { + if (myBrackets.get(i) == Bracket.LPARENTH && myText.charAt(textStartOffset) != ')') return true; + } + + return false; + } + + private int getLineEndOffset(int lineStartOffset) { + int lineEndOffset = CharArrayUtil.indexOf(myText, "\n", lineStartOffset, myLength); + return lineEndOffset < 0 ? myLength : lineEndOffset; + } + + private enum Bracket { + LBRACE('{', true), + LPARENTH('(', true), + RBRACE('}', false), + RPARENTH(')', false); + + private final char myChar; + private boolean myIsOpeningBracket; + + Bracket(char c, boolean isOpeningBracket) { + myIsOpeningBracket = isOpeningBracket; + myChar = c; + } + + boolean isOpening() { + return myIsOpeningBracket; + } + + static Bracket forChar(char c) { + for (Bracket bracket : Bracket.values()) { + if (bracket.myChar == c) return bracket; + } + return null; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java index d85a4cbcaa0e..2532a6df3629 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java @@ -21,6 +21,7 @@ public class LineIndentInfo { public static final LineIndentInfo EMPTY_LINE = new LineIndentInfo(LineType.EMPTY_LINE, -1); public static final LineIndentInfo LINE_WITH_COMMENT = new LineIndentInfo(LineType.LINE_WITH_COMMENT, -1); public static final LineIndentInfo LINE_WITH_TABS = new LineIndentInfo(LineType.LINE_WITH_TABS, -1); + public static final LineIndentInfo LINE_WITH_CONTINUATION_INDENT = new LineIndentInfo(LineType.LINE_WITH_CONTINUATION_INDENT, -1); private final int myIndentSize; private final LineType myType; @@ -51,6 +52,7 @@ public class LineIndentInfo { EMPTY_LINE, LINE_WITH_COMMENT, LINE_WITH_TABS, - LINE_WITH_WHITESPACE_INDENT + LINE_WITH_WHITESPACE_INDENT, + LINE_WITH_CONTINUATION_INDENT } } diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java index 6df9580e9e88..5532f64f9f20 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java @@ -31,10 +31,12 @@ public class LineIndentInfoBuilder { private final CharSequence myText; private final int myLength; private final Commenter myCommenter; + private ContinuationIndentDetector myContinuationIndentDetector; public LineIndentInfoBuilder(@NotNull CharSequence text, @Nullable Language language) { myText = text; myLength = text.length(); + myContinuationIndentDetector = new ContinuationIndentDetector(text); myCommenter = language != null ? LanguageCommenters.INSTANCE.forLanguage(language) : null; } @@ -68,11 +70,14 @@ public class LineIndentInfoBuilder { return LineIndentInfo.LINE_WITH_COMMENT; } else if (CharArrayUtil.indexOf(myText, "\t", lineStartOffset, textStartOffset) > 0) { + myContinuationIndentDetector.feedLineStartingAt(lineStartOffset); return LineIndentInfo.LINE_WITH_TABS; } else { - int indentSize = textStartOffset - lineStartOffset; - return LineIndentInfo.newWhiteSpaceIndent(indentSize); + boolean isContinuationIndent = myContinuationIndentDetector.isContinuationIndent(lineStartOffset); + myContinuationIndentDetector.feedLineStartingAt(lineStartOffset); + return isContinuationIndent ? LineIndentInfo.LINE_WITH_CONTINUATION_INDENT + : LineIndentInfo.newWhiteSpaceIndent(textStartOffset - lineStartOffset); } } diff --git a/platform/lang-impl/testSources/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetectorTest.java b/platform/lang-impl/testSources/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetectorTest.java new file mode 100644 index 000000000000..6e18c400ccf2 --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/psi/codeStyle/autodetect/ContinuationIndentDetectorTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2000-2015 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.codeStyle.autodetect; + +import com.intellij.util.containers.ContainerUtil; +import junit.framework.TestCase; + +import java.util.List; +import java.util.Set; + + +public class ContinuationIndentDetectorTest extends TestCase { + private ContinuationIndentDetector myContinuationIndentDetector; + private List myLineStartOffsets; + + public void configure(String text) { + myContinuationIndentDetector = new ContinuationIndentDetector(text); + myLineStartOffsets = calculateLineStartOffsets(text); + } + + public void testContinuationIndentInsideParenth() { + configure( + "test(\n" + + " \"aaaaa\" + \n" + + " \"bbbbb\");\n" + + "int a = 2;" + ); + + doTest(1, 2); + } + + public void testNoContinuationIndent_OnLineStartingWithRightParenth() { + configure( + "test(\n" + + " \"aaaaa\" + \n" + + " \"bbbbb\"\n" + + ");" + ); + + doTest(1, 2); + } + + public void testNoContinuationIndents_BetweenBraces() { + configure( + "import java.lang.Override;\n" + + "import java.lang.Runnable;\n" + + "\n" + + "class T {\n" + + " \n" + + " void test() {\n" + + " int a;\n" + + " int b;\n" + + " Runnable runnable = new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " System.out.println(\"AAA!\");\n" + + " }\n" + + " }\n" + + " }\n" + + " \n" + + "}" + ); + doTest(); + } + + public void testMixedBraces() { + configure( + "import java.lang.Runnable;\n" + + "\n" + + "class R {\n" + + "\n" + + "\n" + + " void test() {\n" + + " kuu(new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " }\n" + + " },\n" + + " new Runnable() {\n" + + " @Override\n" + + " public void run() {\n" + + " }\n" + + " }\n" + + " );\n" + + " }\n" + + "\n" + + " void kuu(Runnable a, Runnable b) {\n" + + " }\n" + + "}" + ); + doTest(7, 8, 9, 10, 11, 12, 13, 14, 15); + } + + + private void doTest(Integer ...linesWithContinuationIndents) { + Set continuationLines = ContainerUtil.newHashSet(linesWithContinuationIndents); + + for (int currentLine = 0; currentLine < myLineStartOffsets.size(); currentLine++) { + Integer lineStartOffset = myLineStartOffsets.get(currentLine); + boolean isContinuation = myContinuationIndentDetector.isContinuationIndent(lineStartOffset); + if (continuationLines.contains(currentLine)) { + assertTrue("Line " + currentLine + " should start with continuation indent", isContinuation); + } + else { + assertTrue("Line " + currentLine + " should not start with continuation indent" ,!isContinuation); + } + myContinuationIndentDetector.feedLineStartingAt(lineStartOffset); + } + } + + + private List calculateLineStartOffsets(String text) { + List lineStartOffsets = ContainerUtil.newArrayList(); + lineStartOffsets.add(0); + + int lineStart = 0; + int lineFeed; + while ((lineFeed = text.indexOf('\n', lineStart)) > 0) { + lineStart = lineFeed + 1; + lineStartOffsets.add(lineStart); + } + + return lineStartOffsets; + } +}