-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathReplaceTerraItWithTerraValidatesInspection.java
156 lines (132 loc) · 7.81 KB
/
ReplaceTerraItWithTerraValidatesInspection.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//Copyright 2024 Tamás Balog. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.picimako.terra.wdio.inspection;
import static com.picimako.terra.FileTypePreconditions.isInWdioSpecFile;
import static com.picimako.terra.FileTypePreconditions.isWdioSpecFile;
import static com.picimako.terra.psi.js.JSArgumentUtil.getArgumentListOf;
import static com.picimako.terra.wdio.TerraResourceManager.isUsingTerra;
import static com.picimako.terra.wdio.TerraWdioPsiUtil.hasText;
import static com.picimako.terra.wdio.TerraWdioPsiUtil.isTerraIt;
import java.util.Map;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.lang.javascript.psi.JSBlockStatement;
import com.intellij.lang.javascript.psi.JSCallExpression;
import com.intellij.lang.javascript.psi.JSElementVisitor;
import com.intellij.lang.javascript.psi.JSExpression;
import com.intellij.lang.javascript.psi.JSExpressionStatement;
import com.intellij.lang.javascript.psi.impl.JSPsiElementFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import com.picimako.terra.resources.TerraBundle;
import com.picimako.terra.wdio.TerraWdioInspectionBase;
/**
* This inspection reports usage of {@code Terra.it} helpers, and provides quick fixes to replace them with their
* {@code Terra.validates} counterparts.
* <p>
* All three Terra.it helpers are supported, and based on the presence of before hooks, alternative quick fixes may be
* available to merge the particular Terra.it with the {@code before} hook before it.
* <p>
* The quick fix doesn't apply code reformatting at the moment, and the replacement may have an additional empty line.
*/
public class ReplaceTerraItWithTerraValidatesInspection extends TerraWdioInspectionBase {
private static final Map<String, String> IT_TO_VALIDATES = Map.of(
"Terra.it.validatesElement", "Terra.validates.element",
"Terra.it.matchesScreenshot", "Terra.validates.screenshot",
"Terra.it.isAccessible", "Terra.validates.accessibility"
);
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
if (!isUsingTerra(holder.getProject()) || !isWdioSpecFile(session.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JSElementVisitor() {
@Override
public void visitJSExpressionStatement(@NotNull JSExpressionStatement node) {
super.visitJSExpressionStatement(node);
if (isInWdioSpecFile(node) && isTerraIt(node) && node.getExpression() instanceof JSCallExpression) {
var methodExpression = ((JSCallExpression) node.getExpression()).getMethodExpression();
if (methodExpression != null) {
var before = new Ref<JSExpressionStatement>();
if (hasPrecedingBeforeHook(node, before)) {
register(methodExpression, new ReplaceTerraItWithTerraValidatesQuickFix(TerraBundle.inspection("replace.terra.it.before.merged"), before.get()));
register(methodExpression, new ReplaceTerraItWithTerraValidatesQuickFix(TerraBundle.inspection("replace.terra.it.before.unchanged")));
} else {
register(methodExpression, new ReplaceTerraItWithTerraValidatesQuickFix(TerraBundle.inspection("replace.terra.it.simple")));
}
}
}
}
private boolean hasPrecedingBeforeHook(JSExpressionStatement node, Ref<JSExpressionStatement> before) {
JSExpressionStatement previousSibling = PsiTreeUtil.getPrevSiblingOfType(node, JSExpressionStatement.class);
if (previousSibling != null && hasText(previousSibling, "before")) {
before.set(previousSibling);
return true;
}
return false;
}
private void register(JSExpression methodExpression, LocalQuickFix quickFix) {
holder.registerProblem(methodExpression, TerraBundle.inspection("replace.message"), quickFix);
}
};
}
private static final class ReplaceTerraItWithTerraValidatesQuickFix implements LocalQuickFix {
private final String quickFixName;
private SmartPsiElementPointer<JSExpressionStatement> before;
public ReplaceTerraItWithTerraValidatesQuickFix(String quickFixName) {
this.quickFixName = quickFixName;
}
public ReplaceTerraItWithTerraValidatesQuickFix(String quickFixName, JSExpressionStatement before) {
this.quickFixName = quickFixName;
this.before = SmartPointerManager.getInstance(before.getProject()).createSmartPsiElementPointer(before);
}
@Override
public @IntentionName @NotNull String getName() {
return quickFixName;
}
@Override
public @IntentionFamilyName @NotNull String getFamilyName() {
return TerraBundle.inspection("replace.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
var terraItCall = descriptor.getPsiElement(); //e.g.: Terra.it.validatesElement
var terraItStatement = terraItCall.getParent().getParent(); //e.g.: Terra.it.validatesElement();
var originalArgumentList = getArgumentListOf((JSExpressionStatement) terraItStatement);
String arguments = originalArgumentList != null && originalArgumentList.getArguments().length != 0
? originalArgumentList.getText() + ";" //e.g.: ('test name', {selector: '#css-selector'})
: "();";
//e.g.: Terra.validates.element
var terraValidates = createJSStatement(IT_TO_VALIDATES.get(terraItCall.getText()) + arguments, terraItStatement);
var itBlock = createJSStatement("it('INSERT TEST NAME', () => {\n" + "});", terraItStatement);
var itBlockCallback = PsiTreeUtil.findChildOfType(itBlock, JSBlockStatement.class);
//Add right after the opening curly braces.
//itBlockCallback should never be null, since it is created from a fix and correct template text
itBlockCallback.addAfter(terraValidates, itBlockCallback.getFirstChild());
//Copy everything from the before hook to the beginning of the 'it' block
if (before != null) {
var beforeHookCallback = PsiTreeUtil.findChildOfType(before.getElement(), JSBlockStatement.class);
if (beforeHookCallback != null) {
var children = beforeHookCallback.getChildren();
for (int i = children.length - 2; i >= 1; i--) { //skip copying curly braces
itBlockCallback.addAfter(children[i], itBlockCallback.getFirstChild());
}
before.getElement().delete();
}
}
terraItStatement.replace(itBlock);
}
private JSExpressionStatement createJSStatement(String text, PsiElement context) {
return JSPsiElementFactory.createJSStatement(text, context, JSExpressionStatement.class);
}
}
}