blob: 0686a42a2930edf3c58237c660d70a3af9d5c925 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.intellij.codeInspection.i18n;
import com.intellij.ExtensionPoints;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.*;
import com.intellij.lang.properties.PropertiesReferenceManager;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author max
* @author Konstantin Bulenkov
*/
public class InvalidPropertyKeyInspection extends BaseJavaLocalInspectionTool {
@Override
@NotNull
public String getGroupDisplayName() {
return GroupNames.PROPERTIES_GROUP_NAME;
}
@Override
@NotNull
public String getDisplayName() {
return CodeInsightBundle.message("inspection.unresolved.property.key.reference.name");
}
@Override
@NotNull
public String getShortName() {
return "UnresolvedPropertyKey";
}
@Override
@NotNull
public HighlightDisplayLevel getDefaultLevel() {
return HighlightDisplayLevel.ERROR;
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
@Nullable
public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
return checkElement(method, manager, isOnTheFly);
}
@Override
@Nullable
public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
final PsiClassInitializer[] initializers = aClass.getInitializers();
List<ProblemDescriptor> result = new ArrayList<ProblemDescriptor>();
for (PsiClassInitializer initializer : initializers) {
final ProblemDescriptor[] descriptors = checkElement(initializer, manager, isOnTheFly);
if (descriptors != null) {
ContainerUtil.addAll(result, descriptors);
}
}
return result.isEmpty() ? null : result.toArray(new ProblemDescriptor[result.size()]);
}
@Override
@Nullable
public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
List<ProblemDescriptor> result = new ArrayList<ProblemDescriptor>();
appendProblems(manager, isOnTheFly, result, field.getInitializer());
appendProblems(manager, isOnTheFly, result, field.getModifierList());
if (field instanceof PsiEnumConstant) {
appendProblems(manager, isOnTheFly, result, ((PsiEnumConstant)field).getArgumentList());
}
return result.isEmpty() ? null : result.toArray(new ProblemDescriptor[result.size()]);
}
private static void appendProblems(InspectionManager manager, boolean isOnTheFly, List<ProblemDescriptor> result, PsiElement element) {
if (element != null){
final ProblemDescriptor[] descriptors = checkElement(element, manager, isOnTheFly);
if (descriptors != null) {
Collections.addAll(result, descriptors);
}
}
}
@Nullable
private static ProblemDescriptor[] checkElement(PsiElement element, final InspectionManager manager, boolean onTheFly) {
UnresolvedPropertyVisitor visitor = new UnresolvedPropertyVisitor(manager, onTheFly);
element.accept(visitor);
List<ProblemDescriptor> problems = visitor.getProblems();
return problems.isEmpty() ? null : problems.toArray(new ProblemDescriptor[problems.size()]);
}
@Override
@Nullable
public ProblemDescriptor[] checkFile(@NotNull final PsiFile file, @NotNull final InspectionManager manager, boolean isOnTheFly) {
ExtensionPoint<FileCheckingInspection> point = Extensions.getRootArea().getExtensionPoint(ExtensionPoints.INVALID_PROPERTY_KEY_INSPECTION_TOOL);
final FileCheckingInspection[] fileCheckingInspections = point.getExtensions();
for (FileCheckingInspection obj : fileCheckingInspections) {
ProblemDescriptor[] descriptors = obj.checkFile(file, manager, isOnTheFly);
if (descriptors != null) {
return descriptors;
}
}
return null;
}
private static class UnresolvedPropertyVisitor extends JavaRecursiveElementWalkingVisitor {
private final InspectionManager myManager;
private final List<ProblemDescriptor> myProblems = new ArrayList<ProblemDescriptor>();
private final boolean onTheFly;
public UnresolvedPropertyVisitor(final InspectionManager manager, boolean onTheFly) {
myManager = manager;
this.onTheFly = onTheFly;
}
@Override
public void visitAnonymousClass(PsiAnonymousClass aClass) {
final PsiExpressionList argList = aClass.getArgumentList();
if (argList != null) {
argList.accept(this);
}
}
@Override
public void visitClass(PsiClass aClass) {
}
@Override
public void visitField(PsiField field) {
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
Object value = expression.getValue();
if (!(value instanceof String)) return;
String key = (String)value;
if (isComputablePropertyExpression(expression)) return;
Ref<String> resourceBundleName = new Ref<String>();
if (!JavaI18nUtil.isValidPropertyReference(myManager.getProject(), expression, key, resourceBundleName)) {
String bundleName = resourceBundleName.get();
if (bundleName != null) { // can be null if we were unable to resolve literal expression, e.g. when JDK was not set
appendPropertyKeyNotFoundProblem(bundleName, key, expression, myManager, myProblems, onTheFly);
}
}
else if (expression.getParent() instanceof PsiNameValuePair) {
PsiNameValuePair nvp = (PsiNameValuePair)expression.getParent();
if (Comparing.equal(nvp.getName(), AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER)) {
PropertiesReferenceManager manager = PropertiesReferenceManager.getInstance(expression.getProject());
Module module = ModuleUtil.findModuleForPsiElement(expression);
if (module != null) {
List<PropertiesFile> propFiles = manager.findPropertiesFiles(module, key);
if (propFiles.isEmpty()) {
final String description = CodeInsightBundle.message("inspection.invalid.resource.bundle.reference", key);
final ProblemDescriptor problem = myManager.createProblemDescriptor(expression,
description,
(LocalQuickFix)null,
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, onTheFly);
myProblems.add(problem);
}
}
}
}
else if (expression.getParent() instanceof PsiExpressionList && expression.getParent().getParent() instanceof PsiMethodCallExpression) {
final Map<String, Object> annotationParams = new HashMap<String, Object>();
annotationParams.put(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER, null);
if (!JavaI18nUtil.mustBePropertyKey(myManager.getProject(), expression, annotationParams)) return;
final int paramsCount = JavaI18nUtil.getPropertyValueParamsMaxCount(expression);
if (paramsCount == -1) return;
final PsiExpressionList expressions = (PsiExpressionList)expression.getParent();
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expressions.getParent();
final PsiMethod method = methodCall.resolveMethod();
final PsiExpression[] args = expressions.getExpressions();
for (int i = 0; i < args.length; i++) {
if (args[i] == expression) {
if (i + paramsCount >= args.length
&& method != null
&& method.getParameterList().getParametersCount() == i + 2
&& method.getParameterList().getParameters()[i + 1].isVarArgs()
&& !hasArrayTypeAt(i + 1, methodCall)) {
myProblems.add(myManager.createProblemDescriptor(methodCall,
CodeInsightBundle.message("property.has.more.parameters.than.passed", key, paramsCount, args.length - i - 1),
onTheFly, new LocalQuickFix[0],
ProblemHighlightType.GENERIC_ERROR));
}
break;
}
}
}
}
private static void appendPropertyKeyNotFoundProblem(@NotNull String bundleName,
@NotNull String key,
@NotNull PsiLiteralExpression expression,
@NotNull InspectionManager manager,
@NotNull List<ProblemDescriptor> problems,
boolean onTheFly) {
final String description = CodeInsightBundle.message("inspection.unresolved.property.key.reference.message", key);
final List<PropertiesFile> propertiesFiles = filterNotInLibrary(expression.getProject(), JavaI18nUtil.propertiesFilesByBundleName(bundleName, expression));
problems.add(
manager.createProblemDescriptor(
expression,
description,
propertiesFiles.isEmpty() ? null : new JavaCreatePropertyFix(expression, key, propertiesFiles),
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, onTheFly
)
);
}
@NotNull
private static List<PropertiesFile> filterNotInLibrary(@NotNull Project project,
@NotNull List<PropertiesFile> propertiesFiles) {
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
final List<PropertiesFile> result = new ArrayList<PropertiesFile>(propertiesFiles.size());
for (final PropertiesFile file : propertiesFiles) {
if (!fileIndex.isInLibraryClasses(file.getVirtualFile()) && !fileIndex.isInLibrarySource(file.getVirtualFile())) {
result.add(file);
}
}
return result;
}
private static boolean hasArrayTypeAt(int i, PsiMethodCallExpression methodCall) {
return methodCall != null
&& methodCall.getArgumentList().getExpressionTypes().length > i
&& methodCall.getArgumentList().getExpressionTypes()[i] instanceof PsiArrayType;
}
private static boolean isComputablePropertyExpression(PsiExpression expression) {
while (expression != null && expression.getParent() instanceof PsiParenthesizedExpression) expression = (PsiExpression)expression.getParent();
return expression != null && expression.getParent() instanceof PsiExpression;
}
public List<ProblemDescriptor> getProblems() {
return myProblems;
}
}
}