use of com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 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.tags.PhpDocTag 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.tags.PhpDocTag in project phpinspectionsea by kalessil.
the class ClassOverridesFieldOfSuperClassInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpField(@NotNull Field ownField) {
/* skip un-explorable and test classes */
final PhpClass clazz = ownField.getContainingClass();
if (clazz == null || this.isTestContext(clazz)) {
return;
}
/* skip static, constants and un-processable fields */
final PsiElement ownFieldNameId = NamedElementUtil.getNameIdentifier(ownField);
if (null == ownFieldNameId || ownField.isConstant() || ownField.getModifier().isStatic()) {
return;
}
/* ensure field is not defined via annotation */
if (!(ExpressionSemanticUtil.getBlockScope(ownFieldNameId) instanceof PhpClass)) {
return;
}
/* ensure field doesn't have any user-land annotations */
final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(ownField.getDocComment(), PhpDocTag.class);
final boolean annotated = tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
if (annotated) {
return;
}
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
final String ownFieldName = ownField.getName();
final Field parentField = parent == null ? null : OpenapiResolveUtil.resolveField(parent, ownFieldName);
if (parentField != null) {
final PhpClass parentFieldHolder = parentField.getContainingClass();
final PsiElement fieldNameNode = NamedElementUtil.getNameIdentifier(ownField);
if (fieldNameNode != null && parentFieldHolder != null) {
/* re-defining private fields with the same name is pretty suspicious itself */
if (parentField.getModifier().isPrivate()) {
if (REPORT_PRIVATE_REDEFINITION) {
holder.registerProblem(fieldNameNode, MessagesPresentationUtil.prefixWithEa(patternProtectedCandidate.replace("%c%", parentFieldHolder.getFQN())), ProblemHighlightType.WEAK_WARNING);
}
return;
}
/* report only cases when access level is not changed */
if (!ownField.getModifier().getAccess().isWeakerThan(parentField.getModifier().getAccess())) {
/* fire common warning */
holder.registerProblem(fieldNameNode, MessagesPresentationUtil.prefixWithEa(patternShadows.replace("%p%", ownFieldName).replace("%c%", parentFieldHolder.getFQN())), ProblemHighlightType.WEAK_WARNING);
}
}
}
}
};
}
use of com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag in project phpinspectionsea by kalessil.
the class TraitsPropertiesConflictsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
/* ensure there are traits being used at all */
final PhpClass[] traits = clazz.getTraits();
if (traits.length == 0) {
return;
}
/* check conflict with own fields */
for (final Field ownField : clazz.getOwnFields()) {
final String ownFieldName = ownField.getName();
if (!ownFieldName.isEmpty() && !ownField.isConstant() && !this.isDocBlockProperty(ownField, clazz)) {
final PhpModifier modifier = ownField.getModifier();
if (!modifier.isAbstract() && !this.isAnnotated(ownField)) {
final PsiElement ownFieldDefault = OpenapiResolveUtil.resolveDefaultValue(ownField);
for (final PhpClass trait : traits) {
final Field traitField = OpenapiResolveUtil.resolveField(trait, ownFieldName);
if (traitField != null && !this.isDocBlockProperty(traitField, trait)) {
final PsiElement traitFieldDefault = OpenapiResolveUtil.resolveDefaultValue(traitField);
final boolean isError;
if (ownFieldDefault == null || traitFieldDefault == null) {
isError = traitFieldDefault != ownFieldDefault;
} else {
isError = !OpenapiEquivalenceUtil.areEqual(traitFieldDefault, ownFieldDefault);
}
/* error case already covered by the IDEs */
final PsiElement ownFieldNameNode = NamedElementUtil.getNameIdentifier(ownField);
if (!isError && ownFieldNameNode != null) {
holder.registerProblem(ownFieldNameNode, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), clazz.getName(), trait.getName(), ownFieldName), ProblemHighlightType.WEAK_WARNING);
}
break;
}
}
}
}
}
/* check parent class accessibility and map use statements with traits for reporting */
final Map<PhpClass, PsiElement> useReportTargets = new HashMap<>();
for (final PsiElement child : clazz.getChildren()) {
if (child instanceof PhpUseList) {
for (final ClassReference reference : PsiTreeUtil.findChildrenOfType(child, ClassReference.class)) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpClass) {
useReportTargets.putIfAbsent((PhpClass) resolved, reference);
}
}
}
}
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent == null || useReportTargets.isEmpty()) {
useReportTargets.clear();
return;
}
/* iterate parent non-private fields to find conflicting properties */
for (final Field parentField : parent.getFields()) {
final String parentFieldName = parentField.getName();
if (!parentFieldName.isEmpty() && !parentField.isConstant() && !this.isDocBlockProperty(parentField, parent)) {
final PhpModifier modifier = parentField.getModifier();
if (!modifier.isPrivate() && !modifier.isAbstract()) {
final PsiElement parentFieldDefault = OpenapiResolveUtil.resolveDefaultValue(parentField);
for (final PhpClass trait : traits) {
final Field traitField = OpenapiResolveUtil.resolveField(trait, parentFieldName);
if (traitField != null && !this.isDocBlockProperty(traitField, trait)) {
final PsiElement traitFieldDefault = OpenapiResolveUtil.resolveDefaultValue(traitField);
final boolean isError;
if (parentFieldDefault == null || traitFieldDefault == null) {
isError = traitFieldDefault != parentFieldDefault;
} else {
isError = !OpenapiEquivalenceUtil.areEqual(traitFieldDefault, parentFieldDefault);
}
final PsiElement reportTarget = useReportTargets.get(trait);
if (reportTarget != null) {
holder.registerProblem(reportTarget, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), clazz.getName(), trait.getName(), parentFieldName), isError ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.WEAK_WARNING);
}
break;
}
}
}
}
}
useReportTargets.clear();
}
private boolean isAnnotated(@NotNull Field ownField) {
final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(ownField.getDocComment(), PhpDocTag.class);
return tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
}
private boolean isDocBlockProperty(@NotNull Field field, @NotNull PhpClass clazz) {
return ExpressionSemanticUtil.getBlockScope(field) != clazz;
}
};
}
Aggregations