use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ArgumentEqualsDefaultValueInspector method buildVisitor.
@NotNull
@Override
public final PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder problemsHolder, final boolean onTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
this.analyze(reference);
}
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
this.analyze(reference);
}
private void analyze(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && !specialFunctions.contains(functionName)) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0) {
PsiElement reportFrom = null;
PsiElement reportTo = null;
final IElementType valueType = arguments[arguments.length - 1].getNode().getElementType();
if (OpenapiTypesUtil.DEFAULT_VALUES.contains(valueType)) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Function) {
final Parameter[] parameters = ((Function) resolved).getParameters();
if (arguments.length <= parameters.length) {
for (int index = Math.min(parameters.length, arguments.length) - 1; index >= 0; --index) {
final PsiElement value = parameters[index].getDefaultValue();
/* false-positives: magic constants */
if (value instanceof ConstantReference && specialConstants.contains(value.getText())) {
break;
}
/* false-positives: unmatched values */
final PsiElement argument = arguments[index];
if (value == null || !OpeanapiEquivalenceUtil.areEqual(value, argument)) {
break;
}
reportFrom = argument;
reportTo = reportTo == null ? argument : reportTo;
}
}
}
}
if (reportFrom != null) {
problemsHolder.registerProblem(problemsHolder.getManager().createProblemDescriptor(reportFrom, reportTo, message, ProblemHighlightType.LIKE_UNUSED_SYMBOL, onTheFly, new TheLocalFix(reportFrom, reportTo)));
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class SenselessCommaInArrayDefinitionInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpArrayCreationExpression(@NotNull ArrayCreationExpression expression) {
final PsiElement last = expression.getLastChild().getPrevSibling();
final PsiElement candidate = last instanceof PsiWhiteSpace ? last.getPrevSibling() : last;
if (OpenapiTypesUtil.is(candidate, PhpTokenTypes.opCOMMA)) {
holder.registerProblem(candidate, message, new TheLocalFix());
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ExceptionsAnnotatingAndHandlingInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFinally(@NotNull Finally element) {
PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (!phpVersion.hasFeature(PhpLanguageFeature.FINALLY)) {
return;
}
final HashSet<PsiElement> processedRegistry = new HashSet<>();
final HashMap<PhpClass, HashSet<PsiElement>> exceptions = CollectPossibleThrowsUtil.collectNestedAndWorkflowExceptions(element, processedRegistry, holder);
/* report individual statements */
if (exceptions.size() > 0) {
final Set<PsiElement> reportedExpressions = new HashSet<>();
for (final Set<PsiElement> pool : exceptions.values()) {
pool.stream().filter(expression -> !reportedExpressions.contains(expression)).forEach(expression -> {
holder.registerProblem(this.getReportingTarget(expression), messageFinallyExceptions, ProblemHighlightType.GENERIC_ERROR);
reportedExpressions.add(expression);
});
pool.clear();
}
reportedExpressions.clear();
exceptions.clear();
}
/* report try-blocks */
if (processedRegistry.size() > 0) {
processedRegistry.stream().filter(statement -> statement instanceof Try).forEach(statement -> holder.registerProblem(statement.getFirstChild(), messageFinallyExceptions, ProblemHighlightType.GENERIC_ERROR));
processedRegistry.clear();
}
}
@Override
public void visitPhpMethod(@NotNull Method method) {
if (method.isAbstract() || this.isTestContext(method)) {
return;
}
// __toString has magic methods validation, must not raise exceptions
final PsiElement methodName = NamedElementUtil.getNameIdentifier(method);
if (null == methodName || method.getName().equals("__toString")) {
return;
}
/* collect announced cases */
final HashSet<PhpClass> annotatedExceptions = new HashSet<>();
final boolean hasPhpDoc = method.getDocComment() != null;
if (!ThrowsResolveUtil.resolveThrownExceptions(method, annotatedExceptions)) {
return;
}
HashSet<PsiElement> processedRegistry = new HashSet<>();
HashMap<PhpClass, HashSet<PsiElement>> throwsExceptions = CollectPossibleThrowsUtil.collectNestedAndWorkflowExceptions(method, processedRegistry, holder);
processedRegistry.clear();
/* exclude annotated exceptions, identify which has not been thrown */
final Set<PhpClass> annotatedButNotThrownExceptions = new HashSet<>(annotatedExceptions);
/* release bundled expressions */
/* actualize un-thrown exceptions registry */
annotatedExceptions.stream().filter(key -> hasPhpDoc && throwsExceptions.containsKey(key)).forEach(annotated -> {
/* release bundled expressions */
throwsExceptions.get(annotated).clear();
throwsExceptions.remove(annotated);
/* actualize un-thrown exceptions registry */
annotatedButNotThrownExceptions.remove(annotated);
});
/* do reporting now: exceptions annotated, but not thrown */
if (REPORT_NON_THROWN_EXCEPTIONS && annotatedButNotThrownExceptions.size() > 0) {
final List<String> toReport = annotatedButNotThrownExceptions.stream().map(PhpNamedElement::getFQN).collect(Collectors.toList());
final String message = messagePatternUnthrown.replace("%c%", String.join(", ", toReport));
holder.registerProblem(methodName, message, ProblemHighlightType.WEAK_WARNING);
toReport.clear();
}
annotatedButNotThrownExceptions.clear();
/* do reporting now: exceptions thrown but not annotated */
if (throwsExceptions.size() > 0) {
/* deeper analysis needed */
HashMap<PhpClass, HashSet<PsiElement>> unhandledExceptions = new HashMap<>();
if (!annotatedExceptions.isEmpty() && hasPhpDoc) {
/* filter what to report based on annotated exceptions */
for (final PhpClass annotated : annotatedExceptions) {
for (final Map.Entry<PhpClass, HashSet<PsiElement>> throwsExceptionsPair : throwsExceptions.entrySet()) {
final PhpClass thrown = throwsExceptionsPair.getKey();
/* already reported */
if (unhandledExceptions.containsKey(thrown)) {
continue;
}
/* check thrown parents, as annotated not processed here */
final HashSet<PhpClass> thrownVariants = InterfacesExtractUtil.getCrawlInheritanceTree(thrown, true);
if (!thrownVariants.contains(annotated)) {
unhandledExceptions.put(thrown, throwsExceptionsPair.getValue());
throwsExceptions.put(thrown, null);
}
thrownVariants.clear();
}
}
} else {
/* report all, as nothing is annotated */
for (Map.Entry<PhpClass, HashSet<PsiElement>> throwsExceptionsPair : throwsExceptions.entrySet()) {
final PhpClass thrown = throwsExceptionsPair.getKey();
/* already reported */
if (unhandledExceptions.containsKey(thrown)) {
continue;
}
unhandledExceptions.put(thrown, throwsExceptionsPair.getValue());
throwsExceptions.put(thrown, null);
}
}
if (unhandledExceptions.size() > 0) {
for (final Map.Entry<PhpClass, HashSet<PsiElement>> unhandledExceptionsPair : unhandledExceptions.entrySet()) {
final String thrown = unhandledExceptionsPair.getKey().getFQN();
final Set<PsiElement> blamedExpressions = unhandledExceptionsPair.getValue();
if (!configuration.contains(thrown)) {
final String message = messagePattern.replace("%c%", thrown);
for (final PsiElement blame : blamedExpressions) {
final LocalQuickFix fix = hasPhpDoc ? new MissingThrowAnnotationLocalFix(method, thrown) : null;
holder.registerProblem(this.getReportingTarget(blame), message, ProblemHighlightType.WEAK_WARNING, fix);
}
}
blamedExpressions.clear();
}
unhandledExceptions.clear();
}
throwsExceptions.clear();
}
annotatedExceptions.clear();
}
@NotNull
private PsiElement getReportingTarget(@NotNull PsiElement expression) {
PsiElement result = expression;
if (expression instanceof FunctionReference) {
final PsiElement nameNode = (PsiElement) ((FunctionReference) expression).getNameNode();
if (nameNode != null) {
result = nameNode;
}
} else if (expression instanceof PhpThrow) {
final PsiElement subject = ((PhpThrow) expression).getArgument();
if (subject instanceof NewExpression) {
final PsiElement reference = ((NewExpression) subject).getClassReference();
if (reference != null) {
result = reference;
}
}
}
return result;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class AdditionOperationOnArraysInspection method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
final PsiElement operation = expression.getOperation();
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opPLUS)) {
/* do not check nested operations */
final boolean isNestedBinary = expression.getParent() instanceof BinaryExpression;
if (!isNestedBinary) {
/* do not report ' ... + []' and '[] + ...' */
final PsiElement right = expression.getRightOperand();
PsiElement left = expression.getLeftOperand();
while (left instanceof BinaryExpression) {
left = ((BinaryExpression) left).getLeftOperand();
}
if (left != null && right != null) {
final boolean addsImplicitArray = left instanceof ArrayCreationExpression || right instanceof ArrayCreationExpression;
if (!addsImplicitArray) {
this.inspectExpression(operation, expression);
}
}
}
}
}
@Override
public void visitPhpSelfAssignmentExpression(@NotNull SelfAssignmentExpression expression) {
final PsiElement operation = expression.getOperation();
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opPLUS_ASGN)) {
/* do not report '... += []' */
final boolean addsImplicitArray = expression.getValue() instanceof ArrayCreationExpression;
if (!addsImplicitArray) {
this.inspectExpression(operation, expression);
}
}
}
/* inspection itself */
private void inspectExpression(@NotNull PsiElement operation, @NotNull PsiElement expression) {
if (expression instanceof PhpTypedElement) {
final Set<String> types = new HashSet<>();
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) expression, holder.getProject());
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
}
if (types.size() == 1 && types.contains(Types.strArray)) {
holder.registerProblem(operation, message);
}
types.clear();
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ExplodeMissUseInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
/* general structure expectations */
final String functionName = reference.getName();
if (functionName == null || !semanticMapping.containsKey(functionName)) {
return;
}
final PsiElement[] arguments = reference.getParameters();
if (arguments.length != 1) {
return;
}
/* discover possible values */
final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(arguments[0]);
/* do not analyze invariants */
if (1 == values.size()) {
final PsiElement value = values.iterator().next();
values.clear();
if (OpenapiTypesUtil.isFunctionReference(value)) {
/* inner call must be explode() */
final FunctionReference innerCall = (FunctionReference) value;
final String innerFunctionName = innerCall.getName();
if (innerFunctionName == null || !innerFunctionName.equals("explode")) {
return;
}
final PsiElement[] innerArguments = innerCall.getParameters();
if (innerArguments.length != 2) {
return;
}
/* if the parameter is a variable, ensure it used only 2 times (write, read) */
if (arguments[0] instanceof Variable) {
final PhpScopeHolder parentScope = ExpressionSemanticUtil.getScope(reference);
if (null != parentScope) {
final PhpAccessVariableInstruction[] usages = PhpControlFlowUtil.getFollowingVariableAccessInstructions(parentScope.getControlFlow().getEntryPoint(), ((Variable) arguments[0]).getName(), false);
if (2 != usages.length) {
return;
}
}
}
final String replacement = semanticMapping.get(functionName).replace("%f%", innerArguments[0].getText()).replace("%s%", innerArguments[1].getText());
final String message = messagePattern.replace("%e%", replacement);
if (arguments[0] == value) {
holder.registerProblem(reference, message, new UseAlternativeFix(replacement));
} else {
holder.registerProblem(reference, message);
}
}
}
values.clear();
}
};
}
Aggregations