use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class UnusedConstructorDependenciesInspector method buildVisitor.
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@NotNull
private Map<String, Field> getPrivateFields(@NotNull PhpClass clazz) {
final Map<String, Field> privateFields = new HashMap<>();
for (final Field field : clazz.getOwnFields()) {
if (!field.isConstant()) {
final PhpModifier modifiers = field.getModifier();
if (modifiers.isPrivate() && !modifiers.isStatic()) {
final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(field.getDocComment(), PhpDocTag.class);
final boolean annotated = tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
if (!annotated) {
privateFields.put(field.getName(), field);
}
}
}
}
return privateFields;
}
@NotNull
private Map<String, List<FieldReference>> getFieldReferences(@NotNull Method method, @NotNull Map<String, Field> privateFields) {
final Map<String, List<FieldReference>> filteredReferences = new HashMap<>();
if (!method.isAbstract()) {
final Collection<FieldReference> references = PsiTreeUtil.findChildrenOfType(method, FieldReference.class);
for (final FieldReference ref : references) {
final String fieldName = ref.getName();
if (fieldName != null && privateFields.containsKey(fieldName)) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(ref);
if (resolved == null || (resolved instanceof Field && privateFields.containsValue(resolved))) {
filteredReferences.computeIfAbsent(fieldName, k -> new ArrayList<>()).add(ref);
}
}
}
references.clear();
}
return filteredReferences;
}
@NotNull
private Map<String, List<FieldReference>> getMethodsFieldReferences(@NotNull PhpClass clazz, @NotNull Method constructor, @NotNull Map<String, Field> privateFields) {
final Map<String, List<FieldReference>> filteredReferences = new HashMap<>();
/* collect methods to inspect, incl. once from traits */
final List<Method> methodsToCheck = new ArrayList<>();
Collections.addAll(methodsToCheck, clazz.getOwnMethods());
for (final PhpClass trait : clazz.getTraits()) {
Collections.addAll(methodsToCheck, trait.getOwnMethods());
}
/* find references */
for (final Method method : methodsToCheck) {
if (method != constructor) {
final Map<String, List<FieldReference>> innerReferences = getFieldReferences(method, privateFields);
if (!innerReferences.isEmpty()) {
/* merge method's scan results into common container */
innerReferences.forEach((fieldName, references) -> {
filteredReferences.computeIfAbsent(fieldName, name -> new ArrayList<>()).addAll(references);
references.clear();
});
}
innerReferences.clear();
}
}
methodsToCheck.clear();
return filteredReferences;
}
@Override
public void visitPhpMethod(@NotNull Method method) {
/* filter classes which needs to be analyzed */
final PhpClass clazz = method.getContainingClass();
if (null == clazz || clazz.isInterface() || clazz.isTrait() || null == clazz.getOwnConstructor() || 0 == clazz.getOwnFields().length || 0 == clazz.getOwnMethods().length) {
return;
}
/* run inspection only in constructors and if own private fields being defined */
final Method constructor = clazz.getOwnConstructor();
if (method != constructor) {
return;
}
final Map<String, Field> clazzPrivateFields = this.getPrivateFields(clazz);
if (clazzPrivateFields.isEmpty()) {
return;
}
/* === intensive part : extract references === */
final Map<String, List<FieldReference>> constructorsReferences = getFieldReferences(constructor, clazzPrivateFields);
if (!constructorsReferences.isEmpty()) {
/* constructor's references being identified */
final Map<String, List<FieldReference>> otherReferences = getMethodsFieldReferences(clazz, constructor, clazzPrivateFields);
/* methods's references being identified, time to re-visit constructor's references */
constructorsReferences.forEach((fieldName, references) -> {
/* field is not used, report in constructor, IDE detects unused fields */
if (!otherReferences.containsKey(fieldName)) {
/* ensure the field reference in constructor is not bound to closures only */
if (references.stream().anyMatch(r -> ExpressionSemanticUtil.getScope(r) == constructor)) {
this.doReport(holder, references);
}
}
references.clear();
});
/* release references found in the methods */
otherReferences.values().forEach(List::clear);
otherReferences.clear();
}
constructorsReferences.clear();
}
private void doReport(@NotNull ProblemsHolder holder, @NotNull List<FieldReference> fields) {
fields.stream().filter(reference -> {
final PsiElement parent = reference.getParent();
if (OpenapiTypesUtil.isAssignment(parent)) {
return reference == ((AssignmentExpression) parent).getVariable();
}
return false;
}).forEach(reference -> holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message)));
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class LoopWhichDoesNotLoopInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpForeach(@NotNull ForeachStatement loop) {
final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
if (!isBlade && this.isNotLooping(loop)) {
/* false-positive: return first element from generator, iterable and co */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(loop);
final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
if (last != null && !OpenapiTypesUtil.isThrowExpression(last)) {
final PsiElement source = loop.getArray();
if (source instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) source, holder.getProject());
final boolean isValid = resolved != null && resolved.filterUnknown().getTypes().stream().anyMatch(type -> foreachExceptions.contains(Types.getType(type)));
if (isValid) {
return;
}
}
}
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
}
}
@Override
public void visitPhpFor(@NotNull For loop) {
final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
if (!isBlade && this.isNotLooping(loop)) {
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
}
}
@Override
public void visitPhpWhile(@NotNull While loop) {
final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
if (!isBlade && this.isNotLooping(loop)) {
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
}
}
@Override
public void visitPhpDoWhile(@NotNull DoWhile loop) {
final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
if (!isBlade && this.isNotLooping(loop)) {
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
}
}
private boolean isNotLooping(@NotNull PhpPsiElement loop) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(loop);
if (null == body) {
return false;
}
final PsiElement lastExpression = ExpressionSemanticUtil.getLastStatement(body);
final boolean isLoopTerminatedWithLastExpression = lastExpression instanceof PhpBreak || lastExpression instanceof PhpReturn || OpenapiTypesUtil.isThrowExpression(lastExpression);
/* loop is empty or terminates on first iteration */
if (null != lastExpression && !isLoopTerminatedWithLastExpression) {
return false;
}
/* detect continue statements, which makes the loop looping */
for (final PhpContinue expression : PsiTreeUtil.findChildrenOfType(body, PhpContinue.class)) {
int nestingLevel = 0;
PsiElement parent = expression.getParent();
while (null != parent && !(parent instanceof Function) && !(parent instanceof PsiFile)) {
if (OpenapiTypesUtil.isLoop(parent)) {
++nestingLevel;
if (parent == loop) {
/* extract level of continuation from the statement */
int continueLevel = 1;
final PsiElement argument = expression.getArgument();
if (null != argument) {
try {
continueLevel = Integer.parseInt(argument.getText());
} catch (final NumberFormatException notParsed) {
continueLevel = 1;
}
}
/* matched continue for the current loop */
if (continueLevel == nestingLevel) {
return false;
}
}
}
parent = parent.getParent();
}
}
return true;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class DeprecatedConstructorStyleInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
if (!method.isStatic()) {
final PhpClass clazz = method.getContainingClass();
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(method);
if (clazz != null && nameNode != null && !clazz.isTrait() && !clazz.isInterface()) {
final String className = clazz.getName();
if (className.equals(method.getName()) && clazz.findOwnMethodByName("__construct") == null) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, className)), ProblemHighlightType.LIKE_DEPRECATED, new RenameConstructorFix());
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class PropertyCanBeStaticInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpField(@NotNull Field field) {
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(field);
if (nameNode != null) {
final PhpModifier modifier = field.getModifier();
if (!field.isConstant() && !modifier.isStatic() && !modifier.isPublic()) {
final PsiElement defaultValue = field.getDefaultValue();
if (defaultValue instanceof ArrayCreationExpression) {
final PhpClass clazz = field.getContainingClass();
if (clazz != null) {
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent == null || OpenapiResolveUtil.resolveField(parent, field.getName()) == null) {
int intArrayOrStringCount = 0;
for (final PsiElement entry : defaultValue.getChildren()) {
PhpPsiElement item = null;
if (entry instanceof ArrayHashElement) {
item = ((ArrayHashElement) entry).getValue();
} else if (entry instanceof PhpPsiElement) {
item = ((PhpPsiElement) entry).getFirstPsiChild();
}
if (item instanceof ArrayCreationExpression || item instanceof StringLiteralExpression) {
if (++intArrayOrStringCount == 3 && !this.isSuppressed(field)) {
final boolean canUseConstants = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP560);
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(canUseConstants ? messageWithConstants : messageNoConstants));
break;
}
}
}
}
}
}
}
}
}
private boolean isSuppressed(@NotNull PsiElement expression) {
final PsiElement parent = expression.getParent();
if (parent.getParent() instanceof PhpClass) {
final PsiElement previous = ((PhpPsiElement) parent).getPrevPsiSibling();
if (previous instanceof PhpDocComment) {
final String candidate = previous.getText();
if (candidate.contains("@noinspection") && candidate.contains(getShortName())) {
return true;
}
}
}
return false;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor 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());
}
}
}
};
}
Aggregations