From 14183dd8a9df85b56c3d6dee7abfc43308ccb482 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Fri, 27 Dec 2019 06:47:23 +0100 Subject: [PATCH] i18n -> uast GitOrigin-RevId: 9fd81e36b90e6a722da8793803c290c3abcb3b27 --- plugins/java-i18n/src/META-INF/plugin.xml | 2 +- .../codeInspection/i18n/I18nInspection.java | 435 +++++++++--------- .../codeInspection/i18n/JavaI18nUtil.java | 32 ++ .../inspections/i18n/AssertionStmt.java | 5 + .../inspections/i18n/EnumConstantIgnored.java | 10 + .../inspections/i18n/ExceptionCtor.java | 17 + .../i18n/InitializerInAnonymousClass.java | 1 + .../i18n/SpecifiedExceptionCtor.java | 20 + .../i18n/I18NInspectionTest.java | 24 + .../src/org/jetbrains/uast/UastUtils.kt | 28 +- 10 files changed, 364 insertions(+), 210 deletions(-) create mode 100644 plugins/java-i18n/testData/inspections/i18n/AssertionStmt.java create mode 100644 plugins/java-i18n/testData/inspections/i18n/EnumConstantIgnored.java create mode 100644 plugins/java-i18n/testData/inspections/i18n/ExceptionCtor.java create mode 100644 plugins/java-i18n/testData/inspections/i18n/SpecifiedExceptionCtor.java diff --git a/plugins/java-i18n/src/META-INF/plugin.xml b/plugins/java-i18n/src/META-INF/plugin.xml index f741b7f5af04..eb586538f7fa 100644 --- a/plugins/java-i18n/src/META-INF/plugin.xml +++ b/plugins/java-i18n/src/META-INF/plugin.xml @@ -30,7 +30,7 @@ - results = new ArrayList<>(); - final PsiCodeBlock body = method.getBody(); + final UExpression body = method.getUastBody(); if (body != null) { ProblemDescriptor[] descriptors = checkElement(body, manager, isOnTheFly); if (descriptors != null) { @@ -343,22 +357,22 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen } } checkAnnotations(method, manager, isOnTheFly, results); - for (PsiParameter parameter : method.getParameterList().getParameters()) { + for (UParameter parameter : method.getUastParameters()) { checkAnnotations(parameter, manager, isOnTheFly, results); } return results.isEmpty() ? null : results.toArray(ProblemDescriptor.EMPTY_ARRAY); } - @Override @Nullable - public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { + @Override + public ProblemDescriptor[] checkClass(@NotNull UClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { if (isClassNonNls(aClass)) { return null; } - final PsiClassInitializer[] initializers = aClass.getInitializers(); + final UClassInitializer[] initializers = aClass.getInitializers(); List result = new ArrayList<>(); - for (PsiClassInitializer initializer : initializers) { - final ProblemDescriptor[] descriptors = checkElement(initializer.getBody(), manager, isOnTheFly); + for (UClassInitializer initializer : initializers) { + final ProblemDescriptor[] descriptors = checkElement(initializer.getUastBody(), manager, isOnTheFly); if (descriptors != null) { ContainerUtil.addAll(result, descriptors); } @@ -369,10 +383,10 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return result.isEmpty() ? null : result.toArray(ProblemDescriptor.EMPTY_ARRAY); } - private void checkAnnotations(PsiModifierListOwner member, + private void checkAnnotations(UDeclaration member, @NotNull InspectionManager manager, boolean isOnTheFly, List result) { - for (PsiAnnotation annotation : member.getAnnotations()) { + for (UAnnotation annotation : member.getUAnnotations()) { final ProblemDescriptor[] descriptors = checkElement(annotation, manager, isOnTheFly); if (descriptors != null) { ContainerUtil.addAll(result, descriptors); @@ -380,27 +394,29 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen } } - @Override @Nullable - public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) { - PsiClass containingClass = field.getContainingClass(); - if (containingClass == null || isClassNonNls(containingClass)) { + @Override + public ProblemDescriptor[] checkField(@NotNull UField field, @NotNull InspectionManager manager, boolean isOnTheFly) { + if (isClassNonNls(field)) { return null; } - if (AnnotationUtil.isAnnotated(field, AnnotationUtil.NON_NLS, CHECK_EXTERNAL)) { + if (AnnotationUtil.isAnnotated((PsiModifierListOwner)field.getJavaPsi(), AnnotationUtil.NON_NLS, CHECK_EXTERNAL)) { return null; } List result = new ArrayList<>(); - final PsiExpression initializer = field.getInitializer(); + final UExpression initializer = field.getUastInitializer(); if (initializer != null) { ProblemDescriptor[] descriptors = checkElement(initializer, manager, isOnTheFly); if (descriptors != null) { ContainerUtil.addAll(result, descriptors); } - } else if (field instanceof PsiEnumConstant) { - ProblemDescriptor[] descriptors = checkElement(((PsiEnumConstant)field).getArgumentList(), manager, isOnTheFly); - if (descriptors != null) { - ContainerUtil.addAll(result, descriptors); + } else if (field instanceof UEnumConstant) { + List arguments = ((UEnumConstant)field).getValueArguments(); + for (UExpression argument : arguments) { + ProblemDescriptor[] descriptors = checkElement(argument, manager, isOnTheFly); + if (descriptors != null) { + ContainerUtil.addAll(result, descriptors); + } } } checkAnnotations(field, manager, isOnTheFly, result); @@ -413,7 +429,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return "nls"; } - private ProblemDescriptor[] checkElement(@NotNull PsiElement element, @NotNull InspectionManager manager, boolean isOnTheFly) { + private ProblemDescriptor[] checkElement(@NotNull UElement element, @NotNull InspectionManager manager, boolean isOnTheFly) { StringI18nVisitor visitor = new StringI18nVisitor(manager, isOnTheFly); element.accept(visitor); List problems = visitor.getProblems(); @@ -445,7 +461,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen }; } - private class StringI18nVisitor extends JavaRecursiveElementWalkingVisitor { + private class StringI18nVisitor extends AbstractUastVisitor { private final List myProblems = new ArrayList<>(); private final InspectionManager myManager; private final boolean myOnTheFly; @@ -456,76 +472,86 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen } @Override - public void visitAnonymousClass(PsiAnonymousClass aClass) { - visitElement(aClass); // visit argument list but anon. class members should be not visited + public boolean visitObjectLiteralExpression(@NotNull UObjectLiteralExpression objectLiteralExpression) { + for (UExpression argument : objectLiteralExpression.getValueArguments()) { + argument.accept(this); + } + + return true; + } + + @Override + public boolean visitClass(@NotNull UClass node) { + return false; } @Override - public void visitClass(PsiClass aClass) { + public boolean visitField(@NotNull UField node) { + return false; } @Override - public void visitField(PsiField field) { + public boolean visitMethod(@NotNull UMethod node) { + return false; } @Override - public void visitMethod(PsiMethod method) { + public boolean visitInitializer(@NotNull UClassInitializer node) { + return false; } @Override - public void visitClassInitializer(PsiClassInitializer initializer) { - } - - @Override - public void visitLiteralExpression(PsiLiteralExpression expression) { + public boolean visitLiteralExpression(@NotNull ULiteralExpression expression) { Object value = expression.getValue(); - if (!(value instanceof String)) return; + if (!(value instanceof String)) return false; String stringValue = (String)value; if (stringValue.trim().isEmpty()) { - return; + return false; } Set nonNlsTargets = new THashSet<>(); if (canBeI18ned(myManager.getProject(), expression, stringValue, nonNlsTargets)) { - PsiField parentField = PsiTreeUtil.getParentOfType(expression, PsiField.class); + UField parentField = UastUtils.getParentOfType(expression, UField.class); // PsiTreeUtil.getParentOfType(expression, PsiField.class); if (parentField != null) { nonNlsTargets.add(parentField); } final String description = CodeInsightBundle.message("inspection.i18n.message.general.with.value", "#ref"); + PsiElement sourcePsi = expression.getSourcePsi(); + List fixes = new ArrayList<>(); - if (myOnTheFly) { - if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(expression) != null) { + if (myOnTheFly && sourcePsi instanceof PsiLiteralExpression) { + if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) != null) { fixes.add(new I18nizeConcatenationQuickFix()); } fixes.add(new I18nizeQuickFix()); - if (!isNotConstantFieldInitializer(expression)) { + if (!isNotConstantFieldInitializer((PsiExpression)sourcePsi)) { fixes.add(createIntroduceConstantFix()); } - } - final Project project = expression.getManager().getProject(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - if (PsiUtil.isLanguageLevel5OrHigher(expression)) { - for (PsiModifierListOwner element : nonNlsTargets) { - if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) { - if (!element.getManager().isInProject(element) || - facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) { - fixes.add(new NonNlsAnnotationProvider().createFix(element)); + if (PsiUtil.isLanguageLevel5OrHigher(sourcePsi)) { + final JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); + for (PsiModifierListOwner element : nonNlsTargets) { + if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) { + if (!element.getManager().isInProject(element) || + facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) { + fixes.add(new NonNlsAnnotationProvider().createFix(element)); + } } } } } LocalQuickFix[] farr = fixes.toArray(LocalQuickFix.EMPTY_ARRAY); - final ProblemDescriptor problem = myManager.createProblemDescriptor(expression, + final ProblemDescriptor problem = myManager.createProblemDescriptor(sourcePsi, description, myOnTheFly, farr, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); myProblems.add(problem); } + return false; } private boolean isNotConstantFieldInitializer(final PsiExpression expression) { @@ -535,13 +561,13 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen parentField.hasModifierProperty(PsiModifier.STATIC); } - @Override - public void visitAnnotation(PsiAnnotation annotation) { + public boolean visitAnnotation(UAnnotation annotation) { //prevent from @SuppressWarnings - if (!BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName())) { - super.visitAnnotation(annotation); + if (BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName())) { + return true; } + return super.visitAnnotation(annotation); } private List getProblems() { @@ -550,7 +576,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen } private boolean canBeI18ned(@NotNull Project project, - @NotNull PsiLiteralExpression expression, + @NotNull ULiteralExpression expression, @NotNull String value, @NotNull Set nonNlsTargets) { if (ignoreForNonAlpha && !StringUtil.containsAlphaCharacters(value)) { @@ -583,7 +609,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen if (ignoreForAssertStatements && isArgOfAssertStatement(expression)) { return false; } - if (ignoreForExceptionConstructors && ExceptionUtils.isExceptionArgument(expression)) { + if (ignoreForExceptionConstructors && isExceptionArgument(expression)) { return false; } if (ignoreForEnumConstants && isArgOfEnumConstant(expression)) { @@ -598,7 +624,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen if (ignoreForClassReferences && isClassRef(expression, value)) { return false; } - if (ignoreForPropertyKeyReferences && JavaI18nUtil.isPropertyRef(expression, value, null)) { + if (ignoreForPropertyKeyReferences && !PropertiesImplUtil.findPropertiesByKey(project, value).isEmpty()) { return false; } if (ignoreToString && isToString(expression)) { @@ -607,9 +633,9 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen Pattern pattern = myCachedNonNlsPattern; if (pattern != null) { - PsiFile file = expression.getContainingFile(); + PsiFile file = expression.getSourcePsi().getContainingFile(); Document document = PsiDocumentManager.getInstance(project).getDocument(file); - int line = document.getLineNumber(expression.getTextRange().getStartOffset()); + int line = document.getLineNumber(expression.getSourcePsi().getTextRange().getStartOffset()); int lineStartOffset = document.getLineStartOffset(line); CharSequence lineText = document.getCharsSequence().subSequence(lineStartOffset, document.getLineEndOffset(line)); @@ -627,29 +653,27 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return true; } - private static boolean isArgOfEnumConstant(PsiLiteralExpression expression) { - final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class); - if (!(parent instanceof PsiExpressionList)) { - return false; - } - final PsiElement grandparent = parent.getParent(); - return grandparent instanceof PsiEnumConstant; + private static boolean isArgOfEnumConstant(ULiteralExpression expression) { + return expression.getUastParent() instanceof UEnumConstant; } public void cacheNonNlsCommentPattern() { myCachedNonNlsPattern = nonNlsCommentPattern.trim().isEmpty() ? null : Pattern.compile(nonNlsCommentPattern); } - private static boolean isClassRef(final PsiLiteralExpression expression, String value) { + private static boolean isClassRef(final ULiteralExpression expression, String value) { if (StringUtil.startsWithChar(value,'#')) { value = value.substring(1); // A favor for JetBrains team to catch common Logger usage practice. } - return JavaPsiFacade.getInstance(expression.getProject()).findClass(value, GlobalSearchScope.allScope(expression.getProject())) != null; + Project project = Objects.requireNonNull(expression.getSourcePsi()).getProject(); + return JavaPsiFacade.getInstance(project).findClass(value, GlobalSearchScope.allScope(project)) != null; } - private static boolean isClassNonNls(@NotNull PsiClass clazz) { - final PsiDirectory directory = clazz.getContainingFile().getContainingDirectory(); + private static boolean isClassNonNls(@NotNull UDeclaration clazz) { + UFile uFile = UastUtils.getContainingUFile(clazz); + if (uFile == null) return false; + final PsiDirectory directory = uFile.getSourcePsi().getContainingDirectory(); return directory != null && isPackageNonNls(JavaDirectoryService.getInstance().getPackage(directory)); } @@ -662,35 +686,41 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen || isPackageNonNls(psiPackage.getParentPackage()); } - private boolean isPassedToNonNlsVariable(@NotNull PsiLiteralExpression expression, + private boolean isPassedToNonNlsVariable(@NotNull ULiteralExpression expression, final Set nonNlsTargets) { - PsiExpression toplevel = JavaI18nUtil.getTopLevelExpression(expression); - PsiVariable var = null; - if (toplevel instanceof PsiAssignmentExpression) { - PsiExpression lExpression = ((PsiAssignmentExpression)toplevel).getLExpression(); - while (lExpression instanceof PsiArrayAccessExpression) { - lExpression = ((PsiArrayAccessExpression)lExpression).getArrayExpression(); + UExpression toplevel = JavaI18nUtil.getTopLevelExpression(expression); + PsiModifierListOwner var = null; + if (UastExpressionUtils.isAssignment(toplevel)) { + UExpression lExpression = ((UBinaryExpression)toplevel).getLeftOperand(); + while (lExpression instanceof UArrayAccessExpression) { + lExpression = ((UArrayAccessExpression)lExpression).getReceiver(); } - if (lExpression instanceof PsiReferenceExpression) { - final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve(); + if (lExpression instanceof UResolvable) { + final PsiElement resolved = ((UResolvable)lExpression).resolve(); if (resolved instanceof PsiVariable) var = (PsiVariable)resolved; } } if (var == null) { - PsiElement parent = toplevel.getParent(); - if (parent instanceof PsiVariable && toplevel.equals(((PsiVariable)parent).getInitializer())) { - var = (PsiVariable)parent; + UElement parent = toplevel.getUastParent(); + if (parent instanceof UVariable && toplevel.equals(((UVariable)parent).getUastInitializer())) { + if (((UVariable)parent).findAnnotation(AnnotationUtil.NON_NLS) != null) { + return true; + } + + PsiElement psi = parent.getSourcePsi(); + if (psi instanceof PsiModifierListOwner) { + var = (PsiModifierListOwner)psi; + } } - else if (parent instanceof PsiExpressionList) { - parent = parent.getParent(); - if (parent instanceof PsiSwitchLabelStatementBase) { - PsiSwitchStatement switchStatement = ((PsiSwitchLabelStatementBase)parent).getEnclosingSwitchStatement(); - if (switchStatement != null) { - PsiExpression switchStatementExpression = switchStatement.getExpression(); - if (switchStatementExpression instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression)switchStatementExpression).resolve(); - if (resolved instanceof PsiVariable) var = (PsiVariable)resolved; + else if (toplevel instanceof USwitchExpression) { + UExpression switchExpression = ((USwitchExpression)toplevel).getExpression(); + if (switchExpression instanceof UResolvable) { + PsiElement resolved = ((UResolvable)switchExpression).resolve(); + if (resolved instanceof PsiVariable) { + UElement caseParent = expression.getUastParent(); + if (caseParent instanceof USwitchClauseExpression && ((USwitchClauseExpression)caseParent).getCaseValues().contains(expression)) { + var = (PsiVariable)resolved; } } } @@ -724,19 +754,18 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return AnnotationUtil.isAnnotated(parent, AnnotationUtil.NON_NLS, CHECK_EXTERNAL); } - private static boolean isInNonNlsEquals(PsiExpression expression, final Set nonNlsTargets) { - final PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier(expression); - if (call == null || !MethodCallUtils.isEqualsCall(call)) return false; - final PsiExpression[] expressions = call.getArgumentList().getExpressions(); - if (expressions.length != 1) return false; - final PsiExpression arg = PsiUtil.skipParenthesizedExprDown(expressions[0]); - PsiReferenceExpression ref = null; - if (arg instanceof PsiReferenceExpression) { - ref = (PsiReferenceExpression)arg; - } - else if (arg instanceof PsiMethodCallExpression) { - ref = ((PsiMethodCallExpression)arg).getMethodExpression(); - } + private static boolean isInNonNlsEquals(ULiteralExpression expression, final Set nonNlsTargets) { + UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent()); + if (!(parent instanceof UQualifiedReferenceExpression)) return false; + UExpression selector = ((UQualifiedReferenceExpression)parent).getSelector(); + if (!(selector instanceof UCallExpression)) return false; + UCallExpression call = (UCallExpression)selector; + if (!HardcodedMethodConstants.EQUALS.equals(call.getMethodName()) || + !MethodUtils.isEquals(call.resolve())) return false; + final List expressions = call.getValueArguments(); + if (expressions.size() != 1) return false; + final UExpression arg = UastUtils.skipParenthesizedExprDown(expressions.get(0)); + UResolvable ref = ObjectUtils.tryCast(arg, UResolvable.class); if (ref != null) { final PsiElement resolvedEntity = ref.resolve(); if (resolvedEntity instanceof PsiModifierListOwner) { @@ -750,79 +779,69 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return false; } - private static boolean isInNonNlsCall(@NotNull PsiExpression expression, + private static boolean isInNonNlsCall(@NotNull UExpression expression, final Set nonNlsTargets) { - expression = JavaI18nUtil.getTopLevelExpression(expression); - final PsiElement parent = expression.getParent(); - if (parent instanceof PsiExpressionList) { - final PsiElement grParent = parent.getParent(); - if (grParent instanceof PsiMethodCallExpression) { - return isNonNlsCall((PsiMethodCallExpression)grParent, nonNlsTargets); - } - else if (grParent instanceof PsiNewExpression) { - final PsiElement parentOfNew = PsiUtil.skipParenthesizedExprUp(grParent.getParent()); - if (parentOfNew instanceof PsiLocalVariable) { - final PsiLocalVariable newVariable = (PsiLocalVariable)parentOfNew; - if (annotatedAsNonNls(newVariable)) { - return true; - } - nonNlsTargets.add(newVariable); - return false; - } - else if (parentOfNew instanceof PsiAssignmentExpression) { - final PsiExpression lExpression = ((PsiAssignmentExpression)parentOfNew).getLExpression(); - if (lExpression instanceof PsiReferenceExpression) { - final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve(); - if (resolved instanceof PsiModifierListOwner) { - final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved; - if (annotatedAsNonNls(modifierListOwner)) { - return true; - } - nonNlsTargets.add(modifierListOwner); - return false; - } - } - } - } + UExpression parent = UastUtils.skipParenthesizedExprDown(JavaI18nUtil.getTopLevelExpression(expression)); + if (parent instanceof UQualifiedReferenceExpression) { + return isNonNlsCall((UQualifiedReferenceExpression)parent, nonNlsTargets); } - - return false; - } - - private static boolean isNonNlsCall(PsiMethodCallExpression grParent, Set nonNlsTargets) { - final PsiReferenceExpression methodExpression = grParent.getMethodExpression(); - final PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(methodExpression.getQualifierExpression()); - if (qualifier instanceof PsiReferenceExpression) { - final PsiElement resolved = ((PsiReferenceExpression)qualifier).resolve(); - if (resolved instanceof PsiModifierListOwner) { - final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved; - if (annotatedAsNonNls(modifierListOwner)) { + else if (parent != null && UastExpressionUtils.isAssignment(parent)) { + UExpression operand = ((UBinaryExpression)parent).getLeftOperand(); + if (operand instanceof UReferenceExpression && + isNonNlsCall((UReferenceExpression)operand, nonNlsTargets)) return true; + } + else if (parent instanceof UCallExpression) { + UElement parentOfNew = UastUtils.skipParenthesizedExprUp(parent.getUastParent()); + if (parentOfNew instanceof ULocalVariable) { + final ULocalVariable newVariable = (ULocalVariable)parentOfNew; + if (annotatedAsNonNls(newVariable.getPsi())) { return true; } - nonNlsTargets.add(modifierListOwner); + nonNlsTargets.add(newVariable); return false; } - } else if (qualifier instanceof PsiMethodCallExpression) { - final PsiType type = qualifier.getType(); - if (type != null && type.equals(methodExpression.getType())) { - return isNonNlsCall((PsiMethodCallExpression)qualifier, nonNlsTargets); + } + + return false; + } + + private static boolean isNonNlsCall(UReferenceExpression qualifier, Set nonNlsTargets) { + final PsiElement resolved = qualifier.resolve(); + if (resolved instanceof PsiModifierListOwner) { + final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved; + if (annotatedAsNonNls(modifierListOwner)) { + return true; + } + nonNlsTargets.add(modifierListOwner); + } + if (qualifier instanceof UQualifiedReferenceExpression) { + UExpression receiver = UastUtils.skipParenthesizedExprDown(((UQualifiedReferenceExpression)qualifier).getReceiver()); + if (receiver instanceof UReferenceExpression) { + return isNonNlsCall((UReferenceExpression)receiver, nonNlsTargets); } } return false; } - private static boolean isReturnedFromNonNlsMethod(final PsiLiteralExpression expression, final Set nonNlsTargets) { + private static boolean isReturnedFromNonNlsMethod(final ULiteralExpression expression, final Set nonNlsTargets) { PsiMethod method; - PsiNameValuePair nameValuePair = PsiTreeUtil.getParentOfType(expression, PsiNameValuePair.class); + UNamedExpression nameValuePair = UastUtils.getParentOfType(expression, UNamedExpression.class); if (nameValuePair != null) { - method = AnnotationUtil.getAnnotationMethod(nameValuePair); + method = UastUtils.getAnnotationMethod(nameValuePair); } else { - final PsiElement returnStmt = PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class, PsiMethodCallExpression.class); - if (!(returnStmt instanceof PsiReturnStatement)) { + //todo return from lambda + UElement parent = expression.getUastParent(); + while (parent instanceof UCallExpression && + ((UCallExpression)parent).getKind() == UastCallKind.NEW_ARRAY_WITH_INITIALIZER) { + parent = parent.getUastParent(); + } + final UElement returnStmt = UastUtils.getParentOfType(parent, UReturnExpression.class, false, UCallExpression.class, ULambdaExpression.class); + if (!(returnStmt instanceof UReturnExpression)) { return false; } - method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class); + UMethod uMethod = UastUtils.getParentOfType(expression, UMethod.class); + method = uMethod != null ? uMethod.getJavaPsi() : null; } if (method == null) return false; @@ -833,28 +852,22 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen return false; } - private static boolean isToString(final PsiLiteralExpression expression) { - final PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class); + private static boolean isToString(final ULiteralExpression expression) { + final UMethod method = UastUtils.getParentOfType(expression, UMethod.class); if (method == null) return false; final PsiType returnType = method.getReturnType(); return TO_STRING.equals(method.getName()) - && method.getParameterList().isEmpty() + && method.getUastParameters().isEmpty() && returnType != null && "java.lang.String".equals(returnType.getCanonicalText()); } - private static boolean isArgOfJUnitAssertion(PsiExpression expression) { - final PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiExpressionList)) { + private static boolean isArgOfJUnitAssertion(ULiteralExpression expression) { + final UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent()); + if (parent == null || !UastExpressionUtils.isMethodCall(parent)) { return false; } - final PsiElement grandparent = parent.getParent(); - if (!(grandparent instanceof PsiMethodCallExpression)) { - return false; - } - final PsiMethodCallExpression call = (PsiMethodCallExpression)grandparent; - final PsiReferenceExpression methodExpression = call.getMethodExpression(); - @NonNls final String methodName = methodExpression.getReferenceName(); + @NonNls final String methodName = ((UCallExpression)parent).getMethodName(); if (methodName == null) { return false; } @@ -862,7 +875,7 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen if (!methodName.startsWith("assert") && !methodName.equals("fail")) { return false; } - final PsiMethod method = call.resolveMethod(); + final PsiMethod method = ((UCallExpression)parent).resolve(); if (method == null) { return false; } @@ -870,45 +883,51 @@ public class I18nInspection extends AbstractBaseJavaLocalInspectionTool implemen if (containingClass == null) { return false; } - final Project project = expression.getProject(); - final GlobalSearchScope scope = GlobalSearchScope.allScope(project); - final PsiClass junitAssert = JavaPsiFacade.getInstance(project).findClass("junit.framework.Assert", scope); - return junitAssert != null && !containingClass.isInheritor(junitAssert, true); + return InheritanceUtil.isInheritor(containingClass,"org.junit.Assert") || + InheritanceUtil.isInheritor(containingClass,"org.junit.jupiter.api.Assertions") || + InheritanceUtil.isInheritor(containingClass, "junit.framework.Assert"); } - private static boolean isArgOfSpecifiedExceptionConstructor(PsiExpression expression, String[] specifiedExceptions) { + private static boolean isArgOfSpecifiedExceptionConstructor(ULiteralExpression expression, + String[] specifiedExceptions) { if (specifiedExceptions.length == 0) return false; - final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class); - if (!(parent instanceof PsiExpressionList)) { + UCallExpression parent = UastUtils.getParentOfType(expression, UCallExpression.class, true, UClass.class); + if (parent == null || !UastExpressionUtils.isConstructorCall(parent)) { return false; } - final PsiElement grandparent = parent.getParent(); - if (!(grandparent instanceof PsiNewExpression)) { + final PsiMethod resolved = parent.resolve(); + final PsiClass aClass = resolved != null ? resolved.getContainingClass() : null; + if (aClass == null) { return false; } - final PsiJavaCodeReferenceElement reference = - ((PsiNewExpression)grandparent).getClassReference(); - if (reference == null) { - return false; - } - final PsiElement referent = reference.resolve(); - if (!(referent instanceof PsiClass)) { - return false; - } - final PsiClass aClass = (PsiClass)referent; - - for (String specifiedException : specifiedExceptions) { - if (specifiedException.equals(aClass.getQualifiedName())) return true; - - } + return ArrayUtil.contains(aClass.getQualifiedName(), specifiedExceptions); + } + + private static boolean isArgOfAssertStatement(UExpression expression) { + UCallExpression parent = UastUtils.getParentOfType(expression, UCallExpression.class); + return parent != null && "assert".equals(parent.getMethodName()); + } + + public static boolean isExceptionArgument(@NotNull UExpression expression) { + final UCallExpression newExpression = + UastUtils.getParentOfType(expression, UCallExpression.class, true, UBlockExpression.class, UClass.class); + if (newExpression != null) { + if (UastExpressionUtils.isConstructorCall(newExpression)) { + final PsiType newExpressionType = newExpression.getExpressionType(); + return InheritanceUtil.isInheritor(newExpressionType, CommonClassNames.JAVA_LANG_THROWABLE); + } + else if (UastExpressionUtils.isMethodCall(newExpression)) { + String methodName = newExpression.getMethodName(); + if (PsiKeyword.SUPER.equals(methodName) || PsiKeyword.THIS.equals(methodName)) { + PsiMethod ctor = newExpression.resolve(); + return ctor != null && + InheritanceUtil.isInheritor(ctor.getContainingClass(), CommonClassNames.JAVA_LANG_THROWABLE); + } + } + } return false; } - private static boolean isArgOfAssertStatement(PsiExpression expression) { - return PsiTreeUtil.getParentOfType(expression, PsiAssertStatement.class, PsiClass.class) instanceof PsiAssertStatement; - } - - } diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java index a7ba680817b2..6f30bbffe7a7 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java @@ -22,6 +22,7 @@ import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.uast.*; +import org.jetbrains.uast.util.UastExpressionUtils; import java.text.MessageFormat; import java.util.*; @@ -105,6 +106,20 @@ public class JavaI18nUtil extends I18nUtil { return false; } + static boolean isPassedToAnnotatedParam(@NotNull UExpression expression, + final String annFqn, + @Nullable Ref resourceBundleRef, + @Nullable final Set nonNlsTargets) { + UExpression parent = getTopLevelExpression(expression); + if (!(parent instanceof UCallExpression)) return false; + int idx = (((UCallExpression)parent).getValueArguments()).indexOf(expression); + if (idx == -1) return false; + + PsiMethod method = ((UCallExpression)parent).resolve(); + return method != null && isMethodParameterAnnotatedWith(method, idx, null, annFqn, resourceBundleRef, nonNlsTargets); + + } + @NotNull static PsiExpression getTopLevelExpression(@NotNull PsiExpression expression) { while (expression.getParent() instanceof PsiExpression) { @@ -119,6 +134,23 @@ public class JavaI18nUtil extends I18nUtil { return expression; } + @NotNull + static UExpression getTopLevelExpression(@NotNull UExpression expression) { + while (expression.getUastParent() instanceof UExpression) { + final UExpression parent = (UExpression)expression.getUastParent(); + if (parent instanceof UBlockExpression || parent instanceof UReturnExpression) { + break; + } + if (parent instanceof UIfExpression && + ((UIfExpression)parent).getCondition() == expression) { + break; + } + expression = parent; + if (UastExpressionUtils.isAssignment(expression)) break; + } + return expression; + } + static boolean isMethodParameterAnnotatedWith(final PsiMethod method, final int idx, @Nullable Collection processed, diff --git a/plugins/java-i18n/testData/inspections/i18n/AssertionStmt.java b/plugins/java-i18n/testData/inspections/i18n/AssertionStmt.java new file mode 100644 index 000000000000..284ebb9a4e78 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/i18n/AssertionStmt.java @@ -0,0 +1,5 @@ +class Foo { + void foo(String s) { + assert s != null : "abcdefgh"; + } +} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/i18n/EnumConstantIgnored.java b/plugins/java-i18n/testData/inspections/i18n/EnumConstantIgnored.java new file mode 100644 index 000000000000..dc188f237c81 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/i18n/EnumConstantIgnored.java @@ -0,0 +1,10 @@ +enum Test { + CHECKIN("Text1"), + ADD("Rext2"); + + Test(final String id) { + myId = id; + } + + private final String myId; +} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/i18n/ExceptionCtor.java b/plugins/java-i18n/testData/inspections/i18n/ExceptionCtor.java new file mode 100644 index 000000000000..25f8c25a84f7 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/i18n/ExceptionCtor.java @@ -0,0 +1,17 @@ +import java.io.*; +class Foo { + void foo(String s) throws IOException { + throw new IOException("ex"); + } +} + +class MyEx extends IOException { + MyEx() { + this("my ex"); + } + + MyEx(String s) { + super("my ex"); + } + +} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/i18n/InitializerInAnonymousClass.java b/plugins/java-i18n/testData/inspections/i18n/InitializerInAnonymousClass.java index 731273237be2..cd5b656c686f 100644 --- a/plugins/java-i18n/testData/inspections/i18n/InitializerInAnonymousClass.java +++ b/plugins/java-i18n/testData/inspections/i18n/InitializerInAnonymousClass.java @@ -1,5 +1,6 @@ import java.awt.event.ActionListener; import java.awt.event.ActionEvent; +@SuppressWarnings("FooBar") class Test { public static void main(String[] args){ ActionListener listener = new ActionListener(){ diff --git a/plugins/java-i18n/testData/inspections/i18n/SpecifiedExceptionCtor.java b/plugins/java-i18n/testData/inspections/i18n/SpecifiedExceptionCtor.java new file mode 100644 index 000000000000..eb0434afe5d7 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/i18n/SpecifiedExceptionCtor.java @@ -0,0 +1,20 @@ +import java.io.*; +class Foo { + void foo(String s) throws IOException { + if (s == null) { + throw new NullPointerException("null expected here") + } + throw new IOException("ex"); + } +} + +class MyEx extends IOException { + MyEx() { + this("my ex"); + } + + MyEx(String s) { + super("my ex"); + } + +} \ No newline at end of file diff --git a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java index 44e5e1a0cde8..f709a87be29b 100644 --- a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java +++ b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java @@ -54,6 +54,30 @@ public class I18NInspectionTest extends LightJavaCodeInsightFixtureTestCase { doTest(); } public void testAnnotationArgument() { doTest(); } + public void testAssertionStmt() { doTest(); } + public void testExceptionCtor() { doTest(); } + public void testSpecifiedExceptionCtor() { + boolean old = myTool.ignoreForExceptionConstructors; + try { + myTool.ignoreForSpecifiedExceptionConstructors = "java.io.IOException"; + myTool.ignoreForExceptionConstructors = false; + doTest(); + } + finally { + myTool.ignoreForSpecifiedExceptionConstructors = ""; + myTool.ignoreForExceptionConstructors = old; + } + } + + public void testEnumConstantIgnored() { + boolean oldState = myTool.setIgnoreForEnumConstants(true); + try { + doTest(); + } + finally { + myTool.setIgnoreForEnumConstants(oldState); + } + } @Override protected String getTestDataPath() { diff --git a/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt b/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt index cb239a0d5aef..4157fea471bb 100644 --- a/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt +++ b/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt @@ -18,11 +18,11 @@ package org.jetbrains.uast -import com.intellij.codeInsight.completion.CompletionUtilCoreImpl import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ArrayUtil import org.jetbrains.annotations.ApiStatus import java.io.File @@ -168,6 +168,23 @@ fun UReferenceExpression?.getQualifiedName(): String? = (this?.resolve() as? Psi */ fun UExpression.evaluateString(): String? = evaluate() as? String +fun UExpression.skipParenthesizedExprDown(): UExpression? { + var expression = this + while (expression is UParenthesizedExpression) { + expression = expression.expression + } + return expression +} + +fun skipParenthesizedExprUp(elem: UElement?): UElement? { + var parent = elem + while (parent is UParenthesizedExpression) { + parent = parent.uastParent + } + return parent +} + + /** * Get a physical [File] for this file, or null if there is no such file on disk. */ @@ -233,3 +250,12 @@ tailrec fun UElement.isLastElementInControlFlow(scopeElement: UElement? = null): else -> false } +fun UNamedExpression.getAnnotationMethod(): PsiMethod? { + val annotation : UAnnotation = getParentOfType(UAnnotation::class.java, true)!! + val fqn = annotation.qualifiedName ?: return null + val psiClass = JavaPsiFacade.getInstance(sourcePsi!!.project).findClass(fqn, sourcePsi!!.resolveScope) + if (psiClass != null && psiClass.isAnnotationType) { + return ArrayUtil.getFirstElement(psiClass.findMethodsByName(this.name ?: "value", false)) + } + return null +}