package com.google.jstestdriver.idea.assertFramework.jstd; import com.intellij.javascript.testFramework.AbstractTestFileStructureBuilder; import com.intellij.javascript.testFramework.util.JsPsiUtils; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.psi.*; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; public class JstdTestFileStructureBuilder extends AbstractTestFileStructureBuilder<JstdTestFileStructure> { private static final JstdTestFileStructureBuilder INSTANCE = new JstdTestFileStructureBuilder(); private static final String TEST_CASE_NAME = "TestCase"; private static final String ASYNC_TEST_CASE_NAME = "AsyncTestCase"; private JstdTestFileStructureBuilder() {} @NotNull @Override public JstdTestFileStructure buildTestFileStructure(@NotNull JSFile jsFile) { JstdTestFileStructure jsTestFileStructure = new JstdTestFileStructure(jsFile); if (JsPsiUtils.mightContainGlobalCall(jsFile, TEST_CASE_NAME, false)) { List<JSStatement> statements = JsPsiUtils.listStatementsInExecutionOrder(jsFile); for (JSStatement statement : statements) { fillJsTestFileStructure(jsTestFileStructure, statement); } } jsTestFileStructure.postProcess(); return jsTestFileStructure; } private static void fillJsTestFileStructure(@NotNull JstdTestFileStructure jsTestFileStructure, @NotNull JSStatement statement) { if (statement instanceof JSExpressionStatement) { JSExpressionStatement jsExpressionStatement = (JSExpressionStatement) statement; JSExpression expressionOfStatement = jsExpressionStatement.getExpression(); if (expressionOfStatement instanceof JSCallExpression) { // TestCase("testCaseName", { test1: function() {} }); JSCallExpression callExpression = (JSCallExpression) expressionOfStatement; createTestCaseStructure(jsTestFileStructure, callExpression); } else if (expressionOfStatement instanceof JSAssignmentExpression) { // testCase = TestCase("testCaseName"); JSAssignmentExpression jsAssignmentExpression = (JSAssignmentExpression) expressionOfStatement; JSCallExpression rOperandCallExpression = ObjectUtils.tryCast(jsAssignmentExpression.getROperand(), JSCallExpression.class); if (rOperandCallExpression != null) { JstdTestCaseStructure testCaseStructure = createTestCaseStructure(jsTestFileStructure, rOperandCallExpression); if (testCaseStructure != null) { JSDefinitionExpression jsDefinitionExpression = ObjectUtils.tryCast(jsAssignmentExpression.getLOperand(), JSDefinitionExpression.class); if (jsDefinitionExpression != null) { JSReferenceExpression jsReferenceExpression = ObjectUtils.tryCast(jsDefinitionExpression.getExpression(), JSReferenceExpression.class); if (jsReferenceExpression != null) { String refName = jsReferenceExpression.getReferenceName(); if (refName != null) { addPrototypeTests(testCaseStructure, refName, jsExpressionStatement); } } } } } } } if (statement instanceof JSVarStatement) { // var testCase = TestCase("testCaseName"); JSVarStatement jsVarStatement = (JSVarStatement) statement; JSVariable[] jsVariables = ObjectUtils.notNull(jsVarStatement.getVariables(), JSVariable.EMPTY_ARRAY); for (JSVariable jsVariable : jsVariables) { JSCallExpression jsCallExpression = ObjectUtils.tryCast(jsVariable.getInitializer(), JSCallExpression.class); if (jsCallExpression != null) { JstdTestCaseStructure testCaseStructure = createTestCaseStructure(jsTestFileStructure, jsCallExpression); if (testCaseStructure != null) { String refName = jsVariable.getQualifiedName(); if (refName != null) { addPrototypeTests(testCaseStructure, refName, jsVarStatement); } } } } } } private static void addPrototypeTests(@NotNull JstdTestCaseStructure testCaseStructure, @NotNull String referenceName, @NotNull JSStatement refStatement) { List<JSStatement> statements = JsPsiUtils.listStatementsInExecutionOrderNextTo(refStatement); for (JSStatement statement : statements) { JSExpressionStatement expressionStatement = ObjectUtils.tryCast(statement, JSExpressionStatement.class); if (expressionStatement != null) { JSAssignmentExpression assignmentExpr = ObjectUtils.tryCast(expressionStatement.getExpression(), JSAssignmentExpression.class); if (assignmentExpr != null) { JSDefinitionExpression wholeLeftDefExpr = ObjectUtils.tryCast(assignmentExpr.getLOperand(), JSDefinitionExpression.class); if (wholeLeftDefExpr != null) { JSReferenceExpression wholeLeftRefExpr = ObjectUtils.tryCast(wholeLeftDefExpr.getExpression(), JSReferenceExpression.class); if (wholeLeftRefExpr != null) { JSReferenceExpression testCaseAndPrototypeRefExpr = ObjectUtils.tryCast(wholeLeftRefExpr.getQualifier(), JSReferenceExpression.class); if (testCaseAndPrototypeRefExpr != null) { if ("prototype".equals(testCaseAndPrototypeRefExpr.getReferenceName())) { JSReferenceExpression testCaseRefExpr = ObjectUtils.tryCast(testCaseAndPrototypeRefExpr.getQualifier(), JSReferenceExpression.class); if (testCaseRefExpr != null && testCaseRefExpr.getQualifier() == null) { if (referenceName.equals(testCaseRefExpr.getReferenceName())) { addPrototypeTest(testCaseStructure, assignmentExpr.getROperand(), wholeLeftDefExpr); } } } } } } } } } } private static void addPrototypeTest(@NotNull JstdTestCaseStructure testCaseStructure, @Nullable JSExpression rightAssignmentOperand, @NotNull JSDefinitionExpression wholeLeftDefExpr) { JSReferenceExpression wholeLeftRefExpr = ObjectUtils.tryCast(wholeLeftDefExpr.getExpression(), JSReferenceExpression.class); LeafPsiElement testMethodLeafPsiElement = null; if (wholeLeftRefExpr != null) { testMethodLeafPsiElement = ObjectUtils.tryCast(wholeLeftRefExpr.getReferenceNameElement(), LeafPsiElement.class); } if (testMethodLeafPsiElement != null && testMethodLeafPsiElement.getElementType() == JSTokenTypes.IDENTIFIER) { JSFunctionExpression jsFunctionExpression = JsPsiUtils.extractFunctionExpression(rightAssignmentOperand); JstdTestStructure jstdTestStructure = JstdTestStructure.newPrototypeBasedTestStructure(wholeLeftDefExpr, testMethodLeafPsiElement, jsFunctionExpression); if (jstdTestStructure != null) { testCaseStructure.addTestStructure(jstdTestStructure); } } } @Nullable private static JstdTestCaseStructure createTestCaseStructure(@NotNull JstdTestFileStructure jsTestFileStructure, @NotNull JSCallExpression testCaseCallExpression) { JSReferenceExpression referenceExpression = ObjectUtils.tryCast(testCaseCallExpression.getMethodExpression(), JSReferenceExpression.class); if (referenceExpression != null) { String referenceName = referenceExpression.getReferenceName(); if (TEST_CASE_NAME.equals(referenceName) || ASYNC_TEST_CASE_NAME.equals(referenceName)) { JSExpression[] arguments = JsPsiUtils.getArguments(testCaseCallExpression); if (arguments.length >= 1) { String testCaseName = JsPsiUtils.extractStringValue(arguments[0]); if (testCaseName != null) { JSObjectLiteralExpression testsObjectLiteral = null; if (arguments.length >= 2) { testsObjectLiteral = JsPsiUtils.extractObjectLiteralExpression(arguments[1]); } JstdTestCaseStructure testCaseStructure = new JstdTestCaseStructure(jsTestFileStructure, testCaseName, testCaseCallExpression, testsObjectLiteral); jsTestFileStructure.addTestCaseStructure(testCaseStructure); if (testsObjectLiteral != null) { fillTestCaseStructureByObjectLiteral(testCaseStructure, testsObjectLiteral); } return testCaseStructure; } } } } return null; } private static void fillTestCaseStructureByObjectLiteral( @NotNull JstdTestCaseStructure testCaseStructure, @NotNull JSObjectLiteralExpression testsObjectLiteral ) { JSProperty[] properties = JsPsiUtils.getProperties(testsObjectLiteral); for (JSProperty property : properties) { JstdTestStructure testStructure = JstdTestStructure.newPropertyBasedTestStructure(property); if (testStructure != null) { testCaseStructure.addTestStructure(testStructure); } } } public static JstdTestFileStructureBuilder getInstance() { return INSTANCE; } }