use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class IsNullFunctionUsageInspector 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("is_null")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1) {
final PsiElement parent = reference.getParent();
/* check the context */
boolean checksIsNull = true;
PsiElement target = reference;
if (parent instanceof UnaryExpression) {
if (OpenapiTypesUtil.is(((UnaryExpression) parent).getOperation(), PhpTokenTypes.opNOT)) {
checksIsNull = false;
target = parent;
}
} else if (parent instanceof BinaryExpression) {
/* extract is_nulls' expression parts */
final BinaryExpression expression = (BinaryExpression) parent;
final PsiElement secondOperand = OpenapiElementsUtil.getSecondOperand(expression, reference);
if (PhpLanguageUtil.isBoolean(secondOperand)) {
final IElementType operation = expression.getOperationType();
if (PhpTokenTypes.opEQUAL == operation || PhpTokenTypes.opIDENTICAL == operation) {
target = parent;
checksIsNull = PhpLanguageUtil.isTrue(secondOperand);
} else if (operation == PhpTokenTypes.opNOT_EQUAL || operation == PhpTokenTypes.opNOT_IDENTICAL) {
target = parent;
checksIsNull = !PhpLanguageUtil.isTrue(secondOperand);
} else {
target = reference;
}
}
}
/* report the issue */
final boolean wrap = arguments[0] instanceof AssignmentExpression || arguments[0] instanceof TernaryExpression || arguments[0] instanceof BinaryExpression;
final String wrappedArgument = wrap ? String.format("(%s)", arguments[0].getText()) : arguments[0].getText();
final boolean isRegular = ComparisonStyle.isRegular();
final String replacement = String.format("%s %s %s", isRegular ? wrappedArgument : "null", checksIsNull ? "===" : "!==", isRegular ? "null" : wrappedArgument);
holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new CompareToNullFix(replacement));
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class IsCountableCanBeUsedInspector 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("is_array")) {
final boolean isTargetVersion = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP740);
if (isTargetVersion) {
final PsiElement[] arguments = reference.getParameters();
final PsiElement parent = reference.getParent();
if (parent instanceof BinaryExpression && arguments.length == 1) {
final BinaryExpression binary = (BinaryExpression) parent;
final IElementType operation = binary.getOperationType();
if (operation == PhpTokenTypes.opOR) {
/* find the high-level binary expression */
BinaryExpression context = binary;
while (context instanceof BinaryExpression) {
PsiElement up = context.getParent();
while (up instanceof ParenthesizedExpression) {
up = up.getParent();
}
if (up instanceof BinaryExpression && ((BinaryExpression) up).getOperationType() == PhpTokenTypes.opOR) {
context = (BinaryExpression) up;
} else {
break;
}
}
/* check the pattern */
final List<PsiElement> fragments = this.extract(context, PhpTokenTypes.opOR);
if (!fragments.isEmpty()) {
if (fragments.size() > 1) {
for (final PsiElement fragment : fragments) {
if (fragment != reference && fragment instanceof BinaryExpression) {
final BinaryExpression candidate = (BinaryExpression) fragment;
if (candidate.getOperationType() == PhpTokenTypes.kwINSTANCEOF) {
final PsiElement clazz = candidate.getRightOperand();
if (clazz instanceof ClassReference && "Countable".equals(((ClassReference) clazz).getName())) {
final PsiElement subject = candidate.getLeftOperand();
if (subject != null && OpenapiEquivalenceUtil.areEqual(subject, arguments[0])) {
final String argument = subject.getText();
holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(message), argument, argument, argument));
break;
}
}
}
}
}
}
fragments.clear();
}
}
}
}
}
}
@NotNull
private List<PsiElement> extract(@NotNull BinaryExpression binary, @Nullable IElementType operator) {
final List<PsiElement> result = new ArrayList<>();
if (binary.getOperationType() == operator) {
Stream.of(binary.getLeftOperand(), binary.getRightOperand()).filter(Objects::nonNull).map(ExpressionSemanticUtil::getExpressionTroughParenthesis).forEach(expression -> {
if (expression instanceof BinaryExpression) {
result.addAll(this.extract((BinaryExpression) expression, operator));
} else {
result.add(expression);
}
});
} else {
result.add(binary);
}
return result;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class IsIterableCanBeUsedInspector 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("is_array")) {
final boolean isTargetVersion = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP710);
if (isTargetVersion) {
final PsiElement[] arguments = reference.getParameters();
final PsiElement parent = reference.getParent();
if (parent instanceof BinaryExpression && arguments.length == 1) {
final BinaryExpression binary = (BinaryExpression) parent;
final IElementType operation = binary.getOperationType();
if (operation == PhpTokenTypes.opOR) {
/* find the high-level binary expression */
BinaryExpression context = binary;
while (context instanceof BinaryExpression) {
PsiElement up = context.getParent();
while (up instanceof ParenthesizedExpression) {
up = up.getParent();
}
if (up instanceof BinaryExpression && ((BinaryExpression) up).getOperationType() == PhpTokenTypes.opOR) {
context = (BinaryExpression) up;
} else {
break;
}
}
/* check the pattern */
final List<PsiElement> fragments = this.extract(context, PhpTokenTypes.opOR);
if (!fragments.isEmpty()) {
if (fragments.size() > 1) {
for (final PsiElement fragment : fragments) {
if (fragment != reference && fragment instanceof BinaryExpression) {
final BinaryExpression candidate = (BinaryExpression) fragment;
if (candidate.getOperationType() == PhpTokenTypes.kwINSTANCEOF) {
final PsiElement clazz = candidate.getRightOperand();
if (clazz instanceof ClassReference && "Traversable".equals(((ClassReference) clazz).getName())) {
final PsiElement subject = candidate.getLeftOperand();
if (subject != null && OpenapiEquivalenceUtil.areEqual(subject, arguments[0])) {
final String argument = subject.getText();
holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(message), argument, argument, argument));
break;
}
}
}
}
}
}
fragments.clear();
}
}
}
}
}
}
@NotNull
private List<PsiElement> extract(@NotNull BinaryExpression binary, @Nullable IElementType operator) {
final List<PsiElement> result = new ArrayList<>();
if (binary.getOperationType() == operator) {
Stream.of(binary.getLeftOperand(), binary.getRightOperand()).filter(Objects::nonNull).map(ExpressionSemanticUtil::getExpressionTroughParenthesis).forEach(expression -> {
if (expression instanceof BinaryExpression) {
result.addAll(this.extract((BinaryExpression) expression, operator));
} else {
result.add(expression);
}
});
} else {
result.add(binary);
}
return result;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class PropertyInitializationFlawsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpField(@NotNull Field field) {
if (REPORT_DEFAULTS_FLAWS && !field.isConstant()) {
final PhpClass clazz = field.getContainingClass();
final PhpClass parentClazz = clazz == null ? null : OpenapiResolveUtil.resolveSuperClass(clazz);
final Field originField = parentClazz == null ? null : OpenapiResolveUtil.resolveField(parentClazz, field.getName());
final PsiElement fieldDefault = OpenapiResolveUtil.resolveDefaultValue(field);
final PsiElement originDefault = originField == null ? null : OpenapiResolveUtil.resolveDefaultValue(originField);
if (PhpLanguageUtil.isNull(fieldDefault)) {
/* false-positives: typed properties PS will take care of them */
if (!this.isNullableTypedProperty(field)) {
holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageDefaultNull), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropFieldDefaultValueFix());
}
} else if (fieldDefault instanceof PhpPsiElement && originDefault instanceof PhpPsiElement) {
final boolean isDefaultDuplicate = !originField.getModifier().getAccess().isPrivate() && OpenapiEquivalenceUtil.areEqual(fieldDefault, originDefault);
if (isDefaultDuplicate) {
boolean report = true;
/* false-positives: classes reference are the same, but resolved to different classes */
final Set<String> originalClasses = this.findReferencedClasses(originDefault);
if (!originalClasses.isEmpty()) {
final Set<String> fieldClasses = this.findReferencedClasses(fieldDefault);
report = !originalClasses.addAll(fieldClasses);
fieldClasses.clear();
originalClasses.clear();
}
if (report) {
holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageSenselessWrite), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
}
}
}
private boolean isNullableTypedProperty(@Nullable Field field) {
if (field != null && PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP740)) {
final PhpType resolved = OpenapiResolveUtil.resolveDeclaredType(field);
return !resolved.isEmpty() && resolved.getTypes().stream().map(Types::getType).anyMatch(t -> t.equals(Types.strNull) || t.equals(Types.strMixed));
}
return false;
}
@NotNull
private Set<String> findReferencedClasses(@NotNull PsiElement where) {
return PsiTreeUtil.findChildrenOfType(where, ClassReference.class).stream().map(r -> {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(r);
return resolved instanceof PhpClass ? ((PhpClass) resolved).getFQN() : null;
}).collect(Collectors.toSet());
}
@Override
public void visitPhpMethod(@NotNull Method method) {
/* configuration-based toggle */
if (!REPORT_INIT_FLAWS) {
return;
}
/* process only constructors with non-empty body */
final PhpClass clazz = method.getContainingClass();
if (null == clazz || !method.getName().equals("__construct") || clazz.isInterface() || clazz.isTrait()) {
return;
}
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
if (null == body || 0 == ExpressionSemanticUtil.countExpressionsInGroup(body)) {
return;
}
/* collect private properties with default values; stop inspection if none found */
/* protected/public properties init in __construct can be bypassed, so defaults might have sense */
final Map<String, PsiElement> propertiesToCheck = new HashMap<>();
for (final Field field : clazz.getOwnFields()) {
if (!field.isConstant()) {
final PhpModifier modifiers = field.getModifier();
if (modifiers.isPrivate() && !modifiers.isStatic()) {
final PsiElement defaultValue = OpenapiResolveUtil.resolveDefaultValue(field);
if (defaultValue instanceof PhpPsiElement && !PhpLanguageUtil.isNull(defaultValue)) {
propertiesToCheck.put(field.getName(), defaultValue);
} else {
propertiesToCheck.put(field.getName(), null);
}
}
}
}
if (propertiesToCheck.isEmpty()) {
return;
}
/* iterate 1st level instructions and analyze overriding properties */
for (final PsiElement expression : body.getChildren()) {
final PsiElement assignmentCandidate = expression.getFirstChild();
if (!OpenapiTypesUtil.isAssignment(assignmentCandidate)) {
continue;
}
final AssignmentExpression assignment = (AssignmentExpression) assignmentCandidate;
final PsiElement container = assignment.getVariable();
final PsiElement value = assignment.getValue();
if (container instanceof FieldReference && container.getFirstChild().getText().equals("$this")) {
final String overriddenProperty = ((FieldReference) container).getName();
if (null == value || null == overriddenProperty || !propertiesToCheck.containsKey(overriddenProperty)) {
continue;
}
final PsiElement fieldDefault = propertiesToCheck.get(overriddenProperty);
/* Pattern: written and default values are identical */
if ((null == fieldDefault && PhpLanguageUtil.isNull(value)) || (null != fieldDefault && OpenapiEquivalenceUtil.areEqual(value, fieldDefault))) {
/* false-positives: typed properties */
if (!this.isNullableTypedProperty(OpenapiResolveUtil.resolveField(clazz, overriddenProperty))) {
holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(messageSenselessWrite), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
continue;
}
if (null == fieldDefault) {
continue;
}
/* false-positive: property is involved into generating new value */
boolean isPropertyReused = false;
for (final FieldReference candidate : PsiTreeUtil.findChildrenOfType(value, FieldReference.class)) {
if (OpenapiEquivalenceUtil.areEqual(container, candidate)) {
isPropertyReused = true;
break;
}
}
if (!isPropertyReused && REPORT_DEFAULTS_FLAWS) {
holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageDefaultOverride), new DropFieldDefaultValueFix());
}
}
}
propertiesToCheck.clear();
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class PhpUnitTestsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
final PhpClass clazz = method.getContainingClass();
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(method);
final PhpDocComment phpDoc = method.getDocComment();
if (null == clazz || null == nameNode || phpDoc == null) {
return;
}
final boolean isMethodNamedAsTest = method.getName().startsWith("test");
for (final PhpDocTag tag : PsiTreeUtil.findChildrenOfType(phpDoc, PhpDocTag.class)) {
final String tagName = tag.getName();
if (tagName.equals("@dataProvider")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final List<PsiReference> references = Arrays.asList(candidate.getReferences());
if (!references.isEmpty()) {
Collections.reverse(references);
final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
if (resolved instanceof Method) {
if (SUGGEST_TO_USE_NAMED_DATASETS && !((Method) resolved).isAbstract()) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(resolved);
final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
if (last instanceof PhpReturn) {
final PsiElement value = ExpressionSemanticUtil.getReturnValue((PhpReturn) last);
if (value instanceof ArrayCreationExpression) {
final PsiElement firstChild = ((ArrayCreationExpression) value).getFirstPsiChild();
boolean isNamedDataset = firstChild == null;
if (firstChild instanceof ArrayHashElement) {
final PsiElement key = ((ArrayHashElement) firstChild).getKey();
isNamedDataset = key instanceof StringLiteralExpression;
}
if (!isNamedDataset) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageNamedProvider));
}
}
}
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@depends")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final List<PsiReference> references = Arrays.asList(candidate.getReferences());
if (!references.isEmpty()) {
Collections.reverse(references);
final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
if (resolved instanceof Method) {
final Method dependency = (Method) resolved;
if (!dependency.getName().startsWith("test")) {
final PhpDocComment docBlock = dependency.getDocComment();
if (docBlock == null || docBlock.getTagElementsByName("@test").length == 0) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@covers")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final PhpDocRef referenceNeeded = (PhpDocRef) candidate;
final List<PsiReference> references = Arrays.asList(referenceNeeded.getReferences());
Collections.reverse(references);
/* resolve references, populate information about provided entries */
boolean hasCallableReference = false;
boolean hasClassReference = false;
final String referenceText = referenceNeeded.getText();
for (final PsiReference ref : references) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(ref);
if (resolved instanceof PhpClass) {
hasClassReference = true;
hasCallableReference = referenceText.endsWith("::");
break;
} else if (resolved instanceof Function) {
hasCallableReference = true;
hasClassReference = resolved instanceof Method;
break;
}
}
final boolean callableNeeded = referenceText.contains("::") && !referenceText.contains("::<");
if ((callableNeeded && !hasCallableReference) || (!callableNeeded && !hasClassReference)) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(String.format(messageCovers, referenceText)), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@test")) {
if (isMethodNamedAsTest && this.isAnnotation(tag)) {
holder.registerProblem(tag.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messageTest), ProblemHighlightType.LIKE_DEPRECATED, new AmbiguousTestAnnotationLocalFix());
}
}
}
}
private boolean isAnnotation(@NotNull PhpDocTag tag) {
PsiElement previous = tag.getPrevSibling();
previous = previous instanceof PsiWhiteSpace ? previous.getPrevSibling() : previous;
final IElementType start = previous == null ? null : previous.getNode().getElementType();
return start == PhpTokenTypes.DOC_COMMENT_START || start == PhpTokenTypes.DOC_LEADING_ASTERISK;
}
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
final String methodName = reference.getName();
if (methodName != null) {
if (methodName.startsWith("assert") && !methodName.equals("assert")) {
final List<BooleanSupplier> callbacks = new ArrayList<>();
callbacks.add(() -> AssertBoolInvertedStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertBoolOfComparisonStrategy.apply(methodName, reference, holder));
if (SUGGEST_TO_USE_ASSERTSAME) {
callbacks.add(() -> AssertSameStrategy.apply(methodName, reference, holder));
}
if (PROMOTE_PHPUNIT_API) {
final PhpUnitVersion version = PHP_UNIT_VERSION == null ? PhpUnitVersion.PHPUNIT80 : PHP_UNIT_VERSION;
callbacks.add(() -> AssertEmptyStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertConstantStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertInternalTypeStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertInstanceOfStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertResourceExistsStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertCountStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertContainsStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertRegexStrategy.apply(methodName, reference, holder));
/* AssertFileEqualsStrategy and AssertStringEqualsFileStrategy order is important */
callbacks.add(() -> AssertFileEqualsStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertStringEqualsFileStrategy.apply(methodName, reference, holder));
}
for (final BooleanSupplier callback : callbacks) {
if (callback.getAsBoolean()) {
break;
}
}
callbacks.clear();
} else if (methodName.equals("expects")) {
if (PROMOTE_MOCKING_ONCE) {
ExpectsOnceStrategy.apply(methodName, reference, holder);
}
} else if (methodName.equals("will")) {
if (PROMOTE_MOCKING_WILL_RETURN) {
WillReturnStrategy.apply(methodName, reference, holder);
}
}
}
}
};
}
Aggregations