package com.intellij.lang.javascript.flex.flexunit;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.javascript.flex.FlexModuleType;
import com.intellij.lang.javascript.flex.FlexUtils;
import com.intellij.lang.javascript.flex.XmlBackedJSClassImpl;
import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.ecmal4.JSAttribute;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeNameValuePair;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil;
import com.intellij.lang.javascript.psi.types.primitives.JSVoidType;
import com.intellij.lang.javascript.validation.ValidateTypesUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class FlexUnitSupport {
private static final Logger LOG = Logger.getInstance(FlexUnitSupport.class.getName());
@NotNull public final JSClass flexUnit1TestClass;
@Nullable public final JSClass flunitTestClass;
@Nullable private final JSClass flunitTestSuite;
public final boolean flexUnit4Present;
private final JSClass flexUnit1TestSuite;
public static final String FLEX_UNIT_1_TESTCASE_CLASS = "flexunit.framework.TestCase";
public static final String FLEX_UNIT_1_TESTSUITE_CLASS = "flexunit.framework.TestSuite";
public static final String FLEX_UNIT_4_CORE_CLASS = "org.flexunit.runner.FlexUnitCore";
public static final String FLUNIT_TESTCASE_CLASS = "net.digitalprimates.fluint.tests.TestCase";
public static final String FLUNIT_TESTSUITE_CLASS = "net.digitalprimates.fluint.tests.TestSuite";
public static final String TEST_ATTRIBUTE = "Test";
static final String IGNORE_ATTRIBUTE = "Ignore";
public static final String SUITE_ATTRIBUTE = "Suite";
public static final String RUN_WITH_ATTRIBUTE = "RunWith";
public static final String SUITE_RUNNER = "org.flexunit.runners.Suite";
private FlexUnitSupport(@NotNull JSClass flexUnit1TestClass,
@NotNull JSClass flexUnit1TestSuiteClass,
@Nullable JSClass flunitTestClass,
@Nullable JSClass flunitTestSuite,
boolean flexUnit4Present) {
this.flexUnit1TestClass = flexUnit1TestClass;
this.flexUnit1TestSuite = flexUnit1TestSuiteClass;
this.flunitTestClass = flunitTestClass;
this.flunitTestSuite = flunitTestSuite;
this.flexUnit4Present = flexUnit4Present;
}
@Nullable
public static Pair<Module, FlexUnitSupport> getModuleAndSupport(@NotNull PsiElement context) {
final Module module = ModuleUtil.findModuleForPsiElement(context);
final FlexUnitSupport support = getSupport(module);
return support != null ? Pair.create(module, support) : null;
}
@Nullable
public static FlexUnitSupport getSupport(@Nullable FlexBuildConfiguration bc, final Module module) {
if (bc == null) return null;
return getSupport(FlexUtils.getModuleWithDependenciesAndLibrariesScope(module, bc, true));
}
@Nullable
public static FlexUnitSupport getSupport(@Nullable Module module) {
if (module == null) return null;
if (ModuleType.get(module) != FlexModuleType.getInstance() || FlexUtils.getSdkForActiveBC(module) == null) return null;
return getSupport(GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module));
}
private static FlexUnitSupport getSupport(final GlobalSearchScope searchScope) {
PsiElement flexUnit1TestClass = ActionScriptClassResolver.findClassByQNameStatic(FLEX_UNIT_1_TESTCASE_CLASS, searchScope);
if (!(flexUnit1TestClass instanceof JSClass)) return null;
PsiElement flexUnit1TestSuiteClass = ActionScriptClassResolver.findClassByQNameStatic(FLEX_UNIT_1_TESTSUITE_CLASS, searchScope);
if (!(flexUnit1TestSuiteClass instanceof JSClass)) {
LOG.warn(FLEX_UNIT_1_TESTCASE_CLASS + " class is present but " + FLEX_UNIT_1_TESTSUITE_CLASS + " is not");
return null;
}
boolean flexUnit4Present = ActionScriptClassResolver.findClassByQNameStatic(FLEX_UNIT_4_CORE_CLASS, searchScope) instanceof JSClass;
PsiElement flunitTestClass = flexUnit4Present ? ActionScriptClassResolver.findClassByQNameStatic(FLUNIT_TESTCASE_CLASS, searchScope) : null;
PsiElement flunitTestSuiteClass =
(flunitTestClass instanceof JSClass) ? (JSClass)ActionScriptClassResolver.findClassByQNameStatic(FLUNIT_TESTSUITE_CLASS, searchScope) : null;
return new FlexUnitSupport((JSClass)flexUnit1TestClass, (JSClass)flexUnit1TestSuiteClass, (JSClass)flunitTestClass,
(JSClass)flunitTestSuiteClass, flexUnit4Present);
}
public boolean isFlexUnit1Subclass(JSClass clazz) {
return JSInheritanceUtil.isParentClass(clazz, flexUnit1TestClass);
}
public boolean isFlexUnit1SuiteSubclass(JSClass clazz) {
return JSInheritanceUtil.isParentClass(clazz, flexUnit1TestSuite);
}
public boolean isFlunitSubclass(JSClass clazz) {
return flunitTestClass != null && JSInheritanceUtil.isParentClass(clazz, flunitTestClass);
}
public boolean isFlunitSuiteSubclass(JSClass clazz) {
return flunitTestSuite != null && JSInheritanceUtil.isParentClass(clazz, flunitTestSuite);
}
public boolean isPotentialTestClass(@NotNull JSClass clazz) {
if (isFlexUnit1Subclass(clazz) || isFlunitSubclass(clazz)) {
return true;
}
for (JSFunction method : clazz.getFunctions()) {
if (isPotentialTestMethod(method)) {
return true;
}
}
return false;
}
public boolean isPotentialTestMethod(JSFunction method) {
if (method.getKind() == JSFunction.FunctionKind.CONSTRUCTOR) return false;
PsiElement parent = method.getParent();
if (parent instanceof JSClass && (isFlunitSubclass((JSClass)parent) || isFlexUnit1Subclass((JSClass)parent))) {
if (method.getName() != null && method.getName().startsWith("test")) return true;
}
if (method.getAttributeList() != null &&
method.getAttributeList().getAttributesByName(TEST_ATTRIBUTE).length > 0 &&
method.getAttributeList().getAttributesByName(IGNORE_ATTRIBUTE).length == 0) {
return true;
}
return false;
}
public boolean isTestClass(@NotNull JSClass clazz, boolean allowSuite) {
if (clazz instanceof XmlBackedJSClassImpl) return false;
if (clazz.getAttributeList() == null) return false;
if (clazz.getAttributeList().getAccessType() != JSAttributeList.AccessType.PUBLIC) return false;
if (allowSuite && isSuite(clazz)) return true;
final boolean flexUnit1Subclass = isFlexUnit1Subclass(clazz);
if (!flexUnit1Subclass && !flexUnit4Present) return false;
if (getCustomRunner(clazz) == null) {
for (JSFunction method : clazz.getFunctions()) {
if (method.getKind() == JSFunction.FunctionKind.CONSTRUCTOR && ValidateTypesUtil.hasRequiredParameters(method)) return false;
}
}
if (!flexUnit1Subclass && !isFlunitSubclass(clazz)) {
boolean hasTests = false;
for (JSFunction method : clazz.getFunctions()) {
if (isTestMethod(method)) {
hasTests = true;
break;
}
}
if (!hasTests) return false;
}
return true;
}
public boolean isSuite(JSClass clazz) {
if (isFlexUnit1SuiteSubclass(clazz) || isFlunitSuiteSubclass(clazz)) return true;
if (flexUnit4Present && clazz.getAttributeList() != null && clazz.getAttributeList().getAttributesByName(SUITE_ATTRIBUTE).length > 0) {
return true;
}
return false;
}
/**
* @return [RunWith] metadata default attribute value. Can be <code>null</code>, empty string or whatever.
*/
@Nullable
public static String getCustomRunner(JSClass clazz) {
final JSAttribute[] attrs = clazz.getAttributeList().getAttributesByName(RUN_WITH_ATTRIBUTE);
if (attrs.length == 0) return null;
final JSAttributeNameValuePair attr = attrs[0].getValueByName(null);
return attr == null ? null : attr.getSimpleValue();
}
public boolean isTestMethod(JSFunction method) {
if (!(method.getParent() instanceof JSClass) || method.getParent() instanceof XmlBackedJSClassImpl) return false;
JSClass clazz = (JSClass)method.getParent();
// FlexUnit 1: flexunit.framework.TestCase.getTestMethodNames()
// Flunit: net.digitalprimates.fluint.tests.defaultFilterFunction()
// FlexUnit 4: org.flexunit.runners.BlockFlexUnit4ClassRunner
if (method.getName() == null) return false;
if (flexUnit4Present && getCustomRunner(clazz) != null) return true;
if (method.getAttributeList() == null) return false;
if (method.getAttributeList().getAccessType() != JSAttributeList.AccessType.PUBLIC) return false;
if (method.getKind() != JSFunction.FunctionKind.SIMPLE) return false;
if (method.getAttributeList().hasModifier(JSAttributeList.ModifierType.STATIC)) return false;
if (ValidateTypesUtil.hasRequiredParameters(method)) return false;
if (isFlexUnit1Subclass(clazz)) {
if (!method.getName().startsWith("test")) return false;
}
else if (isFlunitSubclass(clazz)) {
if (!method.getName().startsWith("test") && method.getAttributeList().getAttributesByName(TEST_ATTRIBUTE).length == 0) return false;
}
else {
if (!flexUnit4Present) return false;
final JSType returnType = method.getReturnType();
if (returnType != null && !(returnType instanceof JSVoidType)) return false;
if (method.getAttributeList().getAttributesByName(IGNORE_ATTRIBUTE).length > 0) return false;
if (method.getAttributeList().getAttributesByName(TEST_ATTRIBUTE).length == 0) return false;
}
return true;
}
public Collection<JSClass> getSuiteTestClasses(JSClass suiteClass) {
if (!SUITE_RUNNER.equals(getCustomRunner(suiteClass))) return Collections.emptyList();
Collection<JSClass> result = new ArrayList<>();
for (JSField field : suiteClass.getFields()) {
if (field.getAttributeList() == null) continue;
if (field.getAttributeList().hasModifier(JSAttributeList.ModifierType.STATIC)) continue;
if (field.getAttributeList().getAccessType() != JSAttributeList.AccessType.PUBLIC) continue;
final PsiElement typeElement = field.getTypeElement();
if (!(typeElement instanceof JSReferenceExpression)) continue;
final PsiElement type = ((JSReferenceExpression)typeElement).resolve();
if (!(type instanceof JSClass)) continue;
result.add((JSClass)type);
}
return result;
}
}