PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) { if (PATTERN_METHODS.test(call)) { processExpression(call.getArgumentList().getExpressions()[0]); } } private void processExpression(@NotNull PsiExpression expression) { Object patternObject = ExpressionUtils.computeConstantExpression(expression); if (!(patternObject instanceof String pattern)) return; record Token(char character, int pos, int length) {} List<Token> tokens = new ArrayList<>(); int optionalDepth = 0; char[] array = pattern.toCharArray(); boolean inQuote = false; int lastQuoteIndex = 0; int length = 0; for (int pos = 0; pos < array.length; pos++) { char c = array[pos]; //process tokens if (!inQuote && isLetter(c)) { length++; if (pos != array.length - 1 && c == array[pos + 1]) { continue; } else { tokens.add(new Token(c, pos - length + 1, length)); checkPadding(expression, array, pos - length + 1, pos + 1); } } length = 0; //check if it starts quote region or one quote //case with one quote ('') is processed as quote region if (c == '\'') { inQuote = !inQuote; lastQuoteIndex = pos; continue; } //skip whole region in quote if (inQuote) { continue; } checkUnsupportedSymbolsProblem(expression, pos, c); if (c == '[') { optionalDepth++; } if (c == ']') { optionalDepth--; checkOptionalDepthProblem(expression, optionalDepth, pos); if (optionalDepth < 0) { //reset after problem optionalDepth = 0; } } } checkInQuoteProblem(expression, inQuote, lastQuoteIndex); for (Token token : tokens) { checkAvailableCount(expression, token.character, token.pos, token.length); } } private void checkOptionalDepthProblem(@NotNull PsiExpression expression, int optionalDepth, int pos) { if (optionalDepth < 0) { holder.registerProblem(expression, ExpressionUtils.findStringLiteralRange(expression, pos, pos + 1), InspectionGadgetsBundle.message("inspection.incorrect.date.format.message.unpaired", '[')); } } private void checkInQuoteProblem(@NotNull PsiExpression expression, boolean inQuote, int pos) { if (inQuote) { holder.registerProblem(expression, ExpressionUtils.findStringLiteralRange(expression, pos, pos + 1), InspectionGadgetsBundle.message("inspection.incorrect.date.format.message.literal", '\'')); } } private void checkUnsupportedSymbolsProblem(@NotNull PsiExpression expression, int pos, char c) { if (c == '{' || c == '}' || c == '#') { holder.registerProblem(expression, ExpressionUtils.findStringLiteralRange(expression, pos, pos + 1), InspectionGadgetsBundle.message("inspection.incorrect.date.format.message.reserved.character", c)); } } private void checkPadding(@NotNull PsiExpression expression, char[] array, int from, int to) { if (array[from] == 'p' && (to >= array.length || !isLetter(array[to]))) { holder.registerProblem(expression, ExpressionUtils.findStringLiteralRange(expression, from, to), InspectionGadgetsBundle.message("inspection.incorrect.date.format.message.padding", 'p')); } } private static boolean isLetter(char c) { return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; } private void checkAvailableCount(PsiExpression expression, char character, int pos, int length) { if (character == 'p') { //checked during tokenizing return; } BiFunction<Character, Integer, @InspectionMessage String> verifier = ALLOWED_DATE_TIME_FORMATTER.get(character); if (verifier == null) { holder.registerProblem(expression, ExpressionUtils.findStringLiteralRange(expression, pos, pos + length), InspectionGadgetsBundle.message("inspection.incorrect.date.format.message.unsupported", character)); } else { String message = verifier.apply(character, length); if (message != null) { final TextRange range = ExpressionUtils.findStringLiteralRange(expression, pos, pos + length); holder.registerProblem(expression, range, message); } } } }; }