use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class TypeUnsafeComparisonInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
final IElementType operator = expression.getOperationType();
if (operator == PhpTokenTypes.opEQUAL || operator == PhpTokenTypes.opNOT_EQUAL) {
this.analyze(expression, operator);
}
}
private void analyze(@NotNull final BinaryExpression subject, @NotNull final IElementType operator) {
final String targetOperator = PhpTokenTypes.opEQUAL == operator ? "===" : "!==";
final PsiElement left = subject.getLeftOperand();
final PsiElement right = subject.getRightOperand();
if (right instanceof StringLiteralExpression || left instanceof StringLiteralExpression) {
final PsiElement nonStringOperand;
final String literalValue;
if (right instanceof StringLiteralExpression) {
literalValue = ((StringLiteralExpression) right).getContents();
nonStringOperand = ExpressionSemanticUtil.getExpressionTroughParenthesis(left);
} else {
literalValue = ((StringLiteralExpression) left).getContents();
nonStringOperand = ExpressionSemanticUtil.getExpressionTroughParenthesis(right);
}
/* resolve 2nd operand type, if class ensure __toString is implemented */
if (ClassInStringContextStrategy.apply(nonStringOperand, holder, subject, messageToStringMethodMissing)) {
/* TODO: weak warning regarding under-the-hood string casting */
return;
}
/* string literal is numeric or empty, no strict compare possible */
if (!literalValue.isEmpty() && !literalValue.matches("^[+-]?[0-9]*\\.?[0-9]+$")) {
holder.registerProblem(subject, String.format(MessagesPresentationUtil.prefixWithEa(patternCompareStrict), targetOperator), new CompareStrictFix(targetOperator));
return;
}
}
/* some objects supporting direct comparison: search for .compare_objects in PHP sources */
if (left != null && right != null) {
final boolean isComparableObject = this.isComparableObject(left) || this.isComparableObject(right);
if (!isComparableObject) {
holder.registerProblem(subject, String.format(MessagesPresentationUtil.prefixWithEa(patternHarden), targetOperator), ProblemHighlightType.WEAK_WARNING);
}
}
}
private boolean isComparableObject(@NotNull PsiElement operand) {
if (operand instanceof PhpTypedElement) {
final Project project = holder.getProject();
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) operand, project);
if (resolved != null) {
final PhpIndex index = PhpIndex.getInstance(project);
final Set<PhpClass> classes = new HashSet<>();
resolved.filterUnknown().getTypes().stream().filter(t -> t.charAt(0) == '\\').forEach(t -> classes.addAll(OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(Types.getType(t), index)));
for (final PhpClass clazz : classes) {
final boolean hasAny = comparable.contains(clazz.getFQN()) || InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).stream().anyMatch(c -> comparable.contains(c.getFQN()));
if (hasAny) {
classes.clear();
return true;
}
}
classes.clear();
}
}
return false;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class ClassConstantUsageCorrectnessInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClassConstantReference(@NotNull ClassConstantReference constantReference) {
final String constantName = constantReference.getName();
if (constantName != null && constantName.equals("class")) {
final PsiElement reference = constantReference.getClassReference();
if (reference instanceof ClassReference) {
final ClassReference clazz = (ClassReference) reference;
final String referencedQn = reference.getText();
final PsiElement resolved = validReferences.contains(referencedQn) ? null : OpenapiResolveUtil.resolveReference(clazz);
if (resolved instanceof PhpClass) {
/* the resolved class will accumulate case issue in its FQN */
final List<String> variants = this.getVariants(clazz, (PhpClass) resolved);
if (!variants.isEmpty()) {
if (variants.stream().noneMatch(referencedQn::equals)) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message));
}
variants.clear();
}
}
}
}
}
@NotNull
private List<String> getVariants(@NotNull ClassReference reference, @NotNull PhpClass clazz) {
final List<String> result = new ArrayList<>();
final String referenceText = reference.getText();
final String referenceTextLowercase = referenceText.toLowerCase();
if (referenceText.startsWith("\\")) {
/* FQN specified, resolve as we might have case issues there */
final Project project = holder.getProject();
final Collection<PhpClass> resolved = PhpIndex.getInstance(project).getClassesByFQN(referenceText);
if (!resolved.isEmpty()) {
result.add(resolved.iterator().next().getFQN());
}
} else {
PhpNamespace namespace = null;
PsiElement current = reference.getParent();
while (current != null && !(current instanceof PsiFile)) {
if (current instanceof PhpNamespace) {
namespace = (PhpNamespace) current;
break;
}
current = current.getParent();
}
final String classFqn = clazz.getFQN();
if (referenceText.contains("\\")) {
/* RQN specified, check if resolved class in the same NS */
final String NsFqn = namespace == null ? null : namespace.getFQN();
if (NsFqn != null && !NsFqn.equals("\\")) {
final String classFqnLowercase = classFqn.toLowerCase();
if (classFqnLowercase.startsWith(NsFqn.toLowerCase()) || classFqnLowercase.endsWith('\\' + referenceTextLowercase)) {
result.add(classFqn.substring(classFqn.length() - referenceText.length()));
}
}
/* RQN specified, check if resolved class in aliased NS */
final List<PhpUse> uses = new ArrayList<>();
if (namespace != null) {
final GroupStatement body = namespace.getStatements();
if (body != null) {
Arrays.stream(body.getStatements()).filter(statement -> statement instanceof PhpUseList).forEach(statement -> Collections.addAll(uses, ((PhpUseList) statement).getDeclarations()));
}
for (final PhpUse use : uses) {
final String alias = use.getAliasName();
if (alias != null && referenceTextLowercase.startsWith(alias.toLowerCase())) {
final PhpReference targetReference = use.getTargetReference();
if (targetReference != null) {
result.add(clazz.getFQN().replace(targetReference.getText(), alias).replaceAll("^\\\\", ""));
}
}
}
}
uses.clear();
} else {
final List<PhpUse> uses = new ArrayList<>();
if (namespace != null) {
/* find imports inside know namespace */
final GroupStatement body = namespace.getStatements();
if (body != null) {
Arrays.stream(body.getStatements()).filter(statement -> statement instanceof PhpUseList).forEach(statement -> Collections.addAll(uses, ((PhpUseList) statement).getDeclarations()));
}
} else {
final PsiFile file = reference.getContainingFile();
if (file instanceof PhpFile) {
/* find imports inside a file without namespace */
((PhpFile) file).getTopLevelDefs().values().stream().filter(definition -> definition instanceof PhpUse).forEach(definition -> uses.add((PhpUse) definition));
} else {
/* fallback, the most greedy strategy */
uses.addAll(PsiTreeUtil.findChildrenOfType(current, PhpUse.class));
}
}
/* imports (incl. aliases) */
for (final PhpUse use : uses) {
if (use.getFQN().equalsIgnoreCase(classFqn)) {
final String alias = use.getAliasName();
final PsiElement what = use.getFirstChild();
if (alias != null) {
/* alias as it is */
result.add(alias);
} else if (what instanceof ClassReference) {
/* resolve the imported class, as its the source for correct naming */
final PsiElement resolved = OpenapiResolveUtil.resolveReference((ClassReference) what);
if (resolved instanceof PhpClass) {
final PhpClass resolvedImport = (PhpClass) resolved;
final boolean importPrecise = resolvedImport.getFQN().endsWith(what.getText());
if (!importPrecise || !resolvedImport.getName().equals(referenceText)) {
result.add(resolvedImport.getFQN());
}
}
}
} else {
final String alias = use.getAliasName();
if (alias != null && alias.equalsIgnoreCase(referenceText)) {
result.add(alias);
}
}
}
uses.clear();
}
}
return result;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class DisconnectedForeachInstructionInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpForeach(@NotNull ForeachStatement foreach) {
final GroupStatement foreachBody = ExpressionSemanticUtil.getGroupStatement(foreach);
/* ensure foreach structure is ready for inspection */
if (foreachBody != null) {
final PsiElement[] statements = foreachBody.getChildren();
if (statements.length > 0 && Stream.of(statements).anyMatch(s -> OpenapiTypesUtil.is(s, PhpElementTypes.HTML))) {
return;
}
/* pre-collect introduced and internally used variables */
final Set<String> allModifiedVariables = this.collectCurrentAndOuterLoopVariables(foreach);
final Map<PsiElement, Set<String>> instructionDependencies = new HashMap<>();
/* iteration 1 - investigate what are dependencies and influence */
for (final PsiElement oneInstruction : statements) {
if (oneInstruction instanceof PhpPsiElement && !(oneInstruction instanceof PsiComment)) {
final Set<String> individualDependencies = new HashSet<>();
instructionDependencies.put(oneInstruction, individualDependencies);
investigateInfluence((PhpPsiElement) oneInstruction, individualDependencies, allModifiedVariables);
}
}
/* iteration 2 - analyse dependencies */
for (final PsiElement oneInstruction : statements) {
if (oneInstruction instanceof PhpPsiElement && !(oneInstruction instanceof PsiComment)) {
boolean isDependOnModified = false;
/* check if any dependency is overridden */
final Set<String> individualDependencies = instructionDependencies.get(oneInstruction);
if (individualDependencies != null && !individualDependencies.isEmpty()) {
isDependOnModified = individualDependencies.stream().anyMatch(allModifiedVariables::contains);
individualDependencies.clear();
}
/* verify and report if violation detected */
if (!isDependOnModified) {
final ExpressionType target = getExpressionType(oneInstruction);
if (ExpressionType.NEW != target && ExpressionType.ASSIGNMENT != target && ExpressionType.CLONE != target && ExpressionType.INCREMENT != target && ExpressionType.DECREMENT != target && ExpressionType.DOM_ELEMENT_CREATE != target && ExpressionType.ACCUMULATE_IN_ARRAY != target && ExpressionType.CONTROL_STATEMENTS != target) {
/* loops, ifs, switches, try's needs to be reported on keyword, others - complete */
final PsiElement reportingTarget = oneInstruction instanceof ControlStatement || oneInstruction instanceof Try || oneInstruction instanceof PhpSwitch ? oneInstruction.getFirstChild() : oneInstruction;
/* secure exceptions with '<?= ?>' constructions, false-positives with html */
if (!OpenapiTypesUtil.isPhpExpressionImpl(oneInstruction) && oneInstruction.getTextLength() > 0) {
/* inner looping termination/continuation should be taken into account */
final PsiElement loopInterrupter = PsiTreeUtil.findChildOfAnyType(oneInstruction, true, PhpBreak.class, PhpContinue.class, PhpReturn.class, OpenapiPlatformUtil.classes.get("PhpThrow"));
/* operating with variables should be taken into account */
final boolean isVariablesUsed = PsiTreeUtil.findChildOfAnyType(oneInstruction, true, (Class) Variable.class) != null;
if (null == loopInterrupter && isVariablesUsed) {
holder.registerProblem(reportingTarget, MessagesPresentationUtil.prefixWithEa(messageDisconnected));
}
}
}
if (SUGGEST_USING_CLONE && (ExpressionType.DOM_ELEMENT_CREATE == target || ExpressionType.NEW == target)) {
holder.registerProblem(oneInstruction, MessagesPresentationUtil.prefixWithEa(messageUseClone));
}
}
}
}
/* release containers content */
allModifiedVariables.clear();
instructionDependencies.values().forEach(Set::clear);
instructionDependencies.clear();
}
}
private Set<String> collectCurrentAndOuterLoopVariables(@NotNull ForeachStatement foreach) {
final Set<String> variables = new HashSet<>();
PsiElement current = foreach;
while (current != null && !(current instanceof Function) && !(current instanceof PsiFile)) {
if (current instanceof ForeachStatement) {
((ForeachStatement) current).getVariables().forEach(v -> variables.add(v.getName()));
}
current = current.getParent();
}
return variables;
}
private void investigateInfluence(@Nullable PhpPsiElement oneInstruction, @NotNull Set<String> individualDependencies, @NotNull Set<String> allModifiedVariables) {
for (final Variable variable : PsiTreeUtil.findChildrenOfType(oneInstruction, Variable.class)) {
final String variableName = variable.getName();
PsiElement valueContainer = variable;
PsiElement parent = variable.getParent();
while (parent instanceof FieldReference) {
valueContainer = parent;
parent = parent.getParent();
}
/* a special case: `[] = ` and `array() = ` unboxing */
if (OpenapiTypesUtil.is(parent, PhpElementTypes.ARRAY_VALUE)) {
parent = parent.getParent();
if (parent instanceof ArrayCreationExpression) {
parent = parent.getParent();
}
}
final PsiElement grandParent = parent.getParent();
/* writing into variable */
if (parent instanceof AssignmentExpression) {
/* php-specific `list(...) =` , `[...] =` construction */
if (parent instanceof MultiassignmentExpression) {
final MultiassignmentExpression assignment = (MultiassignmentExpression) parent;
if (assignment.getValue() != variable) {
allModifiedVariables.add(variableName);
individualDependencies.add(variableName);
continue;
}
} else {
final AssignmentExpression assignment = (AssignmentExpression) parent;
if (assignment.getVariable() == valueContainer) {
/* we are modifying the variable */
allModifiedVariables.add(variableName);
/* self-assignment and field assignment counted as the variable dependent on itself */
if (assignment instanceof SelfAssignmentExpression || valueContainer instanceof FieldReference) {
individualDependencies.add(variableName);
}
/* assignments as call arguments counted as the variable dependent on itself */
if (grandParent instanceof ParameterList) {
individualDependencies.add(variableName);
}
continue;
}
}
}
/* adding into an arrays; we both depend and modify the container */
if (parent instanceof ArrayAccessExpression && valueContainer == ((ArrayAccessExpression) parent).getValue()) {
allModifiedVariables.add(variableName);
individualDependencies.add(variableName);
}
if (parent instanceof ParameterList) {
if (grandParent instanceof MethodReference) {
/* an object consumes the variable, perhaps modification takes place */
final MethodReference reference = (MethodReference) grandParent;
final PsiElement referenceOperator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
if (OpenapiTypesUtil.is(referenceOperator, PhpTokenTypes.ARROW)) {
final PsiElement variableCandidate = reference.getFirstPsiChild();
if (variableCandidate instanceof Variable) {
allModifiedVariables.add(((Variable) variableCandidate).getName());
continue;
}
}
} else if (OpenapiTypesUtil.isFunctionReference(grandParent)) {
/* php will create variable, if it is by reference */
final FunctionReference reference = (FunctionReference) grandParent;
final int position = ArrayUtils.indexOf(reference.getParameters(), variable);
if (position != -1) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Function) {
final Parameter[] parameters = ((Function) resolved).getParameters();
if (parameters.length > position && parameters[position].isPassByRef()) {
allModifiedVariables.add(variableName);
individualDependencies.add(variableName);
continue;
}
}
}
}
}
/* increment/decrement are also write operations */
final ExpressionType type = this.getExpressionType(parent);
if (ExpressionType.INCREMENT == type || ExpressionType.DECREMENT == type) {
allModifiedVariables.add(variableName);
individualDependencies.add(variableName);
continue;
}
/* TODO: lookup for array access and property access */
individualDependencies.add(variableName);
}
/* handle compact function usage */
for (final FunctionReference reference : PsiTreeUtil.findChildrenOfType(oneInstruction, FunctionReference.class)) {
if (OpenapiTypesUtil.isFunctionReference(reference)) {
final String functionName = reference.getName();
if (functionName != null && functionName.equals("compact")) {
for (final PsiElement argument : reference.getParameters()) {
if (argument instanceof StringLiteralExpression) {
final String compactedVariableName = ((StringLiteralExpression) argument).getContents();
if (!compactedVariableName.isEmpty()) {
individualDependencies.add(compactedVariableName);
}
}
}
}
}
}
}
@NotNull
private ExpressionType getExpressionType(@Nullable PsiElement expression) {
if (expression instanceof PhpBreak || expression instanceof PhpContinue || expression instanceof PhpReturn) {
return ExpressionType.CONTROL_STATEMENTS;
}
/* regular '...;' statements */
if (OpenapiTypesUtil.isStatementImpl(expression)) {
return getExpressionType(((Statement) expression).getFirstPsiChild());
}
/* unary operations */
if (expression instanceof UnaryExpression) {
final PsiElement operation = ((UnaryExpression) expression).getOperation();
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opINCREMENT)) {
return ExpressionType.INCREMENT;
}
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opDECREMENT)) {
return ExpressionType.DECREMENT;
}
}
/* different types of assignments */
if (expression instanceof AssignmentExpression) {
final AssignmentExpression assignment = (AssignmentExpression) expression;
final PsiElement variable = assignment.getVariable();
if (variable instanceof Variable) {
final PsiElement value = assignment.getValue();
if (value instanceof NewExpression) {
return ExpressionType.NEW;
} else if (value instanceof UnaryExpression) {
if (OpenapiTypesUtil.is(((UnaryExpression) value).getOperation(), PhpTokenTypes.kwCLONE)) {
return ExpressionType.CLONE;
}
} else if (value instanceof MethodReference) {
final MethodReference call = (MethodReference) value;
final String methodName = call.getName();
if (methodName != null && methodName.equals("createElement")) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(call);
if (resolved instanceof Method && ((Method) resolved).getFQN().equals("\\DOMDocument.createElement")) {
return ExpressionType.DOM_ELEMENT_CREATE;
}
}
}
/* allow all assignations afterwards */
return ExpressionType.ASSIGNMENT;
}
/* accumulating something in external container */
if (variable instanceof ArrayAccessExpression) {
final ArrayAccessExpression storage = (ArrayAccessExpression) variable;
if (null == storage.getIndex() || null == storage.getIndex().getValue()) {
return ExpressionType.ACCUMULATE_IN_ARRAY;
}
}
}
return ExpressionType.OTHER;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class MockingMethodsCorrectnessInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
final String methodName = reference.getName();
if (methodName != null) {
if (methodName.equals("willReturn")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1 && arguments[0] instanceof MethodReference) {
final String innerMethodName = ((MethodReference) arguments[0]).getName();
if (innerMethodName != null) {
final boolean isTarget = innerMethodName.equals("returnCallback") || innerMethodName.equals("returnValue");
if (isTarget && this.isTestContext(reference)) {
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(reference);
if (nameNode != null) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageWillMethod), new UseWillMethodFix());
}
}
}
}
} else if (methodName.equals("method")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1 && arguments[0] instanceof StringLiteralExpression) {
PhpPsiElement mock = reference.getFirstPsiChild();
/* Handle following construct (->expects())->method('non-existing') */
if (mock instanceof MethodReference && "expects".equals(mock.getName())) {
mock = mock.getFirstPsiChild();
}
if (mock != null && this.isTestContext(reference)) {
this.checkIfMockHasMethod(mock, (StringLiteralExpression) arguments[0]);
}
}
}
}
}
private void checkIfMockHasMethod(@NotNull PsiElement mock, @NotNull StringLiteralExpression methodName) {
final Set<PsiElement> variants = PossibleValuesDiscoveryUtil.discover(mock);
if (variants.size() == 1) {
/* Handle following construct ->getMockBuilder(::class)->getMock() + */
final PsiElement source = variants.iterator().next();
if (source instanceof MethodReference && "getMock".equals(((MethodReference) source).getName())) {
final Optional<MethodReference> builder = PsiTreeUtil.findChildrenOfType(source, MethodReference.class).stream().filter(reference -> "getMockBuilder".equals(reference.getName())).findFirst();
if (builder.isPresent()) {
/* find out if the method has been set with setMethods() method */
final Optional<MethodReference> methodsSetter = PsiTreeUtil.findChildrenOfType(source, MethodReference.class).stream().filter(reference -> "setMethods".equals(reference.getName())).findFirst();
if (methodsSetter.isPresent()) {
final PsiElement[] methodSetterArguments = methodsSetter.get().getParameters();
if (methodSetterArguments.length > 0 && methodSetterArguments[0] instanceof ArrayCreationExpression) {
final boolean isSet = PsiTreeUtil.findChildrenOfType(methodSetterArguments[0], StringLiteralExpression.class).stream().anyMatch(l -> OpenapiEquivalenceUtil.areEqual(l, methodName));
if (isSet) {
return;
}
}
}
/* find out which class it was and check if it has the method */
final PsiElement[] builderArguments = builder.get().getParameters();
if (builderArguments.length == 1 && builderArguments[0] instanceof ClassConstantReference) {
final ClassConstantReference clazz = (ClassConstantReference) builderArguments[0];
if ("class".equals(clazz.getName())) {
final PsiElement classReference = clazz.getClassReference();
if (classReference instanceof ClassReference) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference((ClassReference) classReference);
if (resolved instanceof PhpClass) {
final Method method = OpenapiResolveUtil.resolveMethod((PhpClass) resolved, methodName.getContents());
if (method == null) {
holder.registerProblem(methodName, MessagesPresentationUtil.prefixWithEa(messageUnresolvedMethod), ProblemHighlightType.GENERIC_ERROR);
} else if (method.isFinal()) {
holder.registerProblem(methodName, MessagesPresentationUtil.prefixWithEa(messageFinalMethod), ProblemHighlightType.GENERIC_ERROR);
}
}
}
}
}
}
}
}
variants.clear();
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class MisorderedModifiersInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder problemsHolder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
if (method.isStatic() || method.isAbstract() || method.isFinal()) {
final PhpModifierList modifiersNode = PsiTreeUtil.findChildOfType(method, PhpModifierList.class);
final List<LeafPsiElement> modifiers = PsiTreeUtil.findChildrenOfType(modifiersNode, LeafPsiElement.class).stream().filter(element -> !(element instanceof PsiWhiteSpace)).collect(Collectors.toList());
if (modifiersNode != null && modifiers.size() >= 2) {
final String original = this.getOriginalOrder(modifiers);
final String expected = this.getExpectedOrder(original, standardOrder);
if (!original.equals(expected)) {
problemsHolder.registerProblem(modifiersNode, MessagesPresentationUtil.prefixWithEa(message), new TheLocalFix(expected));
}
}
}
}
@NotNull
private String getOriginalOrder(@NotNull Collection<LeafPsiElement> original) {
return original.stream().map(LeafElement::getText).collect(Collectors.joining(" ")).toLowerCase();
}
@NotNull
private String getExpectedOrder(@NotNull String original, @NotNull Collection<String> expected) {
return expected.stream().filter(original::contains).collect(Collectors.joining(" "));
}
};
}
Aggregations