use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class SubStrShortHandUsageInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && substringFunctions.contains(functionName)) {
final PsiElement[] arguments = reference.getParameters();
if ((arguments.length == 3 || arguments.length == 4) && arguments[2] instanceof BinaryExpression) {
/* check if 3rd argument is "strlen($search) - strlen(...)": "strlen($search)" is not needed */
final BinaryExpression binary = (BinaryExpression) arguments[2];
if (binary.getOperationType() == PhpTokenTypes.opMINUS) {
final PsiElement left = binary.getLeftOperand();
final PsiElement right = binary.getRightOperand();
if (right != null && OpenapiTypesUtil.isFunctionReference(left)) {
final FunctionReference leftCall = (FunctionReference) left;
final String leftName = leftCall.getName();
if (leftName != null && lengthFunctions.contains(leftName)) {
final PsiElement[] leftArguments = leftCall.getParameters();
if (leftArguments.length == 1 && OpenapiEquivalenceUtil.areEqual(leftArguments[0], arguments[0])) {
final PsiElement startOffset = arguments[1];
if (OpenapiEquivalenceUtil.areEqual(right, startOffset)) {
/* case: third parameter is not needed at all */
holder.registerProblem(arguments[2], String.format(MessagesPresentationUtil.prefixWithEa(patternDropLength), arguments[2].getText()), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropThirdParameterFix(holder.getProject(), reference));
} else if (OpenapiTypesUtil.isNumber(startOffset) && OpenapiTypesUtil.isNumber(right)) {
try {
int offset = Integer.parseInt(startOffset.getText()) - Integer.parseInt(right.getText());
if (offset < 0) {
/* case: third parameter can be simplified */
holder.registerProblem(binary, String.format(MessagesPresentationUtil.prefixWithEa(patternSimplifyLength), offset), new SimplifyFix(String.valueOf(offset)));
} else {
/* case: third parameter is not needed at all */
holder.registerProblem(arguments[2], String.format(MessagesPresentationUtil.prefixWithEa(patternDropLength), arguments[2].getText()), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropThirdParameterFix(holder.getProject(), reference));
}
} catch (final NumberFormatException expected) {
// return;
}
}
}
}
}
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class BadExceptionsProcessingInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpTry(@NotNull Try tryStatement) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(tryStatement);
final int expressionsCount = body == null ? 0 : ExpressionSemanticUtil.countExpressionsInGroup(body);
if (expressionsCount > 3) {
holder.registerProblem(tryStatement.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%c%", String.valueOf(expressionsCount))));
}
}
@Override
public void visitPhpCatch(@NotNull Catch catchStatement) {
final Variable variable = catchStatement.getException();
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(catchStatement);
if (variable != null && body != null) {
final String variableName = variable.getName();
if (!StringUtils.isEmpty(variableName)) {
/* incomplete catch statement */
boolean isVariableUsed = false;
for (final Variable usedVariable : PsiTreeUtil.findChildrenOfType(body, Variable.class)) {
if (usedVariable.getName().equals(variableName)) {
isVariableUsed = true;
break;
}
}
if (!isVariableUsed) {
if (ExpressionSemanticUtil.countExpressionsInGroup(body) == 0) {
holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageFailSilently));
} else {
holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageChainedException));
}
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class UnSafeIsSetOverArrayInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpIsset(@NotNull PhpIsset issetExpression) {
/*
* if no parameters, we don't check;
* if multiple parameters, perhaps if-inspection fulfilled and isset's were merged
*
* TODO: still needs analysis regarding concatenations in indexes
*/
final PhpExpression[] arguments = issetExpression.getVariables();
if (arguments.length != 1) {
return;
}
/* gather context information */
PsiElement issetParent = issetExpression.getParent();
boolean issetInverted = false;
if (issetParent instanceof UnaryExpression) {
final PsiElement operator = ((UnaryExpression) issetParent).getOperation();
if (OpenapiTypesUtil.is(operator, PhpTokenTypes.opNOT)) {
issetInverted = true;
issetParent = issetParent.getParent();
}
}
boolean isResultStored = (issetParent instanceof AssignmentExpression || issetParent instanceof PhpReturn);
/* false-positives: ternaries using isset-or-null semantics, there array_key_exist can introduce bugs */
final PsiElement conditionCandidate = issetInverted ? issetExpression.getParent() : issetExpression;
boolean isTernaryCondition = issetParent instanceof TernaryExpression && conditionCandidate == ((TernaryExpression) issetParent).getCondition();
if (isTernaryCondition) {
final TernaryExpression ternary = (TernaryExpression) issetParent;
final PsiElement nullCandidate = issetInverted ? ternary.getTrueVariant() : ternary.getFalseVariant();
if (PhpLanguageUtil.isNull(nullCandidate)) {
return;
}
}
/* do analyze */
final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(arguments[0]);
if (argument == null) {
return;
}
/* false positives: variables in template/global context - too unreliable */
if (argument instanceof Variable && ExpressionSemanticUtil.getBlockScope(argument) == null) {
return;
}
if (!(argument instanceof ArrayAccessExpression)) {
if (argument instanceof FieldReference) {
/* if field is not resolved, it's probably dynamic and isset has a purpose */
final PsiReference referencedField = argument.getReference();
final PsiElement resolvedField = referencedField == null ? null : OpenapiResolveUtil.resolveReference(referencedField);
if (resolvedField == null || !(ExpressionSemanticUtil.getBlockScope(resolvedField) instanceof PhpClass)) {
return;
}
}
if (SUGGEST_TO_USE_NULL_COMPARISON) {
/* false-positives: finally, perhaps fallback to initialization in try */
if (PsiTreeUtil.getParentOfType(issetExpression, Finally.class) == null) {
final List<String> fragments = Arrays.asList(argument.getText(), issetInverted ? "===" : "!==", "null");
if (!ComparisonStyle.isRegular()) {
Collections.reverse(fragments);
}
final String replacement = String.join(" ", fragments);
holder.registerProblem(issetInverted ? issetExpression.getParent() : issetExpression, String.format(MessagesPresentationUtil.prefixWithEa(patternUseNullComparison), replacement), ProblemHighlightType.WEAK_WARNING, new CompareToNullFix(replacement));
}
}
return;
}
/* TODO: has method/function reference as index */
if (REPORT_CONCATENATION_IN_INDEXES && !isResultStored && this.hasConcatenationAsIndex((ArrayAccessExpression) argument)) {
holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(messageConcatenationInIndex));
return;
}
if (SUGGEST_TO_USE_ARRAY_KEY_EXISTS && !isArrayAccess((ArrayAccessExpression) argument)) {
holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(messageUseArrayKeyExists), ProblemHighlightType.WEAK_WARNING);
}
}
/* checks if any of indexes is concatenation expression */
/* TODO: iterator for array access expression */
private boolean hasConcatenationAsIndex(@NotNull ArrayAccessExpression expression) {
PsiElement expressionToInspect = expression;
while (expressionToInspect instanceof ArrayAccessExpression) {
final ArrayIndex index = ((ArrayAccessExpression) expressionToInspect).getIndex();
if (index != null && index.getValue() instanceof BinaryExpression) {
final PsiElement operation = ((BinaryExpression) index.getValue()).getOperation();
if (operation != null && operation.getNode().getElementType() == PhpTokenTypes.opCONCAT) {
return true;
}
}
expressionToInspect = expressionToInspect.getParent();
}
return false;
}
// TODO: partially duplicates semanticalAnalysis.OffsetOperationsInspector.isContainerSupportsArrayAccess()
private boolean isArrayAccess(@NotNull ArrayAccessExpression expression) {
/* ok JB parses `$var[]= ...` always as array, lets make it working properly and report them later */
final PsiElement container = expression.getValue();
if (!(container instanceof PhpTypedElement)) {
return false;
}
final Set<String> containerTypes = new HashSet<>();
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) container, holder.getProject());
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> containerTypes.add(Types.getType(t)));
}
/* failed to resolve, don't try to guess anything */
if (containerTypes.isEmpty()) {
return false;
}
boolean supportsOffsets = false;
for (final String typeToCheck : containerTypes) {
/* assume is just null-ble declaration or we shall just rust to mixed */
if (typeToCheck.equals(Types.strNull)) {
continue;
}
if (typeToCheck.equals(Types.strMixed)) {
supportsOffsets = true;
continue;
}
/* some of possible types are scalars, what's wrong */
if (!StringUtils.isEmpty(typeToCheck) && typeToCheck.charAt(0) != '\\') {
supportsOffsets = false;
break;
}
/* assume class has what is needed, OffsetOperationsInspector should report if not */
supportsOffsets = true;
}
containerTypes.clear();
return supportsOffsets;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ArrayIsListCanBeUsedInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && (functionName.equals("array_keys") || functionName.equals("array_values"))) {
final boolean isTargetVersion = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP800);
if (isTargetVersion) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1) {
final PsiElement context = reference.getParent();
if (context instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) context;
final IElementType operator = binary.getOperationType();
if (OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
final PsiElement second = OpenapiElementsUtil.getSecondOperand(binary, reference);
if (functionName.equals("array_values")) {
if (second != null && OpenapiEquivalenceUtil.areEqual(arguments[0], second)) {
this.report(reference, operator, arguments[0]);
}
} else if (functionName.equals("array_keys")) {
if (OpenapiTypesUtil.isFunctionReference(second)) {
final FunctionReference rightReference = (FunctionReference) second;
final String rightFunctionName = rightReference.getName();
final PsiElement[] rightArguments = rightReference.getParameters();
if ((rightFunctionName != null && rightFunctionName.equals("range")) && rightArguments.length == 2) {
final boolean rangeFromZero = OpenapiTypesUtil.isNumber(rightArguments[0]) && rightArguments[0].getText().equals("0");
if (rangeFromZero && rightArguments[1] instanceof BinaryExpression) {
final BinaryExpression rangeToBinary = (BinaryExpression) rightArguments[1];
if (rangeToBinary.getOperationType() == PhpTokenTypes.opMINUS) {
final PsiElement right = rangeToBinary.getRightOperand();
final PsiElement left = rangeToBinary.getLeftOperand();
if (OpenapiTypesUtil.isNumber(right) && right.getText().equals("1") && OpenapiTypesUtil.isFunctionReference(left)) {
final FunctionReference leftReference = (FunctionReference) left;
final String leftName = leftReference.getName();
final PsiElement[] leftArguments = leftReference.getParameters();
if (leftName != null && leftName.equals("count") && leftArguments.length == 1) {
if (OpenapiEquivalenceUtil.areEqual(arguments[0], leftArguments[0])) {
this.report(reference, operator, arguments[0]);
}
}
}
}
}
}
}
}
}
}
}
}
}
}
private void report(@NotNull FunctionReference reference, @NotNull IElementType operator, @NotNull PsiElement argument) {
final boolean listExpected = operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opEQUAL;
final String replacement = String.format("%s%sarray_is_list(%s)", listExpected ? "" : "!", reference.getImmediateNamespaceName(), argument.getText());
holder.registerProblem(reference.getParent(), String.format(MessagesPresentationUtil.prefixWithEa(message), replacement), new UseArrayIsListFix(replacement));
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class TypeUnsafeArraySearchInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && targetFunctions.contains(functionName)) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 2) {
/* false-positives: array of string literals */
if (arguments[1] instanceof ArrayCreationExpression) {
final PsiElement[] elements = arguments[1].getChildren();
if (elements.length > 0) {
final long validElementsCount = Arrays.stream(elements).filter(element -> OpenapiTypesUtil.is(element, PhpElementTypes.ARRAY_VALUE)).map(PsiElement::getFirstChild).filter(element -> element instanceof StringLiteralExpression).map(literal -> ((StringLiteralExpression) literal).getContents().trim()).filter(content -> !content.isEmpty() && !content.matches("^\\d+$")).count();
if (validElementsCount == elements.length) {
return;
}
}
}
/* false-positives: array and item types are complimentary */
if (arguments[0] instanceof PhpTypedElement && arguments[1] instanceof PhpTypedElement) {
final Project project = holder.getProject();
final PhpType arrayType = OpenapiResolveUtil.resolveType((PhpTypedElement) arguments[1], project);
final Set<String> arrayTypes = arrayType == null ? null : arrayType.filterUnknown().getTypes();
if (arrayTypes != null && arrayTypes.size() == 1) {
final PhpType itemType = OpenapiResolveUtil.resolveType((PhpTypedElement) arguments[0], project);
final Set<String> itemTypes = itemType == null ? null : itemType.filterUnknown().getTypes();
if (itemTypes != null && itemTypes.size() == 1) {
final boolean matching = this.areTypesMatching(itemTypes.iterator().next(), arrayTypes.iterator().next());
if (matching) {
return;
}
}
}
}
/* general case: we need the third argument */
final String replacement = String.format("%s%s(%s, %s, true)", reference.getImmediateNamespaceName(), functionName, arguments[0].getText(), arguments[1].getText());
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message), new MakeSearchTypeSensitiveFix(replacement));
}
}
}
private boolean areTypesMatching(@NotNull String itemType, @NotNull String arrayType) {
boolean result = false;
if (!itemType.isEmpty()) {
result = arrayType.equals((itemType.charAt(0) == '\\' ? itemType : '\\' + itemType) + "[]");
}
return result;
}
};
}
Aggregations