PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitNewExpression(@NotNull PsiNewExpression expression) { if (ConstructionUtils.isReferenceTo(expression.getClassReference(), "java.text.MessageFormat")) { PsiMethod method = expression.resolveMethod(); if (method == null || !method.isConstructor()) { return; } PsiParameterList parameterList = method.getParameterList(); if (parameterList.getParametersCount() < 1) { return; } PsiParameter[] parameters = parameterList.getParameters(); if (parameters.length < 1 || parameters[0] == null) { return; } PsiType type = parameters[0].getType(); if (!CommonClassNames.JAVA_LANG_STRING.equals(type.getCanonicalText())) { return; } PsiExpressionList argumentList = expression.getArgumentList(); if (argumentList == null) { return; } checkStringFormatAndGetIndexes(argumentList.getExpressions()[0]); } } @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) { if (PATTERN_METHODS.test(call)) { List<MessageFormatUtil.MessageFormatPlaceholder> indexes = checkStringFormatAndGetIndexes(call.getArgumentList().getExpressions()[0]); if (indexes != null) { checkIndexes(call, indexes); } } } private void checkIndexes(@NotNull PsiMethodCallExpression call, @NotNull List<MessageFormatUtil.MessageFormatPlaceholder> indexes) { PsiExpression[] expressions = call.getArgumentList().getExpressions(); for (PsiExpression expression : expressions) { if (expression == null || expression instanceof PsiEmptyExpressionImpl) { return; } } int count = call.getArgumentList().getExpressionCount(); int argumentNumber = count - 1; List<Integer> notFoundArguments = new ArrayList<>(); Set<Integer> usedArgumentIndexes = new HashSet<>(); for (MessageFormatUtil.MessageFormatPlaceholder index : indexes) { if (index.index() >= argumentNumber) { notFoundArguments.add(index.index()); } usedArgumentIndexes.add(index.index()); } List<Integer> notUsedArguments = new ArrayList<>(); for (int i = 0; i < argumentNumber; i++) { if (!usedArgumentIndexes.contains(i)) { notUsedArguments.add(i); } } if (notFoundArguments.isEmpty() && notUsedArguments.isEmpty()) { return; } PsiIdentifier[] identifiers = PsiTreeUtil.getChildrenOfType(call.getMethodExpression(), PsiIdentifier.class); if (identifiers == null || identifiers.length != 1) { return; } PsiIdentifier identifier = identifiers[0]; if (!notFoundArguments.isEmpty()) { if (notFoundArguments.size() == 1) { holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.incorrect.message.format.not.found.argument", notFoundArguments.iterator().next())); } else { StringJoiner joiner = new StringJoiner(", "); for (Integer notFoundArgument : notFoundArguments) { joiner.add(notFoundArgument.toString()); } holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.incorrect.message.format.not.found.arguments", joiner.toString())); } } if (!notUsedArguments.isEmpty()) { for (Integer notUsedArgument : notUsedArguments) { int expressionIndex = notUsedArgument + 1; PsiExpression expression = expressions[expressionIndex]; holder.registerProblem(expression, InspectionGadgetsBundle.message("inspection.incorrect.message.format.not.used.argument", notUsedArgument)); } } } @Nullable private List<MessageFormatUtil.MessageFormatPlaceholder> checkStringFormatAndGetIndexes(@Nullable PsiExpression expression) { if (expression == null) { return null; } boolean immediatePattern = true; String pattern = null; if (expression instanceof PsiLiteralExpression literalExpression) { pattern = literalExpression.getText(); } if (pattern == null && expression instanceof PsiReferenceExpression referenceExpression && referenceExpression.resolve() instanceof PsiVariable variable && variable.hasModifierProperty(PsiModifier.FINAL)) { PsiExpression initializer = variable.getInitializer(); if (initializer instanceof PsiLiteralExpression literalExpression) { pattern = literalExpression.getText(); expression = initializer; } } if (pattern == null) { immediatePattern = false; CommonDataflow.DataflowResult dataflowResult = CommonDataflow.getDataflowResult(expression); if (dataflowResult == null) return null; Set<Object> values = dataflowResult.getExpressionValues(expression); if (values.size() != 1) return null; if (!(values.iterator().next() instanceof String value)) { return null; } pattern = value; } MessageFormatUtil.MessageFormatResult result = MessageFormatUtil.checkFormat(pattern); if (result.valid()) { return result.placeholders(); } if (!immediatePattern) { Optional<MessageFormatUtil.MessageFormatError> toHighlight = result.errors().stream() .filter(t -> t.errorType().getSeverity().ordinal() <= MessageFormatUtil.ErrorSeverity.WARNING.ordinal()) .min(Comparator.comparing(t -> t.errorType().getSeverity())); if (toHighlight.isEmpty()) { return null; } MessageFormatUtil.MessageFormatError error = toHighlight.get(); createError(expression, error, pattern, 0, expression.getTextLength()); } else { for (MessageFormatUtil.MessageFormatError error : result.errors()) { createError(expression, error, pattern, -1, 0); } } if (!ContainerUtil.exists(result.errors(), error -> error.errorType().getSeverity() == MessageFormatUtil.ErrorSeverity.RUNTIME_EXCEPTION)) { return result.placeholders(); } return null; } @Nullable private static String getRelatedText(@NotNull String pattern, @NotNull MessageFormatUtil.MessageFormatError error) { if (error.fromIndex() < 0 || error.toIndex() > pattern.length() || error.toIndex() < error.fromIndex()) { return null; } return pattern.substring(error.fromIndex(), error.toIndex()); } private void createError(@NotNull PsiExpression expression, @NotNull MessageFormatUtil.MessageFormatError error, @NotNull String pattern, int start, int end) { MessageFormatUtil.MessageFormatErrorType type = error.errorType(); //it's relevant mostly for IDEA files if (type == MessageFormatUtil.MessageFormatErrorType.QUOTED_PLACEHOLDER) { return; } String relatedText = getRelatedText(pattern, error); if (relatedText == null) { return; } String errorText = getMessageFormatTemplate(type, relatedText); if (start >= 0) { errorText = InspectionGadgetsBundle.message("inspection.incorrect.message.format.pattern", errorText, pattern); } else { start = error.fromIndex(); end = error.toIndex(); } ProblemHighlightType highlightType = getCustomHighlightType(type); if (highlightType == null) { holder.registerProblem(expression, TextRange.create(start, end), errorText); } else { holder.registerProblem(expression, errorText, highlightType, TextRange.create(start, end)); } } @Nullable private static ProblemHighlightType getCustomHighlightType(@NotNull MessageFormatUtil.MessageFormatErrorType type) { if (type.getSeverity() == MessageFormatUtil.ErrorSeverity.WARNING || type.getSeverity() == MessageFormatUtil.ErrorSeverity.WEAK_WARNING) { return ProblemHighlightType.WEAK_WARNING; } return null; } }; }