use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class ParameterDefaultValueIsNotNullInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
this.analyze(method);
}
@Override
public void visitPhpFunction(@NotNull Function function) {
this.analyze(function);
}
private void analyze(@NotNull Function function) {
final Parameter[] arguments = function.getParameters();
if (arguments.length > 0) {
/* collect violations */
final List<Parameter> violations = new ArrayList<>();
for (final Parameter argument : arguments) {
final PsiElement defaultValue = argument.getDefaultValue();
if (defaultValue != null && !PhpLanguageUtil.isNull(defaultValue)) {
/* false-positives: null can not be used due to implicit type hints */
final PhpType declared = OpenapiResolveUtil.resolveDeclaredType(argument);
if (declared.isEmpty() || declared.getTypes().stream().anyMatch(t -> Types.getType(t).equals(Types.strNull))) {
violations.add(argument);
}
}
}
if (!violations.isEmpty()) {
/* false-positives: methods overrides, so violation should be addressed in the parent */
if (function instanceof Method) {
final PhpClass clazz = ((Method) function).getContainingClass();
if (clazz != null) {
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent != null) {
final Method parentMethod = OpenapiResolveUtil.resolveMethod(parent, function.getName());
if (parentMethod != null && !parentMethod.getAccess().isPrivate()) {
violations.clear();
return;
}
}
}
}
/* report violations */
violations.forEach(param -> holder.registerProblem(param, MessagesPresentationUtil.prefixWithEa(message)));
violations.clear();
}
}
}
};
}
use of com.intellij.psi.PsiElementVisitor 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;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class ProperNullCoalescingOperatorUsageInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpBinaryExpression(@NotNull BinaryExpression binary) {
if (binary.getOperationType() == PhpTokenTypes.opCOALESCE && !this.isPartOfCoalesce(binary) && !this.isTypeCasted(binary)) {
final PsiElement left = binary.getLeftOperand();
final PsiElement right = binary.getRightOperand();
if (left != null && right != null) {
/* case: `call() ?? null` */
if (PhpLanguageUtil.isNull(right)) {
if (left instanceof FunctionReference) {
holder.registerProblem(binary, String.format(MessagesPresentationUtil.prefixWithEa(messageSimplify), left.getText()), new UseLeftOperandFix(left.getText()));
}
return;
}
/* case: `returns_string_or_null() ?? []` */
if (ANALYZE_TYPES && left instanceof PhpTypedElement && right instanceof PhpTypedElement) {
final Function scope = ExpressionSemanticUtil.getScope(binary);
if (scope != null) {
final Set<String> leftTypes = this.resolve((PhpTypedElement) left);
if (leftTypes != null && !leftTypes.isEmpty()) {
final Set<String> rightTypes = this.resolve((PhpTypedElement) right);
if (rightTypes != null && !rightTypes.isEmpty()) {
final boolean complimentary = ALLOW_OVERLAPPING_TYPES ? rightTypes.stream().anyMatch(leftTypes::contains) : rightTypes.containsAll(leftTypes);
if (!complimentary && !this.areRelated(rightTypes, leftTypes)) {
holder.registerProblem(binary, String.format(MessagesPresentationUtil.prefixWithEa(messageMismatch), leftTypes.toString(), rightTypes.toString()));
}
rightTypes.clear();
}
leftTypes.clear();
}
}
}
}
}
}
private boolean isTypeCasted(@NotNull BinaryExpression binary) {
final PsiElement parent = binary.getParent();
if (parent instanceof ParenthesizedExpression) {
final PsiElement grandParent = parent.getParent();
if (grandParent instanceof UnaryExpression) {
final PsiElement operator = ((UnaryExpression) grandParent).getOperation();
return operator != null && PhpTokenTypes.tsCAST_OPS.contains(operator.getNode().getElementType());
}
}
return false;
}
private boolean isPartOfCoalesce(@NotNull BinaryExpression binary) {
final PsiElement parent = binary.getParent();
return parent instanceof BinaryExpression && ((BinaryExpression) parent).getOperationType() == PhpTokenTypes.opCOALESCE;
}
private boolean areRelated(@NotNull Set<String> rightTypes, @NotNull Set<String> leftTypes) {
final Set<PhpClass> left = this.extractClasses(leftTypes);
if (!left.isEmpty()) {
final Set<PhpClass> right = this.extractClasses(rightTypes);
if (!right.isEmpty() && left.stream().anyMatch(right::contains)) {
left.clear();
right.clear();
return true;
}
left.clear();
}
return false;
}
private HashSet<PhpClass> extractClasses(@NotNull Set<String> types) {
final HashSet<PhpClass> classes = new HashSet<>();
final PhpIndex index = PhpIndex.getInstance(holder.getProject());
types.stream().filter(t -> t.startsWith("\\")).forEach(t -> OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(t, index).forEach(c -> classes.addAll(InterfacesExtractUtil.getCrawlInheritanceTree(c, true))));
return classes;
}
@Nullable
private Set<String> resolve(@NotNull PhpTypedElement subject) {
final PhpType type = OpenapiResolveUtil.resolveType(subject, holder.getProject());
if (type != null && !type.hasUnknown()) {
final Set<String> types = type.getTypes().stream().map(Types::getType).collect(Collectors.toSet());
if (!types.isEmpty() && !types.contains(Types.strMixed) && !types.contains(Types.strObject)) {
types.remove(Types.strStatic);
types.remove(Types.strNull);
return types;
}
types.clear();
}
return null;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class GetClassUsageInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final Project project = holder.getProject();
if (PhpLanguageLevel.get(project).atLeast(PhpLanguageLevel.PHP710)) {
final String functionName = reference.getName();
if (functionName != null && functionName.equals("get_class")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1 && arguments[0] instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) arguments[0], project);
if (resolved != null) {
final boolean hasNull = resolved.filterUnknown().getTypes().stream().anyMatch(t -> Types.getType(t).equals(Types.strNull));
if ((hasNull || isNullableParameter(arguments[0])) && !isNullabilityChecked(arguments[0])) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
}
}
}
private boolean isNullabilityChecked(@NotNull PsiElement expression) {
/* workaround for https://youtrack.jetbrains.com/issue/WI-38622 */
boolean result = false;
final Function scope = ExpressionSemanticUtil.getScope(expression);
if (scope != null) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(scope);
final List<PsiElement> allUsages = PsiTreeUtil.findChildrenOfType(body, expression.getClass()).stream().filter(e -> OpenapiEquivalenceUtil.areEqual(e, expression)).collect(Collectors.toList());
for (final PsiElement candidate : allUsages.subList(0, allUsages.indexOf(expression))) {
final PsiElement parent = candidate.getParent();
if (parent instanceof PhpEmpty || parent instanceof PhpIsset) {
result = true;
} else if (parent instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) parent;
final IElementType operator = binary.getOperationType();
if (operator == PhpTokenTypes.kwINSTANCEOF) {
result = true;
} else if (OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
final PsiElement second = OpenapiElementsUtil.getSecondOperand(binary, candidate);
result = PhpLanguageUtil.isNull(second);
}
} else if (ExpressionSemanticUtil.isUsedAsLogicalOperand(candidate)) {
result = true;
}
/* break loop when null check being found */
if (result) {
break;
}
}
allUsages.clear();
}
return result;
}
private boolean isNullableParameter(@NotNull PsiElement expression) {
boolean result = false;
if (expression instanceof Variable) {
final Function scope = ExpressionSemanticUtil.getScope(expression);
if (scope != null) {
final String name = ((Variable) expression).getName();
result = Arrays.stream(scope.getParameters()).anyMatch(parameter -> name.equals(parameter.getName()) && PhpLanguageUtil.isNull(parameter.getDefaultValue()));
}
}
return result;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class UnknownInspectionInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpDocTag(@NotNull PhpDocTag tag) {
if (tag.getName().equals("@noinspection")) {
final String[] candidates = tag.getTagValue().replaceAll("[^\\p{L}\\p{Nd}]+", " ").trim().split("\\s+");
if (candidates.length > 0) {
final List<String> unknown = Stream.of(candidates[0]).filter(c -> !inspections.contains(c) && !inspections.contains(c + "Inspection")).collect(Collectors.toList());
if (!unknown.isEmpty()) {
final PsiElement target = tag.getFirstChild();
if (target != null) {
holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(message), String.join(", ", unknown)));
}
unknown.clear();
}
}
}
}
};
}
Aggregations