use of com.jetbrains.php.lang.psi.elements.PhpTypedElement in project phpinspectionsea by kalessil.
the class ClassInStringContextStrategy method apply.
public static boolean apply(@Nullable PsiElement nonStringOperand, @NotNull ProblemsHolder holder, @NotNull PsiElement expression, @NotNull String classHasNoToStringMessage) {
if (null == nonStringOperand) {
return false;
}
final Set<String> resolvedTypes = new HashSet<>();
if (nonStringOperand instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) nonStringOperand, holder.getProject());
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> resolvedTypes.add(Types.getType(t)));
}
}
if (!TypesSemanticsUtil.isNullableObjectInterface(resolvedTypes)) {
resolvedTypes.clear();
return false;
}
/* collect classes to check if __toString() is there */
final PhpIndex index = PhpIndex.getInstance(holder.getProject());
final List<PhpClass> listClasses = new ArrayList<>();
resolvedTypes.stream().filter(fqn -> fqn.charAt(0) == '\\').forEach(fqn -> listClasses.addAll(OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(fqn, index)));
resolvedTypes.clear();
/* check methods, error on first one violated requirements */
for (final PhpClass clazz : listClasses) {
if (OpenapiResolveUtil.resolveMethod(clazz, "__toString") == null) {
holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(classHasNoToStringMessage.replace("%class%", clazz.getFQN())), ProblemHighlightType.ERROR);
break;
}
}
/* terminate inspection, php will call __toString() */
listClasses.clear();
return true;
}
use of com.jetbrains.php.lang.psi.elements.PhpTypedElement in project phpinspectionsea by kalessil.
the class OpAssignShortSyntaxInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignment) {
final PsiElement value = ExpressionSemanticUtil.getExpressionTroughParenthesis(assignment.getValue());
if (value instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) value;
final PsiElement operator = binary.getOperation();
if (operator != null) {
final PsiElement left = binary.getLeftOperand();
final PsiElement right = binary.getRightOperand();
final PsiElement variable = assignment.getVariable();
if (variable != null && left != null && right != null) {
final IElementType operation = operator.getNode().getElementType();
if (mapping.containsKey(operation)) {
final LinkedList<PsiElement> fragments = new LinkedList<>();
fragments.addLast(right);
PsiElement candidate = left;
while (candidate instanceof BinaryExpression) {
final BinaryExpression current = (BinaryExpression) candidate;
final PsiElement rightPart = current.getRightOperand();
if (rightPart != null) {
fragments.addLast(rightPart);
}
if (current.getOperationType() != operation) {
break;
}
candidate = current.getLeftOperand();
}
if (candidate != null && OpenapiEquivalenceUtil.areEqual(variable, candidate)) {
final boolean canShorten = (fragments.size() == 1 || chainingSafeOperators.contains(operation)) && fragments.stream().noneMatch(f -> f instanceof BinaryExpression);
if (canShorten) {
/* false-positives: string elements manipulation, causes a fatal error */
boolean isStringManipulation = false;
if (variable instanceof ArrayAccessExpression) {
final PsiElement stringCandidate = ((ArrayAccessExpression) variable).getValue();
if (stringCandidate instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) stringCandidate, holder.getProject());
if (resolved != null && !resolved.hasUnknown()) {
isStringManipulation = resolved.getTypes().stream().anyMatch(t -> Types.getType(t).equals(Types.strString));
}
}
}
if (!isStringManipulation) {
Collections.reverse(fragments);
final String replacement = String.format("%s %s= %s", candidate.getText(), operator.getText(), fragments.stream().map(PsiElement::getText).collect(Collectors.joining(" " + operator.getText() + " ")));
holder.registerProblem(assignment, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new UseShorthandOperatorFix(replacement));
}
}
}
fragments.clear();
}
}
}
}
}
};
}
use of com.jetbrains.php.lang.psi.elements.PhpTypedElement 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.jetbrains.php.lang.psi.elements.PhpTypedElement in project phpinspectionsea by kalessil.
the class StrlenInEmptyStringCheckContextInspection 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 && (functionName.equals("strlen") || functionName.equals("mb_strlen"))) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0 && ExpressionSemanticUtil.getBlockScope(reference) != null) {
boolean isMatchedPattern = false;
boolean isEmptyString = false;
PsiElement target = null;
/* check explicit numbers comparisons */
final PsiElement parent = reference.getParent();
if (parent instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) parent;
final PsiElement left = binary.getLeftOperand();
final PsiElement secondOperand = OpenapiElementsUtil.getSecondOperand(binary, reference);
/* second operand should be a number */
if (OpenapiTypesUtil.isNumber(secondOperand)) {
final String number = secondOperand.getText();
/* check cases when comparing with 1 */
final IElementType operator = binary.getOperationType();
if (operator == PhpTokenTypes.opGREATER) {
isMatchedPattern = left == reference && number.equals("0");
target = binary;
isEmptyString = false;
} else if (operator == PhpTokenTypes.opLESS || operator == PhpTokenTypes.opGREATER_OR_EQUAL) {
isMatchedPattern = left == reference && number.equals("1");
target = binary;
isEmptyString = operator == PhpTokenTypes.opLESS;
}
/* check cases when checking equality to 0 */
if (!isMatchedPattern && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
isMatchedPattern = number.equals("0");
target = binary;
isEmptyString = operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opEQUAL;
}
}
}
/* checks NON-implicit boolean comparison patterns */
if (!isMatchedPattern && ExpressionSemanticUtil.isUsedAsLogicalOperand(reference)) {
isMatchedPattern = true;
target = reference;
final PsiElement operation = parent instanceof UnaryExpression ? ((UnaryExpression) parent).getOperation() : null;
if (operation != null) {
isEmptyString = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
target = parent;
}
}
/* investigate possible issues */
if (isMatchedPattern) {
final boolean isRegular = ComparisonStyle.isRegular();
final String operator = (isEmptyString ? "=" : "!") + (this.canApplyIdentityOperator(arguments[0]) ? "==" : "=");
final String replacement = String.format(isRegular ? "%s %s ''" : "'' %s %s", isRegular ? arguments[0].getText() : operator, isRegular ? operator : arguments[0].getText());
holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new CompareToEmptyStringFix(replacement));
}
}
}
}
private boolean canApplyIdentityOperator(@NotNull PsiElement value) {
if (value instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) value, holder.getProject());
if (resolved != null && resolved.size() == 1) {
return Types.strString.equals(Types.getType(resolved.getTypes().iterator().next()));
}
}
return false;
}
};
}
Aggregations