use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.
the class PhpUnitTestsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
final PhpClass clazz = method.getContainingClass();
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(method);
final PhpDocComment phpDoc = method.getDocComment();
if (null == clazz || null == nameNode || phpDoc == null) {
return;
}
final boolean isMethodNamedAsTest = method.getName().startsWith("test");
for (final PhpDocTag tag : PsiTreeUtil.findChildrenOfType(phpDoc, PhpDocTag.class)) {
final String tagName = tag.getName();
if (tagName.equals("@dataProvider")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final List<PsiReference> references = Arrays.asList(candidate.getReferences());
if (!references.isEmpty()) {
Collections.reverse(references);
final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
if (resolved instanceof Method) {
if (SUGGEST_TO_USE_NAMED_DATASETS && !((Method) resolved).isAbstract()) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(resolved);
final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
if (last instanceof PhpReturn) {
final PsiElement value = ExpressionSemanticUtil.getReturnValue((PhpReturn) last);
if (value instanceof ArrayCreationExpression) {
final PsiElement firstChild = ((ArrayCreationExpression) value).getFirstPsiChild();
boolean isNamedDataset = firstChild == null;
if (firstChild instanceof ArrayHashElement) {
final PsiElement key = ((ArrayHashElement) firstChild).getKey();
isNamedDataset = key instanceof StringLiteralExpression;
}
if (!isNamedDataset) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageNamedProvider));
}
}
}
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@depends")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final List<PsiReference> references = Arrays.asList(candidate.getReferences());
if (!references.isEmpty()) {
Collections.reverse(references);
final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
if (resolved instanceof Method) {
final Method dependency = (Method) resolved;
if (!dependency.getName().startsWith("test")) {
final PhpDocComment docBlock = dependency.getDocComment();
if (docBlock == null || docBlock.getTagElementsByName("@test").length == 0) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
} else {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@covers")) {
final PsiElement candidate = tag.getFirstPsiChild();
if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
final PhpDocRef referenceNeeded = (PhpDocRef) candidate;
final List<PsiReference> references = Arrays.asList(referenceNeeded.getReferences());
Collections.reverse(references);
/* resolve references, populate information about provided entries */
boolean hasCallableReference = false;
boolean hasClassReference = false;
final String referenceText = referenceNeeded.getText();
for (final PsiReference ref : references) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(ref);
if (resolved instanceof PhpClass) {
hasClassReference = true;
hasCallableReference = referenceText.endsWith("::");
break;
} else if (resolved instanceof Function) {
hasCallableReference = true;
hasClassReference = resolved instanceof Method;
break;
}
}
final boolean callableNeeded = referenceText.contains("::") && !referenceText.contains("::<");
if ((callableNeeded && !hasCallableReference) || (!callableNeeded && !hasClassReference)) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(String.format(messageCovers, referenceText)), ProblemHighlightType.GENERIC_ERROR);
}
}
} else if (tagName.equals("@test")) {
if (isMethodNamedAsTest && this.isAnnotation(tag)) {
holder.registerProblem(tag.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messageTest), ProblemHighlightType.LIKE_DEPRECATED, new AmbiguousTestAnnotationLocalFix());
}
}
}
}
private boolean isAnnotation(@NotNull PhpDocTag tag) {
PsiElement previous = tag.getPrevSibling();
previous = previous instanceof PsiWhiteSpace ? previous.getPrevSibling() : previous;
final IElementType start = previous == null ? null : previous.getNode().getElementType();
return start == PhpTokenTypes.DOC_COMMENT_START || start == PhpTokenTypes.DOC_LEADING_ASTERISK;
}
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
final String methodName = reference.getName();
if (methodName != null) {
if (methodName.startsWith("assert") && !methodName.equals("assert")) {
final List<BooleanSupplier> callbacks = new ArrayList<>();
callbacks.add(() -> AssertBoolInvertedStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertBoolOfComparisonStrategy.apply(methodName, reference, holder));
if (SUGGEST_TO_USE_ASSERTSAME) {
callbacks.add(() -> AssertSameStrategy.apply(methodName, reference, holder));
}
if (PROMOTE_PHPUNIT_API) {
final PhpUnitVersion version = PHP_UNIT_VERSION == null ? PhpUnitVersion.PHPUNIT80 : PHP_UNIT_VERSION;
callbacks.add(() -> AssertEmptyStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertConstantStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertInternalTypeStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertInstanceOfStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertResourceExistsStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertCountStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertContainsStrategy.apply(methodName, reference, holder, version));
callbacks.add(() -> AssertRegexStrategy.apply(methodName, reference, holder));
/* AssertFileEqualsStrategy and AssertStringEqualsFileStrategy order is important */
callbacks.add(() -> AssertFileEqualsStrategy.apply(methodName, reference, holder));
callbacks.add(() -> AssertStringEqualsFileStrategy.apply(methodName, reference, holder));
}
for (final BooleanSupplier callback : callbacks) {
if (callback.getAsBoolean()) {
break;
}
}
callbacks.clear();
} else if (methodName.equals("expects")) {
if (PROMOTE_MOCKING_ONCE) {
ExpectsOnceStrategy.apply(methodName, reference, holder);
}
} else if (methodName.equals("will")) {
if (PROMOTE_MOCKING_WILL_RETURN) {
WillReturnStrategy.apply(methodName, reference, holder);
}
}
}
}
};
}
use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 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.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.
the class OneTimeUseVariablesInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
void checkOneTimeUse(@NotNull PhpPsiElement construct, @NotNull Variable argument) {
final String variableName = argument.getName();
final PsiElement previous = construct.getPrevPsiSibling();
/* verify preceding expression (assignment needed) */
if (null != previous && OpenapiTypesUtil.isAssignment(previous.getFirstChild())) {
final AssignmentExpression assign = (AssignmentExpression) previous.getFirstChild();
/* ensure variables are the same */
final PhpPsiElement assignVariable = assign.getVariable();
final PsiElement assignValue = ExpressionSemanticUtil.getExpressionTroughParenthesis(assign.getValue());
if (null != assignValue && assignVariable instanceof Variable) {
final String assignVariableName = assignVariable.getName();
if (assignVariableName == null || !assignVariableName.equals(variableName)) {
return;
}
/* check if variable as a function/use(...) parameter by reference */
final Function function = ExpressionSemanticUtil.getScope(construct);
if (null != function) {
for (final Parameter param : function.getParameters()) {
if (param.isPassByRef() && param.getName().equals(variableName)) {
return;
}
}
final List<Variable> useList = ExpressionSemanticUtil.getUseListVariables(function);
if (useList != null && !useList.isEmpty()) {
for (final Variable param : useList) {
if (param.getName().equals(variableName)) {
/* detect parameters by reference in use clause */
PsiElement ampersandCandidate = param.getPrevSibling();
if (ampersandCandidate instanceof PsiWhiteSpace) {
ampersandCandidate = ampersandCandidate.getPrevSibling();
}
if (OpenapiTypesUtil.is(ampersandCandidate, PhpTokenTypes.opBIT_AND)) {
return;
}
}
}
useList.clear();
}
}
/* too long return/throw statements can be decoupled as a variable */
if (!ALLOW_LONG_STATEMENTS && assign.getText().length() > 80) {
return;
}
/* heavy part, find usage inside function/method to analyze multiple writes */
final PhpScopeHolder parentScope = ExpressionSemanticUtil.getScope(assign);
if (null != parentScope) {
int countWrites = 0;
int countReads = 0;
for (final PhpAccessVariableInstruction oneCase : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(parentScope.getControlFlow().getEntryPoint(), variableName)) {
final boolean isWrite = oneCase.getAccess().isWrite();
if (isWrite) {
/* false-positives: type specification */
final PsiElement context = oneCase.getAnchor().getParent();
if (OpenapiTypesUtil.isAssignment(context)) {
final boolean typeAnnotated = this.isTypeAnnotated((PhpPsiElement) context.getParent(), variableName);
if (typeAnnotated) {
return;
}
}
}
countWrites += isWrite ? 1 : 0;
countReads += isWrite ? 0 : 1;
if (countWrites > 1 || countReads > 1) {
return;
}
}
}
if (!(assignValue instanceof NewExpression) || PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP540)) {
holder.registerProblem(assignVariable, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%v%", variableName)), new TheLocalFix(holder.getProject(), assign.getParent(), argument, assignValue));
}
}
}
}
@Override
public void visitPhpReturn(@NotNull PhpReturn returnStatement) {
if (ANALYZE_RETURN_STATEMENTS) {
/* if function returning reference, do not inspect returns */
final Function callable = ExpressionSemanticUtil.getScope(returnStatement);
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(callable);
if (null != callable && null != nameNode) {
/* is defined like returning reference */
PsiElement referenceCandidate = nameNode.getPrevSibling();
if (referenceCandidate instanceof PsiWhiteSpace) {
referenceCandidate = referenceCandidate.getPrevSibling();
}
if (OpenapiTypesUtil.is(referenceCandidate, PhpTokenTypes.opBIT_AND)) {
return;
}
}
/* regular function, check one-time use variables */
final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(returnStatement.getArgument());
if (argument instanceof PhpPsiElement) {
final Variable variable = this.getVariable((PhpPsiElement) argument);
if (null != variable) {
checkOneTimeUse(returnStatement, variable);
}
}
}
}
@Override
public void visitPhpMultiassignmentExpression(@NotNull MultiassignmentExpression multiassignmentExpression) {
if (ANALYZE_ARRAY_DESTRUCTURING) {
final PsiElement firstChild = multiassignmentExpression.getFirstChild();
final IElementType nodeType = null == firstChild ? null : firstChild.getNode().getElementType();
if (null != nodeType && (PhpTokenTypes.kwLIST == nodeType || PhpTokenTypes.chLBRACKET == nodeType)) {
final Variable variable = this.getVariable(multiassignmentExpression.getValue());
final PsiElement parent = multiassignmentExpression.getParent();
if (null != variable && OpenapiTypesUtil.isStatementImpl(parent)) {
checkOneTimeUse((PhpPsiElement) parent, variable);
}
}
}
}
/* TODO: once got bored, add foreach source pattern here =) I'm naive but nevertheless ^_^ */
@Override
public void visitPhpThrowExpression(@NotNull PhpThrowExpression expression) {
if (ANALYZE_THROW_STATEMENTS) {
final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getArgument());
if (argument instanceof PhpPsiElement) {
final Variable variable = this.getVariable((PhpPsiElement) argument);
if (null != variable) {
checkOneTimeUse(expression.getExpression(), variable);
}
}
}
}
@Nullable
private Variable getVariable(@Nullable PhpPsiElement expression) {
if (expression != null) {
if (expression instanceof Variable) {
return (Variable) expression;
} else if (expression instanceof MemberReference) {
final MemberReference reference = (MemberReference) expression;
final PsiElement candidate = reference.getFirstChild();
if (candidate instanceof Variable) {
final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
if (OpenapiTypesUtil.is(operator, PhpTokenTypes.ARROW)) {
return (Variable) candidate;
}
}
} else if (OpenapiTypesUtil.isPhpExpressionImpl(expression)) {
/* instanceof passes child classes as well, what isn't correct */
return getVariable(expression.getFirstPsiChild());
}
}
return null;
}
private boolean isTypeAnnotated(@NotNull PhpPsiElement current, @NotNull String variableName) {
boolean result = false;
final PsiElement phpdocCandidate = current.getPrevPsiSibling();
if (phpdocCandidate instanceof PhpDocComment) {
final PhpDocTag[] hints = ((PhpDocComment) phpdocCandidate).getTagElementsByName("@var");
if (hints.length == 1) {
final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
result = specifiedVariable != null && specifiedVariable.getName().equals(variableName);
}
}
return result;
}
};
}
use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.
the class NullableVariablesStrategy method isNullableResult.
private static boolean isNullableResult(@NotNull AssignmentExpression assignment, @NotNull Project project) {
boolean result = false;
final PsiElement assignmentValue = assignment.getValue();
/* primary strategy: resolve types and check nullability */
if (assignmentValue instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) assignmentValue, project);
if (resolved != null) {
final Set<String> types = new HashSet<>();
resolved.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
if (types.contains(Types.strNull) || types.contains(Types.strVoid)) {
types.remove(Types.strNull);
types.remove(Types.strVoid);
if (!types.isEmpty()) {
result = types.stream().noneMatch(t -> !t.startsWith("\\") && !objectTypes.contains(t));
}
}
types.clear();
}
}
/* secondary strategy: support type specification with `@var <type> <variable>` */
if (result) {
final PhpPsiElement variable = assignment.getVariable();
final PsiElement parent = assignment.getParent();
if (variable != null && OpenapiTypesUtil.isStatementImpl(parent) && OpenapiTypesUtil.isAssignment(assignment)) {
final PsiElement previous = ((PhpPsiElement) parent).getPrevPsiSibling();
if (previous instanceof PhpDocComment) {
final PhpDocTag[] hints = ((PhpDocComment) previous).getTagElementsByName("@var");
if (hints.length == 1) {
final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
if (specifiedVariable != null && specifiedVariable.getName().equals(variable.getName())) {
result = Arrays.stream(hints[0].getChildren()).anyMatch(t -> t instanceof PhpDocType && Types.getType(t.getText()).equals(Types.strNull));
}
}
}
}
}
return result;
}
use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 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