use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class DateUsageInspector 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("date")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 2) {
final PsiElement candidate = arguments[1];
if (OpenapiTypesUtil.isFunctionReference(candidate)) {
final FunctionReference inner = (FunctionReference) candidate;
final String innerName = inner.getName();
if (innerName != null && innerName.equals("time") && inner.getParameters().length == 0) {
holder.registerProblem(inner, MessagesPresentationUtil.prefixWithEa(messageDropTime), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropTimeFunctionCallLocalFix(holder.getProject(), arguments[0], arguments[1]));
}
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class StaticClosureCanBeUsedInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunction(@NotNull Function function) {
if (PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP540) && OpenapiTypesUtil.isLambda(function)) {
final boolean isTarget = !OpenapiTypesUtil.is(function.getFirstChild(), PhpTokenTypes.kwSTATIC);
if (isTarget && this.canBeStatic(function)) {
holder.registerProblem(function.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message), new MakeClosureStaticFix());
}
/* learning currently is not feasible: influence of co-dispatched arguments */
}
}
private boolean canBeStatic(@NotNull Function function) {
final boolean isArrowFunction = !OpenapiTypesUtil.is(function.getFirstChild(), PhpTokenTypes.kwFUNCTION);
final PsiElement body = isArrowFunction ? function : ExpressionSemanticUtil.getGroupStatement(function);
if (body != null) {
final boolean isTargetClosure = (isArrowFunction && SUGGEST_FOR_SHORT_FUNCTIONS) || ExpressionSemanticUtil.countExpressionsInGroup((GroupStatement) body) > 0;
if (isTargetClosure) {
/* check if $this or parent:: being used */
for (final PsiElement element : PsiTreeUtil.findChildrenOfAnyType(body, Variable.class, MethodReference.class)) {
if (element instanceof Variable) {
if (((Variable) element).getName().equals("this")) {
return false;
}
} else {
final MethodReference reference = (MethodReference) element;
final PsiElement base = reference.getFirstChild();
if (base instanceof ClassReference && base.getText().equals("parent")) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Method && !((Method) resolved).isStatic()) {
return false;
}
}
}
}
/* Closure::bind(, null, ) -> can be static */
if (!this.canBeStatic(this.usages(function))) {
return false;
}
return true;
}
}
return false;
}
private List<PsiElement> usages(@NotNull Function function) {
final List<PsiElement> usages = new ArrayList<>();
/* case 1: dispatched directly into a call */
final PsiElement parent = function.getParent();
final PsiElement extractedParent = OpenapiTypesUtil.is(parent, PhpElementTypes.CLOSURE) ? parent.getParent() : parent;
if (extractedParent instanceof ParameterList) {
usages.add(extractedParent);
return usages;
}
/* case 2: dispatched into a call via variable */
if (OpenapiTypesUtil.isAssignment(extractedParent)) {
final PsiElement assignmentStorage = ((AssignmentExpression) extractedParent).getVariable();
if (assignmentStorage instanceof Variable) {
final Function scope = ExpressionSemanticUtil.getScope(function);
final GroupStatement scopeBody = scope == null ? null : ExpressionSemanticUtil.getGroupStatement(scope);
if (scopeBody != null) {
final String variableName = ((Variable) assignmentStorage).getName();
for (final Variable usage : PsiTreeUtil.findChildrenOfType(scopeBody, Variable.class)) {
if (variableName.equals(usage.getName()) && usage != assignmentStorage) {
final PsiElement usageContext = usage.getParent();
/* if closure used in other context, side-effects are not predictable */
if (!(usageContext instanceof ParameterList) && !(usageContext instanceof MethodReference)) {
usages.clear();
return usages;
}
usages.add(usageContext);
}
}
}
}
}
/* case 3: dispatched into array (factories/callbacks) in non-scoped context */
if (OpenapiTypesUtil.is(extractedParent, PhpElementTypes.ARRAY_VALUE)) {
usages.add(extractedParent.getParent());
}
return usages;
}
private boolean canBeStatic(@NotNull List<PsiElement> usages) {
for (final PsiElement context : usages) {
if (context instanceof ParameterList) {
final PsiElement referenceCandidate = context.getParent();
if (referenceCandidate instanceof MethodReference) {
/* case: Closure::bind() */
final MethodReference reference = (MethodReference) referenceCandidate;
final String methodName = reference.getName();
if (methodName != null && methodName.equals("bind")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 1 && PhpLanguageUtil.isNull(arguments[1])) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Method && ((Method) resolved).getFQN().equals("\\Closure.bind")) {
continue;
}
}
} else {
final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
if (OpenapiTypesUtil.is(operator, PhpTokenTypes.SCOPE_RESOLUTION)) {
continue;
}
}
} else if (referenceCandidate instanceof FunctionReference) {
/* case: e.g. array_filter($array, $callback) */
final PsiElement resolved = OpenapiResolveUtil.resolveReference((FunctionReference) referenceCandidate);
if (resolved instanceof Function && ((Function) resolved).getNamespaceName().equals("\\")) {
continue;
}
}
} else if (context instanceof MethodReference) {
/* case: $closure->bindTo() */
final MethodReference reference = (MethodReference) context;
final String methodName = reference.getName();
if (methodName != null && methodName.equals("bindTo")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0 && PhpLanguageUtil.isNull(arguments[0])) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Method && ((Method) resolved).getFQN().equals("\\Closure.bindTo")) {
continue;
}
}
}
} else if (context instanceof ArrayHashElement) {
/* case: [ Clazz::class => <callback> ] (factories) */
if (ExpressionSemanticUtil.getScope(context) == null) {
continue;
}
}
return false;
}
return true;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class UnnecessarySemicolonInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpStatement(@NotNull Statement statement) {
final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
if (!isBlade && statement.getChildren().length == 0) {
final PsiElement parent = statement.getParent();
final boolean skip = parent instanceof If || parent instanceof ElseIf || parent instanceof Else || OpenapiTypesUtil.isLoop(parent) || OpenapiTypesUtil.is(parent.getFirstChild(), PhpTokenTypes.kwDECLARE);
if (!skip) {
holder.registerProblem(statement, MessagesPresentationUtil.prefixWithEa(message), new DropUnnecessarySemicolonFix());
}
}
}
@Override
public void visitPhpEchoStatement(@NotNull PhpEchoStatement echo) {
if (!OpenapiTypesUtil.is(echo.getFirstChild(), PhpTokenTypes.kwECHO)) {
final PsiElement last = echo.getLastChild();
if (OpenapiTypesUtil.is(last, PhpTokenTypes.opSEMICOLON)) {
PsiElement next = echo.getNextSibling();
if (next instanceof PsiWhiteSpace) {
next = next.getNextSibling();
}
if (!(next instanceof PhpPsiElement)) {
holder.registerProblem(last, MessagesPresentationUtil.prefixWithEa(message), new DropUnnecessarySemicolonFix());
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class AlterInForeachInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpForeach(@NotNull ForeachStatement foreach) {
/* lookup for reference preceding value */
final Variable objForeachValue = foreach.getValue();
if (null != objForeachValue) {
PsiElement prevElement = objForeachValue.getPrevSibling();
if (prevElement instanceof PsiWhiteSpace) {
prevElement = prevElement.getPrevSibling();
}
if (null != prevElement) {
PhpPsiElement nextExpression = foreach.getNextPsiSibling();
if (OpenapiTypesUtil.is(prevElement, PhpTokenTypes.opBIT_AND)) {
/* === requested by the community, not part of original idea === */
/* look up for parents which doesn't have following statements, eg #53 */
PsiElement foreachParent = foreach.getParent();
while (null == nextExpression && null != foreachParent) {
if (!(foreachParent instanceof GroupStatement)) {
nextExpression = ((PhpPsiElement) foreachParent).getNextPsiSibling();
}
foreachParent = foreachParent.getParent();
if (null == foreachParent || foreachParent instanceof Function || foreachParent instanceof PhpFile) {
break;
}
}
/* === requested by the community, not part of original idea === */
/* allow return/end of control flow after the loop - no issues can be introduced */
boolean isRequirementFullFilled = false;
while (nextExpression instanceof PhpDocComment) {
nextExpression = nextExpression.getNextPsiSibling();
}
if (nextExpression == null || nextExpression instanceof PhpReturn || OpenapiTypesUtil.isThrowExpression(nextExpression)) {
isRequirementFullFilled = true;
}
/* check unset is applied to value-variable */
final String foreachValueName = objForeachValue.getName();
if (nextExpression instanceof PhpUnset && !StringUtils.isEmpty(foreachValueName)) {
for (PhpPsiElement unsetArgument : ((PhpUnset) nextExpression).getArguments()) {
// skip non-variable expressions
if (!(unsetArgument instanceof Variable)) {
continue;
}
// check argument matched foreach value name
final String unsetArgumentName = unsetArgument.getName();
if (!StringUtils.isEmpty(unsetArgumentName) && unsetArgumentName.equals(foreachValueName)) {
isRequirementFullFilled = true;
break;
}
}
}
/* check if warning needs to be reported */
if (!isRequirementFullFilled) {
holder.registerProblem(objForeachValue, MessagesPresentationUtil.prefixWithEa(messageMissingUnset));
}
} else {
/* check for unset in parent foreach-statements: foreach-{foreach}-unset */
ForeachStatement currentForeach = foreach;
while (!(nextExpression instanceof PhpUnset) && currentForeach.getParent() instanceof GroupStatement && currentForeach.getParent().getParent() instanceof ForeachStatement) {
currentForeach = (ForeachStatement) currentForeach.getParent().getParent();
nextExpression = currentForeach.getNextPsiSibling();
}
/* check if un-sets non-reference value - not needed at all, probably forgotten to cleanup */
if (nextExpression instanceof PhpUnset) {
final PhpPsiElement[] unsetArguments = ((PhpUnset) nextExpression).getArguments();
if (unsetArguments.length > 0) {
final String foreachValueName = objForeachValue.getName();
for (PhpPsiElement unsetExpression : unsetArguments) {
if (!(unsetArguments[0] instanceof Variable)) {
continue;
}
final String unsetArgumentName = unsetExpression.getName();
if (!StringUtils.isEmpty(unsetArgumentName) && !StringUtils.isEmpty(foreachValueName) && unsetArgumentName.equals(foreachValueName)) {
holder.registerProblem(unsetExpression, MessagesPresentationUtil.prefixWithEa(patternAmbiguousUnset.replace("%v%", foreachValueName)), ProblemHighlightType.WEAK_WARNING);
}
}
}
}
}
}
}
}
@Override
public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignmentExpression) {
if (!SUGGEST_USING_VALUE_BY_REF) /*|| ... PHP7 ...*/
{
return;
}
final PhpPsiElement operand = assignmentExpression.getVariable();
if (!(operand instanceof ArrayAccessExpression)) {
return;
}
/* ensure assignment structure is complete */
final ArrayAccessExpression container = (ArrayAccessExpression) operand;
if (null == container.getIndex() || null == container.getValue() || !(container.getIndex().getValue() instanceof Variable)) {
return;
}
/* get parts of assignment */
final PhpPsiElement objForeachSourceCandidate = container.getValue();
final PhpPsiElement objForeachKeyCandidate = container.getIndex().getValue();
PsiElement parent = assignmentExpression.getParent();
while (null != parent && !(parent instanceof PhpFile)) {
/* terminate if reached callable */
if (parent instanceof Function) {
return;
}
if (parent instanceof ForeachStatement) {
/* get parts of foreach: array, key, value */
final ForeachStatement objForeach = (ForeachStatement) parent;
final Variable objForeachValue = objForeach.getValue();
final Variable objForeachKey = objForeach.getKey();
final PsiElement objForeachArray = objForeach.getArray();
/* report if aggressive optimization possible: foreach(... as &$value) */
if (null != objForeachArray && null != objForeachKey && null != objForeachValue && OpenapiEquivalenceUtil.areEqual(objForeachKey, objForeachKeyCandidate) && OpenapiEquivalenceUtil.areEqual(objForeachArray, objForeachSourceCandidate)) {
final String strName = objForeachValue.getName();
if (!StringUtils.isEmpty(strName)) {
holder.registerProblem(operand, MessagesPresentationUtil.prefixWithEa(patternSuggestReference.replace("%c%", strName).replace("%v%", strName)), ProblemHighlightType.WEAK_WARNING);
return;
}
}
}
parent = parent.getParent();
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ForeachInvariantsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFor(@NotNull For expression) {
if (expression.getRepeatedExpressions().length == 1) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(expression);
if (body != null && ExpressionSemanticUtil.countExpressionsInGroup(body) > 0) {
final PsiElement indexVariable = this.getCounterVariable(expression);
if (indexVariable != null && this.isCounterVariableIncremented(expression, indexVariable)) {
final PsiElement limit = this.getLoopLimit(expression, indexVariable);
if (limit != null) {
final PsiElement container = this.getContainerByIndex(body, indexVariable);
if (container != null && this.isLimitFor(limit, container)) {
holder.registerProblem(expression.getFirstChild(), MessagesPresentationUtil.prefixWithEa(foreachInvariant), new UseForeachFix(holder.getProject(), expression, indexVariable, null, container, limit));
}
}
}
}
}
}
private boolean isLimitFor(@NotNull PsiElement limit, @NotNull PsiElement container) {
boolean result = false;
final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(limit);
if (values.size() == 1) {
final PsiElement value = values.iterator().next();
if (OpenapiTypesUtil.isFunctionReference(value)) {
final FunctionReference reference = (FunctionReference) value;
final String functionName = reference.getName();
if (functionName != null && functionName.equals("count")) {
final PsiElement[] arguments = reference.getParameters();
result = arguments.length == 1 && OpenapiEquivalenceUtil.areEqual(arguments[0], container);
}
}
}
values.clear();
return result;
}
@Override
public void visitPhpMultiassignmentExpression(@NotNull MultiassignmentExpression assignmentExpression) {
PsiElement value = assignmentExpression.getValue();
if (OpenapiTypesUtil.isPhpExpressionImpl(value)) {
value = value.getFirstChild();
}
if (OpenapiTypesUtil.isFunctionReference(value)) {
final FunctionReference each = (FunctionReference) value;
final String functionName = (each).getName();
final PsiElement[] arguments = each.getParameters();
if (arguments.length == 1 && functionName != null && functionName.equals("each")) {
final PsiElement parent = assignmentExpression.getParent();
if (parent instanceof While || parent instanceof For) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(parent);
if (body != null && ExpressionSemanticUtil.countExpressionsInGroup(body) > 0) {
UseForeachFix fixer = null;
if (parent instanceof While) {
final List<PhpPsiElement> variables = assignmentExpression.getVariables();
if (variables.size() == 2) {
fixer = new UseForeachFix(holder.getProject(), parent, variables.get(0), variables.get(1), arguments[0], null);
}
}
final PsiElement container = arguments[0];
final boolean isContainerUsed = PsiTreeUtil.findChildrenOfType(body, container.getClass()).stream().anyMatch(candidate -> OpenapiEquivalenceUtil.areEqual(candidate, container));
if (!isContainerUsed) {
holder.registerProblem(parent.getFirstChild(), MessagesPresentationUtil.prefixWithEa(eachFunctionUsed), ProblemHighlightType.GENERIC_ERROR, fixer);
}
}
}
}
}
}
@Nullable
private PsiElement getCounterVariable(@NotNull For expression) {
PsiElement result = null;
for (final PhpPsiElement init : expression.getInitialExpressions()) {
if (OpenapiTypesUtil.isAssignment(init)) {
final AssignmentExpression assignment = (AssignmentExpression) init;
final PsiElement value = assignment.getValue();
final PsiElement variable = assignment.getVariable();
if (value != null && variable instanceof Variable && value.getText().equals("0")) {
result = variable;
break;
}
}
}
return result;
}
private boolean isCounterVariableIncremented(@NotNull For expression, @NotNull PsiElement variable) {
boolean result = false;
for (final PsiElement repeat : expression.getRepeatedExpressions()) {
if (repeat instanceof UnaryExpression) {
final UnaryExpression incrementCandidate = (UnaryExpression) repeat;
final PsiElement argument = incrementCandidate.getValue();
if (OpenapiTypesUtil.is(incrementCandidate.getOperation(), PhpTokenTypes.opINCREMENT) && argument != null && OpenapiEquivalenceUtil.areEqual(variable, argument)) {
result = true;
break;
}
}
}
return result;
}
private PsiElement getContainerByIndex(@NotNull GroupStatement body, @NotNull PsiElement variable) {
final Map<String, PsiElement> containers = new HashMap<>();
for (final ArrayAccessExpression offset : PsiTreeUtil.findChildrenOfType(body, ArrayAccessExpression.class)) {
final ArrayIndex index = offset.getIndex();
final PsiElement value = index == null ? null : index.getValue();
if (value instanceof Variable && OpenapiEquivalenceUtil.areEqual(variable, value)) {
final PsiElement container = offset.getValue();
if (container != null) {
containers.put(container.getText(), container);
if (containers.size() > 1) {
break;
}
}
}
}
final PsiElement result = containers.size() == 1 ? containers.values().iterator().next() : null;
containers.clear();
return result;
}
@Nullable
private PsiElement getLoopLimit(@NotNull For expression, @NotNull PsiElement variable) {
final PsiElement[] conditions = expression.getConditionalExpressions();
if (conditions.length == 1) {
for (final PsiElement check : conditions) {
if (check instanceof BinaryExpression) {
final BinaryExpression condition = (BinaryExpression) check;
final PsiElement left = condition.getLeftOperand();
final PsiElement right = condition.getRightOperand();
if (left != null && right != null) {
final PsiElement value;
if (left instanceof Variable && OpenapiEquivalenceUtil.areEqual(variable, left)) {
value = right;
} else if (right instanceof Variable && OpenapiEquivalenceUtil.areEqual(variable, right)) {
value = left;
} else {
value = null;
}
return value;
}
}
}
}
return null;
}
};
}
Aggregations