mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
cleanup IDEA-382376: move com.siyeh.ig.logging package from java-impl to java-impl-inspections
GitOrigin-RevId: 58a24da795ae1b8a3a9875cd687a8c47abb67a27
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b924d3dcc4
commit
2a72828cba
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2003-2021 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.JavaLoggingUtils;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
public final class ClassWithMultipleLoggersInspection extends BaseInspection {
|
||||
|
||||
private final List<String> loggerNames = new ArrayList<>();
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public @NonNls String loggerNamesString = StringUtil.join(JavaLoggingUtils.DEFAULT_LOGGERS, ",");
|
||||
|
||||
public ClassWithMultipleLoggersInspection() {
|
||||
parseString(loggerNamesString, loggerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("loggerNames", InspectionGadgetsBundle.message("logger.class.name"),
|
||||
new JavaClassValidator()
|
||||
.withTitle(InspectionGadgetsBundle.message("choose.logger.class")))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("multiple.loggers.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
parseString(loggerNamesString, loggerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
loggerNamesString = formatString(loggerNames);
|
||||
super.writeSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ClassWithMultipleLoggersVisitor();
|
||||
}
|
||||
|
||||
private class ClassWithMultipleLoggersVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass instanceof PsiTypeParameter) {
|
||||
return;
|
||||
}
|
||||
int numLoggers = 0;
|
||||
for (PsiField field : aClass.getFields()) {
|
||||
if (isLogger(field)) {
|
||||
numLoggers++;
|
||||
}
|
||||
}
|
||||
if (numLoggers <= 1) {
|
||||
return;
|
||||
}
|
||||
registerClassError(aClass);
|
||||
}
|
||||
|
||||
private boolean isLogger(PsiVariable variable) {
|
||||
return loggerNames.contains(variable.getType().getCanonicalText());
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2003-2021 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.JavaLoggingUtils;
|
||||
import com.siyeh.ig.ui.ExternalizableStringSet;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
import static com.intellij.codeInspection.options.OptPane.tab;
|
||||
import static com.intellij.codeInspection.options.OptPane.tabs;
|
||||
|
||||
public final class ClassWithoutLoggerInspection extends BaseInspection {
|
||||
|
||||
private final List<String> loggerNames = new ArrayList<>();
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public @NonNls String loggerNamesString = StringUtil.join(JavaLoggingUtils.DEFAULT_LOGGERS, ",");
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public boolean ignoreSuperLoggers = false;
|
||||
|
||||
@SuppressWarnings("PublicField") public final ExternalizableStringSet annotations = new ExternalizableStringSet();
|
||||
@SuppressWarnings("PublicField") public final ExternalizableStringSet ignoredClasses = new ExternalizableStringSet(CommonClassNames.JAVA_LANG_THROWABLE);
|
||||
|
||||
public ClassWithoutLoggerInspection() {
|
||||
parseString(loggerNamesString, loggerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
tabs(
|
||||
tab(InspectionGadgetsBundle.message("class.without.logger.loggers.tab"),
|
||||
stringList("loggerNames", InspectionGadgetsBundle.message("logger.class.name"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("choose.logger.class")))),
|
||||
tab(InspectionGadgetsBundle.message("options.title.ignored.classes"),
|
||||
stringList("ignoredClasses", InspectionGadgetsBundle.message("ignored.class.hierarchies.border.title"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("choose.class.hierarchy.to.ignore.title"))),
|
||||
checkbox("ignoreSuperLoggers", InspectionGadgetsBundle.message("super.class.logger.option"))),
|
||||
tab(InspectionGadgetsBundle.message("class.without.logger.annotations.tab"),
|
||||
stringList("annotations", InspectionGadgetsBundle.message("ignore.classes.annotated.by"),
|
||||
new JavaClassValidator().annotationsOnly()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("no.logger.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
parseString(loggerNamesString, loggerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
loggerNamesString = formatString(loggerNames);
|
||||
defaultWriteSettings(element, "annotations", "ignoredClasses");
|
||||
annotations.writeSettings(element, "annotations");
|
||||
ignoredClasses.writeSettings(element, "ignoredClasses");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ClassWithoutLoggerVisitor();
|
||||
}
|
||||
|
||||
private class ClassWithoutLoggerVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass.isInterface() || aClass.isEnum() || aClass.isAnnotationType() || aClass.getContainingClass() != null) {
|
||||
return;
|
||||
}
|
||||
if (aClass instanceof PsiTypeParameter || aClass instanceof PsiAnonymousClass) {
|
||||
return;
|
||||
}
|
||||
if (ignoredClasses.stream().anyMatch(ignoredClass -> InheritanceUtil.isInheritor(aClass, ignoredClass))) {
|
||||
return;
|
||||
}
|
||||
if (AnnotationUtil.isAnnotated(aClass, annotations, AnnotationUtil.CHECK_EXTERNAL | AnnotationUtil.CHECK_HIERARCHY)) {
|
||||
return;
|
||||
}
|
||||
final PsiField[] fields = ignoreSuperLoggers ? aClass.getAllFields() : aClass.getFields();
|
||||
if (Stream.of(fields).anyMatch(field -> isLogger(field) && PsiUtil.isAccessible(field, aClass, aClass))) {
|
||||
return;
|
||||
}
|
||||
registerClassError(aClass);
|
||||
}
|
||||
|
||||
private boolean isLogger(PsiVariable variable) {
|
||||
final PsiType type = variable.getType();
|
||||
final String text = type.getCanonicalText();
|
||||
return loggerNames.contains(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInsight.options.JavaIdentifierValidator;
|
||||
import com.intellij.codeInspection.CommonQuickFixBundle;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiClassObjectAccessExpression;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiTypeElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.xmlb.Accessor;
|
||||
import com.intellij.util.xmlb.SerializationFilterBase;
|
||||
import com.intellij.util.xmlb.XmlSerializer;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.PsiReplacementUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.column;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.table;
|
||||
|
||||
|
||||
public final class LoggerInitializedWithForeignClassInspection extends BaseInspection {
|
||||
|
||||
private static final @NonNls String DEFAULT_FACTORY_CLASS_NAMES =
|
||||
// Log4J 1
|
||||
"org.apache.log4j.Logger," +
|
||||
// SLF4J
|
||||
"org.slf4j.LoggerFactory," +
|
||||
// Apache Commons Logging
|
||||
"org.apache.commons.logging.LogFactory," +
|
||||
// Java Util Logging
|
||||
"java.util.logging.Logger," +
|
||||
// Log4J 2
|
||||
"org.apache.logging.log4j.LogManager";
|
||||
|
||||
private static final @NonNls String DEFAULT_FACTORY_METHOD_NAMES =
|
||||
//Log4J 1
|
||||
"getLogger," +
|
||||
// SLF4J
|
||||
"getLogger," +
|
||||
// Apache Commons Logging
|
||||
"getLog," +
|
||||
// Java Util Logging
|
||||
"getLogger," +
|
||||
// Log4J 2
|
||||
"getLogger";
|
||||
private final List<String> loggerFactoryClassNames = new ArrayList<>();
|
||||
private final List<String> loggerFactoryMethodNames = new ArrayList<>();
|
||||
@SuppressWarnings("PublicField")
|
||||
public String loggerClassName = DEFAULT_FACTORY_CLASS_NAMES;
|
||||
@SuppressWarnings("PublicField")
|
||||
public @NonNls String loggerFactoryMethodName = DEFAULT_FACTORY_METHOD_NAMES;
|
||||
|
||||
public boolean ignoreSuperClass = false;
|
||||
public boolean ignoreNonPublicClasses = false;
|
||||
public boolean ignoreNotFinalField = true;
|
||||
{
|
||||
parseString(loggerClassName, loggerFactoryClassNames);
|
||||
parseString(loggerFactoryMethodName, loggerFactoryMethodNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
table("",
|
||||
column("loggerFactoryClassNames", InspectionGadgetsBundle.message("logger.factory.class.name"),
|
||||
new JavaClassValidator()),
|
||||
column("loggerFactoryMethodNames", InspectionGadgetsBundle.message("logger.factory.method.name"),
|
||||
new JavaIdentifierValidator())),
|
||||
checkbox("ignoreSuperClass", InspectionGadgetsBundle.message("logger.initialized.with.foreign.class.ignore.super.class.option")),
|
||||
checkbox("ignoreNonPublicClasses",
|
||||
InspectionGadgetsBundle.message("logger.initialized.with.foreign.class.ignore.non.public.classes.option")),
|
||||
checkbox("ignoreNotFinalField",
|
||||
InspectionGadgetsBundle.message("logger.initialized.with.foreign.class.ignore.not.final.field"))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("logger.initialized.with.foreign.class.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
return new LoggerInitializedWithForeignClassFix((String)infos[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new LoggerInitializedWithForeignClassVisitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
parseString(loggerClassName, loggerFactoryClassNames);
|
||||
parseString(loggerFactoryMethodName, loggerFactoryMethodNames);
|
||||
if (loggerFactoryClassNames.size() != loggerFactoryMethodNames.size() || loggerFactoryClassNames.isEmpty()) {
|
||||
parseString(DEFAULT_FACTORY_CLASS_NAMES, loggerFactoryClassNames);
|
||||
parseString(DEFAULT_FACTORY_METHOD_NAMES, loggerFactoryMethodNames);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
loggerClassName = formatString(loggerFactoryClassNames);
|
||||
loggerFactoryMethodName = formatString(loggerFactoryMethodNames);
|
||||
if (loggerFactoryMethodName.equals(DEFAULT_FACTORY_METHOD_NAMES) && loggerClassName.equals(DEFAULT_FACTORY_CLASS_NAMES)) {
|
||||
// to prevent changing inspection profile with new default, which is mistakenly always written because of bug in serialization below.
|
||||
loggerFactoryMethodName = "getLogger," +
|
||||
"getLogger," +
|
||||
"getLog," +
|
||||
"getLogger";
|
||||
// these broken settings are restored correctly in readSettings()
|
||||
}
|
||||
XmlSerializer.serializeInto(this, element, new SerializationFilterBase() {
|
||||
@Override
|
||||
protected boolean accepts(@NotNull Accessor accessor, @NotNull Object bean, @Nullable Object beanValue) {
|
||||
final @NonNls String factoryName = accessor.getName();
|
||||
if ("loggerClassName".equals(factoryName) && DEFAULT_FACTORY_CLASS_NAMES.equals(beanValue)) return false;
|
||||
if ("loggerFactoryMethodNames".equals(factoryName) && DEFAULT_FACTORY_METHOD_NAMES.equals(beanValue)) return false;
|
||||
if ("ignoreSuperClass".equals(factoryName) && !ignoreSuperClass) return false;
|
||||
if ("ignoreNonPublicClasses".equals(factoryName) && !ignoreNonPublicClasses) return false;
|
||||
if ("ignoreNotFinalField".equals(factoryName) && ignoreNotFinalField) return false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final class LoggerInitializedWithForeignClassFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
private final String newClassName;
|
||||
|
||||
private LoggerInitializedWithForeignClassFix(String newClassName) {
|
||||
this.newClassName = newClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.with.x", newClassName+".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("logger.initialized.with.foreign.class.fix.family.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
if (!(element instanceof PsiClassObjectAccessExpression classObjectAccessExpression)) {
|
||||
return;
|
||||
}
|
||||
PsiReplacementUtil.replaceExpression(classObjectAccessExpression, newClassName + ".class", new CommentTracker());
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggerInitializedWithForeignClassVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClassObjectAccessExpression(@NotNull PsiClassObjectAccessExpression expression) {
|
||||
super.visitClassObjectAccessExpression(expression);
|
||||
PsiElement parent = expression.getParent();
|
||||
if (parent instanceof PsiReferenceExpression referenceExpression) {
|
||||
if (!expression.equals(referenceExpression.getQualifierExpression())) {
|
||||
return;
|
||||
}
|
||||
final @NonNls String name = referenceExpression.getReferenceName();
|
||||
if (!"getName".equals(name)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement grandParent = referenceExpression.getParent();
|
||||
if (!(grandParent instanceof PsiMethodCallExpression methodCallExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpressionList list = methodCallExpression.getArgumentList();
|
||||
if (!list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
parent = methodCallExpression.getParent();
|
||||
}
|
||||
if (!(parent instanceof PsiExpressionList)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement grandParent = parent.getParent();
|
||||
if (!(grandParent instanceof PsiMethodCallExpression methodCallExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
|
||||
final PsiExpression[] expressions = argumentList.getExpressions();
|
||||
if (expressions.length != 1) {
|
||||
return;
|
||||
}
|
||||
PsiClass containingClass = PsiUtil.getContainingClass(expression);
|
||||
while (containingClass instanceof PsiAnonymousClass) {
|
||||
containingClass = PsiUtil.getContainingClass(containingClass);
|
||||
}
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
if (ignoreNonPublicClasses && !containingClass.hasModifierProperty(PsiModifier.PUBLIC)) {
|
||||
return;
|
||||
}
|
||||
final String containingClassName = containingClass.getName();
|
||||
if (containingClassName == null) {
|
||||
return;
|
||||
}
|
||||
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
|
||||
final String referenceName = methodExpression.getReferenceName();
|
||||
final int index = loggerFactoryMethodNames.indexOf(referenceName);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PsiMethod method = methodCallExpression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) {
|
||||
return;
|
||||
}
|
||||
final String className = aClass.getQualifiedName();
|
||||
if (className == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(StreamEx.zip(loggerFactoryClassNames, loggerFactoryMethodNames, (cl, m)-> new Pair<>(cl, m))
|
||||
.noneMatch(expected->expected.first.equals(className) && expected.second.equals(referenceName))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoreNotFinalField) {
|
||||
PsiField field = PsiTreeUtil.getParentOfType(methodCallExpression, PsiField.class);
|
||||
if (field == null) return;
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) return;
|
||||
}
|
||||
|
||||
final PsiTypeElement operand = expression.getOperand();
|
||||
final PsiClass initializerClass = PsiUtil.resolveClassInClassTypeOnly(operand.getType());
|
||||
if (initializerClass == null) {
|
||||
return;
|
||||
}
|
||||
if (containingClass.equals(initializerClass)) {
|
||||
return;
|
||||
}
|
||||
if (ignoreSuperClass && containingClass.isInheritor(initializerClass, true) ||
|
||||
PsiTreeUtil.isAncestor(initializerClass, containingClass, true)) {
|
||||
if (isOnTheFly()) {
|
||||
registerError(expression, ProblemHighlightType.INFORMATION, containingClassName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerError(expression, containingClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2003-2019 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.MakeFieldStaticFinalFix;
|
||||
import com.siyeh.ig.psiutils.JavaLoggingUtils;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
public final class NonStaticFinalLoggerInspection extends BaseInspection {
|
||||
|
||||
private final List<String> loggerClassNames = new ArrayList<>();
|
||||
@SuppressWarnings("PublicField")
|
||||
public String loggerClassName = StringUtil.join(JavaLoggingUtils.DEFAULT_LOGGERS, ",");
|
||||
|
||||
public NonStaticFinalLoggerInspection() {
|
||||
parseString(loggerClassName, loggerClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("loggerClassNames", InspectionGadgetsBundle.message("logger.class.name"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("choose.logger.class"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "NonConstantLogger";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("non.constant.logger.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiField field = (PsiField)infos[0];
|
||||
return MakeFieldStaticFinalFix.buildFixUnconditional(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
parseString(loggerClassName, loggerClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
loggerClassName = formatString(loggerClassNames);
|
||||
super.writeSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new NonStaticFinalLoggerVisitor();
|
||||
}
|
||||
|
||||
private class NonStaticFinalLoggerVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass.isInterface() || aClass.isEnum() || aClass.isAnnotationType()) {
|
||||
return;
|
||||
}
|
||||
if (aClass instanceof PsiTypeParameter) {
|
||||
return;
|
||||
}
|
||||
if (aClass.getContainingClass() != null) {
|
||||
return;
|
||||
}
|
||||
final PsiField[] fields = aClass.getFields();
|
||||
for (final PsiField field : fields) {
|
||||
if (!isLogger(field)) {
|
||||
continue;
|
||||
}
|
||||
if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
continue;
|
||||
}
|
||||
registerFieldError(field, field);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLogger(PsiVariable variable) {
|
||||
final PsiType type = variable.getType();
|
||||
final String text = type.getCanonicalText();
|
||||
return loggerClassNames.contains(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2003-2019 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.JavaRecursiveElementWalkingVisitor;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.JavaLoggingUtils;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
public final class PublicMethodWithoutLoggingInspection extends BaseInspection {
|
||||
|
||||
final List<String> loggerClassNames = new ArrayList<>();
|
||||
@SuppressWarnings("PublicField")
|
||||
public String loggerClassName = StringUtil.join(JavaLoggingUtils.DEFAULT_LOGGERS, ",");
|
||||
|
||||
public PublicMethodWithoutLoggingInspection() {
|
||||
parseString(loggerClassName, loggerClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("loggerClassNames", InspectionGadgetsBundle.message("logger.class.name"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("choose.logger.class"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("public.method.without.logging.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
parseString(loggerClassName, loggerClassNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
loggerClassName = formatString(loggerClassNames);
|
||||
super.writeSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new PublicMethodWithoutLoggingVisitor();
|
||||
}
|
||||
|
||||
private class PublicMethodWithoutLoggingVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
//no drilldown
|
||||
if (method.getNameIdentifier() == null) {
|
||||
return;
|
||||
}
|
||||
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
|
||||
return;
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) {
|
||||
return;
|
||||
}
|
||||
if (method.isConstructor()) {
|
||||
return;
|
||||
}
|
||||
if (PropertyUtil.isSimpleGetter(method) || PropertyUtil.isSimpleSetter(method)) {
|
||||
return;
|
||||
}
|
||||
if (containsLoggingCall(body)) {
|
||||
return;
|
||||
}
|
||||
registerMethodError(method);
|
||||
}
|
||||
|
||||
private boolean containsLoggingCall(PsiCodeBlock block) {
|
||||
final ContainsLoggingCallVisitor visitor = new ContainsLoggingCallVisitor();
|
||||
block.accept(visitor);
|
||||
return visitor.containsLoggingCall();
|
||||
}
|
||||
}
|
||||
|
||||
private class ContainsLoggingCallVisitor extends JavaRecursiveElementWalkingVisitor {
|
||||
private boolean containsLoggingCall;
|
||||
|
||||
@Override
|
||||
public void visitElement(@NotNull PsiElement element) {
|
||||
if (containsLoggingCall) {
|
||||
return;
|
||||
}
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
if (containsLoggingCall) {
|
||||
return;
|
||||
}
|
||||
super.visitMethodCallExpression(expression);
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
final String containingClassName = containingClass.getQualifiedName();
|
||||
if (containingClassName == null) {
|
||||
return;
|
||||
}
|
||||
if (loggerClassNames.contains(containingClassName)) {
|
||||
containsLoggingCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean containsLoggingCall() {
|
||||
return containsLoggingCall;
|
||||
}
|
||||
}
|
||||
}
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.logging;
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptDropdown;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiConstantEvaluationHelper;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiParenthesizedExpression;
|
||||
import com.intellij.psi.PsiPolyadicExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiReturnStatement;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiLiteralUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.PsiReplacementUtil;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.format.FormatDecode;
|
||||
import com.siyeh.ig.format.MessageFormatUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import one.util.streamex.EntryStream;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.dropdown;
|
||||
import static com.intellij.codeInspection.options.OptPane.option;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.siyeh.ig.callMatcher.CallMatcher.anyOf;
|
||||
import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class StringConcatenationArgumentToLogCallInspection extends BaseInspection {
|
||||
|
||||
private static final @NonNls Set<String> logNames = Set.of(
|
||||
"trace",
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error",
|
||||
"fatal",
|
||||
"log"
|
||||
);
|
||||
private static final String LOG4J_LOGGER = "org.apache.logging.log4j.Logger";
|
||||
private static final String LOG4J_BUILDER = "org.apache.logging.log4j.LogBuilder";
|
||||
private static final CallMatcher GET_FORMATTER_LOGGER = staticCall("org.apache.logging.log4j.LogManager", "getFormatterLogger") ;
|
||||
private static final CallMatcher GET_LOGGER = staticCall("org.apache.logging.log4j.LogManager", "getLogger");
|
||||
private static final CallMatcher MESSAGE_FORMAT_FORMAT = anyOf(
|
||||
staticCall("java.text.MessageFormat", "format").parameterCount(2)
|
||||
);
|
||||
private static final String SLF4J_LOGGER = "org.slf4j.Logger";
|
||||
|
||||
@SuppressWarnings("PublicField") public int warnLevel = 0;
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public boolean isLog4JParameterizedLogger = true;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
@Nls String[] options = {
|
||||
InspectionGadgetsBundle.message("all.levels.option"),
|
||||
InspectionGadgetsBundle.message("warn.level.and.lower.option"),
|
||||
InspectionGadgetsBundle.message("info.level.and.lower.option"),
|
||||
InspectionGadgetsBundle.message("debug.level.and.lower.option"),
|
||||
InspectionGadgetsBundle.message("trace.level.option")
|
||||
};
|
||||
return pane(
|
||||
dropdown("warnLevel", InspectionGadgetsBundle.message("warn.on.label"),
|
||||
EntryStream.of(options).mapKeyValue((idx, name) -> option(String.valueOf(idx), name))
|
||||
.toArray(OptDropdown.Option.class)),
|
||||
checkbox("isLog4JParameterizedLogger",
|
||||
InspectionGadgetsBundle.message("log4j.use.parameterized.logger"))
|
||||
.description(InspectionGadgetsBundle.message("log4j.use.parameterized.logger.description"))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.argument.to.log.call.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
if (warnLevel != 0) {
|
||||
node.addContent(new Element("option").setAttribute("name", "warnLevel").setAttribute("value", String.valueOf(warnLevel)));
|
||||
}
|
||||
if (!isLog4JParameterizedLogger) {
|
||||
node.addContent(new Element("option").setAttribute("name", "isLog4JParameterizedLogger")
|
||||
.setAttribute("value", "false"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
if (!(infos[0] instanceof ProblemType problemType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(infos[1] instanceof PsiMethodCallExpression logCall)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(infos[2] instanceof PsiExpression targetExpression)) {
|
||||
return null;
|
||||
}
|
||||
ThreeState formattedLog4J = isFormattedLog4J(logCall);
|
||||
if (formattedLog4J == ThreeState.YES) return null;
|
||||
if (!isLog4JParameterizedLogger && formattedLog4J == ThreeState.UNSURE) return null;
|
||||
return getQuickFix(problemType, targetExpression);
|
||||
}
|
||||
|
||||
public static @Nullable PsiUpdateModCommandQuickFix getQuickFix(@NotNull ProblemType problemType, @NotNull PsiExpression targetExpression) {
|
||||
return switch (problemType) {
|
||||
case CONCATENATION ->
|
||||
StringConcatenationArgumentToLogCallFix.isAvailable(targetExpression) ? new StringConcatenationArgumentToLogCallFix() : null;
|
||||
case STRING_FORMAT -> StringFormatArgumentToLogCallFix.create(targetExpression);
|
||||
case MESSAGE_FORMAT -> MessageFormatArgumentToLogCallFix.create(targetExpression);
|
||||
};
|
||||
}
|
||||
|
||||
private static ThreeState isFormattedLog4J(@NotNull PsiMethodCallExpression logCall) {
|
||||
PsiExpression qualifierExpression = logCall.getMethodExpression().getQualifierExpression();
|
||||
if (qualifierExpression == null) {
|
||||
return ThreeState.NO;
|
||||
}
|
||||
|
||||
boolean isLogBuilder = InheritanceUtil.isInheritor(qualifierExpression.getType(), LOG4J_BUILDER);
|
||||
if (isLogBuilder || InheritanceUtil.isInheritor(qualifierExpression.getType(), LOG4J_LOGGER)) {
|
||||
|
||||
if (isLogBuilder) {
|
||||
while (qualifierExpression != null &&
|
||||
!InheritanceUtil.isInheritor(qualifierExpression.getType(), LOG4J_LOGGER)) {
|
||||
if (qualifierExpression instanceof PsiMethodCallExpression nextCall) {
|
||||
qualifierExpression = PsiUtil.skipParenthesizedExprDown(nextCall.getMethodExpression().getQualifierExpression());
|
||||
}
|
||||
else {
|
||||
qualifierExpression = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (qualifierExpression != null) {
|
||||
if (qualifierExpression instanceof PsiMethodCallExpression callExpression) {
|
||||
PsiMethod method = callExpression.resolveMethod();
|
||||
if (method != null &&
|
||||
method.getContainingFile() == qualifierExpression.getContainingFile() &&
|
||||
(method.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
method.hasModifierProperty(PsiModifier.STATIC))) {
|
||||
PsiReturnStatement[] statements = PsiUtil.findReturnStatements(method);
|
||||
if (statements.length == 1) {
|
||||
PsiReturnStatement statement = statements[0];
|
||||
qualifierExpression = statement.getReturnValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (qualifierExpression instanceof PsiReferenceExpression referenceExpression &&
|
||||
referenceExpression.resolve() instanceof PsiVariable loggerVariable) {
|
||||
if (loggerVariable.getInitializer() instanceof PsiMethodCallExpression callExpression) {
|
||||
if (GET_FORMATTER_LOGGER.test(callExpression)) {
|
||||
return ThreeState.YES;
|
||||
}
|
||||
if (GET_LOGGER.test(callExpression)) {
|
||||
return ThreeState.NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (qualifierExpression instanceof PsiMethodCallExpression callExpression) {
|
||||
if (GET_FORMATTER_LOGGER.test(callExpression)) {
|
||||
return ThreeState.YES;
|
||||
}
|
||||
if (GET_LOGGER.test(callExpression)) {
|
||||
return ThreeState.NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ThreeState.UNSURE;
|
||||
}
|
||||
return ThreeState.NO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new StringConcatenationArgumentToLogCallVisitor();
|
||||
}
|
||||
|
||||
public interface EvaluatedStringFix {
|
||||
void fix(@NotNull PsiMethodCallExpression logCall);
|
||||
}
|
||||
|
||||
private static class StringConcatenationArgumentToLogCallFix extends PsiUpdateModCommandQuickFix implements EvaluatedStringFix {
|
||||
|
||||
StringConcatenationArgumentToLogCallFix() { }
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.argument.to.log.call.quickfix");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
final PsiElement grandParent = element.getParent().getParent();
|
||||
if (!(grandParent instanceof PsiMethodCallExpression methodCallExpression)) {
|
||||
return;
|
||||
}
|
||||
fix(methodCallExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fix(@NotNull PsiMethodCallExpression methodCallExpression) {
|
||||
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
|
||||
final PsiExpression[] arguments = argumentList.getExpressions();
|
||||
if (arguments.length == 0) {
|
||||
return;
|
||||
}
|
||||
final @NonNls StringBuilder newMethodCall = new StringBuilder(methodCallExpression.getMethodExpression().getText());
|
||||
newMethodCall.append('(');
|
||||
PsiExpression argument = arguments[0];
|
||||
int usedArguments;
|
||||
if (!(argument instanceof PsiPolyadicExpression)) {
|
||||
if (!TypeUtils.expressionHasTypeOrSubtype(argument, "org.slf4j.Marker") || arguments.length < 2) {
|
||||
return;
|
||||
}
|
||||
newMethodCall.append(argument.getText()).append(',');
|
||||
argument = arguments[1];
|
||||
usedArguments = 2;
|
||||
if (!(argument instanceof PsiPolyadicExpression)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
usedArguments = 1;
|
||||
}
|
||||
final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)argument;
|
||||
final PsiMethod method = methodCallExpression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
final String methodName = method.getName();
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod[] methods = containingClass.findMethodsByName(methodName, false);
|
||||
boolean varArgs = false;
|
||||
for (PsiMethod otherMethod : methods) {
|
||||
if (otherMethod.isVarArgs()) {
|
||||
varArgs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final List<PsiExpression> newArguments = new ArrayList<>();
|
||||
final PsiExpression[] operands = polyadicExpression.getOperands();
|
||||
boolean addPlus = false;
|
||||
boolean inStringLiteral = false;
|
||||
boolean isStringBlock = false;
|
||||
StringBuilder logText = new StringBuilder();
|
||||
int indent = 0;
|
||||
for (PsiExpression operand : operands) {
|
||||
if (ExpressionUtils.isEvaluatedAtCompileTime(operand)) {
|
||||
final String text = operand.getText();
|
||||
if (ExpressionUtils.hasStringType(operand) && operand instanceof PsiLiteralExpression literalExpression) {
|
||||
final int count = StringUtil.getOccurrenceCount(text, "{}");
|
||||
for (int i = 0; i < count && usedArguments + i < arguments.length; i++) {
|
||||
newArguments.add(PsiUtil.skipParenthesizedExprDown((PsiExpression)arguments[i + usedArguments].copy()));
|
||||
}
|
||||
usedArguments += count;
|
||||
if (!inStringLiteral) {
|
||||
if (addPlus) {
|
||||
newMethodCall.append('+');
|
||||
}
|
||||
inStringLiteral = true;
|
||||
}
|
||||
if (!isStringBlock && literalExpression.isTextBlock()) {
|
||||
indent = PsiLiteralUtil.getTextBlockIndent(literalExpression);
|
||||
}
|
||||
isStringBlock = isStringBlock || literalExpression.isTextBlock();
|
||||
logText.append(literalExpression.getValue());
|
||||
}
|
||||
else if (operand instanceof PsiLiteralExpression && PsiTypes.charType().equals(operand.getType()) && inStringLiteral) {
|
||||
final Object value = ((PsiLiteralExpression)operand).getValue();
|
||||
if (value instanceof Character) {
|
||||
logText.append(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (inStringLiteral) {
|
||||
addLogStrings(newMethodCall, logText, isStringBlock, indent);
|
||||
isStringBlock = false;
|
||||
inStringLiteral = false;
|
||||
}
|
||||
if (addPlus) {
|
||||
newMethodCall.append('+');
|
||||
}
|
||||
newMethodCall.append(text);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newArguments.add(PsiUtil.skipParenthesizedExprDown((PsiExpression)operand.copy()));
|
||||
if (!inStringLiteral) {
|
||||
if (addPlus) {
|
||||
newMethodCall.append('+');
|
||||
}
|
||||
inStringLiteral = true;
|
||||
}
|
||||
logText.append("{}");
|
||||
}
|
||||
addPlus = true;
|
||||
}
|
||||
while (usedArguments < arguments.length) {
|
||||
newArguments.add(arguments[usedArguments++]);
|
||||
}
|
||||
if (inStringLiteral) {
|
||||
addLogStrings(newMethodCall, logText, isStringBlock, indent);
|
||||
}
|
||||
if (!varArgs && newArguments.size() > 2) {
|
||||
newMethodCall.append(", new Object[]{");
|
||||
boolean comma = false;
|
||||
for (PsiExpression newArgument : newArguments) {
|
||||
if (comma) {
|
||||
newMethodCall.append(',');
|
||||
}
|
||||
else {
|
||||
comma = true;
|
||||
}
|
||||
if (newArgument != null) {
|
||||
newMethodCall.append(newArgument.getText());
|
||||
}
|
||||
}
|
||||
newMethodCall.append('}');
|
||||
}
|
||||
else {
|
||||
if (newArguments.size() == 1 && newArguments.get(0) != null &&
|
||||
InheritanceUtil.isInheritor(newArguments.get(0).getType(), CommonClassNames.JAVA_LANG_THROWABLE)) {
|
||||
newMethodCall.append(", String.valueOf(").append(newArguments.get(0).getText()).append(")");
|
||||
}
|
||||
else {
|
||||
for (PsiExpression newArgument : newArguments) {
|
||||
newMethodCall.append(',');
|
||||
if (newArgument != null) {
|
||||
newMethodCall.append(newArgument.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newMethodCall.append(')');
|
||||
PsiReplacementUtil.replaceExpression(methodCallExpression, newMethodCall.toString());
|
||||
}
|
||||
|
||||
private static void addLogStrings(StringBuilder methodCall, StringBuilder logText, boolean isStringBlock, int indent) {
|
||||
if (!isStringBlock) {
|
||||
methodCall.append('"')
|
||||
.append(StringUtil.escapeStringCharacters(logText.toString()))
|
||||
.append('"');
|
||||
logText.delete(0, logText.length());
|
||||
return;
|
||||
}
|
||||
String delimiters = "\n" + " ".repeat(indent);
|
||||
|
||||
String preparedText = StreamEx.of(logText.toString().split("\n", -1))
|
||||
.map(line -> line.endsWith(" ") ? line.substring(0, line.length() - 1) + "\\s" : line)
|
||||
.joining(delimiters, delimiters, "");
|
||||
preparedText = PsiLiteralUtil.escapeTextBlockCharacters(preparedText, true, true, false);
|
||||
methodCall.append("\"\"\"")
|
||||
.append(preparedText)
|
||||
.append("\"\"\"");
|
||||
logText.delete(0, logText.length());
|
||||
}
|
||||
|
||||
public static boolean isAvailable(PsiExpression expression) {
|
||||
if (!(expression instanceof PsiPolyadicExpression polyadicExpression)) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpression[] operands = polyadicExpression.getOperands();
|
||||
for (PsiExpression operand : operands) {
|
||||
if (!ExpressionUtils.isEvaluatedAtCompileTime(operand)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class FormatArgumentToLogCallFix extends PsiUpdateModCommandQuickFix implements EvaluatedStringFix {
|
||||
|
||||
private final @NotNull Map<TextRange, Integer> myTextMapping;
|
||||
|
||||
private final @NotNull String myFormat;
|
||||
|
||||
private final @NotNull Function<String, String> myTransformer;
|
||||
|
||||
private FormatArgumentToLogCallFix(@NotNull Map<TextRange, Integer> textMapping,
|
||||
@NotNull String format,
|
||||
@NotNull Function<String, String> transformer) {
|
||||
myTextMapping = textMapping;
|
||||
myFormat = format;
|
||||
myTransformer = transformer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fix(@NotNull PsiMethodCallExpression callExpression) {
|
||||
PsiExpression[] expressions = callExpression.getArgumentList().getExpressions();
|
||||
if (expressions.length < 1 || expressions.length > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiExpression expression = expressions[0];
|
||||
if (!(expression instanceof PsiMethodCallExpression formatCallExpression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
CommentTracker tracker = new CommentTracker();
|
||||
for (PsiElement child : callExpression.getChildren()) {
|
||||
if (child instanceof PsiExpressionList expressionList) {
|
||||
builder.append(createNewArgumentsFromCall(formatCallExpression, tracker, expressionList.getExpressions()));
|
||||
}
|
||||
else {
|
||||
builder.append(tracker.text(child));
|
||||
}
|
||||
}
|
||||
|
||||
tracker.replace(callExpression, builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
if (!(element.getParent() instanceof PsiReferenceExpression referenceExpression &&
|
||||
referenceExpression.getParent() instanceof PsiMethodCallExpression callExpression)) {
|
||||
return;
|
||||
}
|
||||
fix(callExpression);
|
||||
}
|
||||
|
||||
private @NotNull String createNewArgumentsFromCall(@NotNull PsiMethodCallExpression formatCallExpression,
|
||||
@NotNull CommentTracker tracker,
|
||||
PsiExpression @NotNull [] allArguments) {
|
||||
List<String> arguments = new ArrayList<>();
|
||||
List<Map.Entry<TextRange, Integer>> placeholders =
|
||||
myTextMapping.entrySet()
|
||||
.stream()
|
||||
.sorted(Comparator.<Map.Entry<TextRange, Integer>>comparingInt(t -> t.getKey().getStartOffset()).reversed())
|
||||
.toList();
|
||||
String formatWithPlaceholders = myFormat;
|
||||
PsiExpression[] expressions = formatCallExpression.getArgumentList().getExpressions();
|
||||
for (Map.Entry<TextRange, Integer> placeholder : placeholders) {
|
||||
formatWithPlaceholders = formatWithPlaceholders.substring(0, placeholder.getKey().getStartOffset()) + "{}" +
|
||||
formatWithPlaceholders.substring(placeholder.getKey().getEndOffset());
|
||||
|
||||
arguments.add(tracker.text(expressions[placeholder.getValue()]));
|
||||
}
|
||||
arguments.add(myTransformer.apply(formatWithPlaceholders));
|
||||
Collections.reverse(arguments);
|
||||
if (allArguments.length > 1) {
|
||||
for (int i = 1; i < allArguments.length; i++) {
|
||||
arguments.add(tracker.text(allArguments[i]));
|
||||
}
|
||||
}
|
||||
return "(" + String.join(", ", arguments) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static class MessageFormatArgumentToLogCallFix extends FormatArgumentToLogCallFix {
|
||||
|
||||
private MessageFormatArgumentToLogCallFix(@NotNull Map<TextRange, Integer> result,
|
||||
@NotNull String format) {
|
||||
super(result, format, (s)->s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.argument.to.log.message.format.call.quickfix");
|
||||
}
|
||||
|
||||
|
||||
static @Nullable PsiUpdateModCommandQuickFix create(@NotNull PsiExpression expression) {
|
||||
if (!(expression instanceof PsiMethodCallExpression callExpression)) {
|
||||
return null;
|
||||
}
|
||||
PsiExpression[] arguments = callExpression.getArgumentList().getExpressions();
|
||||
if (arguments.length == 0) return null;
|
||||
PsiExpression firstArgument = arguments[0];
|
||||
if (firstArgument == null) return null;
|
||||
TextInfo textInfo = getTextInfo(firstArgument);
|
||||
if (textInfo == null) return null;
|
||||
String pattern = textInfo.formattedString();
|
||||
String text = textInfo.text();
|
||||
if (pattern == null || text.isEmpty()) return null;
|
||||
MessageFormatUtil.MessageFormatResult result = MessageFormatUtil.checkFormat(pattern);
|
||||
if (!result.valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<TextRange, Integer> mapping = new HashMap<>();
|
||||
|
||||
List<MessageFormatUtil.MessageFormatPlaceholder> placeholders = result.placeholders();
|
||||
for (MessageFormatUtil.MessageFormatPlaceholder placeholder : placeholders) {
|
||||
if (!placeholder.isString()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (placeholder.index() + 1 >= arguments.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TextRange actualRange = ExpressionUtils.findStringLiteralRange(firstArgument, placeholder.range().getStartOffset(),
|
||||
placeholder.range().getEndOffset());
|
||||
if (actualRange == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mapping.put(actualRange, placeholder.index() + 1);
|
||||
}
|
||||
Set<Integer> argumentIndexes = new HashSet<>(mapping.values());
|
||||
if (argumentIndexes.size() != arguments.length - 1) {
|
||||
return null;
|
||||
}
|
||||
return new MessageFormatArgumentToLogCallFix(mapping, text);
|
||||
}
|
||||
}
|
||||
|
||||
private record TextInfo(String text, String formattedString) {
|
||||
}
|
||||
|
||||
private static @Nullable TextInfo getTextInfo(@NotNull PsiExpression expression) {
|
||||
String text = null;
|
||||
String formattedString = null;
|
||||
if (expression instanceof PsiLiteralExpression literalExpression) {
|
||||
if (!(literalExpression.getValue() instanceof String value)) {
|
||||
return null;
|
||||
}
|
||||
formattedString = value;
|
||||
text = literalExpression.getText();
|
||||
}
|
||||
if (expression instanceof PsiPolyadicExpression polyadicExpression && ExpressionUtils.hasStringType(polyadicExpression)) {
|
||||
PsiConstantEvaluationHelper constantEvaluationHelper =
|
||||
JavaPsiFacade.getInstance(expression.getProject()).getConstantEvaluationHelper();
|
||||
Object o = constantEvaluationHelper.computeConstantExpression(polyadicExpression);
|
||||
if (o instanceof String value) {
|
||||
formattedString = value;
|
||||
text = polyadicExpression.getText();
|
||||
}
|
||||
}
|
||||
return new TextInfo(text, formattedString);
|
||||
}
|
||||
|
||||
private static class StringFormatArgumentToLogCallFix extends FormatArgumentToLogCallFix {
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return InspectionGadgetsBundle.message("string.concatenation.argument.to.log.string.format.call.quickfix");
|
||||
}
|
||||
|
||||
private StringFormatArgumentToLogCallFix(@NotNull Map<TextRange, Integer> result,
|
||||
@NotNull String format,
|
||||
@NotNull Function<String, String> transformer) {
|
||||
super(result, format, transformer);
|
||||
}
|
||||
|
||||
static @Nullable PsiUpdateModCommandQuickFix create(@NotNull PsiExpression originalExpression) {
|
||||
if (!(originalExpression instanceof PsiMethodCallExpression callExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FormatDecode.FormatArgument formatArgument =
|
||||
FormatDecode.FormatArgument.extract(callExpression, List.of("format"), List.of("String"), true);
|
||||
if (formatArgument == null || formatArgument.getIndex() != 1) {
|
||||
return null;
|
||||
}
|
||||
PsiExpression expression = formatArgument.getExpression();
|
||||
if (expression == null) return null;
|
||||
TextInfo textInfo = getTextInfo(expression);
|
||||
if (textInfo == null) return null;
|
||||
String formattedString = textInfo.formattedString();
|
||||
String text = textInfo.text();
|
||||
if (formattedString == null || text == null) {
|
||||
return null;
|
||||
}
|
||||
PsiExpression[] arguments = Objects.requireNonNull(callExpression.getArgumentList()).getExpressions();
|
||||
int argumentCount = arguments.length - formatArgument.getIndex();
|
||||
FormatDecode.Validator[] validators;
|
||||
try {
|
||||
validators = FormatDecode.decodeNoVerify(formattedString, argumentCount);
|
||||
}
|
||||
catch (FormatDecode.IllegalFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (argumentCount != validators.length) return null;
|
||||
Map<TextRange, Integer> result = new HashMap<>();
|
||||
for (int i = 0; i < validators.length; i++) {
|
||||
int index = formatArgument.getIndex() + i;
|
||||
if (index >= arguments.length) return null;
|
||||
|
||||
FormatDecode.Validator metaValidator = validators[i];
|
||||
if (metaValidator == null) continue;
|
||||
Collection<FormatDecode.Validator> unpacked = metaValidator instanceof FormatDecode.MultiValidator multi ?
|
||||
multi.getValidators() : List.of(metaValidator);
|
||||
if (unpacked.size() != 1) return null;
|
||||
FormatDecode.Validator validator = unpacked.iterator().next();
|
||||
if (validator == null) return null;
|
||||
PsiExpression argument = arguments[index];
|
||||
if (!possibleToConvert(validator, argument)) return null;
|
||||
TextRange stringRange = validator.getRange();
|
||||
if (stringRange == null) return null;
|
||||
TextRange range = ExpressionUtils.findStringLiteralRange(expression, stringRange.getStartOffset(),
|
||||
stringRange.getEndOffset());
|
||||
if (range == null) return null;
|
||||
result.put(range, index);
|
||||
}
|
||||
int start = 0;
|
||||
while ((start = formattedString.indexOf("%n", start)) != -1) {
|
||||
int escaped = 0;
|
||||
while (true) {
|
||||
if (start - escaped == 0) {
|
||||
break;
|
||||
}
|
||||
if(formattedString.charAt(start - escaped - 1) == '%') {
|
||||
escaped++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (escaped % 2 == 1) {
|
||||
start++;
|
||||
continue;
|
||||
}
|
||||
TextRange range = ExpressionUtils.findStringLiteralRange(expression, start, start + 2);
|
||||
if (range == null) {
|
||||
return null;
|
||||
}
|
||||
text = StringUtil.replaceSubstring(text, range, "\\n");
|
||||
start++;
|
||||
}
|
||||
return new StringFormatArgumentToLogCallFix(result, text, s -> s.replaceAll("%%", "%"));
|
||||
}
|
||||
|
||||
private static boolean possibleToConvert(@NotNull FormatDecode.Validator validator, PsiExpression argument) {
|
||||
FormatDecode.Spec spec = validator.getSpec();
|
||||
if (spec == null) return false;
|
||||
if (spec.conversion() == null ||
|
||||
!StringUtil.isEmpty(spec.width()) ||
|
||||
!StringUtil.isEmpty(spec.dateSpec()) ||
|
||||
!StringUtil.isEmpty(spec.flags()) ||
|
||||
!StringUtil.isEmpty(spec.precision())) {
|
||||
return false;
|
||||
}
|
||||
return switch (spec.conversion()) {
|
||||
case "s" -> true;
|
||||
case "b" -> argument.getType() != null && TypeConversionUtil.isBooleanType(argument.getType());
|
||||
case "d" -> argument.getType() != null && TypeConversionUtil.isIntegralNumberType(argument.getType());
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProblemType {
|
||||
CONCATENATION, STRING_FORMAT, MESSAGE_FORMAT
|
||||
}
|
||||
|
||||
private class StringConcatenationArgumentToLogCallVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
super.visitMethodCallExpression(expression);
|
||||
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
|
||||
final String referenceName = methodExpression.getReferenceName();
|
||||
if (!logNames.contains(referenceName)) {
|
||||
return;
|
||||
}
|
||||
switch (warnLevel) {
|
||||
case 4:
|
||||
if ("debug".equals(referenceName)) return;
|
||||
case 3:
|
||||
if ("info".equals(referenceName)) return;
|
||||
case 2:
|
||||
if ("warn".equals(referenceName)) return;
|
||||
case 1:
|
||||
if ("error".equals(referenceName) || "fatal".equals(referenceName)) return;
|
||||
}
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (!InheritanceUtil.isInheritor(containingClass, SLF4J_LOGGER) &&
|
||||
!InheritanceUtil.isInheritor(containingClass, LOG4J_LOGGER) &&
|
||||
!InheritanceUtil.isInheritor(containingClass, LOG4J_BUILDER)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpressionList argumentList = expression.getArgumentList();
|
||||
final PsiExpression[] arguments = argumentList.getExpressions();
|
||||
if (arguments.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogConcatenationContext result = getLogConcatenationContext(arguments);
|
||||
if (result == null) return;
|
||||
|
||||
registerMethodCallError(expression, result.problemType(), expression, result.argument());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public record LogConcatenationContext(@NotNull PsiExpression argument, @NotNull ProblemType problemType) {
|
||||
}
|
||||
|
||||
public static @Nullable LogConcatenationContext getLogConcatenationContext(PsiExpression @NotNull [] arguments) {
|
||||
PsiExpression argument = arguments[0];
|
||||
|
||||
ProblemType problemType = null;
|
||||
|
||||
if (argument instanceof PsiMethodCallExpression callExpression && (
|
||||
arguments.length == 1 ||
|
||||
(arguments.length == 2 && arguments[1] != null &&
|
||||
InheritanceUtil.isInheritor(arguments[1].getType(), CommonClassNames.JAVA_LANG_THROWABLE))
|
||||
)) {
|
||||
FormatDecode.FormatArgument formatArgument =
|
||||
FormatDecode.FormatArgument.extract(callExpression, List.of("format"), List.of("String"), true);
|
||||
if (formatArgument != null) {
|
||||
problemType = ProblemType.STRING_FORMAT;
|
||||
}
|
||||
else if (MESSAGE_FORMAT_FORMAT.test(callExpression)) {
|
||||
problemType = ProblemType.MESSAGE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
if (problemType == null) {
|
||||
if (!ExpressionUtils.hasStringType(argument)) {
|
||||
if (arguments.length < 2) {
|
||||
return null;
|
||||
}
|
||||
argument = arguments[1];
|
||||
}
|
||||
if (!ExpressionUtils.hasStringType(argument)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!containsNonConstantConcatenation(argument)) {
|
||||
return null;
|
||||
}
|
||||
problemType = ProblemType.CONCATENATION;
|
||||
}
|
||||
|
||||
return new LogConcatenationContext(argument, problemType);
|
||||
}
|
||||
|
||||
private static boolean containsNonConstantConcatenation(@Nullable PsiExpression expression) {
|
||||
if (expression instanceof PsiParenthesizedExpression parenthesizedExpression) {
|
||||
return containsNonConstantConcatenation(parenthesizedExpression.getExpression());
|
||||
}
|
||||
else if (expression instanceof PsiPolyadicExpression polyadicExpression) {
|
||||
if (!ExpressionUtils.hasStringType(polyadicExpression)) {
|
||||
return false;
|
||||
}
|
||||
if (!JavaTokenType.PLUS.equals(polyadicExpression.getOperationTokenType())) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpression[] operands = polyadicExpression.getOperands();
|
||||
for (PsiExpression operand : operands) {
|
||||
if (!ExpressionUtils.isEvaluatedAtCompileTime(operand)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user