[groovy] add support for Geb content definition based methods

This commit is contained in:
Marcin Erdmann
2018-03-23 18:22:13 +03:00
committed by Daniil Ovchinnikov
parent 6d0dde7eaf
commit ee1b8df58d
4 changed files with 113 additions and 98 deletions
@@ -1,25 +1,11 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.geb;
import com.intellij.pom.PomDeclarationSearcher;
import com.intellij.pom.PomTarget;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.Consumer;
@@ -52,11 +38,11 @@ public class GebContentDeclarationSearcher extends PomDeclarationSearcher {
if (!InheritanceUtil.isInheritor(containingClass, "geb.Page")
&& !InheritanceUtil.isInheritor(containingClass, "geb.Module")) return;
Map<String, PsiField> elements = GebUtil.getContentElements(containingClass);
Map<String, PsiMember> contentElements = GebUtil.getContentElements(containingClass);
for (PsiField f : elements.values()) {
if (f.getNavigationElement() == element) {
consumer.consume(f);
for (PsiMember contentElement : contentElements.values()) {
if (contentElement.getNavigationElement() == element) {
consumer.consume(contentElement);
return;
}
}
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.geb;
import com.intellij.psi.*;
@@ -54,10 +40,10 @@ public class GebPageMemberContributor extends NonCodeMembersContributor {
if (contentField instanceof GrField) {
GrField f = (GrField)contentField;
if ("content".equals(f.getName()) && f.hasModifierProperty(PsiModifier.STATIC) && f.getContainingClass() == aClass) {
Map<String, PsiField> elements = GebUtil.getContentElements(aClass);
for (PsiField field : elements.values()) {
if (field.getNavigationElement() == place) {
return; // Don't resolve variable definition.
Map<String, PsiMember> elements = GebUtil.getContentElements(aClass);
for (PsiMember element : elements.values()) {
if (element.getNavigationElement() == place) {
return; // Don't resolve definition.
}
}
}
@@ -65,27 +51,27 @@ public class GebPageMemberContributor extends NonCodeMembersContributor {
}
}
processPageFields(processor, aClass, state);
processPageElements(processor, aClass, state);
}
public static boolean processPageFields(PsiScopeProcessor processor,
@NotNull PsiClass pageClass,
ResolveState state) {
public static boolean processPageElements(PsiScopeProcessor processor,
@NotNull PsiClass pageClass,
ResolveState state) {
Map<String, PsiClass> supers = ClassUtil.getSuperClassesWithCache(pageClass);
String nameHint = ResolveUtil.getNameHint(processor);
for (PsiClass psiClass : supers.values()) {
Map<String, PsiField> contentFields = GebUtil.getContentElements(psiClass);
Map<String, PsiMember> contentElements = GebUtil.getContentElements(psiClass);
if (nameHint == null) {
for (Map.Entry<String, PsiField> entry : contentFields.entrySet()) {
for (Map.Entry<String, PsiMember> entry : contentElements.entrySet()) {
if (!processor.execute(entry.getValue(), state)) return false;
}
}
else {
PsiField field = contentFields.get(nameHint);
if (field != null) {
return processor.execute(field, state);
PsiMember element = contentElements.get(nameHint);
if (element != null) {
return processor.execute(element, state);
}
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.geb;
import com.intellij.psi.*;
@@ -25,7 +11,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightField;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.util.LightCacheKey;
@@ -38,7 +26,7 @@ import java.util.Map;
*/
public class GebUtil {
private static final LightCacheKey<Map<String, PsiField>> KEY = LightCacheKey.createByFileModificationCount();
private static final LightCacheKey<Map<String, PsiMember>> KEY = LightCacheKey.createByFileModificationCount();
public static boolean contributeMembersInsideTest(PsiScopeProcessor processor,
PsiElement place,
@@ -59,8 +47,8 @@ public class GebUtil {
return true;
}
public static Map<String, PsiField> getContentElements(@NotNull PsiClass pageOrModuleClass) {
Map<String, PsiField> res = KEY.getCachedValue(pageOrModuleClass);
public static Map<String, PsiMember> getContentElements(@NotNull PsiClass pageOrModuleClass) {
Map<String, PsiMember> res = KEY.getCachedValue(pageOrModuleClass);
if (res == null) {
res = calculateContentElements(pageOrModuleClass);
res = KEY.putCachedValue(pageOrModuleClass, res);
@@ -69,7 +57,7 @@ public class GebUtil {
return res;
}
private static Map<String, PsiField> calculateContentElements(@NotNull PsiClass pageOrModuleClass) {
private static Map<String, PsiMember> calculateContentElements(@NotNull PsiClass pageOrModuleClass) {
PsiField contentField = pageOrModuleClass.findFieldByName("content", false);
if (!(contentField instanceof GrField)) return Collections.emptyMap();
@@ -77,7 +65,7 @@ public class GebUtil {
GrExpression initializer = ((GrField)contentField).getInitializerGroovy();
if (!(initializer instanceof GrClosableBlock)) return Collections.emptyMap();
Map<String, PsiField> res = new HashMap<>();
Map<String, PsiMember> res = new HashMap<>();
PsiType objectType = PsiType.getJavaLangObject(pageOrModuleClass.getManager(), pageOrModuleClass.getResolveScope());
for (PsiElement e = initializer.getFirstChild(); e != null; e = e.getNextSibling()) {
@@ -87,6 +75,8 @@ public class GebUtil {
GrExpression invokedExpression = methodCall.getInvokedExpression();
if (!(invokedExpression instanceof GrReferenceExpression)) continue;
if (((GrReferenceExpression)invokedExpression).isQualified()) continue;
String name = ((GrReferenceExpression)invokedExpression).getReferenceName();
if (name == null) continue;
GrExpression[] arguments = PsiUtil.getAllArguments((GrCall)e);
if (arguments.length == 0) continue;
@@ -102,24 +92,58 @@ public class GebUtil {
continue;
}
GrLightField field = new GrLightField(pageOrModuleClass, ((GrReferenceExpression)invokedExpression).getReferenceName(), objectType, invokedExpression) {
@Override
public PsiType getTypeGroovy() {
return block.getReturnType();
}
@Override
public PsiType getDeclaredType() {
return null;
}
};
field.getModifierList().addModifier(GrModifierFlags.STATIC_MASK);
res.put(field.getName(), field);
PsiMember target;
if (block.hasParametersSection()) {
target = extractMethodForContent(pageOrModuleClass, name, invokedExpression, block);
}
else {
target = extractFieldForContent(pageOrModuleClass, objectType, name, invokedExpression, block);
}
res.put(name, target);
}
}
return res;
}
@NotNull
private static PsiField extractFieldForContent(@NotNull PsiClass pageOrModuleClass,
@NotNull PsiType objectType, String name,
@NotNull GrExpression invokedExpression,
@NotNull GrClosableBlock block) {
GrLightField field = new GrLightField(pageOrModuleClass, name, objectType, invokedExpression) {
@Override
public PsiType getTypeGroovy() {
return block.getReturnType();
}
@Override
public PsiType getDeclaredType() {
return null;
}
};
field.getModifierList().addModifier(GrModifierFlags.STATIC_MASK);
return field;
}
@NotNull
private static PsiMethod extractMethodForContent(@NotNull PsiClass pageOrModuleClass,
@NotNull String name,
@NotNull GrExpression invokedExpression,
@NotNull GrClosableBlock block) {
GrLightMethodBuilder method = new GrLightMethodBuilder(pageOrModuleClass.getManager(), name) {
@Override
public PsiType getReturnType() {
return block.getReturnType();
}
};
method.setContainingClass(pageOrModuleClass);
method.addModifier(GrModifierFlags.STATIC_MASK);
method.setNavigationElement(invokedExpression);
for (GrParameter parameter : block.getAllParameters()) {
method.addParameter(parameter);
}
return method;
}
}
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.geb
import org.jetbrains.plugins.groovy.codeInspection.assignment.GroovyAssignabilityCheckInspection
@@ -92,6 +78,39 @@ class ParentClass extends geb.Page {
TestUtils.checkCompletionContains(myFixture, "allElements()", "add()", "firstElement()")
}
void testResolveContentFieldsAndMethods() {
myFixture.configureByText("PageWithContent.groovy", """
class PageWithContent extends geb.Page {
static content = {
button { \$('button') }
formField { String name -> \$('input', name: name) }
}
def someMethod() {
<caret>
}
}
""")
TestUtils.checkCompletionContains(myFixture, "button", "formField()")
}
void testContentMethodReturnType() {
myFixture.configureByText("PageWithContent.groovy", """
class PageWithContent extends geb.Page {
static content = {
formField { String name -> \$('input', name: name) }
}
def someMethod() {
formField('username').<caret>
}
}
""")
TestUtils.checkCompletionContains(myFixture, "allElements()", "add()", "firstElement()")
}
void testCheckHighlighting() {
myFixture.enableInspections(GroovyAssignabilityCheckInspection)