use of com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix in project phpinspectionsea by kalessil.
the class SenselessProxyMethodInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
if (clazz.isInterface() || clazz.isTrait()) {
return;
}
for (final Method method : clazz.getOwnMethods()) {
final PsiElement methodNameNode = NamedElementUtil.getNameIdentifier(method);
if (null == methodNameNode || method.isAbstract() || method.getAccess().isPrivate()) {
continue;
}
/* we expect the method to have just one expression - parent invocation */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
if (null == body || 1 != ExpressionSemanticUtil.countExpressionsInGroup(body)) {
continue;
}
final PsiElement lastStatement = ExpressionSemanticUtil.getLastStatement(body);
if (null == lastStatement) {
continue;
}
/* parent invocation can be both direct or via return */
final PsiElement parentReferenceCandidate;
if (lastStatement instanceof PhpReturn) {
parentReferenceCandidate = ExpressionSemanticUtil.getReturnValue((PhpReturn) lastStatement);
} else {
parentReferenceCandidate = lastStatement.getFirstChild();
}
if (!(parentReferenceCandidate instanceof MethodReference)) {
continue;
}
final MethodReference reference = (MethodReference) parentReferenceCandidate;
final String referenceVariable = reference.getFirstChild().getText().trim();
final String referenceName = reference.getName();
if (null == referenceName || !referenceVariable.equals("parent") || !referenceName.equals(method.getName())) {
continue;
}
final Parameter[] methodParameters = method.getParameters();
/* ensure no transformations/reordering happens when dispatching parameters */
final PsiElement[] givenParams = reference.getParameters();
boolean isDispatchingWithoutModifications = (givenParams.length == methodParameters.length);
if (isDispatchingWithoutModifications) {
/* ensure parameters re-dispatched in the same order and state */
for (int index = 0; index < givenParams.length; ++index) {
if (!(givenParams[index] instanceof Variable) || !((Variable) givenParams[index]).getName().equals(methodParameters[index].getName())) {
isDispatchingWithoutModifications = false;
break;
}
}
}
/* ensure no signature changes took place */
boolean isChangingSignature = false;
final PsiReference referenceToMethod = reference.getReference();
if (null != referenceToMethod && isDispatchingWithoutModifications) {
final PsiElement referenceResolved = OpenapiResolveUtil.resolveReference(referenceToMethod);
if (referenceResolved instanceof Method) {
final Method nestedMethod = (Method) referenceResolved;
final Parameter[] parentParameters = nestedMethod.getParameters();
/* verify amount of parameters, visibility, static, abstract, final */
if (parentParameters.length == methodParameters.length && nestedMethod.isAbstract() == method.isAbstract() && nestedMethod.isStatic() == method.isStatic() && nestedMethod.isFinal() == method.isFinal() && nestedMethod.getAccess().equals(method.getAccess())) {
/* analyze if parameters definition has been changed (only ignore naming changes) */
if (methodParameters.length > 0) {
for (int index = 0; index < parentParameters.length; ++index) {
/* by-reference declaration changes: not allowed by PHP, hence not checked */
/* default values changes */
final PsiElement parentDefault = parentParameters[index].getDefaultValue();
final PsiElement methodDefault = methodParameters[index].getDefaultValue();
if ((parentDefault == null || methodDefault == null) && parentDefault != methodDefault) {
isChangingSignature = true;
break;
}
if (methodDefault != null && !OpenapiEquivalenceUtil.areEqual(parentDefault, methodDefault)) {
isChangingSignature = true;
break;
}
/* false-positive: magic constants ARE changing signature */
if (methodDefault instanceof ConstantReference) {
final String constant = ((ConstantReference) methodDefault).getName();
if (constants.contains(constant)) {
isChangingSignature = true;
break;
}
}
/* type definition changes */
final PhpType parentType = OpenapiResolveUtil.resolveDeclaredType(parentParameters[index]);
final PhpType methodType = OpenapiResolveUtil.resolveDeclaredType(methodParameters[index]);
if (!parentType.equals(methodType)) {
isChangingSignature = true;
break;
}
}
}
/* verify returned type declaration */
if (!isChangingSignature) {
final PsiElement methodReturn = OpenapiElementsUtil.getReturnType(method);
final PsiElement parentReturn = OpenapiElementsUtil.getReturnType(nestedMethod);
if (methodReturn != parentReturn) {
isChangingSignature = methodReturn == null || parentReturn == null || !OpenapiEquivalenceUtil.areEqual(methodReturn, parentReturn);
}
}
} else {
/* okay obviously changed */
isChangingSignature = true;
}
} else {
/* we couldn't resolve parent, so we can't report anything */
isChangingSignature = true;
}
}
/* decide if need to report any issues */
if (isDispatchingWithoutModifications && !isChangingSignature) {
holder.registerProblem(methodNameNode, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%s%", method.getName())), ProblemHighlightType.WEAK_WARNING, new DropMethodFix());
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix in project phpinspectionsea by kalessil.
the class SenselessMethodDuplicationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
/* process only real classes and methods */
if (method.isAbstract() || method.isDeprecated() || method.getModifier().isPrivate() || this.isTestContext(method)) {
return;
}
final PhpClass clazz = method.getContainingClass();
if (clazz == null || clazz.isTrait() || clazz.isInterface()) {
return;
}
/* don't take too heavy work */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
final int countExpressions = body == null ? 0 : ExpressionSemanticUtil.countExpressionsInGroup(body);
if (countExpressions == 0 || countExpressions > MAX_METHOD_SIZE) {
return;
}
/* ensure parent, parent methods are existing and contains the same amount of expressions */
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
final Method parentMethod = null == parent ? null : OpenapiResolveUtil.resolveMethod(parent, method.getName());
if (parentMethod == null || parentMethod.isAbstract() || parentMethod.isDeprecated() || parentMethod.getModifier().isPrivate()) {
return;
}
final GroupStatement parentBody = ExpressionSemanticUtil.getGroupStatement(parentMethod);
if (parentBody == null || ExpressionSemanticUtil.countExpressionsInGroup(parentBody) != countExpressions) {
return;
}
/* iterate and compare expressions */
PhpPsiElement ownExpression = body.getFirstPsiChild();
PhpPsiElement parentExpression = parentBody.getFirstPsiChild();
for (int index = 0; index <= countExpressions; ++index) {
/* skip doc-blocks */
while (ownExpression instanceof PhpDocComment) {
ownExpression = ownExpression.getNextPsiSibling();
}
while (parentExpression instanceof PhpDocComment) {
parentExpression = parentExpression.getNextPsiSibling();
}
if (null == ownExpression || null == parentExpression) {
break;
}
/* process comparing 2 nodes */
if (!OpenapiEquivalenceUtil.areEqual(ownExpression, parentExpression)) {
return;
}
ownExpression = ownExpression.getNextPsiSibling();
parentExpression = parentExpression.getNextPsiSibling();
}
/* methods seem to be identical: resolve used classes to avoid ns/imports magic */
boolean areReferencesMatching = true;
final Collection<String> ownReferences = this.getUsedReferences(body);
if (!ownReferences.isEmpty()) {
final Collection<String> parentReferences = this.getUsedReferences(parentBody);
areReferencesMatching = !ownReferences.contains(null) && ownReferences.equals(parentReferences);
parentReferences.clear();
}
ownReferences.clear();
if (!areReferencesMatching) {
return;
}
final PsiElement methodName = NamedElementUtil.getNameIdentifier(method);
if (methodName != null && !this.isOperatingOnPrivateMembers(parentMethod)) {
final boolean canFix = !parentMethod.getAccess().isPrivate();
if (method.getAccess().equals(parentMethod.getAccess())) {
holder.registerProblem(methodName, String.format(MessagesPresentationUtil.prefixWithEa(messagePatternIdentical), method.getName()), canFix ? new DropMethodFix() : null);
} else {
holder.registerProblem(methodName, String.format(MessagesPresentationUtil.prefixWithEa(messagePatternProxy), method.getName()), canFix ? new ProxyCallFix() : null);
}
}
}
private Collection<String> getUsedReferences(@NotNull GroupStatement body) {
final Set<String> fqns = new HashSet<>();
for (final PhpReference reference : PsiTreeUtil.findChildrenOfAnyType(body, ClassReference.class, ConstantReference.class, FunctionReference.class)) {
if (!(reference instanceof MethodReference)) {
final PsiElement entry = OpenapiResolveUtil.resolveReference(reference);
if (entry instanceof PhpNamedElement) {
// We have to use this over resolved entry FQN as some of PhpStorm versions might not resolve the proper symbol
fqns.add(reference.getFQN());
} else {
fqns.add(null);
}
}
}
return fqns;
}
private boolean isOperatingOnPrivateMembers(@NotNull Method method) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
if (body != null) {
for (final MemberReference reference : PsiTreeUtil.findChildrenOfType(body, MemberReference.class)) {
final PsiElement base = reference.getFirstChild();
final boolean resolve = (base instanceof Variable && ((Variable) base).getName().equals("this")) || (base instanceof ClassReference && base.getText().equals("self"));
if (resolve) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpClassMember && ((PhpClassMember) resolved).getModifier().isPrivate()) {
return true;
}
}
}
}
return false;
}
};
}
Aggregations