use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class PrintfScanfArgumentsInspector 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 || !functions.containsKey(functionName)) {
return;
}
/* resolve needed parameter */
final int neededPosition = functions.get(functionName);
final int minimumArgumentsForAnalysis = neededPosition + 1;
StringLiteralExpression pattern = null;
final PsiElement[] params = reference.getParameters();
if (params.length >= minimumArgumentsForAnalysis) {
pattern = ExpressionSemanticUtil.resolveAsStringLiteral(params[neededPosition]);
}
/* not available */
if (null == pattern) {
return;
}
final String content = pattern.getContents().trim();
if (!content.isEmpty()) {
/* find valid placeholders and extract positions specifiers as well */
int countWithoutPositionSpecifier = 0;
int maxPositionSpecifier = 0;
int countParsedAll = 0;
/* do normalization: %%, inline variables */
final String contentAdapted = content.replace("%%", "");
final String contentNoVars = contentAdapted.replaceAll("\\$\\{?\\$?[a-zA-Z0-9]+\\}?", "");
if (contentAdapted.length() != contentNoVars.length()) {
return;
}
final Matcher regexMatcher = regexPlaceHolders.matcher(contentAdapted);
while (regexMatcher.find()) {
++countParsedAll;
if (null != regexMatcher.group(2)) {
maxPositionSpecifier = Math.max(maxPositionSpecifier, Integer.parseInt(regexMatcher.group(2)));
continue;
}
++countWithoutPositionSpecifier;
}
final int expectedParametersCount = minimumArgumentsForAnalysis + Math.max(countWithoutPositionSpecifier, maxPositionSpecifier);
/* check for pattern validity */
final int parametersInPattern = StringUtils.countMatches(content.replace("%%", "").replace("%*", ""), "%");
if (countParsedAll != parametersInPattern) {
holder.registerProblem(params[neededPosition], MessagesPresentationUtil.prefixWithEa(messagePattern), ProblemHighlightType.GENERIC_ERROR);
return;
}
/* check for arguments matching */
if (expectedParametersCount != params.length) {
/* fscanf/sscanf will also return parsed values as an array if no values containers provided */
if (params.length == 2) {
final boolean returnsArray = functionName.equals("fscanf") || functionName.equals("sscanf");
final PsiElement parent = returnsArray ? reference.getParent() : null;
final PsiElement grandParent = parent == null ? null : parent.getParent();
if (returnsArray && parent != null) {
/* false-positive: dispatching/deconstructing into containers */
if (parent instanceof AssignmentExpression || grandParent instanceof AssignmentExpression) {
return;
} else /* false-positive: dispatching into calls */
if (parent instanceof ParameterList && grandParent instanceof FunctionReference) {
return;
}
}
}
/* false-positives: variadic */
if (OpenapiTypesUtil.is(params[params.length - 1].getPrevSibling(), PhpTokenTypes.opVARIADIC)) {
return;
}
/* false-positives: variable modification */
final PsiElement argumentWithPattern = params[neededPosition];
if (argumentWithPattern instanceof Variable) {
final Function function = ExpressionSemanticUtil.getScope(argumentWithPattern);
final GroupStatement body = function == null ? null : ExpressionSemanticUtil.getGroupStatement(function);
if (body != null) {
for (final Variable candidate : PsiTreeUtil.findChildrenOfType(body, Variable.class)) {
final PsiElement parent = candidate.getParent();
final boolean isModified = parent instanceof AssignmentExpression && !OpenapiTypesUtil.isAssignment(parent) && candidate == ((AssignmentExpression) parent).getVariable() && OpenapiEquivalenceUtil.areEqual(candidate, argumentWithPattern);
if (isModified) {
return;
}
}
}
}
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageParameters.replace("%c%", String.valueOf(expectedParametersCount))), ProblemHighlightType.GENERIC_ERROR);
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ThrowRawExceptionInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpThrowExpression(@NotNull PhpThrowExpression expression) {
final PsiElement argument = expression.getArgument();
if (argument instanceof NewExpression) {
final NewExpression newExpression = (NewExpression) argument;
final ClassReference classReference = newExpression.getClassReference();
final String classFqn = classReference == null ? null : classReference.getFQN();
if (classFqn != null) {
if (classFqn.equals("\\Exception")) {
holder.registerProblem(classReference, MessagesPresentationUtil.prefixWithEa(messageRawException), new TheLocalFix());
} else if (REPORT_MISSING_ARGUMENTS && newExpression.getParameters().length == 0) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(classReference);
if (resolved instanceof PhpClass && this.isTarget((PhpClass) resolved)) {
holder.registerProblem(newExpression, MessagesPresentationUtil.prefixWithEa(messageNoArguments));
}
}
}
}
}
private boolean isTarget(@NotNull PhpClass clazz) {
final Method constructor = clazz.getConstructor();
return constructor != null && constructor.getParameters().length == 3 && clazz.findOwnFieldByName("message", false) == null;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class UnqualifiedReferenceInspector 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.isEmpty()) {
/* ensure php version is at least PHP 7.0; makes sense only with PHP7+ opcode */
if (PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP700)) {
if (REPORT_ALL_FUNCTIONS || advancedOpcode.contains(functionName)) {
this.analyzeReference(reference, functionName);
}
if (callbacksPositions.containsKey(functionName)) {
this.analyzeCallback(reference, functionName);
}
}
}
}
@Override
public void visitPhpConstantReference(@NotNull ConstantReference reference) {
final String constantName = reference.getName();
if (constantName != null && !constantName.isEmpty() && REPORT_CONSTANTS) {
/* ensure php version is at least PHP 7.0; makes sense only with PHP7+ opcode */
if (PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP700)) {
this.analyzeReference(reference, constantName);
}
}
}
private void analyzeCallback(@NotNull FunctionReference reference, @NotNull String functionName) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length >= 2) {
final Integer callbackPosition = callbacksPositions.get(functionName);
if (arguments[callbackPosition] instanceof StringLiteralExpression) {
final StringLiteralExpression callback = (StringLiteralExpression) arguments[callbackPosition];
if (callback.getFirstPsiChild() == null) {
final String function = callback.getContents();
final boolean isCandidate = !function.startsWith("\\") && !function.contains("::");
if (isCandidate && (REPORT_ALL_FUNCTIONS || advancedOpcode.contains(function))) {
final PhpIndex index = PhpIndex.getInstance(holder.getProject());
if (!index.getFunctionsByFQN('\\' + functionName).isEmpty()) {
holder.registerProblem(callback, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), function), new TheLocalFix());
}
}
}
}
}
}
private void analyzeReference(@NotNull PhpReference reference, @NotNull String referenceName) {
/* some constants prefixing is making no sense IMO */
if (reference instanceof ConstantReference && falsePositives.contains(referenceName)) {
return;
}
/* NS specification is identified differently for { define } and { call, constant } */
final PsiElement nsCandidate = reference.getFirstChild();
if (nsCandidate instanceof PhpNamespaceReference || OpenapiTypesUtil.is(nsCandidate, PhpTokenTypes.NAMESPACE_RESOLUTION)) {
return;
}
final PhpNamespace ns = this.findNamespace(reference);
if (ns == null) {
return;
}
/* resolve the constant/function, report if it's from the root NS */
final PsiElement subject = OpenapiResolveUtil.resolveReference(reference);
final boolean isFunction = subject instanceof Function;
if (isFunction || subject instanceof Constant) {
/* false-positives: non-root NS function/constant referenced */
if (((PhpNamedElement) subject).getFQN().equals('\\' + referenceName)) {
final GroupStatement body = ns.getStatements();
if (body != null) {
final List<PhpUse> imports = new ArrayList<>();
for (final PsiElement child : body.getChildren()) {
if (child instanceof PhpUseList) {
Collections.addAll(imports, ((PhpUseList) child).getDeclarations());
}
}
/* false-positive: function/constant are imported already */
boolean isImported = false;
for (final PhpUse use : imports) {
final PsiElement candidate = use.getFirstPsiChild();
String importedSymbol = null;
if (candidate instanceof FunctionReference) {
importedSymbol = ((FunctionReference) candidate).getName();
} else if (candidate instanceof ConstantReference) {
importedSymbol = ((ConstantReference) candidate).getName();
}
if (isImported = referenceName.equals(importedSymbol)) {
break;
}
}
imports.clear();
if (!isImported) {
holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), referenceName + (isFunction ? "(...)" : "")), new TheLocalFix());
}
}
}
}
}
@Nullable
private PhpNamespace findNamespace(@NotNull PhpReference reference) {
final PsiFile file = reference.getContainingFile();
if (file.getFileType() == PhpFileType.INSTANCE) {
final List<PhpNamespace> namespaces = new ArrayList<>();
((PhpFile) file).getTopLevelDefs().values().stream().filter(definition -> definition instanceof PhpNamespace).forEach(definition -> namespaces.add((PhpNamespace) definition));
if (namespaces.isEmpty()) {
return null;
} else if (namespaces.size() == 1) {
return namespaces.get(0);
}
namespaces.clear();
}
return (PhpNamespace) PsiTreeUtil.findFirstParent(reference, PARENT_NAMESPACE);
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ArrayPushMissUseInspector 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("array_push")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 2 && OpenapiTypesUtil.isStatementImpl(reference.getParent())) {
PsiElement variadicCandidate = arguments[1].getPrevSibling();
if (variadicCandidate instanceof PsiWhiteSpace) {
variadicCandidate = variadicCandidate.getPrevSibling();
}
if (!OpenapiTypesUtil.is(variadicCandidate, PhpTokenTypes.opVARIADIC)) {
final String replacement = String.format("%s[] = %s", arguments[0].getText(), arguments[1].getText());
holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(messageMisuse), replacement), new UseElementPushFix(replacement));
}
}
}
}
@Override
public void visitPhpArrayAccessExpression(@NotNull ArrayAccessExpression expression) {
if (REPORT_EXCESSIVE_COUNT_CALLS) {
final PsiElement parent = expression.getParent();
if (OpenapiTypesUtil.isAssignment(parent)) {
final PsiElement value = ((AssignmentExpression) parent).getValue();
if (value != expression) {
final ArrayIndex index = expression.getIndex();
if (index != null) {
final PsiElement candidate = index.getValue();
if (OpenapiTypesUtil.isFunctionReference(candidate)) {
final FunctionReference reference = (FunctionReference) candidate;
final String functionName = reference.getName();
if (functionName != null && functionName.equals("count")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1) {
final PsiElement container = expression.getValue();
if (container != null && OpenapiEquivalenceUtil.areEqual(container, arguments[0])) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageUnneeded), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
}
}
}
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class DateIntervalSpecificationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpNewExpression(@NotNull NewExpression expression) {
/* before inspecting check parameters amount */
final PsiElement[] params = expression.getParameters();
if (params.length == 1) {
final ClassReference classReference = expression.getClassReference();
final String classFQN = classReference == null ? null : classReference.getFQN();
if (classFQN == null || !classFQN.equals("\\DateInterval")) {
/* TODO: child classes support */
return;
}
/* now try getting string literal and test against valid patterns */
final StringLiteralExpression pattern = ExpressionSemanticUtil.resolveAsStringLiteral(params[0]);
if (pattern != null && pattern.getFirstPsiChild() == null) {
final String input = pattern.getContents();
if (!regexRegular.matcher(input).find() && !regexDateTimeAlike.matcher(input).find()) {
holder.registerProblem(pattern, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
}
};
}
Aggregations