boolean (ASTNode child1, ASTNode child2) { if (child1 == null || child2 == null || child1.getElementType() == TokenType.WHITE_SPACE || child2.getElementType() == TokenType.WHITE_SPACE) { return true; } ASTNode token1 = TreeUtil.findLastLeaf(child1); ASTNode token2 = TreeUtil.findFirstLeaf(child2); LOG.assertTrue(token1 != null); LOG.assertTrue(token2 != null); IElementType type1 = token1.getElementType(); IElementType type2 = token2.getElementType(); if (!(type1 instanceof IJavaElementType && type2 instanceof IJavaElementType)) return true; // A workaround for IDEA-197644. The lexer below generates GT,EQ instead of GE. if (type1 == JavaTokenType.GE || type2 == JavaTokenType.GE) return true; // for >> lexer generates > and >, we need to handle it separately (IDEA-287538) if (type1 == JavaTokenType.GTGT && type2 != JavaTokenType.GT && type2 != JavaTokenType.EQ) return true; if (type1 != JavaTokenType.GT && type2 == JavaTokenType.GTGT) return true; // Because 21 Preview is not the highest language level. if (ElementType.STRING_TEMPLATE_FRAGMENTS.contains(type1) || ElementType.STRING_TEMPLATE_FRAGMENTS.contains(type2)) return true; Pair<IElementType, IElementType> key = pair(type1, type2); Boolean result = ourTokenStickingMatrix.get(key); if (result == null) { Lexer lexer = JavaParserDefinition.createLexer(LanguageLevel.HIGHEST); String text1 = unescapeTokenText(token1, type1), text2 = unescapeTokenText(token2, type2); lexer.start(text1 + text2); IElementType reparsedType1 = lexer.getTokenType(); String reparsedText1 = lexer.getTokenText(); lexer.advance(); IElementType reparsedType2 = lexer.getTokenType(); String reparsedText2 = lexer.getTokenText(); result = sameTokens(type1, text1, reparsedType1, reparsedText1) && sameTokens(type2, text2, reparsedType2, reparsedText2); ourTokenStickingMatrix.put(key, result); } return result.booleanValue(); }